@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 +15 -1
- package/dist/deepstate.d.ts +16 -1
- package/dist/deepstate.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +56 -12
- package/package.json +1 -1
- package/src/deepstate.ts +106 -10
- package/src/index.ts +1 -1
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
|
package/dist/deepstate.d.ts
CHANGED
|
@@ -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 */
|
package/dist/deepstate.d.ts.map
CHANGED
|
@@ -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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
})
|
|
277
|
-
|
|
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
|
-
|
|
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
|
|
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]:
|
|
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
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
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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
|
-
|
|
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]:
|
|
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";
|