@arrai-innovations/reactive-helpers 11.4.2 → 13.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 (115) hide show
  1. package/.husky/pre-commit +0 -1
  2. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  3. package/.prettierignore +1 -0
  4. package/README.md +11 -10
  5. package/config/listCrud.js +32 -1
  6. package/config/objectCrud.js +47 -1
  7. package/docs/README.md +43 -0
  8. package/docs/config/listCrud.md +97 -0
  9. package/docs/config/objectCrud.md +123 -0
  10. package/docs/use/cancellableIntent.md +163 -0
  11. package/docs/use/combineClasses.md +52 -0
  12. package/docs/use/list.md +285 -0
  13. package/docs/use/listCalculated.md +773 -0
  14. package/docs/use/listFilter.md +877 -0
  15. package/docs/use/listInstance.md +483 -0
  16. package/docs/use/listKeys.md +91 -0
  17. package/docs/use/listRelated.md +685 -0
  18. package/docs/use/listSearch.md +889 -0
  19. package/docs/use/listSort.md +1144 -0
  20. package/docs/use/listSubscription.md +379 -0
  21. package/docs/use/loadingError.md +21 -0
  22. package/docs/use/object.md +437 -0
  23. package/docs/use/objectCalculated.md +606 -0
  24. package/docs/use/objectInstance.md +486 -0
  25. package/docs/use/objectRelated.md +588 -0
  26. package/docs/use/objectSubscription.md +459 -0
  27. package/docs/use/paginatedListInstance.md +147 -0
  28. package/docs/use/search.md +311 -0
  29. package/docs/use/watchesRunning.md +80 -0
  30. package/docs/utils/assignReactiveObject.md +351 -0
  31. package/docs/utils/classes.md +100 -0
  32. package/docs/utils/compact.md +57 -0
  33. package/docs/utils/deleteKey.md +53 -0
  34. package/docs/utils/flattenPaths.md +45 -0
  35. package/docs/utils/getFakeId.md +34 -0
  36. package/docs/utils/keyDiff.md +101 -0
  37. package/docs/utils/loadingCombine.md +28 -0
  38. package/docs/utils/proxyRunning.md +33 -0
  39. package/docs/utils/relatedCalculatedHelpers.md +75 -0
  40. package/docs/utils/set.md +139 -0
  41. package/docs/utils/transformWalk.md +33 -0
  42. package/docs/utils/watches.md +441 -0
  43. package/eslint.config.js +125 -0
  44. package/index.js +34 -3
  45. package/lint-staged.config.js +5 -1
  46. package/makeTypeDoc.sh +25 -0
  47. package/package.json +27 -19
  48. package/tests/unit/config/listCrud.spec.js +5 -0
  49. package/tests/unit/config/objectCrud.spec.js +5 -0
  50. package/tests/unit/crudPromise.js +22 -3
  51. package/tests/unit/mockOnUnmounted.js +1 -1
  52. package/tests/unit/use/cancellableIntent.spec.js +2 -0
  53. package/tests/unit/use/listCalculated.spec.js +4 -3
  54. package/tests/unit/use/listFilter.spec.js +11 -7
  55. package/tests/unit/use/listInstance.spec.js +32 -14
  56. package/tests/unit/use/listRelated.spec.js +3 -2
  57. package/tests/unit/use/listSearch.spec.js +9 -7
  58. package/tests/unit/use/listSort.spec.js +29 -22
  59. package/tests/unit/use/listSubscription.spec.js +11 -5
  60. package/tests/unit/use/objectInstance.spec.js +15 -2
  61. package/tests/unit/use/objectSubscription.spec.js +31 -16
  62. package/tests/unit/use/search.spec.js +2 -2
  63. package/tests/unit/utils/assignReactiveObject.spec.js +21 -13
  64. package/tests/unit/utils/classes.spec.js +7 -6
  65. package/tests/unit/utils/watches.spec.js +4 -4
  66. package/tsconfig.json +19 -0
  67. package/typedoc-local-plugins/customize-output.js +13 -0
  68. package/typedoc.json +12 -0
  69. package/use/cancellableIntent.js +73 -2
  70. package/use/combineClasses.js +41 -31
  71. package/use/list.js +139 -5
  72. package/use/listCalculated.js +175 -10
  73. package/use/listFilter.js +175 -21
  74. package/use/listInstance.js +215 -94
  75. package/use/listRelated.js +260 -72
  76. package/use/listSearch.js +178 -57
  77. package/use/listSort.js +188 -21
  78. package/use/listSubscription.js +211 -63
  79. package/use/loadingError.js +43 -0
  80. package/use/object.js +187 -23
  81. package/use/objectCalculated.js +168 -16
  82. package/use/objectInstance.js +183 -102
  83. package/use/objectRelated.js +276 -84
  84. package/use/objectSubscription.js +169 -17
  85. package/use/paginatedListInstance.js +30 -1
  86. package/use/search.js +39 -23
  87. package/use/watchesRunning.js +41 -6
  88. package/utils/assignReactiveObject.js +139 -88
  89. package/utils/classes.js +57 -48
  90. package/utils/compact.js +28 -2
  91. package/utils/deleteKey.js +14 -1
  92. package/utils/flattenPaths.js +19 -9
  93. package/utils/getFakeId.js +11 -1
  94. package/utils/keyDiff.js +26 -35
  95. package/utils/loadingCombine.js +7 -4
  96. package/utils/proxyRunning.js +16 -8
  97. package/utils/relatedCalculatedHelpers.js +17 -0
  98. package/utils/set.js +66 -0
  99. package/utils/transformWalk.js +7 -6
  100. package/utils/watches.js +205 -71
  101. package/.eslintignore +0 -2
  102. package/.eslintrc.cjs +0 -25
  103. package/.jsdoc2md.json +0 -4
  104. package/config/index.js +0 -2
  105. package/docs.md +0 -1002
  106. package/jsdoc-to-markdown.sh +0 -16
  107. package/tests/unit/.eslintrc.cjs +0 -11
  108. package/tests/unit/config/index.spec.js +0 -18
  109. package/tests/unit/use/index.spec.js +0 -18
  110. package/tests/unit/utils/index.spec.js +0 -18
  111. package/use/index.js +0 -19
  112. package/utils/debugMessage.js +0 -209
  113. package/utils/debugWatch.js +0 -29
  114. package/utils/index.js +0 -16
  115. package/utils/lifecycleDebug.js +0 -126
