@arrai-innovations/reactive-helpers 2.6.1 → 2.8.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.6.1",
3
+ "version": "2.8.0",
4
4
  "description": "VueJS 3 utility composition functions to help manipulate objects and lists.",
5
5
  "main": "index.js",
6
6
  "directories": {
package/use/index.js CHANGED
@@ -4,8 +4,10 @@ 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";
10
+ export * from "./objectRelated.js";
9
11
  export * from "./objectSubscription.js";
10
12
  export * from "./paginatedListInstance.js";
11
13
  export * from "./search.js";
@@ -93,6 +93,9 @@ export function useListCalculated({
93
93
  });
94
94
  }
95
95
  });
96
+ if (calculatedObjectsEffectScopes[objectKey]) {
97
+ calculatedObjectsEffectScopes[objectKey].stop();
98
+ }
96
99
  calculatedObjectsEffectScopes[objectKey] = calculatedObjectsEffectScope;
97
100
  }
98
101
  parentStateObjectsWatch();
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
+ };
@@ -18,7 +18,7 @@ export function useObjectCalculated({
18
18
  }) {
19
19
  const state = reactive({
20
20
  calculatedObjectRules,
21
- calculatedObjectObject: {},
21
+ calculatedObjectObjects: {},
22
22
  object: {},
23
23
  });
24
24
  const calculatedObjectEffectScopes = {};
@@ -33,7 +33,7 @@ export function useObjectCalculated({
33
33
  state.object = new Proxy(parentState.object, {
34
34
  get(target, key, receiver) {
35
35
  if (key === copn) {
36
- return state.calculatedObjectObject;
36
+ return state.calculatedObjectObjects;
37
37
  }
38
38
  return Reflect.get(target, key, receiver);
39
39
  },
@@ -51,7 +51,7 @@ export function useObjectCalculated({
51
51
  return {
52
52
  configurable: true,
53
53
  enumerable: true,
54
- value: state.calculatedObjectObject,
54
+ value: state.calculatedObjectObjects,
55
55
  writable: true,
56
56
  };
57
57
  }
@@ -73,9 +73,15 @@ export function useObjectCalculated({
73
73
  Object.keys(state.calculatedObjectRules),
74
74
  Object.keys(calculatedObjectOriginalFunctions)
75
75
  );
76
+ for (const sameKey of sameKeys) {
77
+ if (calculatedObjectOriginalFunctions[sameKey] !== state.calculatedObjectRules[sameKey]) {
78
+ removedKeys.push(sameKey);
79
+ addedKeys.push(sameKey);
80
+ }
81
+ }
76
82
  for (const removedKey of removedKeys) {
77
83
  delete calculatedObjectOriginalFunctions[removedKey];
78
- delete state.calculatedObjectObject[removedKey];
84
+ delete state.calculatedObjectObjects[removedKey];
79
85
  if (calculatedObjectEffectScopes[removedKey]) {
80
86
  calculatedObjectEffectScopes[removedKey].stop();
81
87
  delete calculatedObjectEffectScopes[removedKey];
@@ -85,21 +91,11 @@ export function useObjectCalculated({
85
91
  calculatedObjectOriginalFunctions[addedKey] = state.calculatedObjectRules[addedKey];
86
92
  calculatedObjectEffectScopes[addedKey] = effectScope();
87
93
  calculatedObjectEffectScopes[addedKey].run(() => {
88
- state.calculatedObjectObject[addedKey] = computed(() =>
94
+ state.calculatedObjectObjects[addedKey] = computed(() =>
89
95
  calculatedObjectOriginalFunctions[addedKey](state.object)
90
96
  );
91
97
  });
92
98
  }
93
- for (const sameKey of sameKeys) {
94
- calculatedObjectOriginalFunctions[sameKey] = state.calculatedObjectRules[sameKey];
95
- calculatedObjectEffectScopes[sameKey].stop();
96
- calculatedObjectEffectScopes[sameKey] = effectScope();
97
- calculatedObjectEffectScopes[sameKey].run(() => {
98
- state.calculatedObjectObject[sameKey] = computed(() =>
99
- calculatedObjectOriginalFunctions[sameKey](state.object)
100
- );
101
- });
102
- }
103
99
  },
104
100
  {
105
101
  immediate: true,
@@ -1,5 +1,5 @@
1
1
  import { cloneDeep } from "lodash";
2
- import { effectScope, reactive } from "vue";
2
+ import { effectScope, reactive, unref } from "vue";
3
3
  import { assignReactiveObject } from "../utils/assignReactiveObject";
4
4
 
5
5
  export class ObjectError extends Error {
@@ -56,7 +56,9 @@ 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
+ // generally you won't have a ref to an object, but indirectly, you could have a ref to a reactive.
60
+ // either way, we want a reactive or plain object. computed isn't going to work for this.
61
+ assignReactiveObject(state.crud.args, unref(crudArgs));
60
62
  }
61
63
 
62
64
  // due to retrieve being called by `useCancelleableIntent`, if called manually then by the watch,
@@ -0,0 +1,121 @@
1
+ import { get, isArray, isUndefined } from "lodash";
2
+ import { computed, effectScope, onScopeDispose, reactive, toRef, unref, watch } from "vue";
3
+ import { keyDiff } from "../utils";
4
+
5
+ export function useObjectRelateds(instances, args) {
6
+ for (const [key, value] of Object.entries(args)) {
7
+ useObjectRelated({
8
+ parentState: instances[key].state,
9
+ ...value,
10
+ });
11
+ }
12
+ }
13
+
14
+ // the single object version of useListRelated
15
+ export function useObjectRelated({
16
+ parentState,
17
+ relatedObjectRules,
18
+ relatedObjectPropertyName = "relatedObject", // NOT REACTIVE
19
+ }) {
20
+ const state = reactive({
21
+ relatedObjectRules,
22
+ relatedObjectObjects: {},
23
+ object: {},
24
+ });
25
+ const relatedObjectEffectScopes = {};
26
+
27
+ // don't change relatedObjectPropertyName on us or it will break
28
+ const ropn = relatedObjectPropertyName + "";
29
+
30
+ const es = effectScope();
31
+
32
+ es.run(() => {
33
+ state.object = new Proxy(parentState.object, {
34
+ get(target, key, receiver) {
35
+ if (key === ropn) {
36
+ return state.relatedObjectObjects;
37
+ }
38
+ return Reflect.get(target, key, receiver);
39
+ },
40
+ ownKeys(target) {
41
+ return Reflect.ownKeys(target).concat(ropn);
42
+ },
43
+ has(target, key) {
44
+ if (key === ropn) {
45
+ return true;
46
+ }
47
+ return Reflect.has(target, key);
48
+ },
49
+ getOwnPropertyDescriptor(target, key) {
50
+ if (key === ropn) {
51
+ return {
52
+ configurable: true,
53
+ enumerable: true,
54
+ value: state.relatedObjectObjects,
55
+ writable: true,
56
+ };
57
+ }
58
+ return Reflect.getOwnPropertyDescriptor(target, key);
59
+ },
60
+ defineProperty() {
61
+ return false;
62
+ },
63
+ });
64
+ state.loading = toRef(parentState, "loading");
65
+ state.error = toRef(parentState, "error");
66
+ state.errored = toRef(parentState, "errored");
67
+ state.deleted = toRef(parentState, "deleted");
68
+
69
+ watch(
70
+ [() => Object.keys(state.relatedObjectRules)],
71
+ () => {
72
+ const { addedKeys: addedRuleKeys, removedKeys: removedRuleKeys } = keyDiff(
73
+ Object.keys(state.relatedObjectRules),
74
+ Object.keys(state.relatedObjectObjects)
75
+ );
76
+ for (const removedRuleKey of removedRuleKeys) {
77
+ delete state.relatedObjectObjects[removedRuleKey];
78
+ if (relatedObjectEffectScopes[removedRuleKey]) {
79
+ relatedObjectEffectScopes[removedRuleKey].stop();
80
+ delete relatedObjectEffectScopes[removedRuleKey];
81
+ }
82
+ }
83
+ for (const addedRuleKey of addedRuleKeys) {
84
+ relatedObjectEffectScopes[addedRuleKey] = effectScope();
85
+ relatedObjectEffectScopes[addedRuleKey].run(() => {
86
+ state.relatedObjectObjects[addedRuleKey] = computed(() => {
87
+ // deal with computed objects being passed.
88
+ const ruleObjects = unref(state.relatedObjectRules?.[addedRuleKey]?.objects);
89
+ const rulePkKey = state.relatedObjectRules?.[addedRuleKey]?.pkKey || addedRuleKey;
90
+ if (!ruleObjects || !rulePkKey) {
91
+ return undefined;
92
+ }
93
+ const value = get(parentState.object, rulePkKey);
94
+ if (isUndefined(value)) {
95
+ return undefined;
96
+ }
97
+ if (isArray(value)) {
98
+ return value.map((e) => ruleObjects[e]);
99
+ }
100
+ return ruleObjects[value];
101
+ });
102
+ });
103
+ }
104
+ },
105
+ {
106
+ immediate: true,
107
+ }
108
+ );
109
+
110
+ onScopeDispose(() => {
111
+ for (const key in relatedObjectEffectScopes) {
112
+ relatedObjectEffectScopes[key].stop();
113
+ }
114
+ });
115
+ });
116
+ return {
117
+ state,
118
+ parentState,
119
+ effectScope: es,
120
+ };
121
+ }
@@ -1,9 +1,9 @@
1
1
  import { cloneDeep } from "lodash";
2
2
  import { computed, effectScope, reactive, toRef } from "vue";
3
3
  import { assignReactiveObject } from "../utils/assignReactiveObject";
4
- import { useObjectInstance } from "./objectInstance";
5
4
  import { useCancellableIntent } from "../utils/cancellableIntent";
6
5
  import { loadingCombine } from "../utils/loadingCombine";
6
+ import { useObjectInstance } from "./objectInstance";
7
7
 
8
8
  export class ObjectSubscriptionError extends Error {
9
9
  constructor(message) {
@@ -30,7 +30,7 @@ export function useObjectSubscriptions(subscriptionArgs) {
30
30
  return subscriptions;
31
31
  }
32
32
 
33
- export function useObjectSubscription({ objectInstance, crudArgs, id, retrieveArgs = {} }) {
33
+ export function useObjectSubscription({ objectInstance, crudArgs, id, retrieveArgs }) {
34
34
  if (retrieveArgs && objectInstance) {
35
35
  throw new ObjectSubscriptionError(
36
36
  "Cannot use retrieveArgs and objectInstance together, set retrieveArgs on objectInstance instead"
@@ -44,7 +44,6 @@ export function useObjectSubscription({ objectInstance, crudArgs, id, retrieveAr
44
44
  args: {},
45
45
  subscribe: undefined,
46
46
  },
47
- id,
48
47
  subscriptionLoading: undefined,
49
48
  subscriptionErrored: false,
50
49
  subscriptionError: null,
@@ -95,7 +94,7 @@ export function useObjectSubscription({ objectInstance, crudArgs, id, retrieveAr
95
94
  let subscribePromise;
96
95
  subscribePromise = state.crud.subscribe({
97
96
  crudArgs: state.crud.args,
98
- id,
97
+ id: objectInstance.state.id,
99
98
  retrieveArgs: state.retrieveArgs,
100
99
  callback: (data, action) => {
101
100
  if (action === "delete") {
@@ -166,13 +165,22 @@ export function useObjectSubscription({ objectInstance, crudArgs, id, retrieveAr
166
165
 
167
166
  subscribeIntent = useCancellableIntent({
168
167
  awaitableWithCancel: subscribe,
169
- watchArguments: [toRef(state, "intendToSubscribe"), toRef(state, "id"), toRef(state, "retrieveArgs")],
168
+ watchArguments: [
169
+ toRef(state, "intendToSubscribe"),
170
+ toRef(objectInstance.state, "id"),
171
+ toRef(state, "retrieveArgs"),
172
+ ],
170
173
  clearActiveOnResolved: false,
171
174
  });
172
175
 
173
176
  retrieveIntent = useCancellableIntent({
174
177
  awaitableWithCancel: objectInstance.retrieve,
175
- watchArguments: [toRef(state, "intendToRetrieve"), toRef(state, "id"), toRef(state, "retrieveArgs")],
178
+ watchArguments: [
179
+ toRef(state, "intendToRetrieve"),
180
+ toRef(objectInstance.state, "id"),
181
+ toRef(state, "retrieveArgs"),
182
+ ],
183
+ nameForLog: "retrieveIntent",
176
184
  });
177
185
  });
178
186