@arrai-innovations/reactive-helpers 11.0.1 → 11.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arrai-innovations/reactive-helpers",
3
- "version": "11.0.1",
3
+ "version": "11.0.3",
4
4
  "description": "VueJS 3 utility composition functions to help manipulate objects and lists.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -191,5 +191,69 @@ describe("use/listRelated", () => {
191
191
  },
192
192
  },
193
193
  });
194
+ intermediateListInstance.state.objects[2].name = "intermediate1a";
195
+ intermediateListInstance.state.objects[2].related_id = "6";
196
+ intermediateListInstance.state.objects[2].related_ids = ["7", "8"];
197
+ intermediateListInstance.state.objects[3].name = "intermediate2a";
198
+ intermediateListInstance.state.objects[3].related_id = "4";
199
+ intermediateListInstance.state.objects[3].related_ids = ["5", "9"];
200
+ relatedListInstance.state.objects[4].name = "related1a";
201
+ relatedListInstance.state.objects[5].name = "related2a";
202
+ relatedListInstance.state.objects[6].name = "related3a";
203
+ relatedListInstance.state.objects[7].name = "related4a";
204
+ relatedListInstance.addListObject({
205
+ id: "8",
206
+ name: "related5",
207
+ });
208
+ relatedListInstance.addListObject({
209
+ id: "9",
210
+ name: "related6",
211
+ });
212
+ expect(deepUnref(listRelated.state.relatedObjects)).toEqual({
213
+ 1: {
214
+ intermediateItems: [
215
+ {
216
+ id: "2",
217
+ name: "intermediate1a",
218
+ related_ids: ["7", "8"],
219
+ related_id: "6",
220
+ },
221
+ {
222
+ id: "3",
223
+ name: "intermediate2a",
224
+ related_ids: ["5", "9"],
225
+ related_id: "4",
226
+ },
227
+ ],
228
+ intermediateItem: {
229
+ id: "2",
230
+ name: "intermediate1a",
231
+ related_ids: ["7", "8"],
232
+ related_id: "6",
233
+ },
234
+ relatedItems: [
235
+ {
236
+ id: "7",
237
+ name: "related4a",
238
+ },
239
+ {
240
+ id: "8",
241
+ name: "related5",
242
+ },
243
+ {
244
+ id: "5",
245
+ name: "related2a",
246
+ },
247
+ {
248
+ id: "9",
249
+ name: "related6",
250
+ },
251
+ ],
252
+ relatedItem: {
253
+ id: "6",
254
+ name: "related3a",
255
+ },
256
+ },
257
+ });
194
258
  });
195
259
  });
@@ -1,5 +1,5 @@
1
- import { doAwaitTimeout } from "../../../utils/index.js";
2
- import { reactive } from "vue";
1
+ import { doAwaitTimeout, doAwaitNot } from "../../../utils/index.js";
2
+ import { reactive, ref } from "vue";
3
3
  import { deepUnref } from "vue-deepunref";
4
4
 
