@arrai-innovations/reactive-helpers 20.1.2 → 21.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 (199) hide show
  1. package/README.md +22 -893
  2. package/config/listCrud.js +30 -27
  3. package/config/objectCrud.js +20 -13
  4. package/index.js +5 -3
  5. package/package.json +5 -3
  6. package/types/config/commonCrud.d.ts +0 -1
  7. package/types/config/listCrud.d.ts +28 -41
  8. package/types/config/objectCrud.d.ts +14 -29
  9. package/types/index.d.ts +5 -4
  10. package/types/tests/unit/config/listCrud.spec.d.ts +0 -1
  11. package/types/tests/unit/config/objectCrud.spec.d.ts +0 -1
  12. package/types/tests/unit/crudPromise.d.ts +42 -15
  13. package/types/tests/unit/mockOnUnmounted.d.ts +0 -1
  14. package/types/tests/unit/poll.d.ts +0 -1
  15. package/types/tests/unit/scopedIt.d.ts +15 -0
  16. package/types/tests/unit/setup.d.ts +2 -0
  17. package/types/tests/unit/use/cancellableIntent.spec.d.ts +0 -1
  18. package/types/tests/unit/use/combineClasses.spec.d.ts +1 -0
  19. package/types/tests/unit/use/list.spec.d.ts +1 -0
  20. package/types/tests/unit/use/listCalculated.spec.d.ts +0 -1
  21. package/types/tests/unit/use/listFilter.spec.d.ts +0 -1
  22. package/types/tests/unit/use/listInstance.spec.d.ts +0 -1
  23. package/types/tests/unit/use/listRelated.spec.d.ts +0 -1
  24. package/types/tests/unit/use/listSearch.spec.d.ts +0 -1
  25. package/types/tests/unit/use/listSort.spec.d.ts +0 -1
  26. package/types/tests/unit/use/listSubscription.spec.d.ts +0 -1
  27. package/types/tests/unit/use/loadingError.spec.d.ts +0 -1
  28. package/types/tests/unit/use/object.spec.d.ts +1 -0
  29. package/types/tests/unit/use/objectCalculated.spec.d.ts +1 -0
  30. package/types/tests/unit/use/objectInstance.spec.d.ts +0 -1
  31. package/types/tests/unit/use/objectRelated.spec.d.ts +1 -0
  32. package/types/tests/unit/use/objectSubscription.spec.d.ts +0 -1
  33. package/types/tests/unit/use/proxyLoadingError.spec.d.ts +0 -1
  34. package/types/tests/unit/use/search.error.spec.d.ts +1 -0
  35. package/types/tests/unit/use/search.spec.d.ts +0 -1
  36. package/types/tests/unit/utils/assignReactiveObject.extra.spec.d.ts +1 -0
  37. package/types/tests/unit/utils/assignReactiveObject.refs.spec.d.ts +1 -0
  38. package/types/tests/unit/utils/assignReactiveObject.spec.d.ts +0 -1
  39. package/types/tests/unit/utils/cancellableFetch.spec.d.ts +1 -0
  40. package/types/tests/unit/utils/classes.spec.d.ts +0 -1
  41. package/types/tests/unit/utils/compact.spec.d.ts +1 -0
  42. package/types/tests/unit/utils/deepUnref.spec.d.ts +1 -0
  43. package/types/tests/unit/utils/deleteKey.spec.d.ts +0 -1
  44. package/types/tests/unit/utils/flattenPaths.spec.d.ts +0 -1
  45. package/types/tests/unit/utils/flattenPathsWithValues.spec.d.ts +1 -0
  46. package/types/tests/unit/utils/getFakePk.spec.d.ts +1 -0
  47. package/types/tests/unit/utils/keepAliveTry.spec.d.ts +1 -0
  48. package/types/tests/unit/utils/keyDiff.spec.d.ts +0 -1
  49. package/types/tests/unit/utils/set.spec.d.ts +0 -1
  50. package/types/tests/unit/utils/transformWalk.spec.d.ts +1 -0
  51. package/types/tests/unit/utils/unwrapNested.spec.d.ts +1 -0
  52. package/types/tests/unit/utils/watches.spec.d.ts +0 -1
  53. package/types/use/cancellableIntent.d.ts +121 -48
  54. package/types/use/combineClasses.d.ts +0 -1
  55. package/types/use/error.d.ts +48 -0
  56. package/types/use/list.d.ts +9 -32
  57. package/types/use/listCalculated.d.ts +107 -49
  58. package/types/use/listFilter.d.ts +13 -31
  59. package/types/use/listInstance.d.ts +137 -71
  60. package/types/use/listRelated.d.ts +106 -43
  61. package/types/use/listSearch.d.ts +24 -12
  62. package/types/use/listSort.d.ts +3 -34
  63. package/types/use/listSubscription.d.ts +40 -57
  64. package/types/use/loading.d.ts +37 -0
  65. package/types/use/loadingError.d.ts +5 -100
  66. package/types/use/object.d.ts +3 -12
  67. package/types/use/objectCalculated.d.ts +3 -9
  68. package/types/use/objectInstance.d.ts +59 -47
  69. package/types/use/objectRelated.d.ts +3 -9
  70. package/types/use/objectSubscription.d.ts +33 -51
  71. package/types/use/proxyError.d.ts +27 -0
  72. package/types/use/proxyLoading.d.ts +15 -0
  73. package/types/use/proxyLoadingError.d.ts +8 -16
  74. package/types/use/search.d.ts +2 -3
  75. package/types/utils/assignReactiveObject.d.ts +0 -1
  76. package/types/utils/cancellableFetch.d.ts +0 -1
  77. package/types/utils/cancellablePromise.d.ts +0 -1
  78. package/types/utils/classes.d.ts +2 -8
  79. package/types/utils/compact.d.ts +0 -1
  80. package/types/utils/deepUnref.d.ts +2 -23
  81. package/types/utils/deleteKey.d.ts +6 -6
  82. package/types/utils/flattenPaths.d.ts +0 -1
  83. package/types/utils/flattenPathsWithValues.d.ts +29 -0
  84. package/types/utils/getFakePk.d.ts +0 -1
  85. package/types/utils/keepAliveTry.d.ts +0 -1
  86. package/types/utils/keyDiff.d.ts +9 -10
  87. package/types/utils/loadingCombine.d.ts +0 -1
  88. package/types/utils/proxyRunning.d.ts +2 -3
  89. package/types/utils/refIfReactive.d.ts +0 -1
  90. package/types/utils/relatedCalculatedHelpers.d.ts +0 -1
  91. package/types/utils/set.d.ts +0 -1
  92. package/types/utils/transformWalk.d.ts +0 -1
  93. package/types/utils/unwrapNested.d.ts +0 -1
  94. package/types/utils/watches.d.ts +0 -1
  95. package/use/cancellableIntent.js +185 -100
  96. package/use/error.js +44 -0
  97. package/use/list.js +51 -111
  98. package/use/listCalculated.js +75 -98
  99. package/use/listFilter.js +91 -186
  100. package/use/listInstance.js +357 -282
  101. package/use/listRelated.js +52 -72
  102. package/use/listSearch.js +49 -86
  103. package/use/listSort.js +121 -237
  104. package/use/listSubscription.js +181 -274
  105. package/use/loading.js +35 -0
  106. package/use/loadingError.js +7 -81
  107. package/use/object.js +47 -67
  108. package/use/objectCalculated.js +103 -65
  109. package/use/objectInstance.js +253 -241
  110. package/use/objectRelated.js +48 -30
  111. package/use/objectSubscription.js +110 -175
  112. package/use/proxyError.js +48 -0
  113. package/use/proxyLoading.js +22 -0
  114. package/use/proxyLoadingError.js +9 -35
  115. package/use/search.js +4 -2
  116. package/utils/assignReactiveObject.js +8 -8
  117. package/utils/classes.js +7 -28
  118. package/utils/deepUnref.js +15 -2
  119. package/utils/deleteKey.js +13 -5
  120. package/utils/flattenPathsWithValues.js +65 -0
  121. package/utils/keyDiff.js +5 -5
  122. package/utils/proxyRunning.js +16 -10
  123. package/types/config/commonCrud.d.ts.map +0 -1
  124. package/types/config/listCrud.d.ts.map +0 -1
  125. package/types/config/objectCrud.d.ts.map +0 -1
  126. package/types/index.d.ts.map +0 -1
  127. package/types/tests/unit/config/listCrud.spec.d.ts.map +0 -1
  128. package/types/tests/unit/config/objectCrud.spec.d.ts.map +0 -1
  129. package/types/tests/unit/crudPromise.d.ts.map +0 -1
  130. package/types/tests/unit/expectHelpers.d.ts +0 -2
  131. package/types/tests/unit/expectHelpers.d.ts.map +0 -1
  132. package/types/tests/unit/mockOnUnmounted.d.ts.map +0 -1
  133. package/types/tests/unit/poll.d.ts.map +0 -1
  134. package/types/tests/unit/use/cancellableIntent.spec.d.ts.map +0 -1
  135. package/types/tests/unit/use/listCalculated.spec.d.ts.map +0 -1
  136. package/types/tests/unit/use/listFilter.spec.d.ts.map +0 -1
  137. package/types/tests/unit/use/listInstance.spec.d.ts.map +0 -1
  138. package/types/tests/unit/use/listRelated.spec.d.ts.map +0 -1
  139. package/types/tests/unit/use/listSearch.spec.d.ts.map +0 -1
  140. package/types/tests/unit/use/listSort.spec.d.ts.map +0 -1
  141. package/types/tests/unit/use/listSubscription.spec.d.ts.map +0 -1
  142. package/types/tests/unit/use/loadingError.spec.d.ts.map +0 -1
  143. package/types/tests/unit/use/objectInstance.spec.d.ts.map +0 -1
  144. package/types/tests/unit/use/objectSubscription.spec.d.ts.map +0 -1
  145. package/types/tests/unit/use/proxyLoadingError.spec.d.ts.map +0 -1
  146. package/types/tests/unit/use/search.spec.d.ts.map +0 -1
  147. package/types/tests/unit/utils/assignReactiveObject.spec.d.ts.map +0 -1
  148. package/types/tests/unit/utils/classes.spec.d.ts.map +0 -1
  149. package/types/tests/unit/utils/deleteKey.spec.d.ts.map +0 -1
  150. package/types/tests/unit/utils/flattenPaths.spec.d.ts.map +0 -1
  151. package/types/tests/unit/utils/keyDiff.spec.d.ts.map +0 -1
  152. package/types/tests/unit/utils/set.spec.d.ts.map +0 -1
  153. package/types/tests/unit/utils/watches.spec.d.ts.map +0 -1
  154. package/types/use/cancellableIntent.d.ts.map +0 -1
  155. package/types/use/combineClasses.d.ts.map +0 -1
  156. package/types/use/list.d.ts.map +0 -1
  157. package/types/use/listCalculated.d.ts.map +0 -1
  158. package/types/use/listFilter.d.ts.map +0 -1
  159. package/types/use/listInstance.d.ts.map +0 -1
  160. package/types/use/listKeys.d.ts +0 -15
  161. package/types/use/listKeys.d.ts.map +0 -1
  162. package/types/use/listRelated.d.ts.map +0 -1
  163. package/types/use/listSearch.d.ts.map +0 -1
  164. package/types/use/listSort.d.ts.map +0 -1
  165. package/types/use/listSubscription.d.ts.map +0 -1
  166. package/types/use/loadingError.d.ts.map +0 -1
  167. package/types/use/object.d.ts.map +0 -1
  168. package/types/use/objectCalculated.d.ts.map +0 -1
  169. package/types/use/objectInstance.d.ts.map +0 -1
  170. package/types/use/objectRelated.d.ts.map +0 -1
  171. package/types/use/objectSubscription.d.ts.map +0 -1
  172. package/types/use/paginatedListInstance.d.ts +0 -60
  173. package/types/use/paginatedListInstance.d.ts.map +0 -1
  174. package/types/use/proxyLoadingError.d.ts.map +0 -1
  175. package/types/use/search.d.ts.map +0 -1
  176. package/types/use/watchesRunning.d.ts +0 -63
  177. package/types/use/watchesRunning.d.ts.map +0 -1
  178. package/types/utils/assignReactiveObject.d.ts.map +0 -1
  179. package/types/utils/cancellableFetch.d.ts.map +0 -1
  180. package/types/utils/cancellablePromise.d.ts.map +0 -1
  181. package/types/utils/classes.d.ts.map +0 -1
  182. package/types/utils/compact.d.ts.map +0 -1
  183. package/types/utils/deepUnref.d.ts.map +0 -1
  184. package/types/utils/deleteKey.d.ts.map +0 -1
  185. package/types/utils/flattenPaths.d.ts.map +0 -1
  186. package/types/utils/getFakePk.d.ts.map +0 -1
  187. package/types/utils/keepAliveTry.d.ts.map +0 -1
  188. package/types/utils/keyDiff.d.ts.map +0 -1
  189. package/types/utils/loadingCombine.d.ts.map +0 -1
  190. package/types/utils/proxyRunning.d.ts.map +0 -1
  191. package/types/utils/refIfReactive.d.ts.map +0 -1
  192. package/types/utils/relatedCalculatedHelpers.d.ts.map +0 -1
  193. package/types/utils/set.d.ts.map +0 -1
  194. package/types/utils/transformWalk.d.ts.map +0 -1
  195. package/types/utils/unwrapNested.d.ts.map +0 -1
  196. package/types/utils/watches.d.ts.map +0 -1
  197. package/use/listKeys.js +0 -105
  198. package/use/paginatedListInstance.js +0 -126
  199. package/use/watchesRunning.js +0 -69
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Get all primitive paths and their corresponding values from an array or object.
3
+ *
4
+ * @module utils/flattenPathsWithValues.js
5
+ */
6
+ /**
7
+ * Turn an array or object into an array of `[path, value]` pairs (for primitives)
8
+ * and a list of container paths (for arrays and objects).
9
+ *
10
+ * Array indexes are wrapped in square brackets and object keys are prefixed with a period.
11
+ *
12
+ * @param {any[] | object} arrayOrObject - Array or object to flatten.
13
+ * @param {object} [options] - Options.
14
+ * @param {string} [options.currentPath=""] - Current path, for recursion or as a starting point.
15
+ * @param {number} [options.depth=0] - Current depth, for recursion.
16
+ * @param {number} [options.limit=0] - Limit the depth of recursion.
17
+ * @returns {{
18
+ * pathValues: [string, any][],
19
+ * containerPaths: string[]
20
+ * }} - Paths and their corresponding values.
21
+ */
22
+ export function flattenPathsWithValues(arrayOrObject: any[] | object, { currentPath, depth, limit }?: {
23
+ currentPath?: string;
24
+ depth?: number;
25
+ limit?: number;
26
+ }): {
27
+ pathValues: [string, any][];
28
+ containerPaths: string[];
29
+ };
@@ -10,4 +10,3 @@
10
10
  * @returns {string} - The fake pk.
