@arrai-innovations/reactive-helpers 11.4.2 → 13.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/.husky/pre-commit +0 -1
  2. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  3. package/.prettierignore +1 -0
  4. package/README.md +11 -10
  5. package/config/listCrud.js +32 -1
  6. package/config/objectCrud.js +47 -1
  7. package/docs/README.md +43 -0
  8. package/docs/config/listCrud.md +97 -0
  9. package/docs/config/objectCrud.md +123 -0
  10. package/docs/use/cancellableIntent.md +163 -0
  11. package/docs/use/combineClasses.md +52 -0
  12. package/docs/use/list.md +285 -0
  13. package/docs/use/listCalculated.md +773 -0
  14. package/docs/use/listFilter.md +877 -0
  15. package/docs/use/listInstance.md +483 -0
  16. package/docs/use/listKeys.md +91 -0
  17. package/docs/use/listRelated.md +685 -0
  18. package/docs/use/listSearch.md +889 -0
  19. package/docs/use/listSort.md +1144 -0
  20. package/docs/use/listSubscription.md +379 -0
  21. package/docs/use/loadingError.md +21 -0
  22. package/docs/use/object.md +437 -0
  23. package/docs/use/objectCalculated.md +606 -0
  24. package/docs/use/objectInstance.md +486 -0
  25. package/docs/use/objectRelated.md +588 -0
  26. package/docs/use/objectSubscription.md +459 -0
  27. package/docs/use/paginatedListInstance.md +147 -0
  28. package/docs/use/search.md +311 -0
  29. package/docs/use/watchesRunning.md +80 -0
  30. package/docs/utils/assignReactiveObject.md +351 -0
  31. package/docs/utils/classes.md +100 -0
  32. package/docs/utils/compact.md +57 -0
  33. package/docs/utils/deleteKey.md +53 -0
  34. package/docs/utils/flattenPaths.md +45 -0
  35. package/docs/utils/getFakeId.md +34 -0
  36. package/docs/utils/keyDiff.md +101 -0
  37. package/docs/utils/loadingCombine.md +28 -0
  38. package/docs/utils/proxyRunning.md +33 -0
  39. package/docs/utils/relatedCalculatedHelpers.md +75 -0
  40. package/docs/utils/set.md +139 -0
  41. package/docs/utils/transformWalk.md +33 -0
  42. package/docs/utils/watches.md +441 -0
  43. package/eslint.config.js +125 -0
  44. package/index.js +34 -3
  45. package/lint-staged.config.js +5 -1
  46. package/makeTypeDoc.sh +25 -0
  47. package/package.json +27 -19
  48. package/tests/unit/config/listCrud.spec.js +5 -0
  49. package/tests/unit/config/objectCrud.spec.js +5 -0
  50. package/tests/unit/crudPromise.js +22 -3
  51. package/tests/unit/mockOnUnmounted.js +1 -1
  52. package/tests/unit/use/cancellableIntent.spec.js +2 -0
  53. package/tests/unit/use/listCalculated.spec.js +4 -3
  54. package/tests/unit/use/listFilter.spec.js +11 -7
  55. package/tests/unit/use/listInstance.spec.js +32 -14
  56. package/tests/unit/use/listRelated.spec.js +3 -2
  57. package/tests/unit/use/listSearch.spec.js +9 -7
  58. package/tests/unit/use/listSort.spec.js +29 -22
  59. package/tests/unit/use/listSubscription.spec.js +11 -5
  60. package/tests/unit/use/objectInstance.spec.js +15 -2
  61. package/tests/unit/use/objectSubscription.spec.js +31 -16
  62. package/tests/unit/use/search.spec.js +2 -2
  63. package/tests/unit/utils/assignReactiveObject.spec.js +21 -13
  64. package/tests/unit/utils/classes.spec.js +7 -6
  65. package/tests/unit/utils/watches.spec.js +4 -4
  66. package/tsconfig.json +19 -0
  67. package/typedoc-local-plugins/customize-output.js +13 -0
  68. package/typedoc.json +12 -0
  69. package/use/cancellableIntent.js +73 -2
  70. package/use/combineClasses.js +41 -31
  71. package/use/list.js +139 -5
  72. package/use/listCalculated.js +175 -10
  73. package/use/listFilter.js +175 -21
  74. package/use/listInstance.js +215 -94
  75. package/use/listRelated.js +260 -72
  76. package/use/listSearch.js +178 -57
  77. package/use/listSort.js +188 -21
  78. package/use/listSubscription.js +211 -63
  79. package/use/loadingError.js +43 -0
  80. package/use/object.js +187 -23
  81. package/use/objectCalculated.js +168 -16
  82. package/use/objectInstance.js +183 -102
  83. package/use/objectRelated.js +276 -84
  84. package/use/objectSubscription.js +169 -17
  85. package/use/paginatedListInstance.js +30 -1
  86. package/use/search.js +39 -23
  87. package/use/watchesRunning.js +41 -6
  88. package/utils/assignReactiveObject.js +139 -88
  89. package/utils/classes.js +57 -48
  90. package/utils/compact.js +28 -2
  91. package/utils/deleteKey.js +14 -1
  92. package/utils/flattenPaths.js +19 -9
  93. package/utils/getFakeId.js +11 -1
  94. package/utils/keyDiff.js +26 -35
  95. package/utils/loadingCombine.js +7 -4
  96. package/utils/proxyRunning.js +16 -8
  97. package/utils/relatedCalculatedHelpers.js +17 -0
  98. package/utils/set.js +66 -0
  99. package/utils/transformWalk.js +7 -6
  100. package/utils/watches.js +205 -71
  101. package/.eslintignore +0 -2
  102. package/.eslintrc.cjs +0 -25
  103. package/.jsdoc2md.json +0 -4
  104. package/config/index.js +0 -2
  105. package/docs.md +0 -1002
  106. package/jsdoc-to-markdown.sh +0 -16
  107. package/tests/unit/.eslintrc.cjs +0 -11
  108. package/tests/unit/config/index.spec.js +0 -18
  109. package/tests/unit/use/index.spec.js +0 -18
  110. package/tests/unit/utils/index.spec.js +0 -18
  111. package/use/index.js +0 -19
  112. package/utils/debugMessage.js +0 -209
  113. package/utils/debugWatch.js +0 -29
  114. package/utils/index.js +0 -16
  115. package/utils/lifecycleDebug.js +0 -126