5
5
  describe("use/useListSort", () => {
@@ -10,6 +10,7 @@ describe("use/useListSort", () => {
10
10
  useListInstances,
11
11
  useListRelated,
12
12
  useListCalculated,
13
+ useListFilter,
13
14
  useListSort,
14
15
  useListSorts,
15
16
  setListSortDefaultOptions,
@@ -46,6 +47,7 @@ describe("use/useListSort", () => {
46
47
  useListInstances = imported.useListInstances;
47
48
  useListRelated = imported.useListRelated;
48
49
  useListCalculated = imported.useListCalculated;
50
+ useListFilter = imported.useListFilter;
49
51
  orderByRules = [
50
52
  { key: "organization", desc: true, localeCompare: false },
51
53
  { key: "lexical_name", desc: false, localeCompare: true },
@@ -339,4 +341,56 @@ describe("use/useListSort", () => {
339
341
  expect(listSort.state.objectsInOrder).toEqual(testOrder4.map((id) => listInstance.state.objects[id]));
340
342
  });
341
343
  });
344
+ it("pass through correctly when parentState changes their order", async () => {
345
+ const listInstance = useListInstance({
346
+ props: reactive({}),
347
+ });
348
+ const allowedFilter = ref((obj) => obj.name !== "two");
349
+ const listFilter = useListFilter({
350
+ parentState: listInstance.state,
351
+ allowedFilter,
352
+ });
353
+ const orderByRules = ref([]);
354
+ const listSort = useListSort({
355
+ parentState: listFilter.state,
356
+ orderByRules,
357
+ });
358
+ await doAwaitNot({
359
+ obj: listFilter.state,
360
+ prop: "running",
361
+ });
362
+ await waitForListSort(listSort);
363
+ expect(listFilter.state.order).toEqual([]);
364
+ expect(listFilter.state.objectsInOrder.map((obj) => obj.id)).toEqual([]);
365
+ expect(listSort.state.order).toEqual([]);
366
+ expect(listSort.state.objectsInOrder.map((obj) => obj.id)).toEqual([]);
367
+ listInstance.addListObject({ id: 1, name: "one" });
368
+ listInstance.addListObject({ id: 2, name: "two" });
369
+ listInstance.addListObject({ id: 3, name: "three" });
370
+ await doAwaitNot({
371
+ obj: listFilter.state,
372
+ prop: "running",
373
+ });
374
+ await waitForListSort(listSort);
375
+ expect(listFilter.state.order).toEqual(["1", "3"]);
376
+ expect(listFilter.state.objectsInOrder.map((obj) => obj.id)).toEqual([1, 3]);
377
+ expect(listSort.state.order).toEqual(["1", "3"]);
378
+ expect(listSort.state.objectsInOrder.map((obj) => obj.id)).toEqual([1, 3]);
379
+ listInstance.updateListObject({ id: 2, name: "twotwo" });
380
+ await doAwaitNot({
381
+ obj: listFilter.state,
382
+ prop: "running",
383
+ });
384
+ await waitForListSort(listSort);
385
+ expect(listSort.state.order).toEqual(["1", "2", "3"]);
386
+ expect(listSort.state.objectsInOrder.map((obj) => obj.id)).toEqual([1, 2, 3]);
387
+ listInstance.updateListObject({ id: 2, name: "two" });
388
+ await doAwaitNot({
389
+ obj: listFilter.state,
390
+ prop: "running",
391
+ });
392
+ await waitForListSort(listSort);
393
+ expect(listSort.state.order).toEqual(["1", "3"]);
394
+ expect(listSort.state.objectsInOrder.map((obj) => obj.id)).toEqual([1, 3]);
395
+ });
342
396
  });
@@ -66,9 +66,6 @@ export function useListCalculated({ parentState, calculatedObjectsRules }) {
66
66
  function calculatedObjectsWatch() {
67
67
  const calculatedObjectsRulesIsEmpty = !state.calculatedObjectsRules || isEmpty(state.calculatedObjectsRules);
68
68
  for (const objectKey of Object.keys(state.calculatedObjects)) {
69
- if (!calculatedObjectsEffectScopes[objectKey]) {
70
- calculatedObjectsEffectScopes[objectKey] = effectScope();
71
- }
72
69
  const originalObject = toRef(parentState.objects, objectKey);
73
70
  const relatedObject = parentState.relatedObjects
74
71
  ? toRef(parentState.relatedObjects, objectKey)
@@ -91,8 +88,13 @@ export function useListCalculated({ parentState, calculatedObjectsRules }) {
91
88
  addedRuleKeys = [];
92
89
  }
93
90
  for (const removedRuleKey of removedRuleKeys) {
91
+ // this is an unofficial api
92
+ calculatedObjectsObject[removedRuleKey].effect.stop();
94
93
  delete calculatedObjectsObject[removedRuleKey];
95
94
  }
95
+ if (!calculatedObjectsEffectScopes[objectKey]) {
96
+ calculatedObjectsEffectScopes[objectKey] = effectScope();
97
+ }
96
98
  calculatedObjectsEffectScopes[objectKey].run(() => {
97
99
  for (const addedRuleKey of addedRuleKeys) {
98
100
  calculatedObjectsObject[addedRuleKey] = computed(() =>
package/use/listKeys.js CHANGED
@@ -42,6 +42,8 @@ export const listRelatedStateKeys = [
42
42
  "relatedObjectsWatchRunning",
43
43
  "relatedObjectsParentStateObjectsWatchRunning",
44
44
  "running",
45
+ "objAndKeyForIdAndRule",
46
+ "fkForIdAndRule",
45
47
  ];
46
48
  export const listRelatedFunctions = [];
47
49
 
@@ -1,6 +1,7 @@
1
- import { difference } from "../utils/index.js";
2
1
  import { keyDiff } from "../utils/keyDiff.js";
3
2
  import { loadingCombine } from "../utils/loadingCombine.js";
3
+ import { getObjectRelatedByKey } from "../utils/relatedCalculatedHelpers.js";
4
+ import { difference } from "../utils/set.js";
4
5
  import {
5
6
  listSubscriptionStateKeys,
6
7
  listInstanceStateKeys,
@@ -15,7 +16,6 @@ import get from "lodash-es/get.js";
15
16
  import identity from "lodash-es/identity.js";
16
17
  import isArray from "lodash-es/isArray.js";
17
18
  import isEmpty from "lodash-es/isEmpty.js";
18
- import isEqual from "lodash-es/isEqual.js";
19
19
  import isUndefined from "lodash-es/isUndefined.js";
20
20
  import { computed, effectScope, onScopeDispose, reactive, toRef, unref, watch } from "vue";
21
21
 
@@ -43,23 +43,47 @@ export function useListRelateds(instances, args) {
43
43
  export function useListRelated({ parentState, relatedObjectsRules }) {
44
44
  const state = reactive({
45
45
  relatedObjectsRules: relatedObjectsRules,
46
- relatedObjects: {},
46
+ relatedObjects: {
47
+ // id: {
48
+ // rule: list of objects or single object,
49
+ // },
50
+ },
51
+ objAndKeyForIdAndRule: {
52
+ // id: {
53
+ // rule: {
54
+ // obj: object,
55
+ // key: string,
56
+ // },
57
+ // },
58
+ },
59
+ fkForIdAndRule: {
60
+ // id: {
61
+ // rule: list of ids or single id,
62
+ // },
63
+ },
47
64
  relatedObjectsParentStateObjectsWatchRunning: false,
48
65
  relatedObjectsWatchRunning: false,
49
66
  });
50
67
  const relatedObjectsEffectScopes = {};
51
68
 
52
69
  function parentStateObjectsWatch() {
53
- const { addedKeys, removedKeys } = keyDiff(Object.keys(parentState.objects), Object.keys(state.relatedObjects));
54
- for (const removedKey of removedKeys) {
55
- delete state.relatedObjects[removedKey];
56
- if (relatedObjectsEffectScopes[removedKey]) {
57
- relatedObjectsEffectScopes[removedKey].stop();
58
- delete relatedObjectsEffectScopes[removedKey];
70
+ const { addedKeys: addedIds, removedKeys: removedIds } = keyDiff(
71
+ Object.keys(parentState.objects),
72
+ Object.keys(state.relatedObjects)
73
+ );
74
+ for (const removedId of removedIds) {
75
+ delete state.relatedObjects[removedId];
76
+ delete state.objAndKeyForIdAndRule[removedId];
77
+ delete state.fkForIdAndRule[removedId];
78
+ if (relatedObjectsEffectScopes[removedId]) {
79
+ relatedObjectsEffectScopes[removedId].stop();
80
+ delete relatedObjectsEffectScopes[removedId];
59
81
  }
60
82
  }
61
- for (const addedKey of addedKeys) {
62
- state.relatedObjects[addedKey] = {};
83
+ for (const addedId of addedIds) {
84
+ state.relatedObjects[addedId] = {};
85
+ state.objAndKeyForIdAndRule[addedId] = {};
86
+ state.fkForIdAndRule[addedId] = {};
63
87
  }
64
88
  state.relatedObjectsParentStateObjectsWatchRunning = false;
65
89
  }
@@ -67,66 +91,58 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
67
91
  function relatedObjectsWatch() {
68
92
  const relatedObjectsRulesIsEmpty = !state.relatedObjectsRules || isEmpty(state.relatedObjectsRules);
69
93
  for (const objectKey of Object.keys(state.relatedObjects)) {
70
- const relatedObjectsObject = state.relatedObjects[objectKey];
71
94
  const originalObjectRef = toRef(parentState.objects, objectKey);
72
95
  const relatedObjectRef = toRef(state.relatedObjects, objectKey);
73
96
  let removedRuleKeys, addedRuleKeys;
74
97
  if (!relatedObjectsRulesIsEmpty) {
75
98
  ({ removedKeys: removedRuleKeys, addedKeys: addedRuleKeys } = keyDiff(
76
99
  Object.keys(state.relatedObjectsRules),
77
- Object.keys(relatedObjectsObject)
100
+ Object.keys(state.relatedObjects[objectKey])
78
101
  ));
79
102
  } else {
80
- if (isEmpty(relatedObjectsObject)) {
103
+ if (isEmpty(state.relatedObjects[objectKey])) {
81
104
  return;
82
105
  }
83
- removedRuleKeys = Object.keys(relatedObjectsObject);
84
- addedRuleKeys = [];
106
+ removedRuleKeys = new Set(Object.keys(state.relatedObjects[objectKey]));
107
+ addedRuleKeys = new Set();
85
108
  }
86
109
  for (const removedRuleKey of removedRuleKeys) {
87
- delete relatedObjectsObject[removedRuleKey];
88
- }
89
- if (!relatedObjectsEffectScopes[objectKey]) {
90
- relatedObjectsEffectScopes[objectKey] = effectScope();
110
+ state.relatedObjects[objectKey][removedRuleKey]?.effect?.stop();
111
+ delete state.relatedObjects[objectKey][removedRuleKey];
112
+ state.objAndKeyForIdAndRule[objectKey][removedRuleKey]?.effect?.stop();
113
+ delete state.objAndKeyForIdAndRule[objectKey][removedRuleKey];
114
+ state.fkForIdAndRule[objectKey][removedRuleKey]?.effect?.stop();
115
+ delete state.fkForIdAndRule[objectKey][removedRuleKey];
91
116
  }
92
- relatedObjectsEffectScopes[objectKey].run(() => {
93
- for (const addedRuleKey of addedRuleKeys) {
94
- relatedObjectsObject[addedRuleKey] = undefined;
95
- const relatedObjectsObjectWatchFn = () => {
96
- // deal with computed objects being passed.
97
- const ruleObjects = unref(state.relatedObjectsRules?.[addedRuleKey]?.objects);
98
- const rulePkKey = state.relatedObjectsRules?.[addedRuleKey]?.pkKey || addedRuleKey;
99
- const ruleOrder = unref(state.relatedObjectsRules?.[addedRuleKey]?.order);
100
- if (!ruleObjects || !rulePkKey) {
101
- if (!isUndefined(relatedObjectsObject[addedRuleKey])) {
102
- relatedObjectsObject[addedRuleKey] = undefined;
103
- }
104
- return;
105
- }
106
- let value;
107
- if (rulePkKey.startsWith("relatedItem.")) {
108
- value = get(unref(relatedObjectRef), rulePkKey.slice(12));
109
- if (isUndefined(value)) {
117
+ if (addedRuleKeys.size) {
118
+ if (!relatedObjectsEffectScopes[objectKey]) {
119
+ relatedObjectsEffectScopes[objectKey] = effectScope();
120
+ }
121
+ relatedObjectsEffectScopes[objectKey].run(() => {
122
+ for (const addedRuleKey of addedRuleKeys) {
123
+ const rules = toRef(state.relatedObjectsRules, addedRuleKey);
124
+ state.objAndKeyForIdAndRule[objectKey][addedRuleKey] = computed(() => {
125
+ const rulePkKey = unref(rules).pkKey || addedRuleKey;
126
+ const object = unref(originalObjectRef);
127
+ const relatedObject = unref(relatedObjectRef);
128
+ return getObjectRelatedByKey(object, relatedObject, rulePkKey);
129
+ });
130
+
131
+ state.fkForIdAndRule[objectKey][addedRuleKey] = computed(() => {
132
+ const ruleOrder = unref(rules).order;
133
+ const relatedObject = unref(relatedObjectRef);
134
+ const [objectForGet, key] = unref(state.objAndKeyForIdAndRule[objectKey][addedRuleKey]);
135
+ let value = get(objectForGet, key);
136
+ if (objectForGet === relatedObject && isUndefined(value)) {
110
137
  // is the first level an array?
111
- const firstLevelKey = rulePkKey.slice(12).split(".")[0];
112
- const firstLevelItem = get(unref(relatedObjectRef), firstLevelKey);
138
+ const firstLevelKey = key.split(".")[0];
139
+ const firstLevelItem = get(relatedObject, firstLevelKey);
113
140
  if (isArray(firstLevelItem)) {
114
- const restOfKey = rulePkKey.slice(12 + firstLevelKey.length + 1);
141
+ const restOfKey = key.slice(firstLevelKey.length + 1);
115
142
  value = firstLevelItem.map((e) => get(e, restOfKey)).flat();
116
143
  }
117
144
  }
118
- } else {
119
- value = get(unref(originalObjectRef), rulePkKey);
120
- }
121
- if (isUndefined(value)) {
122
- if (!isUndefined(relatedObjectsObject[addedRuleKey])) {
123
- relatedObjectsObject[addedRuleKey] = undefined;
124
- }
125
- return;
126
- }
127
- if (isArray(value)) {
128
- // the related list could be sorted differently than the original list.
129
- if (ruleOrder?.length) {
145
+ if (isArray(value) && ruleOrder?.length) {
130
146
  value = value.filter(identity);
131
147
  const indexById = Object.fromEntries(ruleOrder.map((e, i) => [e, i]));
132
148
  value.sort((a, b) => {
@@ -135,24 +151,20 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
135
151
  return aIndex - bIndex;
136
152
  });
137
153
  }
138
- value = value.map((e) => ruleObjects[e]).filter(identity);
139
- } else {
140
- value = ruleObjects[value];
141
- }
142
- if (!isEqual(value, relatedObjectsObject[addedRuleKey])) {
143
- relatedObjectsObject[addedRuleKey] = value;
144
- }
145
- };
146
- watch(
147
- [toRef(state.relatedObjectsRules, addedRuleKey), originalObjectRef, relatedObjectRef],
148
- relatedObjectsObjectWatchFn,
149
- {
150
- deep: true,
151
- immediate: true,
152
- }
153
- );
154
- }
155
- });
154
+ return value;
155
+ });
156
+
157
+ state.relatedObjects[objectKey][addedRuleKey] = computed(() => {
158
+ const value = unref(state.fkForIdAndRule[objectKey][addedRuleKey]);
159
+ const objects = unref(rules).objects;
160
+ if (isArray(value)) {
161
+ return value.map((e) => objects[e]).filter(identity);
162
+ }
163
+ return objects[value];
164
+ });
165
+ }
166
+ });
167
+ }
156
168
  }
157
169
  state.relatedObjectsWatchRunning = false;
158
170
  }
@@ -168,10 +180,7 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
168
180
 
169
181
  watch(() => Object.keys(parentState.objects), parentStateObjectsWatch, { immediate: true });
170
182
  watch(
171
- [
172
- () => Object.keys(state.relatedObjects),
173
- () => (state.relatedObjectsRules ? Object.keys(state.relatedObjectsRules) : state.relatedObjectsRules),
174
- ],
183
+ [() => Object.keys(state.relatedObjects), () => Object.keys(state.relatedObjectsRules || {})],
175
184
  relatedObjectsWatch,
176
185
  { immediate: true }
177
186
  );
package/use/listSort.js CHANGED
@@ -18,7 +18,6 @@ import isUndefined from "lodash-es/isUndefined.js";
18
18
  import throttle from "lodash-es/throttle.js";
19
19
  import zip from "lodash-es/zip.js";
20
20
  import { effectScope, reactive, toRef, unref, watch, computed } from "vue";
21
- import { deepUnref } from "vue-deepunref";
22
21
 
23
22
  const collator = new Intl.Collator(undefined, { numeric: true });
24
23
 
@@ -132,14 +131,17 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
132
131
 
133
132
  function sortCriteriaWatch() {
134
133
  try {
135
- if (!state.orderByRules || !state.orderByRules.filter(identity).length) {
134
+ if (!state.orderByRules?.length || !state.orderByRules.filter(identity).length) {
136
135
  if (!isEmpty(state.sortCriteria)) {
137
136
  for (const removedKey of Object.keys(state.sortCriteria)) {
138
137
  removeSortCriteria(removedKey);
139
138
  }
140
139
  }
141
- assignReactiveObject(state.order, deepUnref(parentState.order));
142
- assignReactiveObject(state.objectsInOrder, deepUnref(parentState.objectsInOrder));
140
+ state.order = [...parentState.order];
141
+ assignReactiveObject(
142
+ state.objectsInOrderRefs,
143
+ state.order.map((e) => toRef(parentState.objects, e))
144
+ );
143
145
  return;
144
146
  }
145
147
  const { removedKeys, addedKeys } = keyDiff(
@@ -152,9 +154,9 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
152
154
 
153
155
  es.run(() => {
154
156
  for (const addedKey of addedKeys) {
155
- const object = toRef(() => parentState.objects[addedKey]);
156
- const relatedObj = toRef(() => parentState.relatedObjects?.[addedKey]);
157
- const calculatedObj = toRef(() => parentState.calculatedObjects?.[addedKey]);
157
+ const object = toRef(parentState.objects, addedKey);
158
+ const relatedObj = toRef(parentState.relatedObjects, addedKey);
159
+ const calculatedObj = toRef(parentState.calculatedObjects, addedKey);
158
160
  addSortCriteria(object, relatedObj, calculatedObj, addedKey);
159
161
  }
160
162
  });
@@ -169,17 +171,15 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
169
171
 
170
172
  function sortWatch() {
171
173
  try {
172
- if (!state.orderByRules || !state.orderByRules.length) {
174
+ if (!state.orderByRules?.length) {
173
175
  state.order = [...parentState.order];
174
176
  assignReactiveObject(
175
177
  state.objectsInOrderRefs,
176
178
  state.order.map((e) => toRef(parentState.objects, e))
177
179
  );
178
- 3;
179
180
  return;
180
181
  }
181
-
182
- let idList = Object.keys(parentState.objects);
182
+ let idList = [...parentState.order];
183
183
  idList.sort((xKey, yKey) => {
184
184
  const xCriteria = state.sortCriteria[xKey];
185
185
  const yCriteria = state.sortCriteria[yKey];
@@ -238,9 +238,7 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
238
238
  watch([toRef(state, "orderByDesc"), toRef(state, "sortCriteria")], throttledSortWatch, {
239
239
  deep: true,
240
240
  });
241
- // we do not need two immediate watches to the same function.
242
- watch(() => Object.keys(parentState.objects), sortCriteriaWatch);
243
- watch(toRef(state, "orderByRules"), sortCriteriaWatch, {
241
+ watch([toRef(state, "orderByRules"), toRef(parentState, "order")], sortCriteriaWatch, {
244
242
  deep: true,
245
243
  immediate: true,
246
244
  });
@@ -15,3 +15,12 @@ export const getObjectRelatedCalculatedByKey = (obj, relatedObj, calculatedObj,
15
15
  }
16
16
  return [getObj, getKey];
17
17
  };
18
+
19
+ export const getObjectRelatedByKey = (obj, relatedObj, key) => {
20
+ let getObj = obj,
21
+ getKey = key.replace(relatedItemRegex, () => {
22
+ getObj = relatedObj;
23
+ return "";
24
+ });
25
+ return [getObj, getKey];
26
+ };