@arrai-innovations/reactive-helpers 21.1.2 → 22.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +109 -21
  3. package/config/commonCrud.js +7 -10
  4. package/config/listCrud.js +33 -33
  5. package/config/objectCrud.js +64 -59
  6. package/package.json +103 -89
  7. package/types/config/listCrud.d.ts +83 -28
  8. package/types/config/objectCrud.d.ts +139 -49
  9. package/types/tests/benchmarks/fixtures.d.ts +60 -0
  10. package/types/tests/benchmarks/listLayers.bench.d.ts +1 -0
  11. package/types/tests/benchmarks/listPush.bench.d.ts +1 -0
  12. package/types/tests/benchmarks/listStream.bench.d.ts +1 -0
  13. package/types/tests/unit/use/lifecycleCleanup.spec.d.ts +1 -0
  14. package/types/tests/unit/use/listPerformance.spec.d.ts +1 -0
  15. package/types/tests/unit/utils/cancellablePromise.spec.d.ts +1 -0
  16. package/types/use/cancellableIntent.d.ts +36 -33
  17. package/types/use/combineClasses.d.ts +5 -2
  18. package/types/use/error.d.ts +39 -19
  19. package/types/use/list.d.ts +27 -26
  20. package/types/use/listCalculated.d.ts +38 -55
  21. package/types/use/listFilter.d.ts +25 -33
  22. package/types/use/listInstance.d.ts +94 -81
  23. package/types/use/listRelated.d.ts +37 -57
  24. package/types/use/listSearch.d.ts +54 -52
  25. package/types/use/listSort.d.ts +31 -40
  26. package/types/use/listSubscription.d.ts +33 -36
  27. package/types/use/loading.d.ts +20 -10
  28. package/types/use/loadingError.d.ts +12 -3
  29. package/types/use/object.d.ts +8 -5
  30. package/types/use/objectCalculated.d.ts +46 -39
  31. package/types/use/objectInstance.d.ts +70 -74
  32. package/types/use/objectRelated.d.ts +51 -41
  33. package/types/use/objectSubscription.d.ts +38 -48
  34. package/types/use/proxyError.d.ts +15 -6
  35. package/types/use/proxyLoading.d.ts +11 -5
  36. package/types/use/proxyLoadingError.d.ts +19 -7
  37. package/types/use/search.d.ts +33 -29
  38. package/types/utils/assignReactiveObject.d.ts +3 -0
  39. package/types/utils/cancellableFetch.d.ts +2 -3
  40. package/types/utils/cancellablePromise.d.ts +42 -8
  41. package/types/utils/classes.d.ts +7 -1
  42. package/types/utils/deepUnref.d.ts +1 -1
  43. package/types/utils/getFakePk.d.ts +2 -2
  44. package/types/utils/isReactiveTyped.d.ts +2 -0
  45. package/types/utils/keyDiff.d.ts +4 -6
  46. package/types/utils/relatedCalculatedHelpers.d.ts +2 -0
  47. package/types/utils/watches.d.ts +1 -1
  48. package/use/cancellableIntent.js +36 -9
  49. package/use/combineClasses.js +2 -2
  50. package/use/error.js +10 -14
  51. package/use/list.js +6 -18
  52. package/use/listCalculated.js +28 -38
  53. package/use/listFilter.js +12 -24
  54. package/use/listInstance.js +101 -60
  55. package/use/listRelated.js +18 -37
  56. package/use/listSearch.js +9 -18
  57. package/use/listSort.js +95 -64
  58. package/use/listSubscription.js +19 -25
  59. package/use/loading.js +5 -7
  60. package/use/loadingError.js +3 -3
  61. package/use/object.js +14 -27
  62. package/use/objectCalculated.js +21 -25
  63. package/use/objectInstance.js +55 -50
  64. package/use/objectRelated.js +19 -25
  65. package/use/objectSubscription.js +18 -30
  66. package/use/proxyError.js +10 -10
  67. package/use/proxyLoading.js +5 -5
  68. package/use/proxyLoadingError.js +13 -8
  69. package/use/search.js +5 -11
  70. package/utils/assignReactiveObject.js +3 -3
  71. package/utils/cancellableFetch.js +3 -3
  72. package/utils/cancellablePromise.js +25 -8
  73. package/utils/classes.js +2 -2
  74. package/utils/deepUnref.js +1 -5
  75. package/utils/getFakePk.js +2 -2
  76. package/utils/isReactiveTyped.js +2 -0
  77. package/utils/keyDiff.js +1 -3
  78. package/utils/relatedCalculatedHelpers.js +2 -0
