@international-iot-association/rct-state 3.0.0-rc.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 ADDED
@@ -0,0 +1,87 @@
1
+ # @international-iot-association/rct-state
2
+
3
+ Legend-State 风格的 RxJS + Immutable 响应式状态库,供工位插件的 React UI 使用。
4
+ 属于**组 A · 协议锁步组**(与 `plugin-contracts`/`plugin-sdk`/`api-bridge` 一起随 `agentApi` major 锁步发布)。
5
+
6
+ ## 安装
7
+
8
+ ```bash
9
+ npm i @international-iot-association/rct-state
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ Basic usage for pure JavaScript:
15
+
16
+ ```tsx
17
+ // file: userData.ts
18
+
19
+ import { observable } from 'rct-state';
20
+
21
+ // define type for state
22
+ interface User {
23
+ id: number;
24
+ name: string;
25
+ }
26
+
27
+ const user: User = {
28
+ id: 1,
29
+ name: 'jojo',
30
+ }
31
+
32
+ // create store
33
+ export const user$ = observable(user);
34
+
35
+ // get user id
36
+ const userId = user$.id.get();
37
+
38
+ // set user id
39
+ user$.id.set(99);
40
+
41
+ // watch user id change
42
+ user$.observe((state) => {
43
+ return state.id
44
+ }, (id) => {
45
+ console.log('user id changed:', id)
46
+ })
47
+
48
+ ```
49
+
50
+ Basic usage for React Native or React component:
51
+
52
+ ```tsx
53
+ import React from 'react';
54
+ import { View, Text } from 'react-native'
55
+ import { user$ } from './userData'
56
+
57
+
58
+ export function UserPanel() {
59
+ const { id, name } = user$.use()
60
+ const userId = user$.id.use()
61
+ const username = user$.useSelector((state) => state.id)
62
+
63
+ // watch value change
64
+ user$.useObserve((state) => state.name, username => {
65
+ console.log(`user name changed: ${username}`)
66
+ })
67
+
68
+ return (
69
+ <View>
70
+ <Text>ID: { id }</Text>
71
+ <Text>Name: { name }</Text>
72
+ </View>
73
+ )
74
+ }
75
+ ```
76
+
77
+ ## Contributing
78
+
79
+ See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
80
+
81
+ ## License
82
+
83
+ MIT
84
+
85
+ ---
86
+
87
+ Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
package/dist/index.cjs ADDED
@@ -0,0 +1,554 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ createDeepProxy: () => createDeepProxy,
34
+ observable: () => observable
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/proxy/wrapper.ts
39
+ var Wrapper = class {
40
+ value;
41
+ keyPath = [];
42
+ originalObject;
43
+ onSet;
44
+ onUse;
45
+ proxy = new Proxy(this, {
46
+ get(target, property) {
47
+ if (property in target) {
48
+ return target[property];
49
+ } else {
50
+ return target.value;
51
+ }
52
+ }
53
+ });
54
+ constructor(value, keyPath = [], originalObject, onSet, onUse) {
55
+ this.value = value;
56
+ this.keyPath = keyPath;
57
+ this.originalObject = originalObject;
58
+ this.onSet = onSet;
59
+ this.onUse = onUse;
60
+ return this.proxy;
61
+ }
62
+ valueOf() {
63
+ return this.value;
64
+ }
65
+ get() {
66
+ return this.peek();
67
+ }
68
+ peek() {
69
+ const value = this.originalObject().getIn(this.keyPath);
70
+ if (typeof value === "function") {
71
+ return value.bind(this.originalObject)();
72
+ } else {
73
+ return value;
74
+ }
75
+ }
76
+ get __keyPath() {
77
+ return this.keyPath;
78
+ }
79
+ get __self() {
80
+ return {
81
+ value: this.value,
82
+ keyPath: this.keyPath
83
+ };
84
+ }
85
+ toString() {
86
+ return this.keyPath.join(".");
87
+ }
88
+ toJSON() {
89
+ return JSON.stringify(this.keyPath);
90
+ }
91
+ set(value) {
92
+ this.onSet(value, this.keyPath);
93
+ }
94
+ use() {
95
+ return this.onUse(this.keyPath);
96
+ }
97
+ };
98
+
99
+ // src/proxy/wrapObject.ts
100
+ var import_immutable = __toESM(require("immutable"), 1);
101
+ function wrapObject(obj, keyPath = [], originalObject, onSet, onUse, _2) {
102
+ return new Proxy(obj, {
103
+ get(target, prop, receiver) {
104
+ switch (prop) {
105
+ case "get":
106
+ return () => originalObject().getIn(keyPath);
107
+ case "peek":
108
+ return () => originalObject().getIn(keyPath);
109
+ case "set":
110
+ return (value) => {
111
+ onSet && onSet(value, keyPath);
112
+ };
113
+ case "use":
114
+ return () => {
115
+ return onUse(keyPath);
116
+ };
117
+ case "__keyPath":
118
+ return keyPath;
119
+ case "isImmutableList":
120
+ return import_immutable.default.isList(target);
121
+ case "isImmutableMap":
122
+ return import_immutable.default.isMap(target);
123
+ case "__self":
124
+ return target;
125
+ // list functions
126
+ case "delete":
127
+ case "remove":
128
+ return (value) => {
129
+ if (import_immutable.default.isList(originalObject().getIn(keyPath))) {
130
+ originalObject().getIn(keyPath).delete(value);
131
+ }
132
+ };
133
+ case "push":
134
+ return (...values) => {
135
+ const targetList = originalObject().getIn(keyPath);
136
+ if (!import_immutable.default.isList(targetList) && !Array.isArray(targetList)) {
137
+ return;
138
+ }
139
+ onSet && onSet([...targetList, ...values], keyPath);
140
+ };
141
+ // default
142
+ default:
143
+ return Reflect.get(target, prop, receiver);
144
+ }
145
+ }
146
+ });
147
+ }
148
+
149
+ // src/proxy/index.ts
150
+ function createDeepProxy(target, path = [], originalObject, onSet, onUse, notifyChange) {
151
+ return new Proxy(target, {
152
+ get(_target, property) {
153
+ if (property === "__self") {
154
+ return _target;
155
+ }
156
+ const keyPath = path.concat([property.toString()]);
157
+ const realValue = originalObject().getIn(keyPath);
158
+ if (typeof realValue === "object" && realValue !== null) {
159
+ return wrapObject(
160
+ // @ts-ignore
161
+ createDeepProxy(
162
+ realValue,
163
+ path.concat([property.toString()]),
164
+ originalObject,
165
+ onSet,
166
+ onUse
167
+ ),
168
+ keyPath,
169
+ originalObject,
170
+ onSet,
171
+ onUse,
172
+ notifyChange
173
+ // realValue
174
+ );
175
+ }
176
+ return new Wrapper(realValue, keyPath, originalObject, onSet, onUse);
177
+ },
178
+ set(_target, property, value, receiver) {
179
+ return Reflect.set(_target, property, value, receiver);
180
+ }
181
+ });
182
+ }
183
+
184
+ // src/manager.ts
185
+ var import_rxjs2 = require("rxjs");
186
+
187
+ // src/hooks/use.ts
188
+ var import_react2 = require("react");
189
+ var import_react_fast_compare2 = __toESM(require("react-fast-compare"), 1);
190
+
191
+ // src/utils/subscribe.ts
192
+ var import_react_fast_compare = __toESM(require("react-fast-compare"), 1);
193
+ var import_rxjs = require("rxjs");
194
+ var import_lodash = __toESM(require("lodash"), 1);
195
+ function generateSubForSpecificChange(config) {
196
+ const defaultValue = config.subject.value ? config.subject.value : config.defaultData ? config.filter(config.defaultData) : void 0;
197
+ const defaultFilteredData = defaultValue !== void 0 ? config.filter(defaultValue) : void 0;
198
+ const sub = config.subject.pipe(
199
+ (0, import_rxjs.startWith)(void 0),
200
+ (0, import_rxjs.map)((data) => {
201
+ if (data === void 0) {
202
+ return void 0;
203
+ }
204
+ return import_lodash.default.cloneDeep(config.filter(data));
205
+ }),
206
+ (0, import_rxjs.pairwise)(),
207
+ (0, import_rxjs.filter)(([prev, next]) => {
208
+ return !(0, import_react_fast_compare.default)(prev, next);
209
+ }),
210
+ (0, import_rxjs.defaultIfEmpty)([defaultFilteredData, defaultFilteredData])
211
+ );
212
+ return sub;
213
+ }
214
+
215
+ // src/hooks/useState.ts
216
+ var import_react = require("react");
217
+ var import_lodash2 = require("lodash");
218
+
219
+ // src/utils/isSSR.ts
220
+ var isBrowser = typeof window !== "undefined";
221
+ var isReactNative = typeof navigator !== "undefined" && navigator.product === "ReactNative";
222
+ var isSSR = !(isBrowser || isReactNative);
223
+
224
+ // src/hooks/useState.ts
225
+ function useStateForClient(initialState) {
226
+ const stateValue = (0, import_react.useRef)(initialState || {});
227
+ const [state, _setState] = (0, import_react.useState)(initialState);
228
+ const setState = (value) => {
229
+ const valueIsFunction = typeof value === "function";
230
+ const targetValue = valueIsFunction ? value(stateValue.current) : value;
231
+ if (!(0, import_lodash2.isEqual)(targetValue, stateValue.current)) {
232
+ stateValue.current = targetValue;
233
+ _setState(targetValue);
234
+ }
235
+ };
236
+ return [state, setState];
237
+ }
238
+ var useState = isSSR ? import_react.useState : useStateForClient;
239
+
240
+ // src/hooks/use.ts
241
+ function useFunc({ keyPath, subSource, getState }) {
242
+ const valueRef = (0, import_react2.useRef)(getState().getIn(keyPath));
243
+ const [value, _setValue] = useState(valueRef.current);
244
+ const setValue = (0, import_react2.useCallback)((nextValue) => {
245
+ if (!(0, import_react_fast_compare2.default)(valueRef.current, nextValue)) {
246
+ valueRef.current = nextValue;
247
+ _setValue(nextValue);
248
+ }
249
+ }, []);
250
+ (0, import_react2.useEffect)(() => {
251
+ const sub = generateSubForSpecificChange({
252
+ subject: subSource,
253
+ filter: () => {
254
+ return getState().getIn(keyPath);
255
+ },
256
+ mark: "useFunc"
257
+ }).subscribe(([_2, next]) => {
258
+ setValue(next);
259
+ });
260
+ return () => {
261
+ if (sub && sub.unsubscribe) {
262
+ sub.unsubscribe();
263
+ }
264
+ };
265
+ }, [keyPath, subSource, getState]);
266
+ return value;
267
+ }
268
+
269
+ // src/hooks/useObserve.ts
270
+ var import_react3 = require("react");
271
+
272
+ // src/proxy/immutableGetter.ts
273
+ var import_immutable2 = require("immutable");
274
+ function createProxiedList(list) {
275
+ return new Proxy(list, {
276
+ get(target, key) {
277
+ const numKey = Number(key);
278
+ if (key === "__self") {
279
+ return target;
280
+ }
281
+ if (key === "isImmutableList") {
282
+ return true;
283
+ }
284
+ if (Number.isNaN(numKey)) {
285
+ return Reflect.get(target, key);
286
+ }
287
+ const value = target.get(numKey);
288
+ if (import_immutable2.Map.isMap(value)) {
289
+ return createProxiedMap(value);
290
+ } else if (import_immutable2.List.isList(value)) {
291
+ return createProxiedList(value);
292
+ }
293
+ return value;
294
+ },
295
+ set(target, key, value) {
296
+ const numKey = Number(key);
297
+ if (Number.isNaN(numKey)) {
298
+ return false;
299
+ }
300
+ target.set(numKey, value);
301
+ return true;
302
+ }
303
+ });
304
+ }
305
+ function createProxiedMap(map2) {
306
+ return new Proxy(map2, {
307
+ get(target, key) {
308
+ const mapValue = target.get(key);
309
+ if (mapValue === void 0) {
310
+ return mapValue;
311
+ }
312
+ if (key === "__self") {
313
+ return target;
314
+ }
315
+ if (key === "isImmutableMap") {
316
+ return true;
317
+ }
318
+ if (import_immutable2.Map.isMap(mapValue)) {
319
+ return createProxiedMap(
320
+ mapValue
321
+ );
322
+ } else if (import_immutable2.List.isList(mapValue)) {
323
+ return createProxiedList(
324
+ mapValue
325
+ );
326
+ }
327
+ return mapValue;
328
+ },
329
+ set(target, key, value) {
330
+ target.set(key, value);
331
+ return true;
332
+ }
333
+ });
334
+ }
335
+ function wrapGetterForMap(data) {
336
+ return createProxiedMap(data);
337
+ }
338
+ function wrapAsImmutable(data) {
339
+ const mapData = (0, import_immutable2.Map)(data);
340
+ return {
341
+ getter: createProxiedMap(mapData),
342
+ mapData
343
+ };
344
+ }
345
+
346
+ // src/hooks/useObserve.ts
347
+ function useObserve({
348
+ selectorFunction,
349
+ subSource,
350
+ onChangeFunction,
351
+ getState
352
+ }) {
353
+ (0, import_react3.useEffect)(() => {
354
+ const sub = generateSubForSpecificChange({
355
+ subject: subSource,
356
+ filter: () => {
357
+ return selectorFunction(wrapGetterForMap(getState()));
358
+ },
359
+ mark: "useObserve"
360
+ }).subscribe(([_2, next]) => {
361
+ onChangeFunction(next);
362
+ });
363
+ return () => {
364
+ if (sub && sub.unsubscribe) {
365
+ sub.unsubscribe();
366
+ }
367
+ };
368
+ }, [subSource, getState, onChangeFunction, selectorFunction]);
369
+ }
370
+
371
+ // src/hooks/useSelector.ts
372
+ var import_react4 = require("react");
373
+
374
+ // src/utils/isImmutableObject.ts
375
+ var IMMUTABLE_ITERABLE = "@@__IMMUTABLE_ITERABLE__@@";
376
+ var IMMUTABLE_RECORD = "@@__IMMUTABLE_RECORD__@@";
377
+ var IMMUTABLE_LIST = "@@__IMMUTABLE_LIST__@@";
378
+ var IMMUTABLE_MAP = "@@__IMMUTABLE_MAP__@@";
379
+ function isImmutableObject(obj) {
380
+ if (typeof obj !== "object") {
381
+ return false;
382
+ }
383
+ const proto = Object.getPrototypeOf(obj);
384
+ if (proto[IMMUTABLE_ITERABLE] || proto[IMMUTABLE_RECORD] || proto[IMMUTABLE_LIST] || proto[IMMUTABLE_MAP]) {
385
+ return true;
386
+ }
387
+ return false;
388
+ }
389
+
390
+ // src/hooks/useSelector.ts
391
+ function useSelector({
392
+ selectorFunction,
393
+ subSource,
394
+ getState
395
+ }) {
396
+ const wrappedSelectorFunction = (0, import_react4.useCallback)(
397
+ (getJS = false) => () => {
398
+ const state = getState();
399
+ const res = selectorFunction(wrapGetterForMap(state));
400
+ if (isImmutableObject(res)) {
401
+ if (getJS) {
402
+ return state.toJS();
403
+ }
404
+ return state;
405
+ }
406
+ return res;
407
+ },
408
+ [selectorFunction, getState]
409
+ );
410
+ const [value, setValue] = useState(wrappedSelectorFunction(true)());
411
+ (0, import_react4.useEffect)(() => {
412
+ const sub = generateSubForSpecificChange({
413
+ subject: subSource,
414
+ filter: wrappedSelectorFunction(),
415
+ mark: "useSelector"
416
+ }).subscribe(([_2, next]) => {
417
+ if (isImmutableObject(next)) {
418
+ setValue(next.toJS());
419
+ } else {
420
+ setValue(next);
421
+ }
422
+ });
423
+ return () => {
424
+ if (sub && sub.unsubscribe) {
425
+ sub.unsubscribe();
426
+ }
427
+ };
428
+ }, [wrappedSelectorFunction, getState, subSource]);
429
+ return value;
430
+ }
431
+
432
+ // src/manager.ts
433
+ var ObserverableManager = class {
434
+ state;
435
+ subject;
436
+ _state$;
437
+ batchProcessing = false;
438
+ stateGetter;
439
+ get state$() {
440
+ return new Proxy(this._state$, {
441
+ get: (target, property, receiver) => {
442
+ switch (property) {
443
+ case "useSelector":
444
+ return this.useSelector;
445
+ case "useObserve":
446
+ return this.useObserve;
447
+ case "batch":
448
+ return this.batch;
449
+ case "peek":
450
+ return this.peek;
451
+ case "observe":
452
+ return this.observe;
453
+ case "__immutable__":
454
+ return this.state;
455
+ default:
456
+ return Reflect.get(target, property, receiver);
457
+ }
458
+ },
459
+ set: () => {
460
+ return false;
461
+ }
462
+ });
463
+ }
464
+ constructor(state) {
465
+ const wrappedState = wrapAsImmutable(state);
466
+ this.state = wrappedState.mapData;
467
+ this.stateGetter = wrappedState.getter;
468
+ this.subject = new import_rxjs2.BehaviorSubject(this.state);
469
+ this._state$ = createDeepProxy(
470
+ state,
471
+ [],
472
+ this.getCurrentState,
473
+ this.onSet,
474
+ this.onUse,
475
+ this.notifyChange
476
+ );
477
+ }
478
+ /**
479
+ * This method will be utilized by the 'get' method of the Proxy and the subscription logic within hooks,
480
+ * with the aim of obtaining the latest 'state' reference.
481
+ * @returns
482
+ */
483
+ getCurrentState = () => {
484
+ return this.state;
485
+ };
486
+ onSet = (value, keyPath) => {
487
+ this.state = this.state.setIn(keyPath, value);
488
+ this.notifyChange();
489
+ };
490
+ notifyChange = () => {
491
+ if (this.batchProcessing) {
492
+ return;
493
+ }
494
+ this.subject.next(this.state);
495
+ };
496
+ onUse = (keyPath) => {
497
+ return useFunc({
498
+ keyPath,
499
+ subSource: this.subject,
500
+ getState: this.getCurrentState
501
+ });
502
+ };
503
+ useSelector = (selector) => {
504
+ return useSelector({
505
+ selectorFunction: selector,
506
+ subSource: this.subject,
507
+ getState: this.getCurrentState
508
+ });
509
+ };
510
+ useObserve = (selector, onChange) => {
511
+ useObserve({
512
+ selectorFunction: selector,
513
+ subSource: this.subject,
514
+ onChangeFunction: onChange,
515
+ getState: this.getCurrentState
516
+ });
517
+ };
518
+ batch = (batchAction) => {
519
+ this.batchProcessing = true;
520
+ batchAction();
521
+ this.batchProcessing = false;
522
+ this.notifyChange();
523
+ };
524
+ peek = () => {
525
+ return this.state.toJS();
526
+ };
527
+ get = () => {
528
+ return this.peek();
529
+ };
530
+ observe = (selector, onChange) => {
531
+ const sub = generateSubForSpecificChange({
532
+ subject: this.subject,
533
+ filter: () => {
534
+ const res = selector(wrapGetterForMap(this.state));
535
+ return res;
536
+ }
537
+ }).subscribe(([_2, next]) => {
538
+ onChange(next);
539
+ });
540
+ return () => {
541
+ if (sub && sub.unsubscribe) {
542
+ sub.unsubscribe();
543
+ }
544
+ };
545
+ };
546
+ };
547
+ function observable(state) {
548
+ return new ObserverableManager(state).state$;
549
+ }
550
+ // Annotate the CommonJS export names for ESM import in node:
551
+ 0 && (module.exports = {
552
+ createDeepProxy,
553
+ observable
554
+ });
@@ -0,0 +1,59 @@
1
+ import Immutable from 'immutable';
2
+
3
+ declare class Wrapper<T extends any> {
4
+ value: any;
5
+ keyPath: string[];
6
+ originalObject: () => Immutable.Map<keyof T, T[keyof T]>;
7
+ onSet: OnSet<T>;
8
+ onUse: OnUse<T>;
9
+ private proxy;
10
+ constructor(value: any, keyPath: string[] | undefined, originalObject: () => Immutable.Map<keyof T, T[keyof T]>, onSet: OnSet<T>, onUse: OnUse<T>);
11
+ valueOf(): any;
12
+ get(): T extends (...args: any[]) => infer R ? R : T;
13
+ peek(): T extends (...args: any[]) => infer R ? R : T;
14
+ get __keyPath(): string[];
15
+ get __self(): {
16
+ value: any;
17
+ keyPath: string[];
18
+ };
19
+ toString(): string;
20
+ toJSON(): string;
21
+ set(value: T): void;
22
+ use(): T;
23
+ }
24
+
25
+ type Unwrap<T> = T extends Wrapper<infer U> ? U : T;
26
+ type FunctionType = (...args: any[]) => any;
27
+ type WrapTypeForNormal<P extends object> = {
28
+ get(): P extends (...args: any[]) => infer R ? R : P;
29
+ peek(): P extends (...args: any[]) => infer R ? R : P;
30
+ set(value: P): void;
31
+ use(): P;
32
+ __keyPath: string[];
33
+ };
34
+ type WrapTypeForFunc<P extends object> = {
35
+ get(): P extends (...args: any[]) => infer R ? R : P;
36
+ peek(): P extends (...args: any[]) => infer R ? R : P;
37
+ use(): P;
38
+ __keyPath: string[];
39
+ };
40
+ type WrapType<T extends object, P extends object> = (P extends FunctionType ? WrapTypeForFunc<P> : WrapTypeForNormal<P>) & T;
41
+ type DeepProxy<T> = {
42
+ [P in keyof T]: T[P] extends object ? WrapType<DeepProxy<T[P]>, T[P]> : Wrapper<T[P]>;
43
+ };
44
+ type OnSet<T extends any> = (value: T, keyPath: string[]) => void;
45
+ type OnUse<T extends any> = (keyPath: string[]) => T;
46
+
47
+ declare function createDeepProxy<T extends object>(target: T, path: string[] | undefined, originalObject: () => Immutable.Map<keyof T, T[keyof T]>, onSet: OnSet<T>, onUse: OnUse<T>, notifyChange: () => void): DeepProxy<T>;
48
+
49
+ type Observable<T> = DeepProxy<T> & {
50
+ useSelector: <R>(selector: (state: T) => R) => Unwrap<R>;
51
+ useObserve: <P>(selector: (state: T) => P, onChange: (value: Unwrap<P>) => void) => void;
52
+ batch: (batchAction: () => void) => void;
53
+ peek: () => T;
54
+ observe: <P>(selector: (state: T) => P, onChange: (value: P) => void) => () => void;
55
+ __immutable__: Immutable.Map<keyof T, T[keyof T]>;
56
+ };
57
+ declare function observable<T extends object>(state: T): Observable<T>;
58
+
59
+ export { type DeepProxy, type Observable, createDeepProxy, observable };
@@ -0,0 +1,59 @@
1
+ import Immutable from 'immutable';
2
+
3
+ declare class Wrapper<T extends any> {
4
+ value: any;
5
+ keyPath: string[];
6
+ originalObject: () => Immutable.Map<keyof T, T[keyof T]>;
7
+ onSet: OnSet<T>;
8
+ onUse: OnUse<T>;
9
+ private proxy;
10
+ constructor(value: any, keyPath: string[] | undefined, originalObject: () => Immutable.Map<keyof T, T[keyof T]>, onSet: OnSet<T>, onUse: OnUse<T>);
11
+ valueOf(): any;
12
+ get(): T extends (...args: any[]) => infer R ? R : T;
13
+ peek(): T extends (...args: any[]) => infer R ? R : T;
14
+ get __keyPath(): string[];
15
+ get __self(): {
16
+ value: any;
17
+ keyPath: string[];
18
+ };
19
+ toString(): string;
20
+ toJSON(): string;
21
+ set(value: T): void;
22
+ use(): T;
23
+ }
24
+
25
+ type Unwrap<T> = T extends Wrapper<infer U> ? U : T;
26
+ type FunctionType = (...args: any[]) => any;
27
+ type WrapTypeForNormal<P extends object> = {
28
+ get(): P extends (...args: any[]) => infer R ? R : P;
29
+ peek(): P extends (...args: any[]) => infer R ? R : P;
30
+ set(value: P): void;
31
+ use(): P;
32
+ __keyPath: string[];
33
+ };
34
+ type WrapTypeForFunc<P extends object> = {
35
+ get(): P extends (...args: any[]) => infer R ? R : P;
36
+ peek(): P extends (...args: any[]) => infer R ? R : P;
37
+ use(): P;
38
+ __keyPath: string[];
39
+ };
40
+ type WrapType<T extends object, P extends object> = (P extends FunctionType ? WrapTypeForFunc<P> : WrapTypeForNormal<P>) & T;
41
+ type DeepProxy<T> = {
42
+ [P in keyof T]: T[P] extends object ? WrapType<DeepProxy<T[P]>, T[P]> : Wrapper<T[P]>;
43
+ };
44
+ type OnSet<T extends any> = (value: T, keyPath: string[]) => void;
45
+ type OnUse<T extends any> = (keyPath: string[]) => T;
46
+
47
+ declare function createDeepProxy<T extends object>(target: T, path: string[] | undefined, originalObject: () => Immutable.Map<keyof T, T[keyof T]>, onSet: OnSet<T>, onUse: OnUse<T>, notifyChange: () => void): DeepProxy<T>;
48
+
49
+ type Observable<T> = DeepProxy<T> & {
50
+ useSelector: <R>(selector: (state: T) => R) => Unwrap<R>;
51
+ useObserve: <P>(selector: (state: T) => P, onChange: (value: Unwrap<P>) => void) => void;
52
+ batch: (batchAction: () => void) => void;
53
+ peek: () => T;
54
+ observe: <P>(selector: (state: T) => P, onChange: (value: P) => void) => () => void;
55
+ __immutable__: Immutable.Map<keyof T, T[keyof T]>;
56
+ };
57
+ declare function observable<T extends object>(state: T): Observable<T>;
58
+
59
+ export { type DeepProxy, type Observable, createDeepProxy, observable };
package/dist/index.js ADDED
@@ -0,0 +1,522 @@
1
+ // src/proxy/wrapper.ts
2
+ var Wrapper = class {
3
+ value;
4
+ keyPath = [];
5
+ originalObject;
6
+ onSet;
7
+ onUse;
8
+ proxy = new Proxy(this, {
9
+ get(target, property) {
10
+ if (property in target) {
11
+ return target[property];
12
+ } else {
13
+ return target.value;
14
+ }
15
+ }
16
+ });
17
+ constructor(value, keyPath = [], originalObject, onSet, onUse) {
18
+ this.value = value;
19
+ this.keyPath = keyPath;
20
+ this.originalObject = originalObject;
21
+ this.onSet = onSet;
22
+ this.onUse = onUse;
23
+ return this.proxy;
24
+ }
25
+ valueOf() {
26
+ return this.value;
27
+ }
28
+ get() {
29
+ return this.peek();
30
+ }
31
+ peek() {
32
+ const value = this.originalObject().getIn(this.keyPath);
33
+ if (typeof value === "function") {
34
+ return value.bind(this.originalObject)();
35
+ } else {
36
+ return value;
37
+ }
38
+ }
39
+ get __keyPath() {
40
+ return this.keyPath;
41
+ }
42
+ get __self() {
43
+ return {
44
+ value: this.value,
45
+ keyPath: this.keyPath
46
+ };
47
+ }
48
+ toString() {
49
+ return this.keyPath.join(".");
50
+ }
51
+ toJSON() {
52
+ return JSON.stringify(this.keyPath);
53
+ }
54
+ set(value) {
55
+ this.onSet(value, this.keyPath);
56
+ }
57
+ use() {
58
+ return this.onUse(this.keyPath);
59
+ }
60
+ };
61
+
62
+ // src/proxy/wrapObject.ts
63
+ import Immutable from "immutable";
64
+ function wrapObject(obj, keyPath = [], originalObject, onSet, onUse, _2) {
65
+ return new Proxy(obj, {
66
+ get(target, prop, receiver) {
67
+ switch (prop) {
68
+ case "get":
69
+ return () => originalObject().getIn(keyPath);
70
+ case "peek":
71
+ return () => originalObject().getIn(keyPath);
72
+ case "set":
73
+ return (value) => {
74
+ onSet && onSet(value, keyPath);
75
+ };
76
+ case "use":
77
+ return () => {
78
+ return onUse(keyPath);
79
+ };
80
+ case "__keyPath":
81
+ return keyPath;
82
+ case "isImmutableList":
83
+ return Immutable.isList(target);
84
+ case "isImmutableMap":
85
+ return Immutable.isMap(target);
86
+ case "__self":
87
+ return target;
88
+ // list functions
89
+ case "delete":
90
+ case "remove":
91
+ return (value) => {
92
+ if (Immutable.isList(originalObject().getIn(keyPath))) {
93
+ originalObject().getIn(keyPath).delete(value);
94
+ }
95
+ };
96
+ case "push":
97
+ return (...values) => {
98
+ const targetList = originalObject().getIn(keyPath);
99
+ if (!Immutable.isList(targetList) && !Array.isArray(targetList)) {
100
+ return;
101
+ }
102
+ onSet && onSet([...targetList, ...values], keyPath);
103
+ };
104
+ // default
105
+ default:
106
+ return Reflect.get(target, prop, receiver);
107
+ }
108
+ }
109
+ });
110
+ }
111
+
112
+ // src/proxy/index.ts
113
+ function createDeepProxy(target, path = [], originalObject, onSet, onUse, notifyChange) {
114
+ return new Proxy(target, {
115
+ get(_target, property) {
116
+ if (property === "__self") {
117
+ return _target;
118
+ }
119
+ const keyPath = path.concat([property.toString()]);
120
+ const realValue = originalObject().getIn(keyPath);
121
+ if (typeof realValue === "object" && realValue !== null) {
122
+ return wrapObject(
123
+ // @ts-ignore
124
+ createDeepProxy(
125
+ realValue,
126
+ path.concat([property.toString()]),
127
+ originalObject,
128
+ onSet,
129
+ onUse
130
+ ),
131
+ keyPath,
132
+ originalObject,
133
+ onSet,
134
+ onUse,
135
+ notifyChange
136
+ // realValue
137
+ );
138
+ }
139
+ return new Wrapper(realValue, keyPath, originalObject, onSet, onUse);
140
+ },
141
+ set(_target, property, value, receiver) {
142
+ return Reflect.set(_target, property, value, receiver);
143
+ }
144
+ });
145
+ }
146
+
147
+ // src/manager.ts
148
+ import { BehaviorSubject } from "rxjs";
149
+
150
+ // src/hooks/use.ts
151
+ import { useCallback, useEffect, useRef as useRef2 } from "react";
152
+ import isEqual3 from "react-fast-compare";
153
+
154
+ // src/utils/subscribe.ts
155
+ import isEqual from "react-fast-compare";
156
+ import {
157
+ defaultIfEmpty,
158
+ filter,
159
+ map,
160
+ pairwise,
161
+ startWith
162
+ } from "rxjs";
163
+ import _ from "lodash";
164
+ function generateSubForSpecificChange(config) {
165
+ const defaultValue = config.subject.value ? config.subject.value : config.defaultData ? config.filter(config.defaultData) : void 0;
166
+ const defaultFilteredData = defaultValue !== void 0 ? config.filter(defaultValue) : void 0;
167
+ const sub = config.subject.pipe(
168
+ startWith(void 0),
169
+ map((data) => {
170
+ if (data === void 0) {
171
+ return void 0;
172
+ }
173
+ return _.cloneDeep(config.filter(data));
174
+ }),
175
+ pairwise(),
176
+ filter(([prev, next]) => {
177
+ return !isEqual(prev, next);
178
+ }),
179
+ defaultIfEmpty([defaultFilteredData, defaultFilteredData])
180
+ );
181
+ return sub;
182
+ }
183
+
184
+ // src/hooks/useState.ts
185
+ import { useRef, useState as ReactUseState } from "react";
186
+ import { isEqual as isEqual2 } from "lodash";
187
+
188
+ // src/utils/isSSR.ts
189
+ var isBrowser = typeof window !== "undefined";
190
+ var isReactNative = typeof navigator !== "undefined" && navigator.product === "ReactNative";
191
+ var isSSR = !(isBrowser || isReactNative);
192
+
193
+ // src/hooks/useState.ts
194
+ function useStateForClient(initialState) {
195
+ const stateValue = useRef(initialState || {});
196
+ const [state, _setState] = ReactUseState(initialState);
197
+ const setState = (value) => {
198
+ const valueIsFunction = typeof value === "function";
199
+ const targetValue = valueIsFunction ? value(stateValue.current) : value;
200
+ if (!isEqual2(targetValue, stateValue.current)) {
201
+ stateValue.current = targetValue;
202
+ _setState(targetValue);
203
+ }
204
+ };
205
+ return [state, setState];
206
+ }
207
+ var useState = isSSR ? ReactUseState : useStateForClient;
208
+
209
+ // src/hooks/use.ts
210
+ function useFunc({ keyPath, subSource, getState }) {
211
+ const valueRef = useRef2(getState().getIn(keyPath));
212
+ const [value, _setValue] = useState(valueRef.current);
213
+ const setValue = useCallback((nextValue) => {
214
+ if (!isEqual3(valueRef.current, nextValue)) {
215
+ valueRef.current = nextValue;
216
+ _setValue(nextValue);
217
+ }
218
+ }, []);
219
+ useEffect(() => {
220
+ const sub = generateSubForSpecificChange({
221
+ subject: subSource,
222
+ filter: () => {
223
+ return getState().getIn(keyPath);
224
+ },
225
+ mark: "useFunc"
226
+ }).subscribe(([_2, next]) => {
227
+ setValue(next);
228
+ });
229
+ return () => {
230
+ if (sub && sub.unsubscribe) {
231
+ sub.unsubscribe();
232
+ }
233
+ };
234
+ }, [keyPath, subSource, getState]);
235
+ return value;
236
+ }
237
+
238
+ // src/hooks/useObserve.ts
239
+ import { useEffect as useEffect2 } from "react";
240
+
241
+ // src/proxy/immutableGetter.ts
242
+ import { Map, List } from "immutable";
243
+ function createProxiedList(list) {
244
+ return new Proxy(list, {
245
+ get(target, key) {
246
+ const numKey = Number(key);
247
+ if (key === "__self") {
248
+ return target;
249
+ }
250
+ if (key === "isImmutableList") {
251
+ return true;
252
+ }
253
+ if (Number.isNaN(numKey)) {
254
+ return Reflect.get(target, key);
255
+ }
256
+ const value = target.get(numKey);
257
+ if (Map.isMap(value)) {
258
+ return createProxiedMap(value);
259
+ } else if (List.isList(value)) {
260
+ return createProxiedList(value);
261
+ }
262
+ return value;
263
+ },
264
+ set(target, key, value) {
265
+ const numKey = Number(key);
266
+ if (Number.isNaN(numKey)) {
267
+ return false;
268
+ }
269
+ target.set(numKey, value);
270
+ return true;
271
+ }
272
+ });
273
+ }
274
+ function createProxiedMap(map2) {
275
+ return new Proxy(map2, {
276
+ get(target, key) {
277
+ const mapValue = target.get(key);
278
+ if (mapValue === void 0) {
279
+ return mapValue;
280
+ }
281
+ if (key === "__self") {
282
+ return target;
283
+ }
284
+ if (key === "isImmutableMap") {
285
+ return true;
286
+ }
287
+ if (Map.isMap(mapValue)) {
288
+ return createProxiedMap(
289
+ mapValue
290
+ );
291
+ } else if (List.isList(mapValue)) {
292
+ return createProxiedList(
293
+ mapValue
294
+ );
295
+ }
296
+ return mapValue;
297
+ },
298
+ set(target, key, value) {
299
+ target.set(key, value);
300
+ return true;
301
+ }
302
+ });
303
+ }
304
+ function wrapGetterForMap(data) {
305
+ return createProxiedMap(data);
306
+ }
307
+ function wrapAsImmutable(data) {
308
+ const mapData = Map(data);
309
+ return {
310
+ getter: createProxiedMap(mapData),
311
+ mapData
312
+ };
313
+ }
314
+
315
+ // src/hooks/useObserve.ts
316
+ function useObserve({
317
+ selectorFunction,
318
+ subSource,
319
+ onChangeFunction,
320
+ getState
321
+ }) {
322
+ useEffect2(() => {
323
+ const sub = generateSubForSpecificChange({
324
+ subject: subSource,
325
+ filter: () => {
326
+ return selectorFunction(wrapGetterForMap(getState()));
327
+ },
328
+ mark: "useObserve"
329
+ }).subscribe(([_2, next]) => {
330
+ onChangeFunction(next);
331
+ });
332
+ return () => {
333
+ if (sub && sub.unsubscribe) {
334
+ sub.unsubscribe();
335
+ }
336
+ };
337
+ }, [subSource, getState, onChangeFunction, selectorFunction]);
338
+ }
339
+
340
+ // src/hooks/useSelector.ts
341
+ import { useCallback as useCallback2, useEffect as useEffect3 } from "react";
342
+
343
+ // src/utils/isImmutableObject.ts
344
+ var IMMUTABLE_ITERABLE = "@@__IMMUTABLE_ITERABLE__@@";
345
+ var IMMUTABLE_RECORD = "@@__IMMUTABLE_RECORD__@@";
346
+ var IMMUTABLE_LIST = "@@__IMMUTABLE_LIST__@@";
347
+ var IMMUTABLE_MAP = "@@__IMMUTABLE_MAP__@@";
348
+ function isImmutableObject(obj) {
349
+ if (typeof obj !== "object") {
350
+ return false;
351
+ }
352
+ const proto = Object.getPrototypeOf(obj);
353
+ if (proto[IMMUTABLE_ITERABLE] || proto[IMMUTABLE_RECORD] || proto[IMMUTABLE_LIST] || proto[IMMUTABLE_MAP]) {
354
+ return true;
355
+ }
356
+ return false;
357
+ }
358
+
359
+ // src/hooks/useSelector.ts
360
+ function useSelector({
361
+ selectorFunction,
362
+ subSource,
363
+ getState
364
+ }) {
365
+ const wrappedSelectorFunction = useCallback2(
366
+ (getJS = false) => () => {
367
+ const state = getState();
368
+ const res = selectorFunction(wrapGetterForMap(state));
369
+ if (isImmutableObject(res)) {
370
+ if (getJS) {
371
+ return state.toJS();
372
+ }
373
+ return state;
374
+ }
375
+ return res;
376
+ },
377
+ [selectorFunction, getState]
378
+ );
379
+ const [value, setValue] = useState(wrappedSelectorFunction(true)());
380
+ useEffect3(() => {
381
+ const sub = generateSubForSpecificChange({
382
+ subject: subSource,
383
+ filter: wrappedSelectorFunction(),
384
+ mark: "useSelector"
385
+ }).subscribe(([_2, next]) => {
386
+ if (isImmutableObject(next)) {
387
+ setValue(next.toJS());
388
+ } else {
389
+ setValue(next);
390
+ }
391
+ });
392
+ return () => {
393
+ if (sub && sub.unsubscribe) {
394
+ sub.unsubscribe();
395
+ }
396
+ };
397
+ }, [wrappedSelectorFunction, getState, subSource]);
398
+ return value;
399
+ }
400
+
401
+ // src/manager.ts
402
+ var ObserverableManager = class {
403
+ state;
404
+ subject;
405
+ _state$;
406
+ batchProcessing = false;
407
+ stateGetter;
408
+ get state$() {
409
+ return new Proxy(this._state$, {
410
+ get: (target, property, receiver) => {
411
+ switch (property) {
412
+ case "useSelector":
413
+ return this.useSelector;
414
+ case "useObserve":
415
+ return this.useObserve;
416
+ case "batch":
417
+ return this.batch;
418
+ case "peek":
419
+ return this.peek;
420
+ case "observe":
421
+ return this.observe;
422
+ case "__immutable__":
423
+ return this.state;
424
+ default:
425
+ return Reflect.get(target, property, receiver);
426
+ }
427
+ },
428
+ set: () => {
429
+ return false;
430
+ }
431
+ });
432
+ }
433
+ constructor(state) {
434
+ const wrappedState = wrapAsImmutable(state);
435
+ this.state = wrappedState.mapData;
436
+ this.stateGetter = wrappedState.getter;
437
+ this.subject = new BehaviorSubject(this.state);
438
+ this._state$ = createDeepProxy(
439
+ state,
440
+ [],
441
+ this.getCurrentState,
442
+ this.onSet,
443
+ this.onUse,
444
+ this.notifyChange
445
+ );
446
+ }
447
+ /**
448
+ * This method will be utilized by the 'get' method of the Proxy and the subscription logic within hooks,
449
+ * with the aim of obtaining the latest 'state' reference.
450
+ * @returns
451
+ */
452
+ getCurrentState = () => {
453
+ return this.state;
454
+ };
455
+ onSet = (value, keyPath) => {
456
+ this.state = this.state.setIn(keyPath, value);
457
+ this.notifyChange();
458
+ };
459
+ notifyChange = () => {
460
+ if (this.batchProcessing) {
461
+ return;
462
+ }
463
+ this.subject.next(this.state);
464
+ };
465
+ onUse = (keyPath) => {
466
+ return useFunc({
467
+ keyPath,
468
+ subSource: this.subject,
469
+ getState: this.getCurrentState
470
+ });
471
+ };
472
+ useSelector = (selector) => {
473
+ return useSelector({
474
+ selectorFunction: selector,
475
+ subSource: this.subject,
476
+ getState: this.getCurrentState
477
+ });
478
+ };
479
+ useObserve = (selector, onChange) => {
480
+ useObserve({
481
+ selectorFunction: selector,
482
+ subSource: this.subject,
483
+ onChangeFunction: onChange,
484
+ getState: this.getCurrentState
485
+ });
486
+ };
487
+ batch = (batchAction) => {
488
+ this.batchProcessing = true;
489
+ batchAction();
490
+ this.batchProcessing = false;
491
+ this.notifyChange();
492
+ };
493
+ peek = () => {
494
+ return this.state.toJS();
495
+ };
496
+ get = () => {
497
+ return this.peek();
498
+ };
499
+ observe = (selector, onChange) => {
500
+ const sub = generateSubForSpecificChange({
501
+ subject: this.subject,
502
+ filter: () => {
503
+ const res = selector(wrapGetterForMap(this.state));
504
+ return res;
505
+ }
506
+ }).subscribe(([_2, next]) => {
507
+ onChange(next);
508
+ });
509
+ return () => {
510
+ if (sub && sub.unsubscribe) {
511
+ sub.unsubscribe();
512
+ }
513
+ };
514
+ };
515
+ };
516
+ function observable(state) {
517
+ return new ObserverableManager(state).state$;
518
+ }
519
+ export {
520
+ createDeepProxy,
521
+ observable
522
+ };
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@international-iot-association/rct-state",
3
+ "version": "3.0.0-rc.1",
4
+ "license": "MIT",
5
+ "publishConfig": {
6
+ "access": "public",
7
+ "registry": "https://registry.npmjs.org/"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/International-IoT-Association/MES.git",
12
+ "directory": "electron-station-plugins/packages/rct-state"
13
+ },
14
+ "description": "Legend-State 风格的 RxJS + Immutable 响应式状态库。",
15
+ "type": "module",
16
+ "main": "./dist/index.cjs",
17
+ "module": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js",
23
+ "require": "./dist/index.cjs"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "scripts": {
30
+ "build": "tsup",
31
+ "typecheck": "tsc --noEmit",
32
+ "clean": "rimraf dist .turbo"
33
+ },
34
+ "dependencies": {
35
+ "immutable": "^4.3.2",
36
+ "lodash": "^4.17.21",
37
+ "react-fast-compare": "^3.2.2",
38
+ "rxjs": "^7.8.1"
39
+ },
40
+ "peerDependencies": {
41
+ "react": ">=18"
42
+ },
43
+ "devDependencies": {
44
+ "@international-iot-association/tsconfig": "1.0.0-rc.1",
45
+ "@types/lodash": "^4.17.13",
46
+ "@types/react": "^19.2.7",
47
+ "react": "^19.2.1",
48
+ "tsup": "^8.4.0",
49
+ "typescript": "^5.9.3"
50
+ },
51
+ "x-internal-version": "1.0.0",
52
+ "x-source-revision": "410a2f3b85ef03b678f04f22ea0a47718f5ddae1"
53
+ }