@montra-interactive/deepstate 0.3.3 → 0.4.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.
package/README.md CHANGED
@@ -191,7 +191,7 @@ const store = state({
191
191
  | `"deep"` | JSON comparison: `JSON.stringify(a) === JSON.stringify(b)` |
192
192
  | `(a, b) => boolean` | Custom comparator function |
193
193
 
194
- ### `nullable(value)` - Nullable Objects
194
+ ### `nullable(value, options?)` - Nullable Objects
195
195
 
196
196
  For properties that can be `null` or an object:
197
197
 
@@ -204,6 +204,9 @@ const store = state({
204
204
 
205
205
  // Start as object, can become null
206
206
  profile: nullable({ bio: "Hello", avatar: "url" }),
207
+
208
+ // With distinct option to control emission deduplication
209
+ settings: nullable({ theme: "dark" }, { distinct: "shallow" }),
207
210
  });
208
211
 
209
212
  // Deep subscription works even when null!
@@ -217,6 +220,17 @@ store.user.name.set("Bob"); // Update nested
217
220
  store.user.set(null); // Back to null
218
221
  ```
219
222
 
223
+ **Distinct Options:**
224
+
225
+ | Value | Description |
226
+ |-------|-------------|
227
+ | `false` | No deduplication (always emits on set) |
228
+ | `"shallow"` | Shallow key-by-key `===` comparison |
229
+ | `"deep"` | JSON comparison: `JSON.stringify(a) === JSON.stringify(b)` (same as default) |
230
+ | `(a, b) => boolean` | Custom comparator; `a`/`b` may be `null` |
231
+
232
+ Without options, nullable nodes use `JSON.stringify` deduplication by default.
233
+
220
234
  ### `select(...observables)` - Combine Observables
221
235
 
222
236
  ```ts
@@ -170,6 +170,19 @@ export interface StateOptions {
170
170
  name?: string;
171
171
  }
172
172
  export declare function state<T extends object>(initialState: T, options?: StateOptions): RxState<T>;
173
+ /** Comparison mode for nullable object distinct checking */
174
+ export type NullableDistinct<T> = false | 'shallow' | 'deep' | ((a: T | null, b: T | null) => boolean);
175
+ /** Options for the nullable() helper */
176
+ export interface NullableOptions<T> {
177
+ /**
178
+ * How to compare nullable object values to prevent duplicate emissions.
179
+ * - false: No deduplication (always emits on set)
180
+ * - 'shallow': Shallow key-by-key === comparison
181
+ * - 'deep': JSON.stringify comparison (default behavior without options)
182
+ * - function: Custom comparator (a, b) => boolean where a/b may be null
183
+ */
184
+ distinct?: NullableDistinct<T>;
185
+ }
173
186
  /**
174
187
  * Marks a value as nullable, allowing it to transition between null and object.
175
188
  * Use this when you want to start with an object value but later set it to null.
@@ -180,6 +193,8 @@ export declare function state<T extends object>(initialState: T, options?: State
180
193
  * user: nullable({ name: "Alice", age: 30 }),
181
194
  * // Can start with null and later be set to object
182
195
  * profile: nullable<{ bio: string }>(null),
196
+ * // With distinct option to control emission deduplication
197
+ * settings: nullable({ theme: 'dark', lang: 'en' }, { distinct: 'shallow' }),
183
198
  * });
184
199
  *
185
200
  * // Use ?. on the nullable property, then access children directly
@@ -187,7 +202,7 @@ export declare function state<T extends object>(initialState: T, options?: State
187
202
  * store.user?.set({ name: "Bob", age: 25 }); // Works!
188
203
  * store.user?.name.set("Charlie"); // After ?. on user, children are directly accessible
189
204
  */
190
- export declare function nullable<T extends object>(value: T | null): T | null;
205
+ export declare function nullable<T extends object>(value: T | null, options?: NullableOptions<T>): T | null;
191
206
  /** Comparison mode for array distinct checking */
192
207
  export type ArrayDistinct<T> = false | 'shallow' | 'deep' | ((a: T[], b: T[]) => boolean);
193
208
  /** Options for the array() helper */
@@ -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/index.d.ts CHANGED
@@ -11,6 +11,6 @@
11
11
  * - select() - Combine multiple observables
12
12
  * - selectFromEach() - Select from each array item with precise change detection
13
13
  */
14
- export { state, nullable, array, type RxState, type Draft, type StateOptions, type ArrayOptions, type ArrayDistinct } from "./deepstate";
14
+ export { state, nullable, array, type RxState, type Draft, type StateOptions, type ArrayOptions, type ArrayDistinct, type NullableOptions, type NullableDistinct } from "./deepstate";
15
15
  export { select, selectFromEach } from "./helpers";
16
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACzI,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACtL,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
package/dist/index.js CHANGED
@@ -238,7 +238,7 @@ function createArrayNode(value, comparator) {
238
238
  };
239
239
  }
240
240
  var NULLABLE_NODE = Symbol("nullableNode");
241
- function createNullableObjectNode(initialValue) {
241
+ function createNullableObjectNode(initialValue, comparator) {
242
242
  const subject$ = new BehaviorSubject(initialValue);
243
243
  let children = null;
244
244
  const pendingChildren = new Map;
@@ -264,7 +264,7 @@ function createNullableObjectNode(initialValue) {
264
264
  }
265
265
  return result;
266
266
  };
267
- const $ = combineLatest2([subject$, lock$]).pipe(filter(([_, unlocked]) => unlocked), map2(([value, _]) => {
267
+ const baseLocked$ = combineLatest2([subject$, lock$]).pipe(filter(([_, unlocked]) => unlocked), map2(([value, _]) => {
268
268
  if (value === null || value === undefined || !children) {
269
269
  return value;
270
270
  }
@@ -273,13 +273,8 @@ function createNullableObjectNode(initialValue) {
273
273
  result[key] = child.get();
274
274
  }
275
275
  return result;
276
- }), distinctUntilChanged2((a, b) => {
277
- if (a === null || a === undefined)
278
- return a === b;
279
- if (b === null || b === undefined)
280
- return false;
281
- return JSON.stringify(a) === JSON.stringify(b);
282
- }), shareReplay(1));
276
+ }));
277
+ const $ = (comparator === undefined ? baseLocked$.pipe(distinctUntilChanged2(nullableDeepComparator)) : comparator ? baseLocked$.pipe(distinctUntilChanged2(comparator)) : baseLocked$).pipe(shareReplay(1));
283
278
  $.subscribe();
284
279
  const nodeState = { children };
285
280
  const updateChildrenRef = () => {
@@ -556,6 +551,7 @@ function findCircularReference(obj, currentPath = "root", seen = new Map) {
556
551
  if (result !== null)
557
552
  return result;
558
553
  }
554
+ seen.delete(obj);
559
555
  return null;
560
556
  }
561
557
  for (const [key, value] of Object.entries(obj)) {
@@ -563,12 +559,16 @@ function findCircularReference(obj, currentPath = "root", seen = new Map) {
563
559
  if (result !== null)
564
560
  return result;
565
561
  }
562
+ seen.delete(obj);
566
563
  return null;
567
564
  }
568
565
  function createNodeForValue(value, maybeNullable = false) {
569
566
  if (isNullableMarked(value)) {
567
+ const options = getNullableMarkerOptions(value);
568
+ const comparator = getNullableComparator(options);
570
569
  delete value[NULLABLE_MARKER];
571
- return createNullableObjectNode(value);
570
+ const initialValue = Object.keys(value).length === 0 ? null : value;
571
+ return createNullableObjectNode(initialValue, comparator);
572
572
  }
573
573
  if (value === null || value === undefined) {
574
574
  if (maybeNullable) {
@@ -801,15 +801,59 @@ function state(initialState, options) {
801
801
  return wrapWithProxy(node, "", debugLog);
802
802
  }
803
803
  var NULLABLE_MARKER = Symbol("nullable");
804
- function nullable(value) {
804
+ function nullableDeepComparator(a, b) {
805
+ if (a === null || a === undefined)
806
+ return a === b;
807
+ if (b === null || b === undefined)
808
+ return false;
809
+ return JSON.stringify(a) === JSON.stringify(b);
810
+ }
811
+ function getNullableComparator(options) {
812
+ if (options.distinct === undefined)
813
+ return;
814
+ if (options.distinct === false)
815
+ return false;
816
+ if (options.distinct === "shallow") {
817
+ return (a, b) => {
818
+ if (a === null || a === undefined)
819
+ return a === b;
820
+ if (b === null || b === undefined)
821
+ return false;
822
+ if (typeof a !== "object" || typeof b !== "object")
823
+ return a === b;
824
+ const aKeys = Object.keys(a);
825
+ const bKeys = Object.keys(b);
826
+ if (aKeys.length !== bKeys.length)
827
+ return false;
828
+ return aKeys.every((key) => a[key] === b[key]);
829
+ };
830
+ }
831
+ if (options.distinct === "deep") {
832
+ return nullableDeepComparator;
833
+ }
834
+ const customFn = options.distinct;
835
+ return (a, b) => customFn(a, b);
836
+ }
837
+ function nullable(value, options = {}) {
805
838
  if (value === null) {
839
+ if (Object.keys(options).length > 0) {
840
+ const marker = Object.create(null);
841
+ marker[NULLABLE_MARKER] = options;
842
+ return marker;
843
+ }
806
844
  return null;
807
845
  }
808
- return Object.assign(value, { [NULLABLE_MARKER]: true });
846
+ return Object.assign(value, { [NULLABLE_MARKER]: options });
809
847
  }
810
848
  function isNullableMarked(value) {
811
849
  return value !== null && typeof value === "object" && NULLABLE_MARKER in value;
812
850
  }
851
+ function getNullableMarkerOptions(value) {
852
+ if (value !== null && typeof value === "object" && NULLABLE_MARKER in value) {
853
+ return value[NULLABLE_MARKER];
854
+ }
855
+ return {};
856
+ }
813
857
  var ARRAY_MARKER = Symbol("array");
814
858
  function array(value, options = {}) {
815
859
  const marked = [...value];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@montra-interactive/deepstate",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "Proxy-based reactive state management with RxJS. Deep nested state observation with full TypeScript support.",
5
5
  "keywords": [
6
6
  "state",
package/src/deepstate.ts CHANGED
@@ -567,7 +567,8 @@ interface NullableNodeCore<T> extends NodeCore<T> {
567
567
  * When value is set to object: children are created lazily from the object's keys
568
568
  */
569
569
  function createNullableObjectNode<T>(
570
- initialValue: T
570
+ initialValue: T,
571
+ comparator?: false | ((a: T, b: T) => boolean),
571
572
  ): NullableNodeCore<T> {
572
573
  // Subject holds the raw value (null or object)
573
574
  const subject$ = new BehaviorSubject<T>(initialValue);
@@ -612,7 +613,7 @@ function createNullableObjectNode<T>(
612
613
  };
613
614
 
614
615
  // Observable that emits the current value, respecting lock
615
- const $ = combineLatest([subject$, lock$]).pipe(
616
+ const baseLocked$ = combineLatest([subject$, lock$]).pipe(
616
617
  filter(([_, unlocked]) => unlocked),
617
618
  map(([value, _]) => {
618
619
  if (value === null || value === undefined || !children) {
@@ -625,11 +626,15 @@ function createNullableObjectNode<T>(
625
626
  }
626
627
  return result as T;
627
628
  }),
628
- distinctUntilChanged((a, b) => {
629
- if (a === null || a === undefined) return a === b;
630
- if (b === null || b === undefined) return false;
631
- return JSON.stringify(a) === JSON.stringify(b);
632
- }),
629
+ );
630
+
631
+ // Apply distinct comparison: use provided comparator, fall back to default, or skip entirely
632
+ const $ = (comparator === undefined
633
+ ? baseLocked$.pipe(distinctUntilChanged(nullableDeepComparator))
634
+ : comparator
635
+ ? baseLocked$.pipe(distinctUntilChanged(comparator))
636
+ : baseLocked$
637
+ ).pipe(
633
638
  shareReplay(1)
634
639
  );
635
640
  $.subscribe(); // Keep hot
@@ -1046,6 +1051,8 @@ function createNestedArrayProjection<T>(
1046
1051
 
1047
1052
  // Helper to detect circular references in an object.
1048
1053
  // Returns a string describing the cycle path (e.g. "root.next.next → root") or null if none found.
1054
+ // Uses DFS with backtracking: only objects on the current ancestor path are in `seen`,
1055
+ // so shared (but non-circular) references across sibling branches are not flagged.
1049
1056
  function findCircularReference(
1050
1057
  obj: unknown,
1051
1058
  currentPath: string = 'root',
@@ -1062,6 +1069,7 @@ function findCircularReference(
1062
1069
  const result = findCircularReference(obj[i], `${currentPath}[${i}]`, seen);
1063
1070
  if (result !== null) return result;
1064
1071
  }
1072
+ seen.delete(obj as object);
1065
1073
  return null;
1066
1074
  }
1067
1075
 
@@ -1069,6 +1077,7 @@ function findCircularReference(
1069
1077
  const result = findCircularReference(value, `${currentPath}.${key}`, seen);
1070
1078
  if (result !== null) return result;
1071
1079
  }
1080
+ seen.delete(obj as object);
1072
1081
  return null;
1073
1082
  }
1074
1083
 
@@ -1078,9 +1087,16 @@ function findCircularReference(
1078
1087
  function createNodeForValue<T>(value: T, maybeNullable: boolean = false): NodeCore<T> {
1079
1088
  // Check for nullable marker (from nullable() helper)
1080
1089
  if (isNullableMarked(value)) {
1090
+ const options = getNullableMarkerOptions(value);
1091
+ const comparator = getNullableComparator(options);
1081
1092
  // Remove the marker before creating the node
1082
1093
  delete (value as Record<symbol, unknown>)[NULLABLE_MARKER];
1083
- return createNullableObjectNode(value) as NodeCore<T>;
1094
+ // If the marked value was a sentinel for nullable(null, options), it has no
1095
+ // own enumerable keys after stripping the marker — pass null as initial value.
1096
+ const initialValue = Object.keys(value as object).length === 0
1097
+ ? null as unknown as T
1098
+ : value;
1099
+ return createNullableObjectNode(initialValue, comparator) as NodeCore<T>;
1084
1100
  }
1085
1101
 
1086
1102
  if (value === null || value === undefined) {
@@ -1405,6 +1421,64 @@ export function state<T extends object>(initialState: T, options?: StateOptions)
1405
1421
  // Symbol to mark a value as nullable
1406
1422
  const NULLABLE_MARKER = Symbol("nullable");
1407
1423
 
1424
+ /** Comparison mode for nullable object distinct checking */
1425
+ export type NullableDistinct<T> =
1426
+ | false // No deduplication (always emits on set)
1427
+ | 'shallow' // Shallow key-by-key === comparison
1428
+ | 'deep' // JSON.stringify comparison (same as default behavior without options)
1429
+ | ((a: T | null, b: T | null) => boolean); // Custom comparator
1430
+
1431
+ /** Options for the nullable() helper */
1432
+ export interface NullableOptions<T> {
1433
+ /**
1434
+ * How to compare nullable object values to prevent duplicate emissions.
1435
+ * - false: No deduplication (always emits on set)
1436
+ * - 'shallow': Shallow key-by-key === comparison
1437
+ * - 'deep': JSON.stringify comparison (default behavior without options)
1438
+ * - function: Custom comparator (a, b) => boolean where a/b may be null
1439
+ */
1440
+ distinct?: NullableDistinct<T>;
1441
+ }
1442
+
1443
+ // Null-safe JSON.stringify comparator, shared between default behavior and distinct: 'deep'
1444
+ function nullableDeepComparator<T>(a: T, b: T): boolean {
1445
+ if (a === null || a === undefined) return a === b;
1446
+ if (b === null || b === undefined) return false;
1447
+ return JSON.stringify(a) === JSON.stringify(b);
1448
+ }
1449
+
1450
+ // Get the distinct comparator function from nullable options
1451
+ // Returns undefined to use default (JSON.stringify), false to disable, or a function for custom comparison
1452
+ function getNullableComparator<T>(options: NullableOptions<T>): false | ((a: T, b: T) => boolean) | undefined {
1453
+ if (options.distinct === undefined) return undefined;
1454
+ if (options.distinct === false) return false;
1455
+
1456
+ if (options.distinct === 'shallow') {
1457
+ return (a, b) => {
1458
+ if (a === null || a === undefined) return a === b;
1459
+ if (b === null || b === undefined) return false;
1460
+ if (typeof a !== 'object' || typeof b !== 'object') return a === b;
1461
+ const aKeys = Object.keys(a);
1462
+ const bKeys = Object.keys(b);
1463
+ if (aKeys.length !== bKeys.length) return false;
1464
+ return aKeys.every(key =>
1465
+ (a as Record<string, unknown>)[key] === (b as Record<string, unknown>)[key]
1466
+ );
1467
+ };
1468
+ }
1469
+
1470
+ if (options.distinct === 'deep') {
1471
+ return nullableDeepComparator;
1472
+ }
1473
+
1474
+ // Custom function — wrap to match the (a: T, b: T) => boolean signature
1475
+ const customFn = options.distinct;
1476
+ return (a, b) => customFn(a as T | null, b as T | null);
1477
+ }
1478
+
1479
+ interface MarkedNullable<T> {
1480
+ [NULLABLE_MARKER]: NullableOptions<T>;
1481
+ }
1408
1482
 
1409
1483
  /**
1410
1484
  * Marks a value as nullable, allowing it to transition between null and object.
@@ -1416,6 +1490,8 @@ const NULLABLE_MARKER = Symbol("nullable");
1416
1490
  * user: nullable({ name: "Alice", age: 30 }),
1417
1491
  * // Can start with null and later be set to object
1418
1492
  * profile: nullable<{ bio: string }>(null),
1493
+ * // With distinct option to control emission deduplication
1494
+ * settings: nullable({ theme: 'dark', lang: 'en' }, { distinct: 'shallow' }),
1419
1495
  * });
1420
1496
  *
1421
1497
  * // Use ?. on the nullable property, then access children directly
@@ -1423,12 +1499,24 @@ const NULLABLE_MARKER = Symbol("nullable");
1423
1499
  * store.user?.set({ name: "Bob", age: 25 }); // Works!
1424
1500
  * store.user?.name.set("Charlie"); // After ?. on user, children are directly accessible
1425
1501
  */
1426
- export function nullable<T extends object>(value: T | null): T | null {
1502
+ export function nullable<T extends object>(value: T | null, options: NullableOptions<T> = {}): T | null {
1427
1503
  if (value === null) {
1504
+ // For null values, we can't attach the marker to the value itself.
1505
+ // We store options on a sentinel object that createNodeForValue will check.
1506
+ if (Object.keys(options).length > 0) {
1507
+ // Store options in a module-level map keyed by a unique object
1508
+ // The caller will pass this null through state(), and createNodeForValue
1509
+ // will receive maybeNullable=true for null children of nullable parents.
1510
+ // For top-level nullable(null, options), we need a different approach:
1511
+ // return a special marker object that looks null-ish but carries options.
1512
+ const marker = Object.create(null) as MarkedNullable<T>;
1513
+ marker[NULLABLE_MARKER] = options;
1514
+ return marker as unknown as T | null;
1515
+ }
1428
1516
  return null;
1429
1517
  }
1430
1518
  // Mark the object so createNodeForValue knows to use NullableNodeCore
1431
- return Object.assign(value, { [NULLABLE_MARKER]: true }) as T | null;
1519
+ return Object.assign(value, { [NULLABLE_MARKER]: options }) as T | null;
1432
1520
  }
1433
1521
 
1434
1522
  // Check if a value was marked as nullable
@@ -1436,6 +1524,14 @@ function isNullableMarked<T>(value: T): boolean {
1436
1524
  return value !== null && typeof value === "object" && NULLABLE_MARKER in value;
1437
1525
  }
1438
1526
 
1527
+ // Extract options from a nullable-marked value
1528
+ function getNullableMarkerOptions<T>(value: T): NullableOptions<T> {
1529
+ if (value !== null && typeof value === "object" && NULLABLE_MARKER in value) {
1530
+ return (value as unknown as MarkedNullable<T>)[NULLABLE_MARKER];
1531
+ }
1532
+ return {};
1533
+ }
1534
+
1439
1535
  // Symbol to mark an array with distinct options
1440
1536
  const ARRAY_MARKER = Symbol("array");
1441
1537
 
package/src/index.ts CHANGED
@@ -12,5 +12,5 @@
12
12
  * - selectFromEach() - Select from each array item with precise change detection
13
13
  */
14
14
 
15
- export { state, nullable, array, type RxState, type Draft, type StateOptions, type ArrayOptions, type ArrayDistinct } from "./deepstate";
15
+ export { state, nullable, array, type RxState, type Draft, type StateOptions, type ArrayOptions, type ArrayDistinct, type NullableOptions, type NullableDistinct } from "./deepstate";
16
16
  export { select, selectFromEach } from "./helpers";