@arrai-innovations/reactive-helpers 1.0.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.
Files changed (53) hide show
  1. package/.circleci/config.yml +91 -0
  2. package/.commitlintrc.json +3 -0
  3. package/.editorconfig +5 -0
  4. package/.eslintignore +2 -0
  5. package/.eslintrc.js +26 -0
  6. package/.husky/commit-msg +2 -0
  7. package/.husky/pre-commit +2 -0
  8. package/.idea/encodings.xml +6 -0
  9. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  10. package/.idea/modules.xml +8 -0
  11. package/.idea/prettier.xml +8 -0
  12. package/.idea/reactive-helpers.iml +15 -0
  13. package/.lintstagedrc +11 -0
  14. package/.prettierignore +4 -0
  15. package/.prettierrc.js +5 -0
  16. package/README.md +598 -0
  17. package/babel.config.js +15 -0
  18. package/index.js +2 -0
  19. package/jest.config.mjs +27 -0
  20. package/package.json +59 -0
  21. package/tests/unit/.eslintrc.js +10 -0
  22. package/tests/unit/expectHelpers.js +6 -0
  23. package/tests/unit/mockOnUnmounted.js +9 -0
  24. package/tests/unit/use/index.spec.js +18 -0
  25. package/tests/unit/use/listFilter.spec.js +332 -0
  26. package/tests/unit/use/listInstance.spec.js +424 -0
  27. package/tests/unit/use/listRelated.spec.js +73 -0
  28. package/tests/unit/use/listSort.spec.js +220 -0
  29. package/tests/unit/use/listSubscription.spec.js +540 -0
  30. package/tests/unit/use/objectInstance.spec.js +897 -0
  31. package/tests/unit/use/objectSubscription.spec.js +671 -0
  32. package/tests/unit/use/search.spec.js +67 -0
  33. package/tests/unit/use/watches.spec.js +252 -0
  34. package/tests/unit/utils/assignReactiveObject.spec.js +127 -0
  35. package/tests/unit/utils/index.spec.js +18 -0
  36. package/tests/unit/utils/keyDiff.spec.js +67 -0
  37. package/tests/unit/utils/set.spec.js +68 -0
  38. package/tests/unit/utils/unrefAndToRawDeep.spec.js +170 -0
  39. package/use/index.js +8 -0
  40. package/use/listFilter.js +157 -0
  41. package/use/listInstance.js +144 -0
  42. package/use/listRelated.js +135 -0
  43. package/use/listSort.js +190 -0
  44. package/use/listSubscription.js +143 -0
  45. package/use/objectInstance.js +222 -0
  46. package/use/objectSubscription.js +188 -0
  47. package/use/search.js +104 -0
  48. package/utils/assignReactiveObject.js +62 -0
  49. package/utils/index.js +5 -0
  50. package/utils/keyDiff.js +10 -0
  51. package/utils/set.js +48 -0
  52. package/utils/unrefAndToRawDeep.js +49 -0
  53. package/utils/watches.js +170 -0