@@ -1,7 +1,36 @@
1
1
  import { useListInstance } from "./listInstance.js";
2
2
 
3
+ /**
4
+ * @module use/paginatedListInstance.js
5
+ *
6
+ */
7
+
8
+ /**
9
+ * @typedef {object} PagedListListanceOptions
10
+ * @property {boolean} [keepOldPages=false] - Whether to keep old pages.
11
+ */
12
+
13
+ /**
14
+ * @typedef {object} PagedListInstanceState
15
+ * @property {number} totalRecords - The total records.
16
+ * @property {number} totalPages - The total pages.
17
+ * @property {number} perPage - The per page.
18
+ */
19
+
20
+ /**
21
+ * @typedef {object} PagedListInstance
22
+ * @property {import('./listInstance.js').ListInstanceState & PagedListInstanceState} state - The state.
23
+ */
24
+
25
+ /**
26
+ *
27
+ * @param {PagedListListanceOptions & import('./listInstance.js').ListInstanceOptions} options - The options.
28
+ * @returns {PagedListInstance} - The paged list instance.
29
+ */
3
30
  export function usePagedListInstance({ keepOldPages = false, ...useListInstanceArgs }) {
4
- const listInstance = useListInstance(useListInstanceArgs);
31
+ const listInstance = /** @type {PagedListInstance & import('./listInstance.js').ListInstance} */ (
32
+ /** @type {unknown} */ (useListInstance(useListInstanceArgs))
33
+ );
5
34
 
6
35
  listInstance.state.totalRecords = 0;
7
36
  listInstance.state.totalPages = 0;
package/use/search.js CHANGED
@@ -15,33 +15,46 @@ const unionReduce = (accumulator, currentValue) => {
15
15
  };
16
16
 
17
17
  /**
18
- * FlexSearch.Document search options
18
+ * FlexSearch.Document search options.
19
+ *
19
20
  * @typedef {object} SearchOptions
20
- * @property {number} limit - limit of results
21
+ * @property {number} limit - Limit of results.
21
22
  */
22
23
 
23
- // eslint-disable-next-line jsdoc/require-property
24
24
  /**
25
- * FlexSearch.Document options
25
+ * Configuration options for creating a document in FlexSearch.
26
+ *
26
27
  * @typedef {object} DocumentOptions
28
+ * @property {string} id - The document field to use as an identifier. Default is "id".
29
+ * @property {boolean|string} tag - The document field to use as a tag. Default is false, can be set to a string.
30
+ * @property {string | string[] | object[]} index - Fields to index. Can be a single string, an array of strings, or an array of objects specifying custom index options.
31
+ * @property {boolean|string|string[]} store - Specifies if and what document fields to store. Can be false, a string, or an array of strings. Default is false.
32
+ * @property {string} [tokenizer] - Specifies the tokenizer to use.
33
+ * @property {number} [minLength] - Minimum length of a token to be indexed.
27
34
  */
28
35
 
29
36
  /**
30
- * @typedef {object} SearchState
31
- * @property {string} search - the search string
32
- * @property {object} results - the results, where the keys are the ids of the objects that match, and the values are true
33
- * @property {boolean} searched - whether the search has been performed
34
- * @property {boolean} searching - whether the search is currently running
37
+ * @typedef {object} SearchRawState
38
+ * @property {string} search - The search string.
39
+ * @property {object} results - The results, where the keys are the ids of the objects that match, and the values are true.
40
+ * @property {boolean} searched - Whether the search has been performed.
41
+ * @property {boolean} searching - Whether the search is currently running.
42
+ * @property {DocumentOptions} customDocumentOptions - FlexSearch.Document options.
43
+ * @property {SearchOptions} customSearchOptions - Search options.
44
+ * @property {number} called - The number of times the search has been called.
45
+ * @property {number} pending - The number of times the search has been called, but has not yet returned.
46
+ * @property {boolean} running - Whether the search is currently running or has pending calls.
35
47
  */
36
48
 
37
49
  /**
38
50
  * @typedef {object} SearchInstance
39
- * @property {SearchState} state - the state
40
- * @property {function} addIndex - add an index
41
- * @property {function} updateIndex - update an index
42
- * @property {function} removeIndex - remove an index
43
- * @property {function} clearIndex - clear the index
44
- * @property {object} effectScope - a Vue effect scope
51
+ * @property {import('vue').UnwrapNestedRefs<SearchRawState>} state - The state.
52
+ * @property {Function} addIndex - Add an index.
53
+ * @property {Function} updateIndex - Update an index.
54
+ * @property {Function} removeIndex - Remove an index.
55
+ * @property {Function} clearIndex - Clear the index.
56
+ * @property {EventTarget} events - An event target.
57
+ * @property {object} effectScope - A Vue effect scope.
45
58
  */
46
59
 
47
60
  const defaultDocumentOptions = {
@@ -53,18 +66,20 @@ const defaultDocumentOptions = {
53
66
  };
54
67
 
55
68
  /**
56
- * A reactive object for passing document options or search options to useSearch
69
+ * A reactive object for passing document options or search options to useSearch.
70
+ *
57
71
  * @typedef {object} SearchProps
58
- * @property {DocumentOptions} customDocumentOptions - FlexSearch.Document options
59
- * @property {SearchOptions} customSearchOptions - search options
72
+ * @property {DocumentOptions} customDocumentOptions - FlexSearch.Document options.
73
+ * @property {SearchOptions} customSearchOptions - Search options.
60
74
  */
61
75
 
62
76
  /**
63
- * A reactive wrapper around FlexSearch.Index
64
- * @param {object} options - options
65
- * @param {SearchProps} options.props - props
66
- * @param {number} [options.throttle] - throttle wait time
67
- * @returns {SearchInstance} - the instance
77
+ * A reactive wrapper around FlexSearch.Index.
78
+ *
79
+ * @param {object} options - Options.
80
+ * @param {SearchProps} options.props - Props.
81
+ * @param {number} [options.throttle] - Throttle wait time.
82
+ * @returns {SearchInstance} - The instance.
68
83
  */
69
84
  export function useSearch({ props, throttle = 500 }) {
70
85
  let searchIndex;
@@ -158,6 +173,7 @@ export function useSearch({ props, throttle = 500 }) {
158
173
  }
159
174
  }
160
175
 
176
+ // @ts-ignore - this is a valid function, not sure why their types don't work for us.
161
177
  const _throttledDoSearch = asyncThrottle(doSearch, throttle);
162
178
  const throttledDoSearch = () => {
163
179
  ++state.called;
@@ -1,8 +1,46 @@
1
- import { loadingCombine } from "../utils/index.js";
1
+ import { loadingCombine } from "../utils/loadingCombine.js";
2
2
  import { computed, effectScope, reactive, unref, watch } from "vue";
3
3
 
4
+ /**
5
+ * Vue Composition API composable function for watches running.
6
+ *
7
+ * @module use/watchesRunning.js
8
+ */
9
+
10
+ /**
11
+ * The state for watches running.
12
+ *
13
+ * @typedef {object} WatchesRunningRawState
14
+ * @property {import('vue').ComputedRef<boolean>} running - Whether the watches are running.
15
+ */
16
+
17
+ /**
18
+ * The state for watches running.
19
+ *
20
+ * @typedef {import('vue').UnwrapNestedRefs<WatchesRunningRawState>} WatchesRunningState
21
+ */
22
+
23
+ /**
24
+ * An instance of `useWatchesRunning`.
25
+ *
26
+ * @typedef {object} WatchesRunning
27
+ * @property {WatchesRunningState} state - The state for watches running.
28
+ * @property {import('vue').EffectScope} effectScope - The effect scope for watches running.
29
+ */
30
+
31
+ /**
32
+ * A composable function for handling watches running. When all the trigger refs are true,
33
+ * the watch sentinel refs are set to true.
34
+ *
35
+ * @param {object} options - The options for the watches running.
36
+ * @param {import('vue').WatchSource[]} options.triggerRefs - The trigger refs.
37
+ * @param {import('vue').Ref<boolean>[]} options.watchSentinelRefs - The watch sentinel refs.
38
+ * @returns {WatchesRunning} - The watches running.
39
+ */
4
40
  export function useWatchesRunning({ triggerRefs, watchSentinelRefs }) {
5
- const state = reactive({});
41
+ const state = reactive({
42
+ running: undefined,
43
+ });
6
44
 
7
45
  const es = effectScope();
8
46
 
@@ -21,10 +59,7 @@ export function useWatchesRunning({ triggerRefs, watchSentinelRefs }) {
21
59
  deep: true,
22
60
  }
23
61
  );
24
- state.running = computed(() => {
25
- const values = watchSentinelRefs.map((ref) => unref(ref));
26
- return loadingCombine(values);
27
- });
62
+ state.running = computed(() => loadingCombine(...watchSentinelRefs.map((ref) => unref(ref))));
28
63
  });
29
64
 
30
65
  return {
@@ -4,44 +4,53 @@ import isArray from "lodash-es/isArray.js";
4
4
  import isObject from "lodash-es/isObject.js";
5
5
  import isObjectLike from "lodash-es/isObjectLike.js";
6
6
  import { isReactive, isRef, toRef, unref } from "vue";
7
+ import isSet from "lodash-es/isSet.js";
7
8
 
8
9
  /**
9
- * Reactive object assignment utilities
10
- * @module utils/assignReactiveObject
10
+ * Reactive object assignment utilities.
11
+ *
12
+ * @module utils/assignReactiveObject.js
11
13
  */
12
14
 
15
+ /**
16
+ * Error thrown when an invalid value is passed to a function.
17
+ */
13
18
  export class AssignReactiveObjectError extends Error {
14
- constructor(message) {
19
+ /**
20
+ * @param {string} message - The error message.
21
+ * @param {string} code - The error code.
22
+ */
23
+ constructor(message, code) {
15
24
  super(message);
16
25
  this.name = "AssignReactiveObjectError";
26
+ this.code = code;
17
27
  }
18
28
  }
19
29
 
20
30
  /**
21
- * @typedef {*} Ref A Vue ref
22
- * @private
23
- */
24
-
25
- /**
26
- * @typedef {Ref|object|Array} ValidTargetOrSource targets and sources must be refs, objects, or arrays
31
+ * @typedef {import("vue").Ref<object | any[]> | object | any[]} ValidTargetOrSource targets and sources must be refs, objects, or arrays
27
32
  * and refs must ultimately resolve to objects or arrays
28
33
  */
29
34
 
30
35
  /**
31
36
  * Validates that a value is an array or an object, and throws an error if it is not.
37
+ *
32
38
  * @private
33
- * @param {string} key The key being validated.
34
- * @param {*} value The value being validated.
39
+ * @param {string} key - The key being validated.
40
+ * @param {*} value - The value being validated.
35
41
  * @throws {AssignReactiveObjectError} If the value is not an array or an object.
36
42
  */
37
43
  function isArrayOrObject(key, value) {
38
44
  if (!(isArray(value) || isObject(value))) {
39
- throw new AssignReactiveObjectError(`${key} must be an object or an array, not ${inspect(value)}`);
45
+ throw new AssignReactiveObjectError(
46
+ `${key} must be an object or an array, not ${inspect(value)}`,
47
+ "invalid-type"
48
+ );
40
49
  }
41
50
  }
42
51
 
43
52
  /**
44
- * @typedef validateTargetAndSourceResult
53
+ * @typedef {object} validateTargetAndSourceResult
45
54
  * @private
46
55
  * @property {ValidTargetOrSource} target The validated target value.
47
56
  * @property {ValidTargetOrSource} source The validated source value.
@@ -50,9 +59,10 @@ function isArrayOrObject(key, value) {
50
59
  /**
51
60
  * Validates that the target and source values are arrays or objects, and returns them.
52
61
  * If either value is a ref, it is dereferenced before validation.
62
+ *
53
63
  * @private
54
- * @param {ValidTargetOrSource} target The target value to validate.
55
- * @param {ValidTargetOrSource} source The source value to validate.
64
+ * @param {ValidTargetOrSource} target - The target value to validate.
65
+ * @param {ValidTargetOrSource} source - The source value to validate.
56
66
  * @returns {validateTargetAndSourceResult} An object containing the validated target and source values.
57
67
  * @throws {AssignReactiveObjectError} If either value is not an array or an object.
58
68
  */
@@ -75,11 +85,12 @@ function validateTargetAndSource(target, source) {
75
85
  /**
76
86
  * Replaces keys in a target object or array with reactive refs to the corresponding keys in a
77
87
  * source object or array.
88
+ *
78
89
  * @private
79
- * @param {ValidTargetOrSource} target The object receiving values.
80
- * @param {ValidTargetOrSource} source The object providing values.
81
- * @param {Array} keys The keys to replace.
82
- * @param {Array} [exclude] Keys to exclude from replacement.
90
+ * @param {ValidTargetOrSource} target - The object receiving values.
91
+ * @param {ValidTargetOrSource} source - The object providing values.
92
+ * @param {Array|Set} keys - The keys to replace.
93
+ * @param {Array} [exclude] - Keys to exclude from replacement.
83
94
  * @returns {boolean} True if any keys were replaced, false otherwise.
84
95
  * @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
85
96
  */
@@ -113,11 +124,11 @@ function reactiveReplaceKeys(target, source, keys, exclude) {
113
124
 
114
125
  /**
115
126
  * Adds to a target the missing keys from a source. `addedKeys` can be precalculated to avoid recalculation.
116
- * @function addReactiveObject
117
- * @param {ValidTargetOrSource} target The object receiving values.
118
- * @param {ValidTargetOrSource} source The object providing values.
119
- * @param {Array} [exclude] Keys to exclude from the addition.
120
- * @param {Array} [addedKeys] Precaulcated array of keys to add, if available. Otherwise, the
127
+ *
128
+ * @param {ValidTargetOrSource} target - The object receiving values.
129
+ * @param {ValidTargetOrSource} source - The object providing values.
130
+ * @param {Array} [exclude] - Keys to exclude from the addition.
131
+ * @param {Array|Set} [addedKeys] - Precaulcated array of keys to add, if available. Otherwise, the
121
132
  * keys will be calculated.
122
133
  * @returns {boolean} True if any keys were added, false otherwise.
123
134
  * @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
@@ -135,11 +146,11 @@ export function addReactiveObject(target, source, exclude, addedKeys = null) {
135
146
 
136
147
  /**
137
148
  * Updates a target with mutually shared keys from a source. `sameKeys` can be precalculated to avoid recalculation.
138
- * @function updateReactiveObject
139
- * @param {ValidTargetOrSource} target The object receiving values.
140
- * @param {ValidTargetOrSource} source The object providing values.
141
- * @param {Array} [exclude] Keys to exclude from the update.
142
- * @param {Array} [sameKeys] Precaulcated array of keys to update, if available. Otherwise, the
149
+ *
150
+ * @param {ValidTargetOrSource} target - The object receiving values.
151
+ * @param {ValidTargetOrSource} source - The object providing values.
152
+ * @param {Array} [exclude] - Keys to exclude from the update.
153
+ * @param {Array|Set} [sameKeys] - Precaulcated array of keys to update, if available. Otherwise, the
143
154
  * keys will be calculated.
144
155
  * @returns {boolean} True if any keys were updated, false otherwise.
145
156
  * @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
@@ -157,17 +168,25 @@ export function updateReactiveObject(target, source, exclude, sameKeys = null) {
157
168
 
158
169
  /**
159
170
  * Adds to a target the missing keys from a source, and updates a target with mutually shared keys from a source.
160
- * @function addOrUpdateReactiveObject
161
- * @param {ValidTargetOrSource} target The object receiving values.
162
- * @param {ValidTargetOrSource} source The object providing values.
163
- * @param {Array} [exclude] Keys to exclude from the addition or update.
164
- * @param {Array} [addedKeys] Precaulcated array of keys to add, if available. Otherwise, the
171
+ *
172
+ * @param {ValidTargetOrSource} target - The object receiving values.
173
+ * @param {ValidTargetOrSource} source - The object providing values.
174
+ * @param {Array} [exclude] - Keys to exclude from the addition or update.
175
+ * @param {Array|Set} [addedKeys] - Precaulcated array of keys to add, if available. Otherwise, the
165
176
  * keys will be calculated.
166
- * @param {Array} [sameKeys] Precaulcated array of keys to update, if available. Otherwise, the
177
+ * @param {Array|Set} [sameKeys] - Precaulcated array of keys to update, if available. Otherwise, the
167
178
  * keys will be calculated.
179
+ * @param {boolean} [doNotSetUndefinedKeys=true] - If true, do not update keys in the target that are undefined in the source.
168
180
  * @returns {boolean} True if any keys were added or updated, false otherwise.
169
181
  */
170
- export function addOrUpdateReactiveObject(target, source, exclude, addedKeys = null, sameKeys = null) {
182
+ export function addOrUpdateReactiveObject(
183
+ target,
184
+ source,
185
+ exclude,
186
+ addedKeys = null,
187
+ sameKeys = null,
188
+ doNotSetUndefinedKeys = true
189
+ ) {
171
190
  if (!addedKeys && !sameKeys) {
172
191
  if (target === source) {
173
192
  return false;
@@ -175,19 +194,34 @@ export function addOrUpdateReactiveObject(target, source, exclude, addedKeys = n
175
194
  ({ target, source } = validateTargetAndSource(target, source));
176
195
  ({ addedKeys, sameKeys } = keyDiff(Object.keys(source) || [], Object.keys(target) || []));
177
196
  }
178
- let didAnything = false;
179
- didAnything |= addReactiveObject(target, source, exclude, addedKeys);
180
- didAnything |= updateReactiveObject(target, source, exclude, sameKeys);
181
- return didAnything;
197
+ // if the source update value is undefined, we should not update the target
198
+ if (sameKeys && doNotSetUndefinedKeys) {
199
+ for (const key of sameKeys) {
200
+ if (source[key] === undefined) {
201
+ if (isSet(sameKeys)) {
202
+ sameKeys.delete(key);
203
+ } else {
204
+ const index = sameKeys.indexOf(key);
205
+ if (index !== -1) {
206
+ sameKeys.splice(index, 1);
207
+ }
208
+ }
209
+ }
210
+ }
211
+ }
212
+
213
+ const wasAdded = addReactiveObject(target, source, exclude, addedKeys);
214
+ const wasUpdated = updateReactiveObject(target, source, exclude, sameKeys);
215
+ return wasAdded || wasUpdated;
182
216
  }
183
217
 
184
218
  /**
185
219
  * Removes keys from a target that are not present in a source.
186
- * @function trimReactiveObject
187
- * @param {ValidTargetOrSource} target The object receiving trimming.
188
- * @param {ValidTargetOrSource|null} source The object that provides the allowed set of keys for calculating `removedKeys`.
189
- * @param {Array} [exclude] Keys to exclude from removal.
190
- * @param {Array} [removedKeys] An array to store removed keys.
220
+ *
221
+ * @param {ValidTargetOrSource} target - The object receiving trimming.
222
+ * @param {ValidTargetOrSource|null} source - The object that provides the allowed set of keys for calculating `removedKeys`.
223
+ * @param {Array} [exclude] - Keys to exclude from removal.
224
+ * @param {Array|Set} [removedKeys] - An array to store removed keys.
191
225
  * @returns {boolean} True if any keys were removed, false otherwise.
192
226
  * @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
193
227
  */
@@ -240,6 +274,15 @@ function checkIfReversed(target, source) {
240
274
  return true;
241
275
  }
242
276
 
277
+ /**
278
+ * Change a target to match a source, where keys missing from the source are removed from the target,
279
+ * keys present in the source are added to the target, and keys present in both are updated in the target.
280
+ * This function is optimized for arrays.
281
+ *
282
+ * @param {ValidTargetOrSource} target - The array receiving updates.
283
+ * @param {ValidTargetOrSource} source - The reactive array to assign.
284
+ * @returns {boolean} True if any keys were added, updated, or removed, false otherwise.
285
+ */
243
286
  export function assignReactiveArray(target, source) {
244
287
  if (target === source) {
245
288
  return false;
@@ -252,8 +295,9 @@ export function assignReactiveArray(target, source) {
252
295
  didAnything = true;
253
296
  } else {
254
297
  const { addedKeys, sameKeys, removedKeys } = keyDiff(Object.keys(source) || [], Object.keys(target) || []);
255
- didAnything |= trimReactiveObject(target, source, null, removedKeys);
256
- didAnything |= addOrUpdateReactiveObject(target, source, null, addedKeys, sameKeys);
298
+ const wasTrimmed = trimReactiveObject(target, source, null, removedKeys);
299
+ const wasChanged = addOrUpdateReactiveObject(target, source, null, addedKeys, sameKeys, false);
300
+ didAnything = wasTrimmed || wasChanged;
257
301
  }
258
302
  return didAnything;
259
303
  }
@@ -261,10 +305,10 @@ export function assignReactiveArray(target, source) {
261
305
  /**
262
306
  * Change a target to match a source, where keys missing from the source are removed from the target,
263
307
  * keys present in the source are added to the target, and keys present in both are updated in the target.
264
- * @function assignReactiveObject
265
- * @param {ValidTargetOrSource} target The target object or array.
266
- * @param {ValidTargetOrSource} source The reactive object to assign.
267
- * @param {Array} [exclude] Keys to exclude from the assignment.
308
+ *
309
+ * @param {ValidTargetOrSource} target - The target object or array.
310
+ * @param {ValidTargetOrSource} source - The reactive object to assign.
311
+ * @param {Array} [exclude] - Keys to exclude from the assignment.
268
312
  * @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
269
313
  * @returns {boolean} True if any keys were added, updated, or removed, false otherwise.
270
314
  */
@@ -280,10 +324,9 @@ export function assignReactiveObject(target, source, exclude) {
280
324
  }
281
325
  ({ target, source } = validateTargetAndSource(target, source));
282
326
  const { addedKeys, sameKeys, removedKeys } = keyDiff(Object.keys(source) || [], Object.keys(target) || []);
283
- let didAnything = false;
284
- didAnything |= trimReactiveObject(target, source, exclude, removedKeys);
285
- didAnything |= addOrUpdateReactiveObject(target, source, exclude, addedKeys, sameKeys);
286
- return didAnything;
327
+ const wasTrimmed = trimReactiveObject(target, source, exclude, removedKeys);
328
+ const wasChanged = addOrUpdateReactiveObject(target, source, exclude, addedKeys, sameKeys, false);
329
+ return wasTrimmed || wasChanged;
287
330
  }
288
331
 
289
332
  /**
@@ -291,21 +334,21 @@ export function assignReactiveObject(target, source, exclude) {
291
334
  * keys present in the source are added to the target, and keys present in both are updated in the target.
292
335
  *
293
336
  * As an internal function, this function does not validate its arguments and has no optional arguments.
337
+ *
294
338
  * @private
295
- * @param {ValidTargetOrSource} target The object receiving updates.
296
- * @param {ValidTargetOrSource} source The object providing updates.
297
- * @param {Array} exclude Keys to exclude from the update.
298
- * @param {Array} addedKeys Precaulcated array of keys to add, if available. Otherwise, the
339
+ * @param {ValidTargetOrSource} target - The object receiving updates.
340
+ * @param {ValidTargetOrSource} source - The object providing updates.
341
+ * @param {Array} exclude - Keys to exclude from the update.
342
+ * @param {Array|Set} addedKeys - Precaulcated array of keys to add, if available. Otherwise, the
299
343
  * keys will be calculated.
300
- * @param {Array} sameKeys Precaulcated array of keys to update, if available. Otherwise, the
344
+ * @param {Array|Set} sameKeys - Precaulcated array of keys to update, if available. Otherwise, the
301
345
  * keys will be calculated.
302
- * @param {string} path The current path, used to rescope exclude for the next level.
303
- * @param {Function} fn The recursive function to call, likely the calling function itself.
346
+ * @param {string} path - The current path, used to rescope exclude for the next level.
347
+ * @param {Function} fn - The recursive function to call, likely the calling function itself.
304
348
  * @returns {boolean} True if any keys were added, updated, or removed, false otherwise.
305
349
  */
306
350
  function recursiveInner(target, source, exclude, addedKeys, sameKeys, path, fn) {
307
- let didAnything = false;
308
- didAnything |= addReactiveObject(target, source, exclude, addedKeys);
351
+ const wasAdded = addReactiveObject(target, source, exclude, addedKeys);
309
352
  const keysForRecurse = [];
310
353
  const keysForReplace = [];
311
354
  for (const key of sameKeys) {
@@ -317,7 +360,7 @@ function recursiveInner(target, source, exclude, addedKeys, sameKeys, path, fn)
317
360
  }
318
361
  }
319
362
  }
320
- didAnything |= reactiveReplaceKeys(target, source, keysForReplace, exclude);
363
+ const wasReplaced = reactiveReplaceKeys(target, source, keysForReplace, exclude);
321
364
  for (const key of keysForRecurse) {
322
365
  // scope exclude for this next level, remove keys that don't start with the current path, trim keys that do to remove the current path
323
366
  const nextLevelExclude = exclude
@@ -326,7 +369,7 @@ function recursiveInner(target, source, exclude, addedKeys, sameKeys, path, fn)
326
369
  const nextPath = isArray(source[key]) ? `${path}[${key}]` : `${path}.${key}`;
327
370
  fn(target[key], source[key], nextLevelExclude, nextPath);
328
371
  }
329
- return didAnything;
372
+ return wasAdded || wasReplaced;
330
373
  }
331
374
 
332
375
  /**
@@ -334,29 +377,37 @@ function recursiveInner(target, source, exclude, addedKeys, sameKeys, path, fn)
334
377
  * keys present in the source are added to the target, and keys present in both are updated in the target.
335
378
  *
336
379
  * An internal function to avoid validating arguments repeatedly.
380
+ *
337
381
  * @private
338
- * @param {ValidTargetOrSource} target The object receiving updates.
339
- * @param {ValidTargetOrSource} source The object providing updates.
340
- * @param {Array} [exclude] Keys to exclude from the assignment.
341
- * @param {string} [path] The current path, used to rescope exclude for the next level.
382
+ * @param {ValidTargetOrSource} target - The object receiving updates.
383
+ * @param {ValidTargetOrSource} source - The object providing updates.
384
+ * @param {Array} [exclude] - Keys to exclude from the assignment.
385
+ * @param {string} [path] - The current path, used to rescope exclude for the next level.
342
386
  * @returns {boolean} True if any keys were added, updated, or removed, false otherwise.
343
387
  * @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
344
388
  */
345
389
  function assignReactiveObjectRecursive(target, source, exclude, path = "") {
346
390
  let { addedKeys, sameKeys, removedKeys } = keyDiff(Object.keys(source) || [], Object.keys(target) || []);
347
- let didAnything = false;
348
- didAnything |= trimReactiveObject(target, source, exclude, removedKeys);
349
- didAnything |= recursiveInner(target, source, exclude, addedKeys, sameKeys, path, assignReactiveObjectRecursive);
350
- return didAnything;
391
+ const wasTrimmed = trimReactiveObject(target, source, exclude, removedKeys);
392
+ const recursiveDidAnything = recursiveInner(
393
+ target,
394
+ source,
395
+ exclude,
396
+ addedKeys,
397
+ sameKeys,
398
+ path,
399
+ assignReactiveObjectRecursive
400
+ );
401
+ return wasTrimmed || recursiveDidAnything;
351
402
  }
352
403
 
353
404
  /**
354
405
  * Recursively change a target to match a source, where keys missing from the source are removed from the target,
355
406
  * keys present in the source are added to the target, and keys present in both are updated in the target.
356
- * @function assignReactiveObjectDeep
357
- * @param {ValidTargetOrSource} target The object receiving updates.
358
- * @param {ValidTargetOrSource} source The object providing updates.
359
- * @param {Array} [exclude] Keys to exclude from the assignment.
407
+ *
408
+ * @param {ValidTargetOrSource} target - The object receiving updates.
409
+ * @param {ValidTargetOrSource} source - The object providing updates.
410
+ * @param {Array} [exclude] - Keys to exclude from the assignment.
360
411
  * @returns {boolean} True if any keys were added, updated, or removed, false otherwise.
361
412
  * @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
362
413
  */
@@ -374,26 +425,26 @@ export function assignReactiveObjectDeep(target, source, exclude) {
374
425
  * keys present in both are updated in the target. Missing keys are not removed.
375
426
  *
376
427
  * As an internal function, this function does not validate its argument.
428
+ *
377
429
  * @private
378
- * @param {ValidTargetOrSource} target The object receiving updates.
379
- * @param {ValidTargetOrSource} source The object providing updates.
380
- * @param {Array} [exclude] Keys to exclude from the update.
381
- * @param {string} [path] The current path, used to rescope exclude for the next level.
430
+ * @param {ValidTargetOrSource} target - The object receiving updates.
431
+ * @param {ValidTargetOrSource} source - The object providing updates.
432
+ * @param {Array} [exclude] - Keys to exclude from the update.
433
+ * @param {string} [path] - The current path, used to rescope exclude for the next level.
382
434
  * @returns {boolean} True if any keys were added or updated, false otherwise.
383
435
  */
384
436
  function addOrUpdateReactiveObjectRecursive(target, source, exclude, path = "") {
385
- let addedKeys,
386
- sameKeys = keyDiff(Object.keys(source) || [], Object.keys(target) || []);
437
+ let { addedKeys, sameKeys } = keyDiff(Object.keys(source) || [], Object.keys(target) || []);
387
438
  return recursiveInner(target, source, exclude, addedKeys, sameKeys, path, addOrUpdateReactiveObjectRecursive);
388
439
  }
389
440
 
390
441
  /**
391
442
  * Recursively change a target to match a source, where keys present in the source are added to the target, and
392
443
  * keys present in both are updated in the target. Missing keys are not removed.
393
- * @function addOrUpdateReactiveObjectDeep
394
- * @param {ValidTargetOrSource} target The object receiving updates.
395
- * @param {ValidTargetOrSource} source The object providing updates.
396
- * @param {Array} [exclude] Keys to exclude from the update.
444
+ *
445
+ * @param {ValidTargetOrSource} target - The object receiving updates.
446
+ * @param {ValidTargetOrSource} source - The object providing updates.
447
+ * @param {Array} [exclude] - Keys to exclude from the update.
397
448
  * @returns {boolean} True if any keys were added or updated, false otherwise.
398
449
  * @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
399
450
  */