@@ -18,44 +18,37 @@ import { computed, effectScope, nextTick, onScopeDispose, reactive, ref, toRef,
18
18
 
19
19
  // todo: pkKey is misnamed, it should be fkKey... this will be a major breaking change
20
20
  /**
21
- * The rule for defining relationships for objects in a list.
22
- *
23
- * @typedef {object} ListRelatedRule
24
- * @property {string} pkKey - Specifies the foreign key used to link objects across lists. Planned to be renamed to
25
- * 'fkKey' to better reflect its usage.
21
+ * @typedef {object} ListRelatedRule - The rule for defining relationships for objects in a list.
22
+ * @property {string} [pkKey] - Specifies the foreign key used to link objects across lists. Defaults to the rule's
23
+ * own key when omitted. Planned to be renamed to 'fkKey' to better reflect its usage.
26
24
  * @property {string[]} [order] - Specifies the order in which related objects should be sorted, if applicable.
27
25
  * @property {import('./listInstance.js').ObjectsByPk} objects - The objects that can be related based on the foreign key.
28
26
  */
29
27
 
30
28
  /**
31
- * The rules for defining relationships among objects in a list.
32
- *
33
29
  * @typedef {{
34
30
  * [rule: string]: ListRelatedRule,
35
- * }} ListRelatedRules
31
+ * }} ListRelatedRules - The rules for defining relationships among objects in a list.
36
32
  */
37
33
 
38
34
  /**
39
- * Represents the internal state used by the list related composition function. It manages and computes the relationships
40
- * between objects based on specified rules, providing real-time updates to related objects as the parent state changes.
41
- *
42
- * @typedef {object} ListRelatedRawState
35
+ * @typedef {object} ListRelatedRawState - Represents the internal state used by the list related composition function. It manages and computes the relationships between objects based on specified rules, providing real-time updates to related objects as the parent state changes.
43
36
  * @property {{
44
37
  * [pk: import('../config/commonCrud.js').Pk]: {
45
- * [rule: string]: import('vue').ComputedRef<any>,
38
+ * [rule: string]: any,
46
39
  * },
47
- * }} relatedObjects - Stores computed references to related objects, allowing for dynamic access based on object pk and specific rules.
40
+ * }} relatedObjects - The related objects, by object pk and then rule name. Each entry is backed by a computed, but it is read through a reactive proxy that unwraps it, so reads yield the related object (or array of related objects) and never carry a `.value`.
48
41
  * @property {ListRelatedRules} relatedObjectsRules - Defines the rules for establishing relationships, such as foreign key links and sorting orders.
49
42
  * @property {{
50
43
  * [pk: import('../config/commonCrud.js').Pk]: {
51
44
  * [rule: string]: import('vue').ComputedRef<[object, string]>,
52
45
  * },
53
- * }} objAndKeyForPkAndRule - Maps each object pk and rule to a tuple consisting of the related object and its respective key, facilitating direct data manipulation.
46
+ * }} objAndKeyForPkAndRule - Maps each object pk and rule to a tuple consisting of the related object and its respective key, facilitating direct data manipulation. Reads through the reactive state unwrap the computed to the tuple itself, so `.value` is not used.
54
47
  * @property {{
55
48
  * [pk: import('../config/commonCrud.js').Pk]: {
56
- * [rule: string]: import('vue').ComputedRef<any>,
49
+ * [rule: string]: any,
57
50
  * },
58
- * }} fkForPkAndRule - Maintains computed references to the foreign keys for each object pk and rule, crucial for navigating complex data relationships.
51
+ * }} fkForPkAndRule - The foreign key for each object pk and rule, crucial for navigating complex data relationships. Each entry is backed by a computed that the reactive proxy unwraps on read.
59
52
  * @property {boolean} relatedObjectsParentStateObjectsWatchRunning - Flags whether the watch on parent state objects is currently active, ensuring updates trigger as needed.
60
53
  * @property {boolean} relatedObjectsWatchRunning - Indicates if watches on the related objects themselves are active, managing updates efficiently.
61
54
  * @property {boolean} relatedRunning - Signals whether any computations related to object relationships are currently in progress.
@@ -63,41 +56,31 @@ import { computed, effectScope, nextTick, onScopeDispose, reactive, ref, toRef,
63
56
  */
64
57
 
65
58
  /**
66
- * The raw state properties for a parent of a list related property.
67
- *
68
59
  * @typedef {(
69
60
  * import('./listInstance.js').ListInstanceRawState &
70
61
  * Partial<import('./listSubscription.js').ListSubscriptionRawState>
71
- * )} ListRelatedParentRawState
62
+ * )} ListRelatedParentRawState - The raw state properties for a parent of a list related property.
72
63
  */
73
64
 
74
65
  /**
75
- * The type for a parentState object.
76
- *
77
- * @typedef {import('vue').UnwrapNestedRefs<ListRelatedParentRawState>} ListRelatedParentState
66
+ * @typedef {import('vue').UnwrapNestedRefs<ListRelatedParentRawState>} ListRelatedParentState - The type for a parentState object.
78
67
  */
79
68
 
80
69
  /**
81
- * The state for a list related property.
82
- *
83
70
  * @typedef {import('vue').UnwrapNestedRefs<
84
71
  * ListRelatedParentRawState &
85
72
  * ListRelatedRawState
86
- * >} ListRelatedState
73
+ * >} ListRelatedState - The state for a list related property.
87
74
  */
88
75
 
89
76
  /**
90
- * The options for the list related composition function.
91
- *
92
- * @typedef {object} ListRelatedOptions
77
+ * @typedef {object} ListRelatedOptions - The options for the list related composition function.
93
78
  * @property {ListRelatedParentState} parentState - The parent state object.
94
79
  * @property {import('vue').Ref<ListRelatedRules>} relatedObjectsRules - The rules for the related objects.
95
80
  */
96
81
 
97
82
  /**
98
- * The properties for the list related composition function.
99
- *
100
- * @typedef {object} ListRelatedProperties
83
+ * @typedef {object} ListRelatedProperties - The properties for the list related composition function.
101
84
  * @property {ListRelatedState} state - The state for the list related property.
102
85
  * @property {ListRelatedParentState} parentState - The parent state object.
103
86
  * @property {() => void} stop - Stops all effects of the list related property.
@@ -106,9 +89,7 @@ import { computed, effectScope, nextTick, onScopeDispose, reactive, ref, toRef,
106
89
  // if we provided functions, we would add a typedef and mix them into ListRelated
107
90
 
108
91
  /**
109
- * An instance of `useListRelated`.
110
- *
111
- * @typedef {ListRelatedProperties} ListRelated
92
+ * @typedef {ListRelatedProperties} ListRelated - An instance of `useListRelated`.
112
93
  */
113
94
 
114
95
  /**
@@ -348,7 +329,7 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
348
329
 
349
330
  es.run(() => {
350
331
  watch(
351
- () => Object.keys(parentState.objects),
332
+ () => parentState.objectsVersion,
352
333
  () => {
353
334
  state.relatedObjectsParentStateObjectsWatchRunning = true;
354
335
  },
@@ -356,7 +337,7 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
356
337
  );
357
338
  watch(() => Object.keys(parentState.objects), parentStateObjectsWatch, { immediate: true });
358
339
  watch(
359
- [() => Object.keys(state.relatedObjects), () => Object.keys(state.relatedObjectsRules || {})],
340
+ [() => parentState.objectsVersion, () => Object.keys(state.relatedObjectsRules || {})],
360
341
  () => {
361
342
  state.relatedObjectsWatchRunning = true;
362
343
  },
package/use/listSearch.js CHANGED
@@ -21,9 +21,7 @@ import { refIfReactive } from "../utils/refIfReactive.js";
21
21
  */
22
22
 
23
23
  /**
24
- * Represents the raw reactive state used by the list search functionality.
25
- *
26
- * @typedef {object} ListSearchRawState
24
+ * @typedef {object} ListSearchRawState - Represents the raw reactive state used by the list search functionality.
27
25
  * @property {import('./listInstance.js').ObjectsByPk} objects - Currently filtered objects based on the search.
28
26
  * @property {import('./listInstance.js').ObjectsInOrder} objectsInOrder - The list of objects sorted according to the current search criteria.
29
27
  * @property {import('./listInstance.js').ListOrder} order - The current sort order of object pks after search have been applied.
@@ -46,7 +44,7 @@ import { refIfReactive } from "../utils/refIfReactive.js";
46
44
  * Partial<import('./listRelated.js').ListRelatedRawState> &
47
45
  * Partial<import('./listCalculated.js').ListCalculatedRawState> &
48
46
  * Partial<import('./listFilter.js').ListFilterRawState>
49
- * )} ListSearchParentRawState
47
+ * )} ListSearchParentRawState - The raw, pre-unwrapped parent state consumed by the list search mixin, aggregating the upstream list composable states.
50
48
  */
51
49
 
52
50
  /**
@@ -54,7 +52,7 @@ import { refIfReactive } from "../utils/refIfReactive.js";
54
52
  */
55
53
 
56
54
  /**
57
- * @typedef {import('vue').ToRefs<ListSearchParentState>} ListSearchParentStateToRefs
55
+ * @typedef {import('vue').ToRefs<ListSearchParentState>} ListSearchParentStateToRefs - The parent list-search state converted to individual Vue refs.
58
56
  */
59
57
 
60
58
  /**
@@ -86,9 +84,7 @@ import { refIfReactive } from "../utils/refIfReactive.js";
86
84
  */
87
85
 
88
86
  /**
89
- * The properties on a list search instance.
90
- *
91
- * @typedef {object} ListSearchProperties
87
+ * @typedef {object} ListSearchProperties - The properties on a list search instance.
92
88
  * @property {ListSearchState} state - The state.
93
89
  * @property {import('./search.js').SearchInstance} textSearchIndex - The text search index.
94
90
  * @property {() => void} stop - Stops the effect scope and cleans up resources.
@@ -97,9 +93,7 @@ import { refIfReactive } from "../utils/refIfReactive.js";
97
93
  // if we provided functions, we would add a typedef and mix them into ListSearch
98
94
 
99
95
  /**
100
- * The provided list search instance, containing properties and functions.
101
- *
102
- * @typedef {ListSearchProperties} ListSearch
96
+ * @typedef {ListSearchProperties} ListSearch - The provided list search instance, containing properties and functions.
103
97
  */
104
98
 
105
99
  /**
@@ -123,14 +117,11 @@ export function useListSearches(listSearchArgs) {
123
117
  }
124
118
 
125
119
  /**
126
- * FlexSearch.Document options, specifically for .index. Their documentation isn't very clear on this.
127
- * Typically, it would be a list of dot-separated keys to index.
128
- *
129
- * @typedef {string | string[] | object[]} TextSearchRules
120
+ * @typedef {string | string[] | object[]} TextSearchRules - FlexSearch.Document options, specifically for .index. Their documentation isn't very clear on this. Typically, it would be a list of dot-separated keys to index.
130
121
  */
131
122
 
132
123
  /**
133
- * @typedef {object} ListSearchProps
124
+ * @typedef {object} ListSearchProps - The consumer-supplied props configuring a list's text search (rules, value, and FlexSearch options).
134
125
  * @property {TextSearchRules} textSearchRules - Rules for what to search for. Keys are the keys to search for, values are functions that take the object and return The value to search for.
135
126
  * @property {string} textSearchValue - The value to search for.
136
127
  * @property {object} customDocumentOptions - FlexSearch.Document options.
@@ -139,7 +130,7 @@ export function useListSearches(listSearchArgs) {
139
130
  */
140
131
 
141
132
  /**
142
- * @typedef {object} ListSearchInstanceOptions
133
+ * @typedef {object} ListSearchInstanceOptions - The configuration options used to create a list search instance.
143
134
  * @property {object} parentState - The list being filtered.
144
135
  * @property {ListSearchProps} [props] - Reactive properties.
145
136
  * @property {number} [throttle=500] - Throttle wait time.
@@ -271,7 +262,7 @@ export function useListSearch({ parentState, props, throttle = 500, showAllWhenE
271
262
  addedKeys: addedObjectPks,
272
263
  removedKeys: removedObjectPks,
273
264
  sameKeys: sameObjectPks,
274
- } = keyDiff(Object.keys(parentState.objects), Object.keys(state.objects));
265
+ } = keyDiff(Object.keys(parentState.objects), Object.keys(objectEffectScopes));
275
266
  const { addedKeys: addedTextSearchRules, removedKeys: removedTextSearchRules } = keyDiff(
276
267
  state.textSearchRules,
277
268
  previousTextSearchRules
package/use/listSort.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import { keyDiff } from "../utils/keyDiff.js";
2
+ import { loadingCombine } from "../utils/loadingCombine.js";
3
+ import { proxyRunning } from "../utils/proxyRunning.js";
2
4
  import get from "lodash-es/get.js";
3
5
  import identity from "lodash-es/identity.js";
4
6
  import throttle from "lodash-es/throttle.js";
5
- import { computed, effectScope, reactive, ref, toRef, toRefs, unref, watch } from "vue";
7
+ import { computed, effectScope, onScopeDispose, reactive, ref, toRef, toRefs, unref, watch } from "vue";
6
8
 
7
9
  /**
8
10
  * Provides a Vue 3 composable for sorting lists based on dynamic and customizable rules. This module integrates
@@ -23,7 +25,7 @@ const defaultOptions = {
23
25
 
24
26
  /**
25
27
  * Sets default configuration options for all list sorting operations within the application. This function allows
26
- * global settings to be specified that affect the behavior of sorting operations unless overridden by specific
28
+ * global settings to be specified that affect the behaviour of sorting operations unless overridden by specific
27
29
  * instance configurations.
28
30
  *
29
31
  * @param {object} options - Configuration options to set as defaults for list sorting.
@@ -35,25 +37,19 @@ export function setListSortDefaultOptions({ sortThrottleWait }) {
35
37
  }
36
38
 
37
39
  /**
38
- * Describes a rule for ordering elements in a list. Each rule can directly reference a property of the list items,
39
- * or define a function to compute the value used for sorting. Rules can be configured to sort in ascending or descending
40
- * order and can utilize locale-aware string comparison if necessary.
41
- *
42
40
  * @typedef {{
43
41
  * key: string,
44
42
  * keyFn?: (object: any, state: ListSortState) => any,
45
43
  * desc?: boolean,
46
44
  * localeCompare?: boolean
47
- * }} OrderByRule
45
+ * }} OrderByRule - Describes a rule for ordering elements in a list. Each rule can directly reference a property of the list items, or define a function to compute the value used for sorting. Rules can be configured to sort in ascending or descending order and can utilize locale-aware string comparison if necessary.
48
46
  */
49
47
 
50
48
  /**
51
- * Represents the raw state used by the list sorting functionality. Includes all configurations and state necessary
52
- * to manage sorting operations within a Vue application.
53
- *
54
- * @typedef {object} ListSortRawState
49
+ * @typedef {object} ListSortRawState - Represents the raw state used by the list sorting functionality. Includes all configurations and state necessary to manage sorting operations within a Vue application.
55
50
  * @property {OrderByRule[]} orderByRules - Current sorting rules applied to the list.
56
51
  * @property {boolean[]} orderByDesc - Flags indicating whether each sort criterion is in descending order.
52
+ * @property {import('vue').ComputedRef<boolean|undefined>} running - Whether the sort is settling a pending reorder, combined with the upstream running state so it propagates through the composed list state. True from when a new order is computed until the throttled reorder lands.
57
53
  */
58
54
 
59
55
  /**
@@ -66,36 +62,29 @@ export function setListSortDefaultOptions({ sortThrottleWait }) {
66
62
  * Partial<import('./listCalculated.js').ListCalculatedRawState> &
67
63
  * Partial<import('./listFilter.js').ListFilterRawState> &
68
64
  * Partial<import('./listSearch.js').ListSearchRawState>
69
- * )} ListSortParentRawState
65
+ * )} ListSortParentRawState - The raw, pre-unwrapped parent state consumed by the list sort mixin, aggregating the upstream list composable states.
70
66
  */
71
67
 
72
68
  /**
73
- * @typedef {import('vue').UnwrapNestedRefs<ListSortParentRawState>} ListSortParentState
69
+ * @typedef {import('vue').UnwrapNestedRefs<ListSortParentRawState>} ListSortParentState - The unwrapped reactive parent state consumed by the list sort mixin.
74
70
  */
75
71
 
76
72
  /**
77
- * The reactive state used by the list sorting functionality. Includes all configurations and state necessary to manage
78
- * sorting operations within a Vue application.
79
- *
80
73
  * @typedef {import('vue').UnwrapNestedRefs<
81
74
  * ListSortParentRawState &
82
75
  * ListSortRawState
83
- * >} ListSortState
76
+ * >} ListSortState - The reactive state used by the list sorting functionality. Includes all configurations and state necessary to manage sorting operations within a Vue application.
84
77
  */
85
78
 
86
79
  /**
87
- * The configuration options for initializing a list sort instance.
88
- *
89
- * @typedef {object} ListSortOptions
80
+ * @typedef {object} ListSortOptions - The configuration options for initializing a list sort instance.
90
81
  * @property {ListSortParentState} parentState - The parent state containing the list data and any associated state needed for sorting.
91
82
  * @property {OrderByRule[]|import('vue').Ref<OrderByRule[]>} orderByRules - Rules defining how the list should be sorted, including key and direction.
92
83
  * @property {number | symbol} sortThrottleWait - Optional throttle wait time to limit the frequency of sort operations, enhancing performance.
93
84
  */
94
85
 
95
86
  /**
96
- * The properties available on a list sort instance.
97
- *
98
- * @typedef {object} ListSortProperties
87
+ * @typedef {object} ListSortProperties - The properties available on a list sort instance.
99
88
  * @property {ListSortState} state - The reactive state for the list sort.
100
89
  * @property {ListSortParentState} parentState - The parent state.
101
90
  * @property {() => void} stop - A function to stop the effect scope and clean up resources.
@@ -104,9 +93,7 @@ export function setListSortDefaultOptions({ sortThrottleWait }) {
104
93
  // if we provided functions, we would add a typedef and mix them into ListSort
105
94
 
106
95
  /**
107
- * The list sort instance, including reactive state and utilities to manage list sorting operations.
108
- *
109
- * @typedef {ListSortProperties} ListSort
96
+ * @typedef {ListSortProperties} ListSort - The list sort instance, including reactive state and utilities to manage list sorting operations.
110
97
  */
111
98
 
112
99
  /**
@@ -177,6 +164,13 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
177
164
  })();
178
165
  const es = effectScope();
179
166
 
167
+ /** @type {import('vue').Ref<boolean|undefined>} */
168
+ const parentRunning = ref(undefined);
169
+ proxyRunning(parentState, "running", parentRunning);
170
+ // True from the moment a new order is pending until the (possibly throttled) reorder lands.
171
+ const sortWatchRunning = ref(true);
172
+ const running = computed(() => loadingCombine(sortWatchRunning.value, parentRunning.value));
173
+
180
174
  const internalState = reactive({
181
175
  orderByRules,
182
176
  orderByDesc: computed(() =>
@@ -197,52 +191,59 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
197
191
  if (!obj) {
198
192
  return [];
199
193
  }
200
- return internalState.orderByRules
201
- ?.filter((r) => r && r.key)
202
- .map((r) => {
203
- if (!r) {
204
- return undefined;
205
- }
206
- if (r.keyFn) {
207
- return r.keyFn(obj, parentState);
208
- }
209
- if (r.key.startsWith("relatedItem.")) {
210
- return get(parentState.relatedObjects?.[pk], r.key.slice(12));
211
- }
212
- if (r.key.startsWith("calculatedItem.")) {
213
- return get(parentState.calculatedObjects?.[pk], r.key.slice(15));
214
- }
215
- return get(obj, r.key);
216
- });
194
+ return (
195
+ internalState.orderByRules
196
+ ?.filter((r) => r && r.key)
197
+ .map((r) => {
198
+ if (!r) {
199
+ return undefined;
200
+ }
201
+ if (r.keyFn) {
202
+ return r.keyFn(obj, parentState);
203
+ }
204
+ if (r.key.startsWith("relatedItem.")) {
205
+ return get(parentState.relatedObjects?.[pk], r.key.slice(12));
206
+ }
207
+ if (r.key.startsWith("calculatedItem.")) {
208
+ return get(parentState.calculatedObjects?.[pk], r.key.slice(15));
209
+ }
210
+ return get(obj, r.key);
211
+ }) ?? []
212
+ );
217
213
  })
218
214
  );
219
215
  criteriaMap[pk] = { scope, crit };
220
216
  return crit;
221
217
  }
