@montra-interactive/deepstate 0.1.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/dist/deepstate.d.ts +189 -0
- package/dist/deepstate.d.ts.map +1 -0
- package/dist/deepstate.js +881 -0
- package/dist/deepstate.js.map +1 -0
- package/dist/helpers.d.ts +61 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +48 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +750 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
- package/src/deepstate.ts +1332 -0
- package/src/helpers.ts +138 -0
- package/src/index.ts +15 -0
package/src/helpers.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convenience helpers for deepstate.
|
|
3
|
+
*
|
|
4
|
+
* These are optional utilities that build on top of the core state() function.
|
|
5
|
+
* They can be imported from 'deepstate' or 'deepstate/helpers'.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Observable, combineLatest } from "rxjs";
|
|
9
|
+
import { distinctUntilChanged, map } from "rxjs/operators";
|
|
10
|
+
|
|
11
|
+
// Deep equality check (duplicated to keep helpers independent)
|
|
12
|
+
function deepEqual(a: unknown, b: unknown): boolean {
|
|
13
|
+
if (a === b) return true;
|
|
14
|
+
if (a === null || b === null) return false;
|
|
15
|
+
if (typeof a !== "object" || typeof b !== "object") return false;
|
|
16
|
+
|
|
17
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
18
|
+
if (a.length !== b.length) return false;
|
|
19
|
+
return a.every((item, i) => deepEqual(item, b[i]));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
23
|
+
|
|
24
|
+
const keysA = Object.keys(a);
|
|
25
|
+
const keysB = Object.keys(b);
|
|
26
|
+
if (keysA.length !== keysB.length) return false;
|
|
27
|
+
|
|
28
|
+
return keysA.every((key) =>
|
|
29
|
+
deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key])
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Helper to check if something is an Observable
|
|
34
|
+
function isObservable(obj: unknown): obj is Observable<unknown> {
|
|
35
|
+
return (
|
|
36
|
+
obj !== null &&
|
|
37
|
+
typeof obj === "object" &&
|
|
38
|
+
"subscribe" in obj &&
|
|
39
|
+
typeof (obj as Record<string, unknown>).subscribe === "function"
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Helper type to extract the value type from an Observable
|
|
44
|
+
type ObservableValue<T> = T extends Observable<infer U> ? U : never;
|
|
45
|
+
|
|
46
|
+
// Type for array of observables -> tuple of their values
|
|
47
|
+
type ObservableValues<T extends Observable<unknown>[]> = {
|
|
48
|
+
[K in keyof T]: ObservableValue<T[K]>;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Type for object of observables -> object of their values
|
|
52
|
+
type ObservableObjectValues<T extends Record<string, Observable<unknown>>> = {
|
|
53
|
+
[K in keyof T]: ObservableValue<T[K]>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Select multiple observables and combine them into a single observable.
|
|
58
|
+
*
|
|
59
|
+
* @example Array form - returns tuple
|
|
60
|
+
* ```ts
|
|
61
|
+
* select(myState.user.name, myState.user.profile.address.city)
|
|
62
|
+
* .subscribe(([name, city]) => console.log(name, city));
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* @example Object form - returns object with named keys
|
|
66
|
+
* ```ts
|
|
67
|
+
* select({
|
|
68
|
+
* name: myState.user.name,
|
|
69
|
+
* city: myState.user.profile.address.city,
|
|
70
|
+
* }).subscribe(({ name, city }) => console.log(name, city));
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export function select<T extends Observable<unknown>[]>(
|
|
74
|
+
...observables: T
|
|
75
|
+
): Observable<ObservableValues<T>>;
|
|
76
|
+
export function select<T extends Record<string, Observable<unknown>>>(
|
|
77
|
+
observables: T
|
|
78
|
+
): Observable<ObservableObjectValues<T>>;
|
|
79
|
+
export function select(
|
|
80
|
+
...args: Observable<unknown>[] | [Record<string, Observable<unknown>>]
|
|
81
|
+
): Observable<unknown> {
|
|
82
|
+
// Object form: select({ name: obs1, city: obs2 })
|
|
83
|
+
if (args.length === 1 && !isObservable(args[0])) {
|
|
84
|
+
const obj = args[0] as Record<string, Observable<unknown>>;
|
|
85
|
+
const keys = Object.keys(obj);
|
|
86
|
+
const observables = keys.map((k) => obj[k]);
|
|
87
|
+
|
|
88
|
+
return combineLatest(observables).pipe(
|
|
89
|
+
map((values) => {
|
|
90
|
+
const result: Record<string, unknown> = {};
|
|
91
|
+
keys.forEach((key, i) => {
|
|
92
|
+
result[key] = values[i];
|
|
93
|
+
});
|
|
94
|
+
return result;
|
|
95
|
+
})
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Array form: select(obs1, obs2, obs3)
|
|
100
|
+
return combineLatest(args as Observable<unknown>[]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Select a derived value from each item in an array, with precise change detection.
|
|
105
|
+
* Only emits when the selected/derived values actually change, not when other
|
|
106
|
+
* properties of the array items change.
|
|
107
|
+
*
|
|
108
|
+
* @example Select a single property from each item
|
|
109
|
+
* ```ts
|
|
110
|
+
* selectFromEach(myState.items, item => item.price)
|
|
111
|
+
* .subscribe(prices => {
|
|
112
|
+
* const total = prices.reduce((a, b) => a + b, 0);
|
|
113
|
+
* });
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* @example Select multiple properties / derived values
|
|
117
|
+
* ```ts
|
|
118
|
+
* selectFromEach(myState.items, item => ({
|
|
119
|
+
* name: item.name,
|
|
120
|
+
* total: item.price * item.qty
|
|
121
|
+
* })).subscribe(summaries => console.log(summaries));
|
|
122
|
+
* ```
|
|
123
|
+
*
|
|
124
|
+
* @param arrayNode - An RxArray node from your state (e.g., `myState.items`)
|
|
125
|
+
* @param selector - A function that extracts/derives a value from each item
|
|
126
|
+
* @returns An Observable that emits an array of selected values
|
|
127
|
+
*/
|
|
128
|
+
export function selectFromEach<T, U>(
|
|
129
|
+
arrayNode: Observable<ReadonlyArray<T>>,
|
|
130
|
+
selector: (item: T, index: number) => U
|
|
131
|
+
): Observable<U[]> {
|
|
132
|
+
return arrayNode.pipe(
|
|
133
|
+
map((items) => items.map(selector)),
|
|
134
|
+
distinctUntilChanged((a, b) =>
|
|
135
|
+
a.length === b.length && a.every((v, i) => deepEqual(v, b[i]))
|
|
136
|
+
)
|
|
137
|
+
);
|
|
138
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* deepstate - Proxy-based RxJS state management
|
|
3
|
+
*
|
|
4
|
+
* Core exports:
|
|
5
|
+
* - state() - Create reactive state from plain objects
|
|
6
|
+
* - nullable() - Mark a property as nullable (can transition between null and object)
|
|
7
|
+
* - RxState, Draft - Type exports
|
|
8
|
+
*
|
|
9
|
+
* Helper exports:
|
|
10
|
+
* - select() - Combine multiple observables
|
|
11
|
+
* - selectFromEach() - Select from each array item with precise change detection
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export { state, nullable, type RxState, type Draft } from "./deepstate";
|
|
15
|
+
export { select, selectFromEach } from "./helpers";
|