11
11
  */
12
12
  export function getFakePk(arraySetMapOrObject: any[] | Set<any> | Map<any, any> | object, key?: string): string;
13
- //# sourceMappingURL=getFakePk.d.ts.map
@@ -12,4 +12,3 @@ export function tryOnActivated(fn: Function, target?: any): void;
12
12
  * @param {any} [target] - The target to call the function on.
13
13
  */
14
14
  export function tryOnDeactivated(fn: Function, target?: any): void;
15
- //# sourceMappingURL=keepAliveTry.d.ts.map
@@ -8,23 +8,23 @@
8
8
  * Result object of keyDiff and keyDiffDeep.
9
9
  *
10
10
  * @typedef {object} KeyDiffResult
11
- * @property {Set} [sameKeys] - If sameKeys option is true, return keys that are the same.
12
- * @property {Set} [removedKeys] - If removedKeys option is true, return keys that are removed.
13
- * @property {Set} [addedKeys] - If addedKeys option is true, return keys that are added.
11
+ * @property {Set<string>} [sameKeys] - If sameKeys option is true, return keys that are the same.
12
+ * @property {Set<string>} [removedKeys] - If removedKeys option is true, return keys that are removed.
13
+ * @property {Set<string>} [addedKeys] - If addedKeys option is true, return keys that are added.
14
14
  */