222
218
 
219
+ function syncCriteria(newKeys) {
220
+ const { addedKeys, removedKeys } = keyDiff(newKeys, Object.keys(criteriaMap));
221
+ for (const pk of removedKeys) {
222
+ criteriaMap[pk].scope.stop();
223
+ delete criteriaMap[pk];
224
+ }
225
+ for (const pk of addedKeys) {
226
+ ensureCriteria(pk);
227
+ }
228
+ }
229
+
223
230
  es.run(() => {
224
231
  watch(
225
- () => Object.keys(parentState.objects),
226
- (newKeys) => {
227
- const { addedKeys, removedKeys } = keyDiff(newKeys, Object.keys(criteriaMap));
228
- for (const pk of removedKeys) {
229
- criteriaMap[pk].scope.stop();
230
- delete criteriaMap[pk];
231
- }
232
- for (const pk of addedKeys) {
233
- ensureCriteria(pk);
234
- }
232
+ () => parentState.objectsVersion,
233
+ () => {
234
+ syncCriteria(Object.keys(parentState.objects));
235
235
  },
236
236
  { immediate: true, flush: "sync" }
237
237
  );
238
+ watch(() => Object.keys(parentState.objects), syncCriteria);
238
239
  });
239
240
 
240
241
  const rawOrder = computed(() => {
241
242
  const arr = [...unref(toRef(parentState, "order"))];
242
243
  const rulesArr = internalState.orderByRules?.filter(identity) || [];
243
244
  return arr.sort((a, b) => {
244
- const aCrit = criteriaMap[a].crit ?? [];
245
- const bCrit = criteriaMap[b].crit ?? [];
245
+ const aCrit = criteriaMap[a]?.crit ?? [];
246
+ const bCrit = criteriaMap[b]?.crit ?? [];
246
247
  for (let i = 0; i < rulesArr.length; i++) {
247
248
  const rule = rulesArr[i];
248
249
  let x = aCrit[i],
@@ -285,16 +286,45 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
285
286
  });
286
287
 
287
288
  const order = ref([]);
288
- const assignOrder =
289
- sortThrottleWaitNumber > 0
290
- ? throttle((v) => {
291
- order.value = v;
292
- }, sortThrottleWaitNumber)
293
- : (v) => {
294
- order.value = v;
295
- };
289
+ const writeOrder = (v) => {
290
+ order.value = v;
291
+ // the pending reorder has landed; let running settle unless the parent is still running
292
+ sortWatchRunning.value = false;
293
+ };
294
+ // Held separately from assignOrder so the throttle's own cancel stays typed.
295
+ const throttledOrder = sortThrottleWaitNumber > 0 ? throttle(writeOrder, sortThrottleWaitNumber) : null;
296
+ const assignOrder = throttledOrder ?? writeOrder;
296
297
 
297
- watch(rawOrder, (v) => assignOrder(v), { immediate: true });
298
+ es.run(() => {
299
+ // Raise running synchronously the moment a new order is pending, so a throttled reorder keeps
300
+ // running true until it lands. This mirrors the related, calculated, and search layers, and
301
+ // lets the composed manager's state.running reflect the final reorder settling. Watch cheap
302
+ // signals rather than rawOrder itself: a sync watcher re-evaluates its source on every
303
+ // invalidation, and rawOrder performs the full sort, so watching it synchronously re-sorts
304
+ // the list once per reactive write while a page is being pushed. The batched objectsVersion
305
+ // covers structural changes and the rule reads cover rule changes; reorders triggered another
306
+ // way (a row edit, a parent subset change) raise running in the pre-flush watcher below.
307
+ watch(
308
+ () => [parentState.objectsVersion, internalState.orderByRules, internalState.orderByDesc],
309
+ () => {
310
+ sortWatchRunning.value = true;
311
+ },
312
+ { flush: "sync" }
313
+ );
314
+ watch(
315
+ rawOrder,
316
+ (v) => {
317
+ sortWatchRunning.value = true;
318
+ assignOrder(v);
319
+ },
320
+ { immediate: true }
321
+ );
322
+ // A throttled trailing reorder is a timer, not a reactive effect, so disposal would otherwise
323
+ // leave it pending and let it write order after the layer stopped.
324
+ if (throttledOrder) {
325
+ onScopeDispose(() => throttledOrder.cancel());
326
+ }
327
+ });
298
328
 
299
329
  // 6) objectsInOrder just follows that
300
330
  const objectsInOrder = computed(() => order.value.map((pk) => parentState.objects[pk]));
@@ -307,6 +337,7 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
307
337
  objects,
308
338
  order,
309
339
  objectsInOrder,
340
+ running,
310
341
  }),
311
342
  parentState,
312
343
  stop: () => {
@@ -33,36 +33,30 @@ export class ListSubscriptionError extends Error {
33
33
  }
34
34
 
35
35
  /**
36
- * The raw state of a list subscription.
37
- *
38
- * @typedef {object} ListSubscriptionMyState
36
+ * @typedef {object} ListSubscriptionMyState - The raw state of a list subscription.
39
37
  * @property {import('vue').Ref<boolean>|boolean} intendToList - If this is true, the list should be fetched, or re-fetched if arguments change.
40
38
  * @property {import('vue').Ref<boolean>|boolean} intendToSubscribe - If this is true, the subscription should start or restart if arguments change.
41
39
  * @property {import('vue').Ref<boolean>} subscribed - Whether the subscription is active.
42
40
  */
43
41
 
44
42
  /**
45
- * @typedef {import('vue').ToRefs<import('./listInstance.js').ListInstanceState>} ListInstanceStateRefs
43
+ * @typedef {import('vue').ToRefs<import('./listInstance.js').ListInstanceState>} ListInstanceStateRefs - The list instance's reactive state converted to individual Vue refs.
46
44
  */
47
45
 
48
46
  /**
49
- * The raw state of a list subscription, including the state from the list instance.
50
- *
51
47
  * @typedef {ListSubscriptionMyState & (
52
48
  * Pick<import('./loadingError.js').LoadingErrorStatus, "loading" | "error" | "errored">
53
- * ) & ListInstanceStateRefs} ListSubscriptionRawState
49
+ * ) & ListInstanceStateRefs} ListSubscriptionRawState - The raw state of a list subscription, including the state from the list instance.
54
50
  */
55
51
 
56
52
  /**
57
- * A reactive object that manages a list of objects, as returned by `useListInstance`.
58
- *
59
- * @typedef {import('vue').Reactive<ListSubscriptionRawState>} ListSubscriptionState
53
+ * @typedef {import('vue').Reactive<ListSubscriptionRawState>} ListSubscriptionState - A reactive object that manages a list of objects, as returned by `useListInstance`.
60
54
  */
61
55
 
62
56
  /**
63
- * The methods available on a list subscription.
64
- *
65
- * @typedef {Pick<import('./loadingError.js').LoadingErrorStatus, "clearError">} ListSubscriptionFunctions
57
+ * @typedef {Pick<import('./loadingError.js').LoadingErrorStatus, "clearError"> & {
58
+ * stop: () => void
59
+ * }} ListSubscriptionFunctions - The methods available on a list subscription.
66
60
  */
67
61
 
68
62
  /**
@@ -70,13 +64,11 @@ export class ListSubscriptionError extends Error {
70
64
  * state: ListSubscriptionState,
71
65
  * listInstance: import('./listInstance.js').ListInstance,
72
66
  * loadingError: import('./loadingError.js').LoadingErrorStatus,
73
- * }} ListSubscriptionContext
67
+ * }} ListSubscriptionContext - The context (state, list instance, and loading/error status) bound to the shared list subscription functions.
74
68
  */
75
69
 
76
70
  /**
77
- * The properties of a list subscription.
78
- *
79
- * @typedef {object} ListSubscriptionProperties
71
+ * @typedef {object} ListSubscriptionProperties - The properties of a list subscription.
80
72
  * @property {ListSubscriptionState} state - The reactive state of the list subscription.
81
73
  * @property {import('./listInstance.js').ListInstance} listInstance - The list instance used by the subscription.
82
74
  * @property {import('./cancellableIntent.js').CancellableIntent} listIntent - The `CancellableIntent` instance managing if the list should be (re)fetched.
@@ -84,21 +76,16 @@ export class ListSubscriptionError extends Error {
84
76
  */
85
77
 
86
78
  /**
87
- * An instance of a list subscription, returned by `useListSubscription`.
88
- *
89
- * @typedef {ListSubscriptionFunctions & ListSubscriptionProperties} ListSubscription
79
+ * @typedef {ListSubscriptionFunctions & ListSubscriptionProperties} ListSubscription - An instance of a list subscription, returned by `useListSubscription`.
90
80
  */
91
81
 
92
82
  /**
93
- * @typedef {object} ListSubscriptionOwnOptions
83
+ * @typedef {object} ListSubscriptionOwnOptions - The options specific to a list subscription, namely an optional pre-built list instance to reuse.
94
84
  * @property {import("./listInstance.js").ListInstance} [listInstance] - A list instance to use instead of creating one.
95
85
  */
96
86
 
97
87
  /**
98
- * Defines the settings required to establish a list subscription, detailing how list instances should handle updates
99
- * and subscriptions based on the given properties.
100
- *
101
- * @typedef {import("./listInstance.js").ListInstanceOptions & ListSubscriptionOwnOptions} ListSubscriptionOptions
88
+ * @typedef {import("./listInstance.js").ListInstanceOptions & ListSubscriptionOwnOptions} ListSubscriptionOptions - Defines the settings required to establish a list subscription, detailing how list instances should handle updates and subscriptions based on the given properties.
102
89
  */
103
90
 
104
91
  /**
@@ -352,5 +339,12 @@ export function useListSubscription({ listInstance, props, handlers }) {
352
339
  listIntent,
353
340
  subscribeIntent,
354
341
  clearError: proxyLoadingError.clearError,
342
+ // Stops both intents, mirroring useObjectSubscription, so a caller that owns this
343
+ // subscription's lifetime does not have to know its intent inventory. The wrapped
344
+ // instance keeps its state and still takes manual calls.
345
+ stop: () => {
346
+ listIntent.stop();
347
+ subscribeIntent.stop();
348
+ },
355
349
  };
356
350
  }
package/use/loading.js CHANGED
@@ -1,25 +1,23 @@
1
1
  import { readonly, ref } from "vue";
2
2
 
3
3
  /**
4
- * @typedef {import("vue").Ref<boolean|undefined>} LoadingRef
5
- * @typedef {Readonly<LoadingRef>} LoadingReadonlyRef
4
+ * @typedef {import("vue").Ref<boolean|undefined>} LoadingRef - A Vue ref to the loading flag, which is a boolean or undefined.
5
+ * @typedef {Readonly<LoadingRef>} LoadingReadonlyRef - A readonly Vue ref to the loading flag, which is a boolean or undefined.
6
6
  */
7
7
 
8
8
  /**
9
- * @typedef {object} LoadingProperties
9
+ * @typedef {object} LoadingProperties - The reactive loading-state member (loading) contributed by the useLoading composable.
10
10
  * @property {LoadingReadonlyRef} loading - Whether the component is loading.
11
11
  */
12
12
 
13
13
  /**
14
- * @typedef {object} LoadingFunctions
14
+ * @typedef {object} LoadingFunctions - The loading-state actions (setLoading, clearLoading) contributed by the useLoading composable.
15
15
  * @property {() => void} setLoading - Set the loading state to true.
16
16
  * @property {() => void} clearLoading - Set the loading state to false.
17
17
  */
18
18
 
19
19
  /**
20
- * The loading state API.
21
- *
22
- * @typedef {LoadingProperties & LoadingFunctions} LoadingStatus
20
+ * @typedef {LoadingProperties & LoadingFunctions} LoadingStatus - The loading state API.
23
21
  */
24
22
 
25
23
  /**
@@ -2,9 +2,9 @@ import { useLoading } from "./loading.js";
2
2
  import { useError } from "./error.js";
3
3
 
4
4
  /**
5
- * @typedef {import('./loading.js').LoadingProperties & import('./error.js').ErrorProperties} LoadingErrorProperties
6
- * @typedef {import('./loading.js').LoadingFunctions & import('./error.js').ErrorFunctions} LoadingErrorFunctions
7
- * @typedef {LoadingErrorProperties & LoadingErrorFunctions} LoadingErrorStatus
5
+ * @typedef {import('./loading.js').LoadingProperties & import('./error.js').ErrorProperties} LoadingErrorProperties - The combined reactive loading and error state members contributed by the useLoadingError composable.
6
+ * @typedef {import('./loading.js').LoadingFunctions & import('./error.js').ErrorFunctions} LoadingErrorFunctions - The combined loading and error state actions contributed by the useLoadingError composable.
7
+ * @typedef {LoadingErrorProperties & LoadingErrorFunctions} LoadingErrorStatus - The combined loading and error state API (properties plus actions) returned by useLoadingError.
8
8
  */
9
9
 
10
10
  /**