@arrai-innovations/reactive-helpers 2.7.0 → 3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arrai-innovations/reactive-helpers",
3
- "version": "2.7.0",
3
+ "version": "3.0.0",
4
4
  "description": "VueJS 3 utility composition functions to help manipulate objects and lists.",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -601,7 +601,7 @@ describe("use/objectSubscription.js", function () {
601
601
  },
602
602
  });
603
603
  objectSubscription.objectInstance.state.crud.retrieve = customCrudRetrieve;
604
- objectSubscription.state.crud.subscribe = customCrudSubscribe;
604
+ objectSubscription.objectInstance.state.crud.subscribe = customCrudSubscribe;
605
605
 
606
606
  const subscribePromise = objectSubscription.subscribe();
607
607
  crudRetrieveResolve(crudRetrieveResolved);
package/use/index.js CHANGED
@@ -4,6 +4,7 @@ export * from "./listInstance.js";
4
4
  export * from "./listRelated.js";
5
5
  export * from "./listSort.js";
6
6
  export * from "./listSubscription.js";
7
+ export * from "./object.js";
7
8
  export * from "./objectCalculated.js";
8
9
  export * from "./objectInstance.js";
9
10
  export * from "./objectRelated.js";
@@ -1,7 +1,7 @@
1
1
  import inspect from "browser-util-inspect";
2
2
  import { cloneDeep } from "lodash";
3
3
  import { computed, effectScope, reactive } from "vue";
4
- import { assignReactiveObject } from "../utils/assignReactiveObject";
4
+ import { addOrUpdateReactiveObject, assignReactiveObject } from "../utils/assignReactiveObject";
5
5
  import { getFakeId } from "../utils/getFakeId";
6
6
 