15
15
  /**
16
16
  * Calculate the difference between two arrays of keys, in terms of what keys
17
17
  * are the same, what keys are removed, and what keys are added.
18
18
  *
19
- * @param {string[]|Set} newKeys - Keys to consider as new.
20
- * @param {string[]|Set} oldKeys - Keys to consider as old.
19
+ * @param {string[]|Set<string>} newKeys - Keys to consider as new.
20
+ * @param {string[]|Set<string>} oldKeys - Keys to consider as old.
21
21
  * @param {object} [options] - Which differences are returned.
22
22
  * @param {boolean} [options.sameKeys=true] - If true, return keys that are the same.
23
23
  * @param {boolean} [options.removedKeys=true] - If true, return keys that are removed.
24
24
  * @param {boolean} [options.addedKeys=true] - If true, return keys that are added.
25
25
  * @returns {KeyDiffResult} - The differences.
26
26
  */
27
- export function keyDiff(newKeys: string[] | Set<any>, oldKeys: string[] | Set<any>, { sameKeys, removedKeys, addedKeys }?: {
27
+ export function keyDiff(newKeys: string[] | Set<string>, oldKeys: string[] | Set<string>, { sameKeys, removedKeys, addedKeys }?: {
28
28
  sameKeys?: boolean;
29
29
  removedKeys?: boolean;
30
30
  addedKeys?: boolean;
@@ -50,14 +50,13 @@ export type KeyDiffResult = {
50
50
  /**
51
51
  * - If sameKeys option is true, return keys that are the same.
52
52
  */
53
- sameKeys?: Set<any>;
53
+ sameKeys?: Set<string>;
54
54
  /**
55
55
  * - If removedKeys option is true, return keys that are removed.
56
56
  */
57
- removedKeys?: Set<any>;
57
+ removedKeys?: Set<string>;
58
58
  /**
59
59
  * - If addedKeys option is true, return keys that are added.
60
60
  */
61
- addedKeys?: Set<any>;
61
+ addedKeys?: Set<string>;
62
62
  };
63
- //# sourceMappingURL=keyDiff.d.ts.map
@@ -6,4 +6,3 @@
6
6
  * @returns {boolean | undefined} The combined loading state.
7
7
  */
8
8
  export function loadingCombine(...loadingStates: (boolean | undefined)[]): boolean | undefined;
9
- //# sourceMappingURL=loadingCombine.d.ts.map
@@ -3,7 +3,6 @@
3
3
  *
4
4
  * @param {import("vue").UnwrapNestedRefs<object>} parentState - The parent state.
5
5
  * @param {string} parentStateProp - The parent state property.
6
- * @param {import("vue").Ref<boolean>} ref - The ref to proxy to.
6
+ * @param {import("vue").Ref<boolean>} childRef - The ref to proxy to.
7
7
  */
8
- export function proxyRunning(parentState: import("vue").UnwrapNestedRefs<object>, parentStateProp: string, ref: import("vue").Ref<boolean>): void;
9
- //# sourceMappingURL=proxyRunning.d.ts.map
8
+ export function proxyRunning(parentState: import("vue").UnwrapNestedRefs<object>, parentStateProp: string, childRef: import("vue").Ref<boolean>): void;
@@ -1,2 +1 @@
1
1
  export function refIfReactive<T, K extends keyof T>(source: (T & object) | undefined | null, property: K, defaultValue?: T[K]): import("vue").ComputedRef<T[K]> | import("vue").Ref<T[K]>;
2
- //# sourceMappingURL=refIfReactive.d.ts.map
@@ -2,4 +2,3 @@ export const relatedItemRegex: RegExp;
2
2
  export const calculatedItemRegex: RegExp;
3
3
  export function getObjectRelatedCalculatedByKey(obj: object, relatedObj: object, calculatedObj: object, key: string): [object, string];
4
4
  export function getObjectRelatedByKey(obj: object, relatedObj: object, key: string): [object, string];
5
- //# sourceMappingURL=relatedCalculatedHelpers.d.ts.map
@@ -4,4 +4,3 @@ export function intersection(setA: Set<any>, setB: Set<any>): Set<any>;
4
4
  export function symmetricDifference(setA: Set<any>, setB: Set<any>): Set<any>;
5
5
  export function difference(setA: Set<any>, setB: Set<any>): Set<any>;
6
6
  export function equals(setA: Set<any>, setB: Set<any>): boolean;
7
- //# sourceMappingURL=set.d.ts.map
@@ -1,2 +1 @@
1
1
  export function transformWalk(obj: object, transformFn: Function, path?: string): object;
2
- //# sourceMappingURL=transformWalk.d.ts.map
@@ -1,2 +1 @@
1
1
  export function unwrapNested<T>(arg: T): import("vue").UnwrapRef<T>;
2
- //# sourceMappingURL=unwrapNested.d.ts.map
@@ -152,4 +152,3 @@ export class AwaitNot {
152
152
  */
153
153
  stop(): void;
154
154
  }
155
- //# sourceMappingURL=watches.d.ts.map
@@ -1,44 +1,101 @@
1
1
  import identity from "lodash-es/identity.js";
2
2
  import isEmpty from "lodash-es/isEmpty.js";
3
3
  import isEqual from "lodash-es/isEqual.js";
4
- import { computed, effectScope, nextTick, onScopeDispose, reactive, readonly, watch } from "vue";
5
- import { deepUnref } from "../utils/deepUnref.js";
4
+ import { computed, effectScope, nextTick, onScopeDispose, reactive, readonly, toRef, watch } from "vue";
6
5
  import { tryOnActivated, tryOnDeactivated } from "../utils/keepAliveTry.js";
6
+ import { deepUnref } from "vue-deepunref";
7
+ import { useLoadingError } from "./loadingError.js";
7
8
 
8
9
  /**
9
10
  * @module use/cancellableIntent.js - A composable function for handling cancellable intents.
10
11
  */
11
12
 
13
+ /**
14
+ * Custom error class for list subscription errors.
15
+ */
16
+ export class CancellableIntentError extends Error {
17
+ /**
18
+ * Creates a new CancellableIntentError.
19
+ *
20
+ * @param {string} message - The error message.
21
+ * @param {string} code - The error code.
22
+ */
23
+ constructor(message, code) {
24
+ super(message);
25
+ this.name = "CancellableIntentError";
26
+ this.code = code;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * @typedef {number} RunId - A unique identifier for a single execution ("run") of an intent.
32
+ * This is incremented each time `watchArguments` change and the intent re-triggers.
33
+ * Enables distinguishing results or effects from overlapping async runs.
34
+ */
35
+
12
36
  /**
13
37
  * @typedef {object} CancellableIntentRawState - The raw state of the cancellable intent.
14
- * @property {number|undefined} activeCount - The number of active intents.
15
- * @property {boolean|undefined} active - Whether there are active intents.
16
- * @property {number|undefined} resolvingCount - The number of resolving intents.
17
- * @property {boolean|undefined} resolving - Whether there are resolving intents.
18
- * @property {boolean} errored - Whether there was an error.
19
- * @property {Error|null} error - The error that occurred.
38
+ * @property {import('vue').ComputedRef<boolean>|undefined} active - Whether there are active intents.
39
+ * @property {import('vue').ComputedRef<boolean>|undefined} resolving - Whether there are resolving intents.
20
40
  * @property {boolean} clearActiveOnResolved - Whether to clear the active state when the promise resolves.
41
+ * @property {RunId|null} lastRunId - The most recent run ID issued for a triggered intent. Useful for associating async results with their originating trigger.
42
+ * @property {import('vue').DeepReadonly<object>} watchArguments - The watch arguments.
43
+ * @property {import('vue').DeepReadonly<object>} guardArguments - The guard arguments.
44
+ */
45
+
46
+ /**
47
+ * @typedef {import("vue").Reactive<
48
+ * CancellableIntentRawState &
49
+ * Pick<import('./error.js').ErrorStatus, 'error' | 'errored'>
50
+ * >} CancellableIntentState - The state of the cancellable intent.
51
+ */
52
+
53
+ /**
54
+ * @typedef {() => boolean} IsCurrentRunFn - A function that checks if the current run ID matches the last run ID.
55
+ */
56
+
57
+ /**
58
+ * The common run tracking arguments.
59
+ *
60
+ * @typedef {object} CommonRunTracking
61
+ * @property {RunId} runId - The unique identifier for your run.
62
+ * @property {IsCurrentRunFn} isCurrentRun - A function that checks if the current run ID matches your run ID.
21
63
  */
22
64
 
23
65
  /**
24
- * @typedef {import("vue").UnwrapNestedRefs<CancellableIntentRawState>} CancellableIntentState - The state of the cancellable intent.
66
+ * @typedef {(runTracking: CommonRunTracking) => import('../utils/cancellablePromise.js').MaybeCancellablePromise<void>} AwaitableWithCancel - A function that returns a promise that can be cancelled.
67
+ */
68
+
69
+ /**
70
+ * @typedef {import("vue").UnwrapNestedRefs<object>|{[key: string]: import('vue').Ref<any>}} WatchGuardArguments - The reactive object to watch for changes.
25
71
  */
26
72
 
27
73
  /**
28
74
  * @typedef {object} CancellableIntentOptions - The options for the cancellable intent.
29
- * @property {() => import('../utils/cancellablePromise.js').MaybeCancellablePromise<void>} awaitableWithCancel - The function that returns a promise that can be cancelled.
30
- * @property {import("vue").UnwrapNestedRefs<object>} [watchArguments={}] - The reactive object to watch for changes.
31
- * @property {import("vue").UnwrapNestedRefs<object>} [guardArguments={}] - The reactive object to watch for truthiness before running the intent.
75
+ * @property {AwaitableWithCancel} awaitableWithCancel - The function that returns a promise that can be cancelled. Receives the run ID as an argument.
76
+ * @property {WatchGuardArguments} [watchArguments={}] - The reactive object to watch for changes.
77
+ * @property {WatchGuardArguments} [guardArguments={}] - The reactive object to watch for truthiness before running the intent.
32
78
  * @property {boolean} [clearActiveOnResolved=true] - Whether to clear the active state when the promise resolves.
33
79
  */
34
80
 
35
81
  /**
36
- * @typedef {object} CancellableIntent - The instance of the cancellable intent.
82
+ * Cancel function signature for cancellable intent.
83
+ *
84
+ * @typedef {Function} CancelFn
85
+ * @param {any} reason - The reason for cancellation.
86
+ * @param {boolean} [forceClearActive=false] - Whether to force clear the active state.
87
+ * @returns {Promise<void>} - A promise that resolves when the cancellation is complete.
88
+ */
89
+
90
+ /**
91
+ * @typedef {object} MyCancellableIntent - The instance of the cancellable intent.
37
92
  * @property {CancellableIntentState} state - The state of the cancellable intent.
38
- * @property {import('vue').UnwrapNestedRefs<object>} watchArguments - The watch arguments.
39
- * @property {import('vue').UnwrapNestedRefs<object>} guardArguments - The guard arguments.
40
- * @property {Function} stop - Stop the cancellable intent.
41
- * @property {Function} cancel - Cancel the cancellable intent.
93
+ * @property {() => void} stop - Stop the cancellable intent.
94
+ * @property {CancelFn} cancel - Cancel the cancellable intent.
95
+ */
96
+
97
+ /**
98
+ * @typedef {MyCancellableIntent & Pick<import('./error.js').ErrorStatus, "clearError">} CancellableIntent - The instance of the cancellable intent.
42
99
  */
43
100
 
44
101
  /**
@@ -91,73 +148,100 @@ export function useCancellableIntent({
91
148
  if (typeof awaitableWithCancel !== "function") {
92
149
  throw new Error("awaitableWithCancel must be a function");
93
150
  }
94
- const state = reactive(
95
- /** @type {CancellableIntentRawState} */
96
- {
97
- activeCount: undefined, // the active count doesn't mean much when not using clearActiveOnResolved
98
- active: undefined,
99
- resolvingCount: undefined,
100
- resolving: undefined,
101
- errored: false,
102
- error: null,
103
- clearActiveOnResolved,
104
- }
105
- );
151
+ const es = effectScope();
152
+ let myRunId = 0; // each intent has its own namespace of intention session ids
153
+ const internalState = reactive({
154
+ watchArguments,
155
+ guardArguments,
156
+ activeCount: undefined, // the active count doesn't mean much when not using clearActiveOnResolved
157
+ resolvingCount: undefined,
158
+ });
159
+ const loadingError = es.run(() => useLoadingError());
160
+ /** @type {CancellableIntentState} */
161
+ const state = reactive({
162
+ active: es.run(() =>
163
+ computed(() => {
164
+ if (internalState.activeCount === undefined) {
165
+ return undefined;
166
+ }
167
+ return internalState.activeCount > 0;
168
+ })
169
+ ),
170
+ resolving: es.run(() =>
171
+ computed(() => {
172
+ if (internalState.resolvingCount === undefined) {
173
+ return undefined;
174
+ }
175
+ return internalState.resolvingCount > 0;
176
+ })
177
+ ),
178
+ errored: loadingError.errored,
179
+ error: loadingError.error,
180
+ clearActiveOnResolved,
181
+ lastRunId: null,
182
+ watchArguments: readonly(internalState.watchArguments),
183
+ guardArguments: readonly(internalState.guardArguments),
184
+ });
106
185
  let previousWatchValues = null,
107
186
  cancelFunction = null;
108
187
 
109
- const es = effectScope();
110
-
111
- function stop() {
112
- // effect scopes are stopped automatically in onUnmounted / a parent onScopeDispose; this is for other use cases
113
- es.stop();
114
- }
115
-
116
- async function cancel(/** @type {any} */ reason) {
117
- if (cancelFunction) {
118
- return cancelFunction(reason)
119
- .catch(console.error)
120
- .finally(() => {
121
- if (!state.clearActiveOnResolved) {
122
- cancelFunction = null;
123
- state.activeCount--;
124
- }
125
- });
126
- }
127
- return false;
128
- }
129
-
130
188
  const doIntentWatch = () => {
131
- state.errored = false;
132
- state.error = null;
133
- if (state.activeCount === undefined) {
134
- state.activeCount = 0;
189
+ loadingError.clearError();
190
+ if (internalState.activeCount === undefined) {
191
+ internalState.activeCount = 0;
192
+ }
193
+ internalState.activeCount += 1;
194
+ if (internalState.resolvingCount === undefined) {
195
+ internalState.resolvingCount = 0;
135
196
  }
136
- state.activeCount += 1;
137
- if (state.resolvingCount === undefined) {
138
- state.resolvingCount = 0;
197
+ internalState.resolvingCount += 1;
198
+ let thisRunId = ++myRunId;
199
+ state.lastRunId = thisRunId;
200
+ let awaitablePromise;
201
+ try {
202
+ awaitablePromise = awaitableWithCancel({
203
+ runId: thisRunId,
204
+ isCurrentRun: () => thisRunId === state.lastRunId,
205
+ });
206
+ } catch (error) {
207
+ // no need to cancel if we throw even before we get a promise
208
+ loadingError.setError(error);
209
+ internalState.resolvingCount--;
210
+ // even if you don't clear active on resolved, we didn't resolve
211
+ internalState.activeCount--;
212
+ return;
213
+ }
214
+
215
+ if (!awaitablePromise || typeof awaitablePromise.then !== "function") {
216
+ loadingError.setError(
217
+ new CancellableIntentError(
218
+ "awaitableWithCancel must return a thenable (Promise-like) object",
219
+ "invalid-promise"
220
+ )
221
+ );
222
+ return;
139
223
  }
140
- state.resolvingCount += 1;
141
- let awaitablePromise = awaitableWithCancel();
142
224
 
143
225
  if (awaitablePromise.cancel) {
144
226
  cancelFunction = awaitablePromise.cancel.bind(awaitablePromise);
227
+ } else {
228
+ cancelFunction = null;
145
229
  }
146
230
  // we don't want to await this, because we want to be able to cancel it
147
231
  awaitablePromise
148
- .catch(async (err) => {
149
- await cancel("Error in awaitableWithCancel");
150
- console.error(err);
151
- state.errored = true;
152
- state.error = err;
153
- throw err;
154
- })
155
- .finally(() => {
156
- state.resolvingCount--;
232
+ .then(() => {
157
233
  if (state.clearActiveOnResolved) {
158
234
  cancelFunction = null;
159
- state.activeCount--;
235
+ internalState.activeCount--;
160
236
  }
237
+ })
238
+ .catch(async (err) => {
239
+ const cancelPromise = instance.cancel("Error in awaitableWithCancel", true);
240
+ loadingError.setError(err);
241
+ await cancelPromise;
242
+ })
243
+ .finally(() => {
244
+ internalState.resolvingCount--;
161
245
  });
162
246
  };
163
247
 
@@ -171,18 +255,18 @@ export function useCancellableIntent({
171
255
  runningIntentWatch = true;
172
256
  // this locked section is to deal with multiple intentWatches running while waiting for cancel to resolve
173
257
  try {
174
- let newWatchValues = deepUnref(Object.values(watchArguments));
258
+ let newWatchValues = Object.values(deepUnref(internalState.watchArguments));
175
259
  if (isEqual(newWatchValues, previousWatchValues)) {
176
260
  return;
177
261
  }
178
- await cancel("Intent watch cancelled"); // this can take time so guards and watches can change!
179
- newWatchValues = deepUnref(Object.values(watchArguments));
262
+ await instance.cancel("Intent watch cancelled"); // this can take time so guards and watches can change!
263
+ newWatchValues = Object.values(deepUnref(internalState.watchArguments));
180
264
  if (isEqual(newWatchValues, previousWatchValues)) {
181
265
  return;
182
266
  }
183
267
  previousWatchValues = newWatchValues;
184
- const guardValues = deepUnref(Object.values(guardArguments));
185
- if (Object.values(newWatchValues).every(identity)) {
268
+ const guardValues = Object.values(deepUnref(internalState.guardArguments));
269
+ if (newWatchValues.every(identity)) {
186
270
  // if any guards are true, delay the watch.
187
271
  if (guardValues && !isEmpty(guardValues) && guardValues.some(identity)) {
188
272
  delayedWatch = doIntentWatch;
@@ -203,7 +287,7 @@ export function useCancellableIntent({
203
287
  const guardWatch = () => {
204
288
  if (delayedWatch) {
205
289
  // if all guards are false, run the watch
206
- if (Object.values(guardArguments).every((x) => !x)) {
290
+ if (!Object.values(deepUnref(guardArguments)).some(identity)) {
207
291
  const myDelayedWatch = delayedWatch;
208
292
  delayedWatch = null;
209
293
  myDelayedWatch();
@@ -214,7 +298,7 @@ export function useCancellableIntent({
214
298
  const cleanup = () => {
215
299
  // cancel the intent when the component is deactivated
216
300
  // noinspection JSIgnoredPromiseFromCall
217
- cancel("Component deactivated");
301
+ instance.cancel("Component deactivated");
218
302
  if (state.clearActiveOnResolved) {
219
303
  // otherwise it clears when resolved
220
304
  cancelFunction = null;
@@ -223,33 +307,38 @@ export function useCancellableIntent({
223
307
  previousWatchValues = null;
224
308
  };
225
309
 
226
- es.run(() => {
227
- state.active = computed(() => {
228
- if (state.activeCount === undefined) {
229
- return undefined;
230
- }
231
- return state.activeCount > 0;
232
- });
233
- state.resolving = computed(() => {
234
- if (state.resolvingCount === undefined) {
235
- return undefined;
310
+ /** @type {CancellableIntent} */
311
+ const instance = {
312
+ state,
313
+ // effect scopes are stopped automatically in onUnmounted / a parent onScopeDispose; this is for other use cases
314
+ stop: () => es.stop(),
315
+ cancel: async (/** @type {any} */ reason, forceClearActive = false) => {
316
+ if (cancelFunction) {
317
+ const toCancel = cancelFunction;
318
+ cancelFunction = null;
319
+ if (!state.clearActiveOnResolved || forceClearActive) {
320
+ internalState.activeCount--;
321
+ }
322
+ return toCancel(reason).catch(console.error);
236
323
  }
237
- return state.resolvingCount > 0;
238
- });
239
-
240
- watch(() => Object.values(watchArguments), intentWatch, {
324
+ return false;
325
+ },
326
+ clearError: loadingError.clearError,
327
+ };
328
+ es.run(() => {
329
+ watch(toRef(internalState, "watchArguments"), intentWatch, {
241
330
  // this can't be immediate because subscribe wants to look at our state, which won't exist yet.
242
331
  deep: true,
243
332
  });
244
333
 
245
- watch(() => Object.values(guardArguments), guardWatch, {
334
+ watch(toRef(internalState, "guardArguments"), guardWatch, {
246
335
  // we can't possibly have a delayed watch immediately
247
336
  deep: true,
248
337
  });
249
338
 
250
339
  tryOnActivated(() => {
251
- state.activeCount = 0;
252
- state.resolvingCount = 0;
340
+ internalState.activeCount = 0;
341
+ internalState.resolvingCount = 0;
253
342
  // trigger the intent watch manually to get current watch values
254
343
  // noinspection JSIgnoredPromiseFromCall
255
344
  intentWatch();
@@ -257,13 +346,9 @@ export function useCancellableIntent({
257
346
  tryOnDeactivated(cleanup);
258
347
  onScopeDispose(cleanup);
259
348
 
349
+ // intentWatch can't be immediate because subscribe wants to look at our state, which won't exist yet.
350
+ // but we do want to run immediately-ish
260
351
  nextTick().then(intentWatch);
261
352
  });
262
- return {
263
- state,
264
- watchArguments: readonly(watchArguments),
265
- guardArguments: readonly(guardArguments),
266
- stop,
267
- cancel,
268
- };
353
+ return instance;
269
354
  }
package/use/error.js ADDED
@@ -0,0 +1,44 @@
1
+ import { readonly, ref } from "vue";
2
+
3
+ /**
4
+ * @typedef {() => void} ClearErrorFn
5
+ * @typedef {import("vue").Ref<Error|null>} ErrorRef
6
+ * @typedef {import("vue").Ref<boolean>} ErroredRef
7
+ * @typedef {Readonly<ErrorRef>} ErrorReadonlyRef
8
+ * @typedef {Readonly<ErroredRef>} ErroredReadonlyRef
9
+ */
10
+
11
+ /**
12
+ * The error state API.
13
+ *
14
+ * @typedef {object} ErrorStatus
15
+ * @property {ErrorReadonlyRef} error - The error that occurred.
16
+ * @property {ErroredReadonlyRef} errored - Whether an error has occurred.
17
+ * @property {(error: Error) => void} setError - Set the error state.
18
+ * @property {ClearErrorFn} clearError - Clear the error state.
19
+ */
20
+
21
+ /**
22
+ * A composable function for managing error state.
23
+ *
24
+ * @returns {ErrorStatus} - An object containing reactive fields and actions for error state.
25
+ */
26
+ export function useError() {
27
+ /** @type {ErrorRef} */
28
+ const error = ref(null);
29
+ /** @type {ErroredRef} */
30
+ const errored = ref(false);
31
+
32
+ return {
33
+ error: readonly(error),
34
+ errored: readonly(errored),
35
+ setError: (e) => {
36
+ error.value = e;
37
+ errored.value = true;
38
+ },
39
+ clearError: () => {
40
+ error.value = null;
41
+ errored.value = false;
42
+ },
43
+ };
44
+ }