@@ -20,6 +20,83 @@ import isEmpty from "lodash-es/isEmpty.js";
20
20
  import isUndefined from "lodash-es/isUndefined.js";
21
21
  import { computed, effectScope, onScopeDispose, reactive, ref, toRef, unref, watch } from "vue";
22
22
 
23
+ /**
24
+ * Vue Composition API composable function for managing relationships among objects in a list.
25
+ * It enables linking objects based on predefined rules and dynamically adjusts as the underlying data changes.
26
+ *
27
+ * @module use/listRelated.js
28
+ */
29
+
30
+ // todo: pkKey is misnamed, it should be fkKey... this will be a major breaking change
31
+ /**
32
+ * The rule for defining relationships for objects in a list.
33
+ *
34
+ * @typedef {object} ListRelatedRule
35
+ * @property {string} pkKey - Specifies the foreign key used to link objects across lists. Planned to be renamed to
36
+ * 'fkKey' to better reflect its usage.
37
+ * @property {string[]} order - Specifies the order in which related objects should be sorted, if applicable.
38
+ * @property {import('./listInstance.js').ObjectsById} objects - The objects that can be related based on the foreign key.
39
+ */
40
+
41
+ /**
42
+ * The rules for defining relationships among objects in a list.
43
+ *
44
+ * @typedef {import('vue').UnwrapNestedRefs<{
45
+ * [rule: string]: ListRelatedRule,
46
+ * }>} ListRelatedRules
47
+ */
48
+
49
+ /**
50
+ * Represents the internal state used by the list related composition function. It manages and computes the relationships
51
+ * between objects based on specified rules, providing real-time updates to related objects as the parent state changes.
52
+ *
53
+ * @typedef {object} ListRelatedRawState
54
+ * @property {{
55
+ * [id: string]: {
56
+ * [rule: string]: import('vue').ComputedRef<any>,
57
+ * },
58
+ * }} relatedObjects - Stores computed references to related objects, allowing for dynamic access based on object ID and specific rules.
59
+ * @property {ListRelatedRules} relatedObjectsRules - Defines the rules for establishing relationships, such as foreign key links and sorting orders.
60
+ * @property {{
61
+ * [id: string]: {
62
+ * [rule: string]: import('vue').ComputedRef<[object, string]>,
63
+ * },
64
+ * }} objAndKeyForIdAndRule - Maps each object ID and rule to a tuple consisting of the related object and its respective key, facilitating direct data manipulation.
65
+ * @property {{
66
+ * [id: string]: {
67
+ * [rule: string]: import('vue').ComputedRef<any>,
68
+ * },
69
+ * }} fkForIdAndRule - Maintains computed references to the foreign keys for each object ID and rule, crucial for navigating complex data relationships.
70
+ * @property {boolean} relatedObjectsParentStateObjectsWatchRunning - Flags whether the watch on parent state objects is currently active, ensuring updates trigger as needed.
71
+ * @property {boolean} relatedObjectsWatchRunning - Indicates if watches on the related objects themselves are active, managing updates efficiently.
72
+ * @property {boolean} relatedRunning - Signals whether any computations related to object relationships are currently in progress.
73
+ * @property {import('vue').Ref<boolean>} running - General flag that indicates if the list-related logic is processing, used to manage UI feedback or prevent concurrent operations.
74
+ */
75
+
76
+ /**
77
+ * The raw state properties for a parent of a list related property.
78
+ *
79
+ * @typedef {(
80
+ * import('./listInstance.js').ListInstanceRawState &
81
+ * Partial<import('./listSubscription.js').ListSubscriptionRawState>
82
+ * )} ListRelatedParentRawState
83
+ */
84
+
85
+ /**
86
+ * The type for a parentState object.
87
+ *
88
+ * @typedef {import('vue').UnwrapNestedRefs<ListRelatedParentRawState>} ListRelatedParentState
89
+ */
90
+
91
+ /**
92
+ * The state for a list related property.
93
+ *
94
+ * @typedef {import('vue').UnwrapNestedRefs<
95
+ * ListRelatedParentRawState &
96
+ * ListRelatedRawState
97
+ * >} ListRelatedState
98
+ */
99
+
23
100
  const parentStateKeys = difference(
24
101
  new Set([
25
102
  ...listInstanceStateKeys,
@@ -32,39 +109,140 @@ const parentStateKeys = difference(
32
109
  new Set(listRelatedStateKeys)
33
110
  );
34
111
 
35
- export function useListRelateds(instances, args) {
36
- for (const [key, value] of Object.entries(args)) {
37
- useListRelated({
38
- parentState: instances[key].state,
39
- ...value,
40
- });
112
+ /**
113
+ * The options for the list related composition function.
114
+ *
115
+ * @typedef {object} ListRelatedOptions
116
+ * @property {ListRelatedParentState} parentState - The parent state object.
117
+ * @property {import('vue').Ref<ListRelatedRules>} relatedObjectsRules - The rules for the related objects.
118
+ */
119
+
120
+ /**
121
+ * The properties for the list related composition function.
122
+ *
123
+ * @typedef {object} ListRelatedProperties
124
+ * @property {ListRelatedState} state - The state for the list related property.
125
+ * @property {ListRelatedParentState} parentState - The parent state object.
126
+ * @property {import('./watchesRunning.js').WatchesRunning} watchesRunning - The watches running instance.
127
+ * @property {import('vue').EffectScope} effectScope - The effect scope for the list related property.
128
+ */
129
+
130
+ // if we provided functions, we would add a typedef and mix them into ListRelated
131
+
132
+ /**
133
+ * An instance of `useListRelated`.
134
+ *
135
+ * @typedef {ListRelatedProperties} ListRelated
136
+ */
137
+
138
+ /**
139
+ * Creates and manages multiple instances of list-related properties, linking each to corresponding parent instances
140
+ * based on provided configuration.
141
+ *
142
+ * @param {{
143
+ * [key: string]: ListRelatedOptions
144
+ * }} listRelatedArgs - The options for the list related properties.
145
+ * @returns {{[key: string]: ListRelated}} - The instances of the list related properties.
146
+ */
147
+ export function useListRelateds(listRelatedArgs) {
148
+ /** @type {{[key: string]: ListRelated}} */
149
+ const relateds = {};
150
+ for (const [key, value] of Object.entries(listRelatedArgs)) {
151
+ relateds[key] = useListRelated(value);
41
152
  }
153
+ return relateds;
42
154
  }
43
155
 
156
+ /**
157
+ * Initializes and returns an instance of a related objects manager. This function sets up reactive states
158
+ * and computations that dynamically adjust as the parent list's state changes. It uses defined rules
159
+ * for object relationships to compute and update related objects in real-time, ensuring that changes in the parent
160
+ * state are reflected in the relationships defined by the rules.
161
+ *
162
+ * @example
163
+ * ```vue
164
+ * <script setup>
165
+ * import { useListInstance, useListRelated } from "@arrai-innovations/reactive-helpers";
166
+ * import { reactive, toRef } from "vue";
167
+ *
168
+ * const props = defineProps({
169
+ * someListFilter: {
170
+ * type: String,
171
+ * default: "",
172
+ * description: "The filter to apply to the list.",
173
+ * },
174
+ * objects: {
175
+ * type: Object,
176
+ * default: () => ({}),
177
+ * description: "The objects to relate to.",
178
+ * },
179
+ * order: {
180
+ * type: Array,
181
+ * default: () => [],
182
+ * description: "The order of the list.",
183
+ * },
184
+ * });
185
+ *
186
+ * const listInstanceProps = reactive({
187
+ * crudArgs: {
188
+ * // whatever arguments are required for your configured list crud function to get the right endpoint
189
+ * },
190
+ * listArgs: {
191
+ * // whatever arguments are required for your configured list function to get the right list
192
+ * someListFilter: toRef(props, "someListFilter"),
193
+ * },
194
+ * retrieveArgs: {
195
+ * // whatever arguments are required for your configured list function to get items back looking as expected
196
+ * },
197
+ * intendToList: false,
198
+ * });
199
+ * listInstanceProps.intendToList = computed(()=> !!props.someListFilter);
200
+ * const listInstance = useListInstance({ props: listInstanceProps });
201
+ * const listRelatedProps = reactive({
202
+ * parentState: listInstance.state, // reactive-to-reactive so no need for toRef
203
+ * relatedObjectsRules: {
204
+ * someRule: {
205
+ * // this can point to a key or an array of keys to relate to
206
+ * pkKey: "dot.separated.key.to.id.on.an.listInstance.object",
207
+ * objects: toRef(props, "objects"),
208
+ * order: toRef(props, "order"),
209
+ * },
210
+ * },
211
+ * });
212
+ * const listRelated = useListRelated(listRelatedProps);
213
+ * </script>
214
+ * <template>
215
+ * <ul>
216
+ * <!-- reactive list of objects, re-retrieving the list as someListFilter changes. -->
217
+ * <li v-for="obj in listInstance.state.objectsInOrder">
218
+ * {{ obj }}
219
+ * <div>
220
+ * <!-- the related object or objects based on the rule -->
221
+ * {{ listRelated.state.relatedObjects[obj.id].someRule }}
222
+ * </div>
223
+ * </li>
224
+ * </ul>
225
+ * </template>
226
+ * ```
227
+ *
228
+ * @param {ListRelatedOptions} options - The configuration options including the parent state and rules for related
229
+ * objects.
230
+ * @returns {ListRelated} - A reactive instance that manages related objects, providing real-time updates and
231
+ * maintaining the integrity of object relationships as per the specified rules.
232
+ */
44
233
  export function useListRelated({ parentState, relatedObjectsRules }) {
45
- const state = reactive({
46
- relatedObjectsRules: relatedObjectsRules,
47
- relatedObjects: {
48
- // id: {
49
- // rule: list of objects or single object,
50
- // },
51
- },
52
- objAndKeyForIdAndRule: {
53
- // id: {
54
- // rule: {
55
- // obj: object,
56
- // key: string,
57
- // },
58
- // },
59
- },
60
- fkForIdAndRule: {
61
- // id: {
62
- // rule: list of ids or single id,
63
- // },
64
- },
65
- relatedObjectsParentStateObjectsWatchRunning: false,
66
- relatedObjectsWatchRunning: false,
67
- });
234
+ /** @type {ListRelatedState} */
235
+ // @ts-ignore - we'll add the missing properties later, taking refs from parentState
236
+ const state = reactive(
237
+ /** @type {ListRelatedRawState} */ {
238
+ relatedObjectsRules,
239
+ relatedObjects: {},
240
+ objAndKeyForIdAndRule: {},
241
+ fkForIdAndRule: {},
242
+ relatedObjectsParentStateObjectsWatchRunning: false,
243
+ relatedObjectsWatchRunning: false,
244
+ }
245
+ );
68
246
  const relatedObjectsEffectScopes = {};
69
247
 
70
248
  function parentStateObjectsWatch() {
@@ -89,6 +267,55 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
89
267
  state.relatedObjectsParentStateObjectsWatchRunning = false;
90
268
  }
91
269
 
270
+ function applyRuleToObject(objectKey, ruleKey, originalObjectRef, relatedObjectRef) {
271
+ const rule = toRef(state.relatedObjectsRules, ruleKey);
272
+ state.objAndKeyForIdAndRule[objectKey][ruleKey] = computed(() => {
273
+ const rulePkKey = unref(rule).pkKey || ruleKey;
274
+ const object = unref(originalObjectRef);
275
+ const relatedObject = unref(relatedObjectRef);
276
+ return getObjectRelatedByKey(object, relatedObject, rulePkKey);
277
+ });
278
+
279
+ state.fkForIdAndRule[objectKey][ruleKey] = computed(() =>
280
+ computeForeignKey(ruleKey, objectKey, rule, relatedObjectRef)
281
+ );
282
+
283
+ state.relatedObjects[objectKey][ruleKey] = computed(() => {
284
+ const value = unref(state.fkForIdAndRule[objectKey][ruleKey]);
285
+ const objects = unref(rule).objects;
286
+ if (isArray(value)) {
287
+ return value.map((e) => objects[e]).filter(identity);
288
+ }
289
+ return objects[value];
290
+ });
291
+ }
292
+
293
+ function computeForeignKey(ruleKey, objectKey, rule, relatedObjectRef) {
294
+ const ruleOrder = unref(rule).order;
295
+ const relatedObject = unref(relatedObjectRef);
296
+ const [objectForGet, key] = unref(state.objAndKeyForIdAndRule[objectKey][ruleKey]);
297
+ let value = get(objectForGet, key);
298
+ if (objectForGet === relatedObject && isUndefined(value)) {
299
+ // Handle nested arrays
300
+ const firstLevelKey = key.split(".")[0];
301
+ const firstLevelItem = get(relatedObject, firstLevelKey);
302
+ if (isArray(firstLevelItem)) {
303
+ const restOfKey = key.slice(firstLevelKey.length + 1);
304
+ value = firstLevelItem.map((e) => get(e, restOfKey)).flat();
305
+ }
306
+ }
307
+ if (isArray(value) && ruleOrder?.length) {
308
+ value = value.filter(identity);
309
+ const indexById = Object.fromEntries(ruleOrder.map((e, i) => [e, i]));
310
+ value.sort((a, b) => {
311
+ const aIndex = indexById[a];
312
+ const bIndex = indexById[b];
313
+ return aIndex - bIndex;
314
+ });
315
+ }
316
+ return value;
317
+ }
318
+
92
319
  function relatedObjectsWatch() {
93
320
  const relatedObjectsRulesIsEmpty = !state.relatedObjectsRules || isEmpty(state.relatedObjectsRules);
94
321
  for (const objectKey of Object.keys(state.relatedObjects)) {
@@ -100,7 +327,7 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
100
327
  ));
101
328
  } else {
102
329
  if (isEmpty(state.relatedObjects[objectKey])) {
103
- return;
330
+ continue;
104
331
  }
105
332
  removedRuleKeys = new Set(Object.keys(state.relatedObjects[objectKey]));
106
333
  addedRuleKeys = new Set();
@@ -121,48 +348,7 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
121
348
  const relatedObjectRef = toRef(state.relatedObjects, objectKey);
122
349
  relatedObjectsEffectScopes[objectKey].run(() => {
123
350
  for (const addedRuleKey of addedRuleKeys) {
124
- const rules = toRef(state.relatedObjectsRules, addedRuleKey);
125
- state.objAndKeyForIdAndRule[objectKey][addedRuleKey] = computed(() => {
126
- const rulePkKey = unref(rules).pkKey || addedRuleKey;
127
- const object = unref(originalObjectRef);
128
- const relatedObject = unref(relatedObjectRef);
129
- return getObjectRelatedByKey(object, relatedObject, rulePkKey);
130
- });
131
-
132
- state.fkForIdAndRule[objectKey][addedRuleKey] = computed(() => {
133
- const ruleOrder = unref(rules).order;
134
- const relatedObject = unref(relatedObjectRef);
135
- const [objectForGet, key] = unref(state.objAndKeyForIdAndRule[objectKey][addedRuleKey]);
136
- let value = get(objectForGet, key);
137
- if (objectForGet === relatedObject && isUndefined(value)) {
138
- // is the first level an array?
139
- const firstLevelKey = key.split(".")[0];
140
- const firstLevelItem = get(relatedObject, firstLevelKey);
141
- if (isArray(firstLevelItem)) {
142
- const restOfKey = key.slice(firstLevelKey.length + 1);
143
- value = firstLevelItem.map((e) => get(e, restOfKey)).flat();
144
- }
145
- }
146
- if (isArray(value) && ruleOrder?.length) {
147
- value = value.filter(identity);
148
- const indexById = Object.fromEntries(ruleOrder.map((e, i) => [e, i]));
149
- value.sort((a, b) => {
150
- const aIndex = indexById[a];
151
- const bIndex = indexById[b];
152
- return aIndex - bIndex;
153
- });
154
- }
155
- return value;
156
- });
157
-
158
- state.relatedObjects[objectKey][addedRuleKey] = computed(() => {
159
- const value = unref(state.fkForIdAndRule[objectKey][addedRuleKey]);
160
- const objects = unref(rules).objects;
161
- if (isArray(value)) {
162
- return value.map((e) => objects[e]).filter(identity);
163
- }
164
- return objects[value];
165
- });
351
+ applyRuleToObject(objectKey, addedRuleKey, originalObjectRef, relatedObjectRef);
166
352
  }
167
353
  });
168
354
  }
@@ -198,9 +384,11 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
198
384
  ],
199
385
  });
200
386
 
387
+ // @ts-ignore - proxy the running property
201
388
  state.relatedRunning = toRef(watchesRunning.state, "running");
202
389
  const parentRunning = ref(undefined);
203
390
  proxyRunning(parentState, "running", parentRunning);
391
+ // @ts-ignore - combine the running properties
204
392
  state.running = computed(() => loadingCombine(watchesRunning.state.running, parentRunning.value));
205
393
 
206
394
  onScopeDispose(() => {