7
7
  export class ListError extends Error {
@@ -80,7 +80,7 @@ export function useListInstance({ crudArgs, listArgs = {}, retrieveArgs = {} })
80
80
  // prevent linking of all instances to the same default .args object
81
81
  Object.assign(state.crud, cloneDeep(defaultCrud));
82
82
  if (crudArgs) {
83
- assignReactiveObject(state.crud.args, crudArgs);
83
+ addOrUpdateReactiveObject(state.crud.args, crudArgs);
84
84
  }
85
85
 
86
86
  const defaultPageCallback = (newObjects) => {
@@ -1,10 +1,9 @@
1
- import { computed, effectScope, reactive, toRef } from "vue";
2
- import { useListInstance } from "./listInstance";
3
- import { cloneDeep, isEmpty, isObject } from "lodash";
4
- import { assignReactiveObject } from "../utils/assignReactiveObject";
5
1
  import inspect from "browser-util-inspect";
2
+ import { cloneDeep, isEmpty, isObject } from "lodash";
3
+ import { computed, effectScope, reactive, toRef } from "vue";
6
4
  import { useCancellableIntent } from "../utils/cancellableIntent";
7
5
  import { loadingCombine } from "../utils/loadingCombine";
6
+ import { useListInstance } from "./listInstance";
8
7
 
9
8
  export class ListSubscriptionError extends Error {
10
9
  constructor(message) {
@@ -14,13 +13,11 @@ export class ListSubscriptionError extends Error {
14
13
  }
15
14
 
16
15
  const defaultCrud = {
17
- args: {},
18
16
  subscribe: undefined,
19
17
  };
20
18
 
21
- export function setListSubscriptionCrud({ subscribe, args = {} }) {
19
+ export function setListSubscriptionCrud({ subscribe }) {
22
20
  defaultCrud.subscribe = subscribe;
23
- Object.assign(defaultCrud.args, args);
24
21
  }
25
22
 
26
23
  export function useListSubscriptions(args, listInstances = {}) {
@@ -35,23 +32,18 @@ export function useListSubscription({ listInstance, crudArgs, listArgs, retrieve
35
32
  if (!listInstance) {
36
33
  listInstance = useListInstance({ crudArgs, listArgs, retrieveArgs });
37
34
  }
35
+ if (!listInstance.state.crud.subscribe) {
36
+ listInstance.state.crud.subscribe = defaultCrud.subscribe;
37
+ }
38
+
38
39
  let subscribeIntent, listIntent;
39
40
  const state = reactive({
40
- crud: {
41
- args: {},
42
- subscribe: undefined,
43
- },
44
41
  subscriptionLoading: undefined,
45
42
  subscriptionErrored: false,
46
43
  subscriptionError: null,
47
44
  intendToList: false,
48
45
  intendToSubscribe: false,
49
46
  });
50
- // prevent linking of all instances to the same default .args object
51
- Object.assign(state.crud, cloneDeep(defaultCrud));
52
- if (crudArgs) {
53
- assignReactiveObject(state.crud.args, crudArgs);
54
- }
55
47
 
56
48
  function publicSubscribe({ list = true } = {}) {
57
49
  let didSubscribe = false;
@@ -160,8 +152,8 @@ export function useListSubscription({ listInstance, crudArgs, listArgs, retrieve
160
152
  subscribeIntent = useCancellableIntent({
161
153
  awaitableWithCancel: () => {
162
154
  // this function cannot be async, or the resulting promise will lose its .cancel() method
163
- const subscribePromise = state.crud.subscribe({
164
- crudArgs: cloneDeep(state.crud.args),
155
+ const subscribePromise = listInstance.state.crud.subscribe({
156
+ crudArgs: cloneDeep(listInstance.state.crud.args),
165
157
  listArgs: cloneDeep(listInstance.state.listArgs),
166
158
  retrieveArgs: cloneDeep(listInstance.state.retrieveArgs),
167
159
  subscriptionEventCallback,
@@ -175,7 +167,11 @@ export function useListSubscription({ listInstance, crudArgs, listArgs, retrieve
175
167
  catchPromise.cancel = subscribePromise.cancel;
176
168
  return catchPromise;
177
169
  },
178
- watchArguments: [toRef(state, "intendToSubscribe"), toRef(state, "listArgs"), toRef(state, "retrieveArgs")],
170
+ watchArguments: [
171
+ toRef(state, "intendToSubscribe"),
172
+ toRef(listInstance.state, "listArgs"),
173
+ toRef(state, "retrieveArgs"),
174
+ ],
179
175
  clearActiveOnResolved: false,
180
176
  });
181
177
 
@@ -186,7 +182,12 @@ export function useListSubscription({ listInstance, crudArgs, listArgs, retrieve
186
182
  listInstance.clearList();
187
183
  return listInstance.list();
188
184
  },
189
- watchArguments: [toRef(state, "intendToList"), toRef(state, "listArgs"), toRef(state, "retrieveArgs")],
185
+ watchArguments: [
186
+ toRef(state, "intendToList"),
187
+ toRef(listInstance.state, "listArgs"),
188
+ toRef(state, "retrieveArgs"),
189
+ ],
190
+ nameOnLog: "listIntent",
190
191
  });
191
192
  });
192
193
 
package/use/object.js ADDED
@@ -0,0 +1,99 @@
1
+ import { effectScope, shallowReactive, shallowReadonly, toRef, watch } from "vue";
2
+ import { useObjectCalculated } from "./objectCalculated";
3
+ import { useObjectInstance } from "./objectInstance";
4
+ import { useObjectRelated } from "./objectRelated";
5
+ import { useObjectSubscription } from "./objectSubscription";
6
+
7
+ // Manages a chain of useObject functions, based on existence of keys in props: intendToRetrieve, relatedObjectRules, calculatedObjectRules
8
+ export const useObject = ({ props }) => {
9
+ const managed = shallowReactive({
10
+ objectInstance: null,
11
+ objectSubscription: null,
12
+ objectRelated: null,
13
+ objectCalculated: null,
14
+ });
15
+ const es = effectScope();
16
+
17
+ managed.objectInstance = useObjectInstance({
18
+ crudArgs: toRef(props, "crudArgs"),
19
+ id: toRef(props, "id"),
20
+ retrieveArgs: toRef(props, "retrieveArgs"),
21
+ });
22
+
23
+ const intentPropsWatch = () => {
24
+ es.run(() => {
25
+ let nextState = managed.objectInstance?.state;
26
+ // true or false, having a key is intent to use
27
+ const hasIntendToRetrieve = "intendToRetrieve" in props;
28
+ if (hasIntendToRetrieve && !managed.objectSubscription) {
29
+ managed.objectSubscription = useObjectSubscription({
30
+ objectInstance: managed.objectInstance,
31
+ });
32
+ managed.objectSubscription.state.intendToRetrieve = toRef(props, "intendToRetrieve");
33
+ } else if (!hasIntendToRetrieve && managed.objectSubscription) {
34
+ managed.objectSubscription.effectScope.stop();
35
+ managed.objectSubscription = null;
36
+ }
37
+ const hasRelatedObjectRules = "relatedObjectRules" in props;
38
+ if (hasRelatedObjectRules && !managed.objectRelated) {
39
+ nextState = managed.objectSubscription?.state || nextState;
40
+ managed.objectRelated = useObjectRelated({
41
+ parentState: nextState,
42
+ relatedObjectRules: toRef(props, "relatedObjectRules"),
43
+ });
44
+ } else if (!hasRelatedObjectRules && managed.objectRelated) {
45
+ managed.objectRelated.effectScope.stop();
46
+ managed.objectRelated = null;
47
+ }
48
+ const hasCalculatedObjectRules = "calculatedObjectRules" in props;
49
+ if (hasCalculatedObjectRules && !managed.objectCalculated) {
50
+ nextState = managed.objectRelated?.state || nextState;
51
+ managed.objectCalculated = useObjectCalculated({
52
+ parentState: nextState,
53
+ calculatedObjectRules: toRef(props, "calculatedObjectRules"),
54
+ });
55
+ } else if (!hasCalculatedObjectRules && managed.objectCalculated) {
56
+ managed.objectCalculated.effectScope.stop();
57
+ managed.objectCalculated = null;
58
+ }
59
+ });
60
+ };
61
+
62
+ let exposedState;
63
+
64
+ es.run(() => {
65
+ watch(
66
+ [
67
+ toRef(props, "intendToRetrieve"),
68
+ toRef(props, "relatedObjectRules"),
69
+ toRef(props, "calculatedObjectRules"),
70
+ ],
71
+ intentPropsWatch,
72
+ { immediate: true }
73
+ );
74
+ const getState = () =>
75
+ managed.objectCalculated?.state ||
76
+ managed.objectRelated?.state ||
77
+ managed.objectSubscription?.state ||
78
+ managed.objectInstance.state;
79
+ // used as proxy to have the properties and not be settable, so we only have to override get
80
+ const proxyBase = shallowReadonly({
81
+ loading: null,
82
+ error: null,
83
+ errored: null,
84
+ object: null,
85
+ });
86
+ exposedState = new Proxy(proxyBase, {
87
+ // get values from the current state
88
+ get(target, key) {
89
+ return Reflect.get(getState(), key);
90
+ },
91
+ });
92
+ });
93
+
94
+ return {
95
+ managed: shallowReadonly(managed),
96
+ state: exposedState,
97
+ effectScope: es,
98
+ };
99
+ };
@@ -67,12 +67,19 @@ export function useObjectCalculated({
67
67
  state.deleted = toRef(parentState, "deleted");
68
68
 
69
69
  watch(
70
- [() => Object.keys(state.calculatedObjectRules)],
70
+ [() => state.calculatedObjectRules && Object.keys(state.calculatedObjectRules)],
71
71
  () => {
72
- const { addedKeys, removedKeys, sameKeys } = keyDiff(
73
- Object.keys(state.calculatedObjectRules),
74
- Object.keys(calculatedObjectOriginalFunctions)
75
- );
72
+ let addedKeys = [],
73
+ removedKeys = [],
74
+ sameKeys = [];
75
+ if (!state.calculatedObjectRules) {
76
+ removedKeys = Object.keys(calculatedObjectOriginalFunctions);
77
+ } else {
78
+ ({ addedKeys, removedKeys, sameKeys } = keyDiff(
79
+ Object.keys(state.calculatedObjectRules),
80
+ Object.keys(calculatedObjectOriginalFunctions)
81
+ ));
82
+ }
76
83
  for (const sameKey of sameKeys) {
77
84
  if (calculatedObjectOriginalFunctions[sameKey] !== state.calculatedObjectRules[sameKey]) {
78
85
  removedKeys.push(sameKey);
@@ -1,6 +1,6 @@
1
1
  import { cloneDeep } from "lodash";
2
2
  import { effectScope, reactive } from "vue";
3
- import { assignReactiveObject } from "../utils/assignReactiveObject";
3
+ import { addOrUpdateReactiveObject, assignReactiveObject } from "../utils/assignReactiveObject";
4
4
 
5
5
  export class ObjectError extends Error {
6
6
  constructor(message) {
@@ -56,7 +56,7 @@ export function useObjectInstance({ crudArgs, id, retrieveArgs }) {
56
56
  // prevent linking of all instances to the same default .args object
57
57
  Object.assign(state.crud, cloneDeep(defaultCrud));
58
58
  if (crudArgs) {
59
- assignReactiveObject(state.crud.args, crudArgs);
59
+ addOrUpdateReactiveObject(state.crud.args, crudArgs);
60
60
  }
61
61
 
62
62
  // due to retrieve being called by `useCancelleableIntent`, if called manually then by the watch,
@@ -67,12 +67,19 @@ export function useObjectRelated({
67
67
  state.deleted = toRef(parentState, "deleted");
68
68
 
69
69
  watch(
70
- [() => Object.keys(state.relatedObjectRules)],
70
+ [() => state.relatedObjectRules && Object.keys(state.relatedObjectRules)],
71
71
  () => {
72
- const { addedKeys: addedRuleKeys, removedKeys: removedRuleKeys } = keyDiff(
73
- Object.keys(state.relatedObjectRules),
74
- Object.keys(state.relatedObjectObjects)
75
- );
72
+ let addedRuleKeys = [],
73
+ removedRuleKeys = [];
74
+ if (!state.relatedObjectRules) {
75
+ removedRuleKeys = Object.keys(state.relatedObjectObjects);
76
+ } else {
77
+ ({ addedKeys: addedRuleKeys, removedKeys: removedRuleKeys } = keyDiff(
78
+ Object.keys(state.relatedObjectRules),
79
+ Object.keys(state.relatedObjectObjects)
80
+ ));
81
+ }
82
+
76
83
  for (const removedRuleKey of removedRuleKeys) {
77
84
  delete state.relatedObjectObjects[removedRuleKey];
78
85
  if (relatedObjectEffectScopes[removedRuleKey]) {
@@ -1,9 +1,8 @@
1
- import { cloneDeep } from "lodash";
2
1
  import { computed, effectScope, reactive, toRef } from "vue";
3
2
  import { assignReactiveObject } from "../utils/assignReactiveObject";
4
- import { useObjectInstance } from "./objectInstance";
5
3
  import { useCancellableIntent } from "../utils/cancellableIntent";
6
4
  import { loadingCombine } from "../utils/loadingCombine";
5
+ import { useObjectInstance } from "./objectInstance";
7
6
 
8
7
  export class ObjectSubscriptionError extends Error {
9
8
  constructor(message) {
@@ -13,13 +12,11 @@ export class ObjectSubscriptionError extends Error {
13
12
  }
14
13
 
15
14
  const defaultCrud = {
16
- args: {},
17
15
  subscribe: undefined,
18
16
  };
19
17
 
20
- export function setObjectSubscriptionCrud({ subscribe, args = {} }) {
18
+ export function setObjectSubscriptionCrud({ subscribe }) {
21
19
  defaultCrud.subscribe = subscribe;
22
- Object.assign(defaultCrud.args, args);
23
20
  }
24
21
 
25
22
  export function useObjectSubscriptions(subscriptionArgs) {
@@ -30,7 +27,7 @@ export function useObjectSubscriptions(subscriptionArgs) {
30
27
  return subscriptions;
31
28
  }
32
29
 
33
- export function useObjectSubscription({ objectInstance, crudArgs, id, retrieveArgs = {} }) {
30
+ export function useObjectSubscription({ objectInstance, crudArgs, id, retrieveArgs }) {
34
31
  if (retrieveArgs && objectInstance) {
35
32
  throw new ObjectSubscriptionError(
36
33
  "Cannot use retrieveArgs and objectInstance together, set retrieveArgs on objectInstance instead"
@@ -39,12 +36,10 @@ export function useObjectSubscription({ objectInstance, crudArgs, id, retrieveAr
39
36
  if (!objectInstance) {
40
37
  objectInstance = useObjectInstance({ crudArgs, id, retrieveArgs });
41
38
  }
39
+ if (!objectInstance.state.crud.subscribe) {
40
+ objectInstance.state.crud.subscribe = defaultCrud.subscribe;
41
+ }
42
42
  const state = reactive({
43
- crud: {
44
- args: {},
45
- subscribe: undefined,
46
- },
47
- id,
48
43
  subscriptionLoading: undefined,
49
44
  subscriptionErrored: false,
50
45
  subscriptionError: null,
@@ -52,11 +47,6 @@ export function useObjectSubscription({ objectInstance, crudArgs, id, retrieveAr
52
47
  intendToSubscribe: false,
53
48
  intendToRetrieve: false,
54
49
  });
55
- // prevent linking of all instances to the same default .args object
56
- Object.assign(state.crud, cloneDeep(defaultCrud));
57
- if (crudArgs) {
58
- assignReactiveObject(state.crud.args, crudArgs);
59
- }
60
50
 
61
51
  let subscribeIntent, retrieveIntent;
62
52
 
@@ -93,9 +83,9 @@ export function useObjectSubscription({ objectInstance, crudArgs, id, retrieveAr
93
83
  state.subscriptionErrored = false;
94
84
  state.subscriptionError = null;
95
85
  let subscribePromise;
96
- subscribePromise = state.crud.subscribe({
97
- crudArgs: state.crud.args,
98
- id,
86
+ subscribePromise = objectInstance.state.crud.subscribe({
87
+ crudArgs: objectInstance.state.crud.args,
88
+ id: objectInstance.state.id,
99
89
  retrieveArgs: state.retrieveArgs,
100
90
  callback: (data, action) => {
101
91
  if (action === "delete") {
@@ -166,13 +156,22 @@ export function useObjectSubscription({ objectInstance, crudArgs, id, retrieveAr
166
156
 
167
157
  subscribeIntent = useCancellableIntent({
168
158
  awaitableWithCancel: subscribe,
169
- watchArguments: [toRef(state, "intendToSubscribe"), toRef(state, "id"), toRef(state, "retrieveArgs")],
159
+ watchArguments: [
160
+ toRef(state, "intendToSubscribe"),
161
+ toRef(objectInstance.state, "id"),
162
+ toRef(state, "retrieveArgs"),
163
+ ],
170
164
  clearActiveOnResolved: false,
171
165
  });
172
166
 
173
167
  retrieveIntent = useCancellableIntent({
174
168
  awaitableWithCancel: objectInstance.retrieve,
175
- watchArguments: [toRef(state, "intendToRetrieve"), toRef(state, "id"), toRef(state, "retrieveArgs")],
169
+ watchArguments: [
170
+ toRef(state, "intendToRetrieve"),
171
+ toRef(objectInstance.state, "id"),
172
+ toRef(state, "retrieveArgs"),
173
+ ],
174
+ nameForLog: "retrieveIntent",
176
175
  });
177
176
  });
178
177
 
@@ -1,8 +1,8 @@
1
+ import inspect from "browser-util-inspect";
2
+ import { isArray, isObject } from "lodash";
3
+ import { isReactive, isRef, toRef, unref } from "vue";
1
4
  import { keyDiff } from "./keyDiff.js";
2
5
  import { union } from "./set.js";
3
- import { isReactive, toRef } from "vue";
4
- import { isArray, isObject } from "lodash";
5
- import inspect from "browser-util-inspect";
6
6
 
7
7
  export class AssignReactiveObjectError extends Error {
8
8
  constructor(message) {
@@ -20,6 +20,16 @@ function isArrayOrObject(key, value) {
20
20
  export function addOrUpdateReactiveObject(target, source, exclude = [], addedKeys = null, sameKeys = null) {
21
21
  isArrayOrObject("target", target);
22
22
  isArrayOrObject("source", source);
23
+ if (isRef(target)) {
24
+ const unrefedTarget = unref(target);
25
+ isArrayOrObject("unrefedTarget", unrefedTarget);
26
+ target = unrefedTarget;
27
+ }
28
+ if (isRef(source)) {
29
+ const unrefedSource = unref(source);
30
+ isArrayOrObject("unrefedSource", unrefedSource);
31
+ source = unrefedSource;
32
+ }
23
33
  if (!addedKeys && !sameKeys) {
24
34
  ({ addedKeys, sameKeys } = keyDiff(Object.keys(source) || [], Object.keys(target) || []));
25
35
  }
@@ -42,6 +52,16 @@ export function assignReactiveObject(target, source, exclude = []) {
42
52
  }
43
53
  isArrayOrObject("target", target);
44
54
  isArrayOrObject("source", source);
55
+ if (isRef(target)) {
56
+ const unrefedTarget = unref(target);
57
+ isArrayOrObject("unrefedTarget", unrefedTarget);
58
+ target = unrefedTarget;
59
+ }
60
+ if (isRef(source)) {
61
+ const unrefedSource = unref(source);
62
+ isArrayOrObject("unrefedSource", unrefedSource);
63
+ source = unrefedSource;
64
+ }
45
65
  const targetIsArray = isArray(target);
46
66
  const { addedKeys, sameKeys, removedKeys } = keyDiff(Object.keys(source) || [], Object.keys(target) || []);
47
67
  if (targetIsArray) {