@@ -0,0 +1,170 @@
1
+ import { reactive, ref, toRef, unref } from "vue";
2
+ import { circular, unrefAndToRawDeep } from "../../../utils";
3
+
4
+ describe("unrefAndToRawDeep", () => {
5
+ it("should unref refs", () => {
6
+ const obj = {
7
+ a: ref(1),
8
+ b: ref(true),
9
+ c: ref("foo"),
10
+ d: ref(null),
11
+ };
12
+ expect(unrefAndToRawDeep(obj)).toEqual({
13
+ a: 1,
14
+ b: true,
15
+ c: "foo",
16
+ d: null,
17
+ });
18
+ });
19
+ it("should toRaw reactive", () => {
20
+ const obj = {
21
+ state: reactive({
22
+ a: 1,
23
+ b: true,
24
+ c: "foo",
25
+ d: null,
26
+ }),
27
+ parentState: reactive({
28
+ a: 1,
29
+ b: true,
30
+ c: "foo",
31
+ d: null,
32
+ }),
33
+ };
34
+ expect(unrefAndToRawDeep(obj)).toEqual({
35
+ state: {
36
+ a: 1,
37
+ b: true,
38
+ c: "foo",
39
+ d: null,
40
+ },
41
+ parentState: {
42
+ a: 1,
43
+ b: true,
44
+ c: "foo",
45
+ d: null,
46
+ },
47
+ });
48
+ });
49
+ it("should handle arrays", () => {
50
+ const arr = [ref(1), ref(true), ref("foo"), ref(null)];
51
+ const arr2 = [
52
+ reactive({
53
+ a: 1,
54
+ b: true,
55
+ c: "foo",
56
+ d: null,
57
+ }),
58
+ reactive({
59
+ a: 2,
60
+ b: false,
61
+ c: "bar",
62
+ d: NaN,
63
+ }),
64
+ ];
65
+ expect(unrefAndToRawDeep(arr)).toEqual([1, true, "foo", null]);
66
+ expect(unrefAndToRawDeep(arr2)).toEqual([
67
+ {
68
+ a: 1,
69
+ b: true,
70
+ c: "foo",
71
+ d: null,
72
+ },
73
+ {
74
+ a: 2,
75
+
76
+ b: false,
77
+ c: "bar",
78
+ d: NaN,
79
+ },
80
+ ]);
81
+ });
82
+ it("should toRaw reactive with circular ref", () => {
83
+ const state = reactive({
84
+ objects: {
85
+ 1: {
86
+ id: 1,
87
+ name: "one",
88
+ parent: 2,
89
+ children: [],
90
+ refProperty: ref(null),
91
+ },
92
+ 2: {
93
+ id: 2,
94
+ name: "two",
95
+ parent: null,
96
+ children: [1],
97
+ refProperty: ref(null),
98
+ },
99
+ 3: {
100
+ id: 3,
101
+ name: "three",
102
+ parent: null,
103
+ children: [],
104
+ },
105
+ },
106
+ });
107
+ state.objects["1"].relatedObjects = reactive({
108
+ parent: toRef(state.objects, "2"),
109
+ });
110
+ state.objects["2"].relatedObjects = reactive({
111
+ children: [toRef(state.objects, "1")],
112
+ });
113
+ state.objects["1"].refProperty = toRef(state.objects["3"], "name");
114
+ state.objects["2"].refProperty = toRef(state.objects["3"], "name");
115
+ expect(state.objects["1"]).toBe(unref(state.objects["2"].relatedObjects.children[0]));
116
+ expect(state.objects["2"]).toBe(state.objects["1"].relatedObjects.parent);
117
+ expect(state.objects["1"].refProperty).toBe(state.objects["3"].name);
118
+ expect(state.objects["2"].refProperty).toBe(state.objects["3"].name);
119
+ expect(unrefAndToRawDeep(state)).toEqual({
120
+ objects: {
121
+ 1: {
122
+ id: 1,
123
+ name: "one",
124
+ parent: 2,
125
+ children: [],
126
+ refProperty: "three",
127
+ relatedObjects: {
128
+ parent: {
129
+ id: 2,
130
+ name: "two",
131
+ parent: null,
132
+ children: [1],
133
+ refProperty: "three",
134
+ relatedObjects: {
135
+ children: [circular],
136
+ },
137
+ },
138
+ },
139
+ },
140
+ 2: {
141
+ id: 2,
142
+ name: "two",
143
+ parent: null,
144
+ children: [1],
145
+ refProperty: "three",
146
+ relatedObjects: {
147
+ children: [
148
+ {
149
+ id: 1,
150
+ name: "one",
151
+ parent: 2,
152
+ children: [],
153
+ refProperty: "three",
154
+ relatedObjects: {
155
+ parent: circular,
156
+ },
157
+ },
158
+ ],
159
+ },
160
+ },
161
+ 3: {
162
+ id: 3,
163
+ name: "three",
164
+ parent: null,
165
+ children: [],
166
+ },
167
+ },
168
+ });
169
+ });
170
+ });
package/use/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export * from "./listFilter.js";
2
+ export * from "./listInstance.js";
3
+ export * from "./listRelated.js";
4
+ export * from "./listSort.js";
5
+ export * from "./listSubscription.js";
6
+ export * from "./objectInstance.js";
7
+ export * from "./objectSubscription.js";
8
+ export * from "./search.js";
@@ -0,0 +1,157 @@
1
+ import { useSearch } from "./search";
2
+ import { keyDiff } from "../utils/keyDiff";
3
+ import { get, identity, isEmpty } from "lodash";
4
+ import { computed, effectScope, onScopeDispose, reactive, toRef, watch, watchEffect } from "vue";
5
+
6
+ export function useListFilters(listFilterArgs, parentInstances) {
7
+ const filters = {};
8
+ for (const [key, value] of Object.entries(listFilterArgs)) {
9
+ filters[key] = useListFilter({ parentState: parentInstances[key].state || parentInstances[key], ...value });
10
+ }
11
+ return filters;
12
+ }
13
+
14
+ export function useListFilter({
15
+ parentState,
16
+ useTextSearch = false,
17
+ textSearchRules = [],
18
+ textSearchValue = "",
19
+ allowedValues = {},
20
+ excludedValues = {},
21
+ allowedFilter,
22
+ excludedFilter,
23
+ }) {
24
+ const state = reactive({
25
+ objectIndexes: {},
26
+ objects: {},
27
+ textSearchRules,
28
+ textSearchValue,
29
+ allowedValues,
30
+ excludedValues,
31
+ allowedFilter,
32
+ excludedFilter,
33
+ });
34
+
35
+ const es = effectScope();
36
+
37
+ let textSearchIndex;
38
+
39
+ es.run(() => {
40
+ if (useTextSearch) {
41
+ textSearchIndex = useSearch();
42
+ textSearchIndex.state.search = toRef(state, "textSearchValue");
43
+ state.searched = toRef(textSearchIndex.state, "searched");
44
+ state.searching = toRef(textSearchIndex.state, "searching");
45
+ }
46
+
47
+ state.order = computed(() => {
48
+ if (!parentState.order) {
49
+ return parentState.order;
50
+ }
51
+ return parentState.order.filter((id) => state.objects[id]);
52
+ });
53
+ state.objectsInOrder = computed(() => {
54
+ if (!parentState.order) {
55
+ return [];
56
+ }
57
+ const order = state.order;
58
+ const objects = state.objects;
59
+ return order.map((key) => objects[key]).filter(identity);
60
+ });
61
+
62
+ watchEffect(() => {
63
+ const allowedValuesEmpty = !state.allowedValues || isEmpty(state.allowedValues);
64
+ const excludedValuesEmpty = !state.excludedValues || isEmpty(state.excludedValues);
65
+ const resultsEmpty = useTextSearch
66
+ ? !textSearchIndex.state.results || isEmpty(textSearchIndex.state.results)
67
+ : undefined;
68
+ const searched = useTextSearch ? textSearchIndex.state.searched : undefined;
69
+
70
+ const inResults = (object) => {
71
+ if (!allowedValuesEmpty && !state.allowedValues[object.id]) {
72
+ return false;
73
+ }
74
+ if (!excludedValuesEmpty && state.excludedValues[object.id]) {
75
+ return false;
76
+ }
77
+ if (state.allowedFilter && !state.allowedFilter(object)) {
78
+ return false;
79
+ }
80
+ if (state.excludedFilter && state.excludedFilter(object)) {
81
+ return false;
82
+ }
83
+ if (!useTextSearch) {
84
+ return true;
85
+ }
86
+ if (!searched && resultsEmpty) {
87
+ return true;
88
+ }
89
+ return !!textSearchIndex.state.results[object.id];
90
+ };
91
+ const { removedKeys, sameKeys, addedKeys } = keyDiff(
92
+ Object.keys(parentState.objects),
93
+ Object.keys(state.objects)
94
+ );
95
+ for (const removedKey of removedKeys) {
96
+ delete state.objects[removedKey];
97
+ }
98
+ for (const addedKey of addedKeys) {
99
+ if (inResults(parentState.objects[addedKey])) {
100
+ state.objects[addedKey] = toRef(parentState.objects, addedKey);
101
+ }
102
+ }
103
+ for (const sameKey of sameKeys) {
104
+ if (inResults(parentState.objects[sameKey])) {
105
+ if (state.objects[sameKey] !== parentState.objects[sameKey]) {
106
+ state.objects[sameKey] = toRef(parentState.objects, sameKey);
107
+ }
108
+ } else {
109
+ delete state.objects[sameKey];
110
+ }
111
+ }
112
+ });
113
+
114
+ if (useTextSearch) {
115
+ const stopIndexWatch = {};
116
+
117
+ watchEffect(() => {
118
+ const { removedKeys, addedKeys } = keyDiff(
119
+ Object.keys(parentState.objects),
120
+ Object.keys(state.objectIndexes)
121
+ );
122
+ for (const removedKey of removedKeys) {
123
+ delete state.objectIndexes[removedKey];
124
+ textSearchIndex.removeIndex(removedKey);
125
+ if (stopIndexWatch[removedKey]) {
126
+ stopIndexWatch[removedKey]();
127
+ delete stopIndexWatch[removedKey];
128
+ }
129
+ }
130
+ for (const addedKey of addedKeys) {
131
+ state.objectIndexes[addedKey] = true;
132
+ textSearchIndex.addIndex(
133
+ addedKey,
134
+ state.textSearchRules.map((o) => get(parentState.objects[addedKey], o)).join(" ")
135
+ );
136
+ stopIndexWatch[addedKey] = watch(
137
+ [toRef(state, "textSearchRules"), toRef(parentState.objects, "addedKey")],
138
+ (textSearchRules, object) => {
139
+ textSearchIndex.updateIndex(addedKey, textSearchRules.map((o) => get(object, o)).join(" "));
140
+ }
141
+ );
142
+ }
143
+ });
144
+ onScopeDispose(() => {
145
+ for (const key in stopIndexWatch) {
146
+ stopIndexWatch[key]();
147
+ }
148
+ });
149
+ }
150
+ });
151
+ return {
152
+ state,
153
+ parentState,
154
+ textSearchIndex,
155
+ effectScope: es,
156
+ };
157
+ }
@@ -0,0 +1,144 @@
1
+ import { isEmpty, keyBy } from "lodash";
2
+ import { effectScope, reactive, unref } from "vue";
3
+ import { addOrUpdateReactiveObject, assignReactiveObject } from "../utils/assignReactiveObject";
4
+ import inspect from "browser-util-inspect";
5
+
6
+ export class ListError extends Error {
7
+ constructor(message, code) {
8
+ super(message);
9
+ this.name = "ListError";
10
+ this.code = code;
11
+ }
12
+ }
13
+
14
+ const defaultCrud = reactive({
15
+ args: {},
16
+ list: undefined,
17
+ });
18
+
19
+ export function setListInstanceCrud({ list, args = {} } = {}) {
20
+ defaultCrud.list = list;
21
+ assignReactiveObject(defaultCrud.args, args);
22
+ }
23
+
24
+ export function useListInstances(listInstanceArgs) {
25
+ const instances = {};
26
+ for (const [key, value] of Object.entries(listInstanceArgs)) {
27
+ instances[key] = useListInstance(value);
28
+ }
29
+ return instances;
30
+ }
31
+
32
+ export function useListInstance({ crudArgs, defaultListArgs = {}, defaultRetrieveArgs = {} }) {
33
+ const state = reactive({
34
+ listInstanceCrud: {
35
+ args: {},
36
+ list: undefined,
37
+ },
38
+ defaultRetrieveArgs,
39
+ defaultListArgs,
40
+ objects: {},
41
+ loading: undefined,
42
+ errored: false,
43
+ error: null,
44
+ addedOrder: [],
45
+ });
46
+ assignReactiveObject(state.listInstanceCrud, defaultCrud);
47
+ if (crudArgs) {
48
+ assignReactiveObject(state.listInstanceCrud.args, crudArgs);
49
+ }
50
+
51
+ async function list({ listArgs, retrieveArgs } = {}) {
52
+ if (state.loading) {
53
+ throw new ListError("already loading.");
54
+ }
55
+ if (isEmpty(retrieveArgs) && !isEmpty(unref(state.defaultRetrieveArgs))) {
56
+ retrieveArgs = unref(state.defaultRetrieveArgs);
57
+ }
58
+ if (isEmpty(listArgs) && !isEmpty(unref(state.defaultListArgs))) {
59
+ listArgs = unref(state.defaultListArgs);
60
+ }
61
+ state.loading = true;
62
+ state.errored = false;
63
+ state.error = null;
64
+ return state.listInstanceCrud
65
+ .list({
66
+ crudArgs: state.listInstanceCrud.args,
67
+ retrieveArgs,
68
+ listArgs,
69
+ pageCallback: (newObjects) => {
70
+ addOrUpdateReactiveObject(state.objects, keyBy(newObjects, "id"));
71
+ },
72
+ })
73
+ .then(() => {
74
+ return Promise.resolve(true);
75
+ })
76
+ .catch((error) => {
77
+ state.errored = true;
78
+ state.error = error;
79
+ return Promise.resolve(false);
80
+ })
81
+ .finally(() => {
82
+ state.loading = false;
83
+ });
84
+ }
85
+
86
+ function addListObject(object) {
87
+ if (!object.id) {
88
+ throw new ListError(`addListObject: object missing id.\n${inspect(object)}`, "missing-id");
89
+ }
90
+ if (object.id in state.objects) {
91
+ throw new ListError(`addListObject: list already has object for id: ${inspect(object.id)}`, "duplicate-id");
92
+ }
93
+ state.objects[object.id] = {};
94
+ state.addedOrder.push(object.id);
95
+ assignReactiveObject(state.objects[object.id], object);
96
+ }
97
+
98
+ function updateListObject(object) {
99
+ if (!object.id) {
100
+ throw new ListError(`updateListObject: object missing id.\n${inspect(object)}`, "missing-id");
101
+ }
102
+ if (!(object.id in state.objects)) {
103
+ throw new ListError(
104
+ `updateListObject: list missing object for update by id: ${inspect(object.id)}`,
105
+ "missing-object"
106
+ );
107
+ }
108
+ assignReactiveObject(state.objects[object.id], object);
109
+ }
110
+
111
+ function deleteListObject(objectId) {
112
+ if (!(objectId in state.objects)) {
113
+ throw new ListError(
114
+ `deleteListObject: list missing object for removal by id: ${inspect(objectId)}`,
115
+ "missing-object"
116
+ );
117
+ }
118
+ state.addedOrder.splice(state.addedOrder.indexOf(objectId), 1);
119
+ delete state.objects[objectId];
120
+ }
121
+
122
+ function getFakeId() {
123
+ let fakeId;
124
+ do {
125
+ fakeId = Math.floor(Math.random() * Number.MIN_SAFE_INTEGER);
126
+ } while (fakeId in state.objects);
127
+ return fakeId;
128
+ }
129
+
130
+ const es = effectScope();
131
+
132
+ // we could have effects? let's keep the interface to keep our options open to add without major changes.
133
+ es.run(() => {});
134
+
135
+ return {
136
+ state,
137
+ list,
138
+ addListObject,
139
+ updateListObject,
140
+ deleteListObject,
141
+ effectScope: es,
142
+ getFakeId,
143
+ };
144
+ }
@@ -0,0 +1,135 @@
1
+ import { computed, effectScope, onScopeDispose, reactive, toRefs, watch } from "vue";
2
+ import { get, isArray, isEmpty, isUndefined } from "lodash";
3
+ import { keyDiff } from "../utils/keyDiff";
4
+
5
+ export function useListRelateds(instances, args) {
6
+ for (const [key, value] of Object.entries(args)) {
7
+ useListRelated({ listInstance: instances[key], ...value });
8
+ }
9
+ }
10
+
11
+ export function useListRelated({
12
+ parentState,
13
+ relatedObjectsRules,
14
+ relatedObjectsPropertyName = "relatedObjects", // NOT REACTIVE
15
+ }) {
16
+ const state = reactive({
17
+ relatedObjectsRules: relatedObjectsRules,
18
+ relatedObjectsObjects: {},
19
+ objects: {},
20
+ });
21
+ const relatedObjectsEffectScopes = {};
22
+ const combinedEffectScopes = {};
23
+
24
+ // don't change relatedObjectsPropertyName on us or it will break
25
+ const ropn = relatedObjectsPropertyName + "";
26
+
27
+ function parentStateObjectsWatch() {
28
+ const { addedKeys, removedKeys } = keyDiff(
29
+ Object.keys(parentState.objects),
30
+ Object.keys(state.relatedObjectsObjects)
31
+ );
32
+ for (const removedKey of removedKeys) {
33
+ delete state.relatedObjectsObjects[removedKey];
34
+ delete state.objects[removedKey];
35
+ if (relatedObjectsEffectScopes[removedKey]) {
36
+ relatedObjectsEffectScopes[removedKey].stop();
37
+ delete relatedObjectsEffectScopes[removedKey];
38
+ }
39
+ if (combinedEffectScopes[removedKey]) {
40
+ combinedEffectScopes[removedKey].stop();
41
+ delete combinedEffectScopes[removedKey];
42
+ }
43
+ }
44
+ for (const addedKey of addedKeys) {
45
+ state.relatedObjectsObjects[addedKey] = {};
46
+ combinedEffectScopes[addedKey] = effectScope();
47
+ combinedEffectScopes[addedKey].run(() => {
48
+ state.objects[addedKey] = computed(() => ({
49
+ ...toRefs(state.relatedObjectsObjects[addedKey]),
50
+ ...toRefs(parentState.objects[addedKey]),
51
+ }));
52
+ });
53
+ }
54
+ }
55
+
56
+ function relatedObjectsWatch() {
57
+ if (state.relatedObjectsRules === false) {
58
+ return;
59
+ }
60
+ const relatedObjectsRulesIsEmpty = isEmpty(state.relatedObjectsRules);
61
+ for (const objectKey of Object.keys(state.relatedObjectsObjects)) {
62
+ const relatedObjectsEffectScope = effectScope();
63
+ relatedObjectsEffectScope.run(() => {
64
+ const relatedObjectsObject = state.relatedObjectsObjects[objectKey];
65
+ const originalObject = parentState.objects[objectKey];
66
+ if (!relatedObjectsObject[ropn]) {
67
+ relatedObjectsObject[ropn] = {};
68
+ }
69
+ let removedRuleKeys, addedRuleKeys;
70
+ if (!relatedObjectsRulesIsEmpty) {
71
+ ({ removedKeys: removedRuleKeys, addedKeys: addedRuleKeys } = keyDiff(
72
+ Object.keys(state.relatedObjectsRules),
73
+ Object.keys(relatedObjectsObject[ropn])
74
+ ));
75
+ } else {
76
+ if (isEmpty(relatedObjectsObject[ropn])) {
77
+ return;
78
+ }
79
+ removedRuleKeys = Object.keys(relatedObjectsObject[ropn]);
80
+ addedRuleKeys = [];
81
+ }
82
+ for (const removedRuleKey of removedRuleKeys) {
83
+ delete relatedObjectsObject[ropn][removedRuleKey];
84
+ }
85
+ for (const addedRuleKey of addedRuleKeys) {
86
+ relatedObjectsObject[ropn][addedRuleKey] = computed(() => {
87
+ const ruleObjects = state.relatedObjectsRules?.[addedRuleKey]?.objects;
88
+ const rulePkKey = state.relatedObjectsRules?.[addedRuleKey]?.pkKey || addedRuleKey;
89
+ if (!ruleObjects || !rulePkKey) {
90
+ return undefined;
91
+ }
92
+ const value = get(originalObject, rulePkKey);
93
+ if (isUndefined(value)) {
94
+ return undefined;
95
+ }
96
+ if (isArray(value)) {
97
+ return value.map((e) => ruleObjects[e]);
98
+ }
99
+ return ruleObjects[value];
100
+ });
101
+ }
102
+ });
103
+ relatedObjectsEffectScopes[objectKey] = relatedObjectsEffectScope;
104
+ }
105
+ parentStateObjectsWatch();
106
+ }
107
+
108
+ const es = effectScope();
109
+
110
+ es.run(() => {
111
+ watch(() => Object.keys(parentState.objects), parentStateObjectsWatch, { immediate: true });
112
+ watch(
113
+ [
114
+ () => Object.keys(state.relatedObjectsObjects),
115
+ () => (state.relatedObjectsRules ? Object.keys(state.relatedObjectsRules) : state.relatedObjectsRules),
116
+ ],
117
+ relatedObjectsWatch,
118
+ { immediate: true }
119
+ );
120
+
121
+ onScopeDispose(() => {
122
+ for (const objectKey of Object.keys(relatedObjectsEffectScopes)) {
123
+ relatedObjectsEffectScopes[objectKey].stop();
124
+ }
125
+ for (const objectKey of Object.keys(combinedEffectScopes)) {
126
+ combinedEffectScopes[objectKey].stop();
127
+ }
128
+ });
129
+ });
130
+ return {
131
+ state,
132
+ parentState,
133
+ effectScope: es,
134
+ };
135
+ }