@arrai-innovations/reactive-helpers 13.0.4 → 13.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.
package/docs/README.md CHANGED
@@ -26,6 +26,7 @@
26
26
  - [use/objectRelated](use/objectRelated.md)
27
27
  - [use/objectSubscription](use/objectSubscription.md)
28
28
  - [use/paginatedListInstance](use/paginatedListInstance.md)
29
+ - [use/proxyLoadingError](use/proxyLoadingError.md)
29
30
  - [use/search](use/search.md)
30
31
  - [use/watchesRunning](use/watchesRunning.md)
31
32
  - [utils/assignReactiveObject](utils/assignReactiveObject.md)
@@ -289,6 +289,8 @@ The props to set for the crud.
289
289
 
290
290
  > **setListCrud**(`options`): `void`
291
291
 
292
+ Set the list and subscribe functions for the default crud.
293
+
292
294
  #### Parameters
293
295
 
294
296
  • **options**: [`ListCrudFunctions`](listCrud.md#listcrudfunctions) & `Partial`\<[`ListCrudArgs`](listCrud.md#listcrudargs)\>
@@ -298,3 +300,7 @@ The options for the default crud.
298
300
  #### Returns
299
301
 
300
302
  `void`
303
+
304
+ #### Throws
305
+
306
+ - If unknown keys are passed.
@@ -326,6 +326,10 @@ The arguments to be passed to the crud functions.
326
326
 
327
327
  `void`
328
328
 
329
+ #### Throws
330
+
331
+ - If an invalid function is passed, or if the function is not a function.
332
+
329
333
  ***
330
334
 
331
335
  ### setObjectCrud()
@@ -343,3 +347,7 @@ The options for the object crud functions.
343
347
  #### Returns
344
348
 
345
349
  `void`
350
+
351
+ #### Throws
352
+
353
+ - if unknown keys are passed.
package/docs/use/list.md CHANGED
@@ -254,6 +254,9 @@ Current text query used for filtering the list.
254
254
 
255
255
  > **useList**(`options`): [`ListManager`](list.md#listmanager)
256
256
 
257
+ Creates and manages an enhanced list instance by orchestrating various list-related composables.
258
+ It ensures seamless integration of all list functionalities such as sorting, searching, filtering, and advanced state management.
259
+
257
260
  #### Parameters
258
261
 
259
262
  • **options**: [`ListOptions`](list.md#listoptions)
@@ -266,12 +269,19 @@ The options for the list./.
266
269
 
267
270
  - The managed stack of list-related composable functions.
268
271
 
272
+ #### Example
273
+
274
+ ```vue
275
+ ```
276
+
269
277
  ***
270
278
 
271
279
  ### useLists()
272
280
 
273
281
  > **useLists**(`listOptions`): `object`
274
282
 
283
+ Initializes multiple list management instances with provided configurations.
284
+
275
285
  #### Parameters
276
286
 
277
287
  • **listOptions**
@@ -8,25 +8,55 @@
8
8
 
9
9
  ## Interfaces
10
10
 
11
- ### LoadingError
11
+ ### LoadingErrorMutations
12
12
 
13
13
  #### Properties
14
14
 
15
- ##### clearError()
15
+ ##### clearLoading()
16
16
 
17
- > **clearError**: () => `void`
17
+ > **clearLoading**: () => `void`
18
18
 
19
- Clear the error state.
19
+ Clear the loading state.
20
20
 
21
21
  ###### Returns
22
22
 
23
23
  `void`
24
24
 
25
- ##### clearLoading()
25
+ ##### setError()
26
26
 
27
- > **clearLoading**: () => `void`
27
+ > **setError**: (`error`) => `void`
28
28
 
29
- Clear the loading state.
29
+ Set the error state.
30
+
31
+ ###### Parameters
32
+
33
+ • **error**: `any`
34
+
35
+ ###### Returns
36
+
37
+ `void`
38
+
39
+ ##### setLoading()
40
+
41
+ > **setLoading**: () => `void`
42
+
43
+ Set the loading state.
44
+
45
+ ###### Returns
46
+
47
+ `void`
48
+
49
+ ***
50
+
51
+ ### LoadingErrorStatus
52
+
53
+ #### Properties
54
+
55
+ ##### clearError()
56
+
57
+ > **clearError**: () => `void`
58
+
59
+ Clear the error state.
30
60
 
31
61
  ###### Returns
32
62
 
@@ -50,29 +80,13 @@ Whether an error has occurred.
50
80
 
51
81
  Whether the component is loading.
52
82
 
53
- ##### setError()
54
-
55
- > **setError**: (`error`) => `void`
56
-
57
- Set the error state.
83
+ ## Type Aliases
58
84
 
59
- ###### Parameters
60
-
61
- • **error**: `any`
62
-
63
- ###### Returns
64
-
65
- `void`
66
-
67
- ##### setLoading()
68
-
69
- > **setLoading**: () => `void`
70
-
71
- Set the loading state.
85
+ ### LoadingError
72
86
 
73
- ###### Returns
87
+ > **LoadingError**\<\>: [`LoadingErrorStatus`](loadingError.md#loadingerrorstatus) & [`LoadingErrorMutations`](loadingError.md#loadingerrormutations)
74
88
 
75
- `void`
89
+ #### Type Parameters
76
90
 
77
91
  ## Functions
78
92
 
@@ -502,6 +502,8 @@ Whether the subscription is loading.
502
502
 
503
503
  > **useObject**(`options`): [`ObjectManager`](object.md#objectmanager)
504
504
 
505
+ Initializes a chain of useObject* functions, returning an object of them.
506
+
505
507
  #### Parameters
506
508
 
507
509
  • **options**: [`ObjectManagerOptions`](object.md#objectmanageroptions)
@@ -514,12 +516,105 @@ The options to be passed to useObjectInstance, useObjectSubscription, useObjectR
514
516
 
515
517
  - An object managing a chain of useObject* instances.
516
518
 
519
+ #### Example
520
+
521
+ ```
522
+ <script setup>
523
+ import { useObject } from "@arrai-innovations/reactive-helpers";
524
+ import { reactive, ref, toRef } from "vue";
525
+
526
+ const someObjectsSource = reactive({
527
+ objects: {
528
+ '1': { id: 1, name: 'one', secondOrderId: 15 },
529
+ '2': { id: 2, name: 'two', secondOrderId: 10 },
530
+ '3': { id: 3, name: 'three', secondOrderId: 5 },
531
+ },
532
+ });
533
+ const someOtherObjectsSource = reactive({
534
+ objects: {
535
+ '5': { id: 5, name: 'five' },
536
+ '10': { id: 10, name: 'ten' },
537
+ '15': { id: 15, name: 'fifteen' },
538
+ },
539
+ });
540
+ const props = defineProps({
541
+ app: { type: String, required: true },
542
+ model: { type: String, required: true },
543
+ id: { type: String, default: "" },
544
+ });
545
+
546
+ const objectProps = reactive({
547
+ crudArgs: {
548
+ app: toRef(props, "app"),
549
+ model: toRef(props, "model"),
550
+ },
551
+ retrieveArgs: {
552
+ fields: ['foo', 'bar'],
553
+ },
554
+ relatedObjectRules: {
555
+ firstOrder: {
556
+ pkKey: 'some_objects_id',
557
+ objects: someObjectsSource.objects,
558
+ },
559
+ some_objects_list_ids: {
560
+ // pkKey defaults to match rule name
561
+ objects: someObjectsSource.objects,
562
+ order: ['3','1','2'],
563
+ },
564
+ secondOrder: {
565
+ pkKey: 'relatedObject.secondOrderId',
566
+ objects: someOtherObjectsSource.objects,
567
+ },
568
+ },
569
+ calculatedObjectRules: {
570
+ someRule: (object, relatedObject, calculatedObjects) => {
571
+ // some complex calculation, relatedObjects would be assuming there was a listRelated between the two
572
+ // calculatedObjects would be the other calculated objects in the list
573
+ // including yourself, so try not to create circular dependencies
574
+ // this is used as a computed body.
575
+ return object.foo + object.name;
576
+ },
577
+ ...
578
+ },
579
+ intendToRetrieve: false,
580
+ intendToSubscribe: false,
581
+ });
582
+ objectProps.intendToRetrieve = objectProps.intendToSubscribe = computed(()=> !!props.id);
583
+ const objectManager = useObject(objectProps);
584
+ // objectManager.state.object comes back from the server (via configured crud retrieve function)
585
+ // { id: 2, name: 'two', foo: 'bar', some_objects_id: 2, some_objects_list_ids: ['1','2','3'] }
586
+ </script>
587
+ <template>
588
+ <div v-if="objectManager.state.loading">Loading...</div>
589
+ <div v-else-if="objectManager.state.errored">Error: {{ objectManager.state.error.message }}</div>
590
+ <div v-else-if="objectManager.state.object.id">
591
+ <p>Foo: {{ objectManager.state.object.foo }}</p>
592
+ <!-- 'bar' -->
593
+
594
+ <p>{{ objectManager.state.relatedObject.firstOrder }}</p>
595
+ <!-- { id: 2, name: 'two', secondOrderId: 10 } -->
596
+
597
+ <p>{{ objectManager.state.relatedObject.some_objects_list_ids }}</p>
598
+ <!-- [{ id: 3, name: 'three', secondOrderId: 5 }, { id: 1, name: 'one', secondOrderId: 15 }, { id: 2, name: 'two', secondOrderId: 10 }] -->
599
+
600
+ <p>{{ objectManager.state.relatedObject.secondOrder }}</p>
601
+ <!-- { id: 10, name: 'ten' } -->
602
+
603
+ <p>{{ objectManager.state.calculatedObject.someRule }}</p>
604
+ <!-- 'bartwo' -->
605
+ </div>
606
+ <div v-else>Object not found.</div>
607
+ </template>
608
+ ```
609
+
517
610
  ***
518
611
 
519
612
  ### useObjects()
520
613
 
521
614
  > **useObjects**(`objectArgs`): `object`
522
615
 
616
+ Initializes multiple useObject instances, returning an object of them based on the keys of the objectArgs.
617
+
523
618
  #### Parameters
524
619
 
525
620
  • **objectArgs**
@@ -0,0 +1,49 @@
1
+ [**@arrai-innovations/reactive-helpers**](../README.md) • **Docs**
2
+
3
+ ***
4
+
5
+ [@arrai-innovations/reactive-helpers](../README.md) / use/proxyLoadingError
6
+
7
+ # use/proxyLoadingError
8
+
9
+ ## References
10
+
11
+ ### ProxyLoadingError
12
+
13
+ Renames and re-exports [LoadingErrorStatus](loadingError.md#loadingerrorstatus)
14
+
15
+ ## Type Aliases
16
+
17
+ ### WatchableLoadingErrors
18
+
19
+ > **WatchableLoadingErrors**\<\>: `UnwrapNestedRefs` \| `Ref` \| [`WatchableLoadingErrorsRaw`](proxyLoadingError.md#watchableloadingerrorsraw)
20
+
21
+ #### Type Parameters
22
+
23
+ ***
24
+
25
+ ### WatchableLoadingErrorsRaw
26
+
27
+ > **WatchableLoadingErrorsRaw**\<\>: [`use/loadingError`](loadingError.md)[]
28
+
29
+ #### Type Parameters
30
+
31
+ ## Functions
32
+
33
+ ### useProxyLoadingError()
34
+
35
+ > **useProxyLoadingError**(`loadingErrors`): [`LoadingErrorStatus`](loadingError.md#loadingerrorstatus)
36
+
37
+ A composable function for managing aggregated loading and error states across multiple sources.
38
+
39
+ #### Parameters
40
+
41
+ • **loadingErrors**: [`WatchableLoadingErrors`](proxyLoadingError.md#watchableloadingerrors)
42
+
43
+ A collection of loading error statuses to monitor and aggregate.
44
+
45
+ #### Returns
46
+
47
+ [`LoadingErrorStatus`](loadingError.md#loadingerrorstatus)
48
+
49
+ An object containing aggregated reactive fields and actions for loading and error states.
@@ -30,6 +30,11 @@ The normalized form of the CSS classes, either as a string of space-separated cl
30
30
 
31
31
  > **combineClasses**(...`classes`): [`CombinedClasses`](classes.md#combinedclasses)
32
32
 
33
+ Combines and normalizes different formats of CSS class specifications into a single format suitable for Vue.js
34
+ components. If objects are in the mix, objects are returned. Otherwise, a string is returned.
35
+
36
+ We unref your refs, so probably want a computed around this.
37
+
33
38
  #### Parameters
34
39
 
35
40
  • ...**classes**: ([`CombinedClassesArgument`](classes.md#combinedclassesargument) \| [`CombinedClassesArgument`](classes.md#combinedclassesargument)[])[]
@@ -50,6 +55,8 @@ A variable list of class specifications in
50
55
 
51
56
  > **objectifyClasses**(...`classes`): `object` \| `object`[]
52
57
 
58
+ Normalize various ways of specifying CSS classes into an object for use in Vue.js.
59
+
53
60
  #### Parameters
54
61
 
55
62
  • ...**classes**: (`string` \| `string`[] \| `string`[][] \| `object` \| `Ref`\<`string` \| `string`[] \| `string`[][]\>)[]
@@ -69,6 +76,8 @@ An object or array of objects containing CSS classes. Arrays are used if refs ar
69
76
 
70
77
  > **stringifyClass**(`cls`): `string`
71
78
 
79
+ Normalizes various ways of specifying CSS classes into a space-separated list of CSS classes.
80
+
72
81
  #### Parameters
73
82
 
74
83
  • **cls**: `string` \| `string`[] \| `object`
@@ -87,6 +96,8 @@ A space-separated list of CSS classes.
87
96
 
88
97
  > **stringifyClasses**(...`classes`): `string`
89
98
 
99
+ Normalizes various ways of specifying CSS classes into a space-separated list of CSS classes.
100
+
90
101
  #### Parameters
91
102
 
92
103
  • ...**classes**: (`string` \| `string`[] \| `object`)[]
@@ -30,6 +30,8 @@ The object or array to compact sparse arrays in.
30
30
 
31
31
  > **removeEmptyObjects**(`obj`): `void`
32
32
 
33
+ Remove empty objects from a mixed object array tree. Mutates the object.
34
+
33
35
  #### Parameters
34
36
 
35
37
  • **obj**: `any`
@@ -46,6 +48,8 @@ The object or array to remove empty objects from.
46
48
 
47
49
  > **removeEmptyObjectsAndCompactSparseArrays**(`obj`): `void`
48
50
 
51
+ Remove empty objects and undefined values from arrays in a mixed object array tree. Mutates the object.
52
+
49
53
  #### Parameters
50
54
 
51
55
  • **obj**: `any`
@@ -24,6 +24,8 @@
24
24
 
25
25
  > **getObjectRelatedByKey**(`obj`, `relatedObj`, `key`): [`any`, `string`]
26
26
 
27
+ Get the object and key of a related item.
28
+
27
29
  #### Parameters
28
30
 
29
31
  • **obj**: `any`
@@ -50,6 +52,8 @@ The object and key of the related item.
50
52
 
51
53
  > **getObjectRelatedCalculatedByKey**(`obj`, `relatedObj`, `calculatedObj`, `key`): [`any`, `string`]
52
54
 
55
+ Get the object and key of a calculated item.
56
+
53
57
  #### Parameters
54
58
 
55
59
  • **obj**: `any`
package/docs/utils/set.md CHANGED
@@ -12,6 +12,8 @@
12
12
 
13
13
  > **difference**(`setA`, `setB`): `Set`\<`any`\>
14
14
 
15
+ Returns the difference of two sets, containing elements present only in the first set but not in the second.
16
+
15
17
  #### Parameters
16
18
 
17
19
  • **setA**: `Set`\<`any`\>
@@ -28,12 +30,22 @@ The second set.
28
30
 
29
31
  A new Set containing elements from the first set that are not in the second set.
30
32
 
33
+ #### Example
34
+
35
+ ```ts
36
+ const a = new Set([1, 2, 3]);
37
+ const b = new Set([3, 4]);
38
+ console.log(difference(a, b)); // Set(1, 2)
39
+ ```
40
+
31
41
  ***
32
42
 
33
43
  ### equals()
34
44
 
35
45
  > **equals**(`setA`, `setB`): `boolean`
36
46
 
47
+ Tests if two sets are equal, meaning they contain exactly the same elements.
48
+
37
49
  #### Parameters
38
50
 
39
51
  • **setA**: `Set`\<`any`\>
@@ -50,12 +62,22 @@ The second set.
50
62
 
51
63
  Returns true if both sets are equal in size and content, otherwise false.
52
64
 
65
+ #### Example
66
+
67
+ ```ts
68
+ const a = new Set([1, 2, 3]);
69
+ const b = new Set([1, 2, 3]);
70
+ console.log(equals(a, b)); // true
71
+ ```
72
+
53
73
  ***
54
74
 
55
75
  ### intersection()
56
76
 
57
77
  > **intersection**(`setA`, `setB`): `Set`\<`any`\>
58
78
 
79
+ Returns the intersection of two sets, containing only elements that are present in both sets.
80
+
59
81
  #### Parameters
60
82
 
61
83
  • **setA**: `Set`\<`any`\>
@@ -72,12 +94,22 @@ The second set.
72
94
 
73
95
  A new Set containing common elements from both input sets.
74
96
 
97
+ #### Example
98
+
99
+ ```ts
100
+ const a = new Set([1, 2, 3]);
101
+ const b = new Set([2, 3, 4]);
102
+ console.log(intersection(a, b)); // Set(2, 3)
103
+ ```
104
+
75
105
  ***
76
106
 
77
107
  ### isSuperset()
78
108
 
79
109
  > **isSuperset**(`set`, `subset`): `boolean`
80
110
 
111
+ Checks if one set is a superset of another set, meaning all elements of the subset are contained within the set.
112
+
81
113
  #### Parameters
82
114
 
83
115
  • **set**: `Set`\<`any`\>
@@ -94,12 +126,22 @@ The candidate subset.
94
126
 
95
127
  Returns true if the set contains all elements of the subset, otherwise false.
96
128
 
129
+ #### Example
130
+
131
+ ```ts
132
+ const a = new Set([1, 2, 3]);
133
+ const b = new Set([2, 3]);
134
+ console.log(isSuperset(a, b)); // true
135
+ ```
136
+
97
137
  ***
98
138
 
99
139
  ### symmetricDifference()
100
140
 
101
141
  > **symmetricDifference**(`setA`, `setB`): `Set`\<`any`\>
102
142
 
143
+ Returns the symmetric difference of two sets, containing elements present in only one of the sets.
144
+
103
145
  #### Parameters
104
146
 
105
147
  • **setA**: `Set`\<`any`\>
@@ -116,12 +158,22 @@ The second set.
116
158
 
117
159
  A new Set containing elements that are only in one of the input sets.
118
160
 
161
+ #### Example
162
+
163
+ ```ts
164
+ const a = new Set([1, 2, 3]);
165
+ const b = new Set([3, 4, 5]);
166
+ console.log(symmetricDifference(a, b)); // Set(1, 2, 4, 5)
167
+ ```
168
+
119
169
  ***
120
170
 
121
171
  ### union()
122
172
 
123
173
  > **union**(`setA`, `setB`): `Set`\<`any`\>
124
174
 
175
+ Returns the union of two sets, containing all unique elements from both sets.
176
+
125
177
  #### Parameters
126
178
 
127
179
  • **setA**: `Set`\<`any`\>
@@ -137,3 +189,11 @@ The second set.
137
189
  `Set`\<`any`\>
138
190
 
139
191
  A new Set containing all elements from both input sets.
192
+
193
+ #### Example
194
+
195
+ ```ts
196
+ const a = new Set([1, 2, 3]);
197
+ const b = new Set([3, 4, 5]);
198
+ console.log(union(a, b)); // Set(1, 2, 3, 4, 5)
199
+ ```
@@ -12,6 +12,9 @@
12
12
 
13
13
  > **transformWalk**(`obj`, `transformFn`, `path`): `any`
14
14
 
15
+ Recursively walks through an object's values and applies a transformation function to each value.
16
+ The value recursed into is the transformed value, not the original value.
17
+
15
18
  #### Parameters
16
19
 
17
20
  • **obj**: `any`
@@ -31,3 +34,29 @@ The path to the current value.
31
34
  `any`
32
35
 
33
36
  The transformed object.
37
+
38
+ #### Example
39
+
40
+ ```ts
41
+ const obj = {
42
+ a: 1,
43
+ b: {
44
+ c: 2,
45
+ d: [3, 4, { e: 5 }]
46
+ }
47
+ };
48
+
49
+ const transformed = transformWalk(obj, (key, value, path) => {
50
+ if (key === "e") {
51
+ return value * 2;
52
+ }
53
+ return value;
54
+ });
55
+ // transformed = {
56
+ // a: 1,
57
+ // b: {
58
+ // c: 2,
59
+ // d: [3, 4, { e: 10 }]
60
+ // }
61
+ // }
62
+ ```
package/index.js CHANGED
@@ -19,6 +19,7 @@ export * from "./use/objectInstance.js";
19
19
  export * from "./use/objectRelated.js";
20
20
  export * from "./use/objectSubscription.js";
21
21
  export * from "./use/paginatedListInstance.js";
22
+ export * from "./use/proxyLoadingError.js";
22
23
  export * from "./use/search.js";
23
24
  export * from "./use/watchesRunning.js";
24
25
  export * from "./utils/assignReactiveObject.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arrai-innovations/reactive-helpers",
3
- "version": "13.0.4",
3
+ "version": "13.1.0",
4
4
  "description": "VueJS 3 utility composition functions to help manipulate objects and lists.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -30,31 +30,31 @@
30
30
  "homepage": "https://github.com/arrai-innovations/reactive-helpers#readme",
31
31
  "devDependencies": {
32
32
  "@arrai-innovations/commitlint-config": "^1.1.0",
33
- "@commitlint/cli": "^19.3.0",
33
+ "@commitlint/cli": "^19.4.0",
34
34
  "@godaddy/dmd": "^1.0.4",
35
35
  "@trivago/prettier-plugin-sort-imports": "^4.3.0",
36
36
  "@types/browser-util-inspect": "^0.2.4",
37
37
  "@types/lodash-es": "^4.17.12",
38
- "@typescript-eslint/eslint-plugin": "^7.14.1",
39
- "@typescript-eslint/parser": "^7.14.1",
38
+ "@typescript-eslint/eslint-plugin": "^7.18.0",
39
+ "@typescript-eslint/parser": "^7.18.0",
40
40
  "@vitest/coverage-v8": "^1.6.0",
41
- "@vue/compiler-sfc": "^3.3.4",
41
+ "@vue/compiler-sfc": "^3.4.37",
42
42
  "@vue/eslint-config-prettier": "^9.0.0",
43
43
  "@vue/test-utils": "^2.3.2",
44
44
  "doctoc": "^2.1.0",
45
45
  "eslint": "^8.56.0",
46
- "eslint-plugin-jsdoc": "^48.5.0",
46
+ "eslint-plugin-jsdoc": "^48.11.0",
47
47
  "eslint-plugin-vitest": "~0.5.4",
48
48
  "eslint-plugin-vitest-globals": "^1.3.1",
49
- "eslint-plugin-vue": "^9.26.0",
49
+ "eslint-plugin-vue": "^9.27.0",
50
50
  "flush-promises": "^1.0.2",
51
- "globals": "^15.6.0",
52
- "jsdom": "^24.1.0",
53
- "lint-staged": "^15.2.7",
54
- "prettier": "^3.3.2",
55
- "typedoc": "^0.26.1",
56
- "typedoc-plugin-markdown": "^4.1.0",
57
- "typescript": "^5.5.2",
51
+ "globals": "^15.9.0",
52
+ "jsdom": "^24.1.1",
53
+ "lint-staged": "^15.2.8",
54
+ "prettier": "^3.3.3",
55
+ "typedoc": "^0.26.5",
56
+ "typedoc-plugin-markdown": "^4.2.3",
57
+ "typescript": "^5.5.4",
58
58
  "vitest": "^1.6.0"
59
59
  },
60
60
  "dependencies": {
@@ -0,0 +1,43 @@
1
+ import { useLoadingError } from "../../../use/loadingError.js";
2
+ import { ref } from "vue";
3
+ import { describe, it, expect } from "vitest";
4
+
5
+ describe("useLoadingError", () => {
6
+ let loadingError;
7
+
8
+ beforeEach(() => {
9
+ loadingError = useLoadingError();
10
+ });
11
+
12
+ it("should initialize with default states", () => {
13
+ expect(loadingError.loading.value).toBe(undefined);
14
+ expect(loadingError.error.value).toBe(null);
15
+ expect(loadingError.errored.value).toBe(false);
16
+ });
17
+
18
+ it("should set loading state", () => {
19
+ loadingError.setLoading();
20
+ expect(loadingError.loading.value).toBe(true);
21
+ });
22
+
23
+ it("should clear loading state", () => {
24
+ loadingError.setLoading();
25
+ loadingError.clearLoading();
26
+ expect(loadingError.loading.value).toBe(false);
27
+ });
28
+
29
+ it("should set error state and mark as errored", () => {
30
+ const error = new Error("Test Error");
31
+ loadingError.setError(error);
32
+ expect(loadingError.error.value).toBe(error);
33
+ expect(loadingError.errored.value).toBe(true);
34
+ });
35
+
36
+ it("should clear error state", () => {
37
+ const error = new Error("Test Error");
38
+ loadingError.setError(error);
39
+ loadingError.clearError();
40
+ expect(loadingError.error.value).toBe(null);
41
+ expect(loadingError.errored.value).toBe(false);
42
+ });
43
+ });
@@ -0,0 +1,64 @@
1
+ import { useProxyLoadingError } from "../../../use/proxyLoadingError.js";
2
+ import { ref } from "vue";
3
+ import { describe, it, expect } from "vitest";
4
+
5
+ describe("useProxyLoadingError", () => {
6
+ let loadingError1, loadingError2;
7
+
8
+ beforeEach(() => {
9
+ loadingError1 = {
10
+ loading: ref(false),
11
+ error: ref(null),
12
+ errored: ref(false),
13
+ clearError: vi.fn(),
14
+ };
15
+ loadingError2 = {
16
+ loading: ref(false),
17
+ error: ref(null),
18
+ errored: ref(false),
19
+ clearError: vi.fn(),
20
+ };
21
+ });
22
+
23
+ it("should initialize with default states when no errors", () => {
24
+ const proxyLoadingError = useProxyLoadingError([loadingError1, loadingError2]);
25
+ expect(proxyLoadingError.loading.value).toBe(false);
26
+ expect(proxyLoadingError.error.value).toBe(null);
27
+ expect(proxyLoadingError.errored.value).toBe(false);
28
+ });
29
+
30
+ it("should reflect loading state when one source is loading", () => {
31
+ loadingError1.loading.value = true;
32
+ const proxyLoadingError = useProxyLoadingError([loadingError1, loadingError2]);
33
+ expect(proxyLoadingError.loading.value).toBe(true);
34
+ });
35
+
36
+ it("should reflect error state when one source has an error", () => {
37
+ const error = new Error("Test Error");
38
+ loadingError2.error.value = error;
39
+ loadingError2.errored.value = true;
40
+ const proxyLoadingError = useProxyLoadingError([loadingError1, loadingError2]);
41
+ expect(proxyLoadingError.error.value).toBe(error);
42
+ expect(proxyLoadingError.errored.value).toBe(true);
43
+ });
44
+
45
+ it("should clear all errors when clearError is called", () => {
46
+ const proxyLoadingError = useProxyLoadingError([loadingError1, loadingError2]);
47
+ proxyLoadingError.clearError();
48
+ expect(loadingError1.clearError).toHaveBeenCalled();
49
+ expect(loadingError2.clearError).toHaveBeenCalled();
50
+ });
51
+
52
+ it("should reflect correct aggregate state when combining multiple sources", () => {
53
+ loadingError1.loading.value = true;
54
+ loadingError2.errored.value = true;
55
+ const error = new Error("Another Error");
56
+ loadingError2.error.value = error;
57
+
58
+ const proxyLoadingError = useProxyLoadingError([loadingError1, loadingError2]);
59
+
60
+ expect(proxyLoadingError.loading.value).toBe(true); // One source is loading
61
+ expect(proxyLoadingError.errored.value).toBe(true); // One source has an error
62
+ expect(proxyLoadingError.error.value).toBe(error); // Error is aggregated
63
+ });
64
+ });
@@ -34,7 +34,7 @@ import { computed, effectScope, onScopeDispose, reactive, ref, toRef, unref, wat
34
34
  * @typedef {object} ListRelatedRule
35
35
  * @property {string} pkKey - Specifies the foreign key used to link objects across lists. Planned to be renamed to
36
36
  * 'fkKey' to better reflect its usage.
37
- * @property {string[]} order - Specifies the order in which related objects should be sorted, if applicable.
37
+ * @property {string[]} [order] - Specifies the order in which related objects should be sorted, if applicable.
38
38
  * @property {import('./listInstance.js').ObjectsById} objects - The objects that can be related based on the foreign key.
39
39
  */
40
40
 
@@ -5,14 +5,28 @@ import { readonly, ref } from "vue";
5
5
  */
6
6
 
7
7
  /**
8
- * @typedef {object} LoadingError
8
+ * The common API for loading and error states.
9
+ *
10
+ * @typedef {object} LoadingErrorStatus
9
11
  * @property {Readonly<import("vue").Ref<boolean|undefined>>} loading - Whether the component is loading.
10
12
  * @property {Readonly<import("vue").Ref<Error|null>>} error - The error that occurred.
11
13
  * @property {Readonly<import("vue").Ref<boolean>>} errored - Whether an error has occurred.
14
+ * @property {() => void} clearError - Clear the error state.
15
+ */
16
+
17
+ /**
18
+ * The writable API for loading and error states.
19
+ *
20
+ * @typedef {object} LoadingErrorMutations
12
21
  * @property {() => void} setLoading - Set the loading state.
13
22
  * @property {() => void} clearLoading - Clear the loading state.
14
23
  * @property {(error) => void} setError - Set the error state.
15
- * @property {() => void} clearError - Clear the error state.
24
+ */
25
+
26
+ /**
27
+ * The instance of useLoadingError.
28
+ *
29
+ * @typedef {LoadingErrorStatus & LoadingErrorMutations} LoadingError
16
30
  */
17
31
 
18
32
  /**
@@ -0,0 +1,59 @@
1
+ import { computed, readonly, unref } from "vue";
2
+ import { loadingCombine } from "../utils/loadingCombine.js";
3
+
4
+ /**
5
+ * @typedef {(
6
+ * import('./loadingError.js').LoadingErrorStatus[]
7
+ * )} WatchableLoadingErrorsRaw
8
+ */
9
+
10
+ /**
11
+ * A watchable collection of loading errors.
12
+ *
13
+ * @typedef {(
14
+ * import('vue').UnwrapNestedRefs<WatchableLoadingErrorsRaw> |
15
+ * import('vue').Ref<WatchableLoadingErrorsRaw> |
16
+ * WatchableLoadingErrorsRaw
17
+ * )} WatchableLoadingErrors
18
+ */
19
+
20
+ /**
21
+ * The instance of useProxyLoadingError.
22
+ *
23
+ * @typedef {import('./loadingError.js').LoadingErrorStatus} ProxyLoadingError
24
+ */
25
+
26
+ /**
27
+ * A composable function for managing aggregated loading and error states across multiple sources.
28
+ *
29
+ * @param {WatchableLoadingErrors} loadingErrors - A collection of loading error statuses to monitor and aggregate.
30
+ * @returns {ProxyLoadingError} An object containing aggregated reactive fields and actions for loading and error states.
31
+ */
32
+ export function useProxyLoadingError(loadingErrors) {
33
+ /** @type {import("vue").ComputedRef<boolean|undefined>} */
34
+ const loading = computed(() =>
35
+ loadingCombine(...unref(loadingErrors).map((loadingError) => unref(loadingError.loading)))
36
+ );
37
+ /** @type {import("vue").ComputedRef<Error|null>} */
38
+ const error = computed(
39
+ () =>
40
+ unref(loadingErrors)
41
+ .map((loadingError) => unref(loadingError.error))
42
+ .find((error) => error) || null
43
+ );
44
+ /** @type {import("vue").ComputedRef<boolean>} */
45
+ const errored = computed(() =>
46
+ unref(loadingErrors)
47
+ .map((loadingError) => unref(loadingError.errored))
48
+ .some((errored) => errored)
49
+ );
50
+
51
+ return {
52
+ loading: readonly(loading),
53
+ error: readonly(error),
54
+ errored: readonly(errored),
55
+ clearError: () => {
56
+ unref(loadingErrors).forEach((loadingError) => loadingError.clearError());
57
+ },
58
+ };
59
+ }