@arrai-innovations/reactive-helpers 4.2.1 → 5.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": "4.2.1",
3
+ "version": "5.0.0",
4
4
  "description": "VueJS 3 utility composition functions to help manipulate objects and lists.",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -12,9 +12,7 @@
12
12
  "test": "jest",
13
13
  "coverage": "npm run coverage:clean; npm test -- --coverage=true",
14
14
  "coverage:clean": "rm -rf coverage",
15
- "_postinstall": "if [ \"$NODE_ENV\" != \"production\" ] && [ ./node_modules/.bin/is-ci ]; then ./node_modules/.bin/husky install; fi",
16
- "prepublishOnly": "pinst --disable",
17
- "postpublish": "pinst --enable"
15
+ "prepare": "husky install"
18
16
  },
19
17
  "repository": {
20
18
  "type": "git",
@@ -50,7 +48,6 @@
50
48
  "browser-util-inspect": "^0.2.0",
51
49
  "flexsearch": "^0.7.21",
52
50
  "husky": "^7.0.4",
53
- "is-ci": "^3.0.1",
54
51
  "jest": "^27.5.1",
55
52
  "pinst": "^3.0.0",
56
53
  "vue-deepunref": "^1.0.1"
package/use/list.js CHANGED
@@ -6,7 +6,7 @@ import { usePagedListInstance } from "./paginatedListInstance";
6
6
  import { effectScope, reactive, shallowReactive, shallowReadonly, toRef, watch } from "vue";
7
7
 
8
8
  // the big brother of useObject, managing a chain of useList* instances.
9
- export const useList = ({ props, functions, paged = false, passThroughPropertyNames = [] }) => {
9
+ export const useList = ({ props, functions, paged = false, keepOldPages = false, passThroughPropertyNames = [] }) => {
10
10
  const managed = shallowReactive({
11
11
  listInstance: null,
12
12
  listSubscription: null,
@@ -20,6 +20,7 @@ export const useList = ({ props, functions, paged = false, passThroughPropertyNa
20
20
  functions,
21
21
  retrieveArgs: toRef(props, "retrieveArgs"),
22
22
  listArgs: toRef(props, "listArgs"),
23
+ keepOldPages,
23
24
  });
24
25
 
25
26
  const intentPropsWatch = () => {
@@ -33,7 +33,7 @@ export function useListInstances(listInstanceArgs) {
33
33
 
34
34
  export function useListInstance({ crudArgs, listArgs = {}, retrieveArgs = {}, functions = {} }) {
35
35
  // ### touching the _objectsMap or _objectsProxy directly will not trigger reactivity ###
36
- const _objectsMap = new Map();
36
+ const _objectsMap = new Map(); // maps are ordered, if you don't clear lists, you need to insert pages in order.
37
37
  // ### touching the _objectsMap or _objectsProxy directly will not trigger reactivity ###
38
38
  const _objectsProxy = new Proxy(_objectsMap, {
39
39
  get(target, prop) {
package/use/object.js CHANGED
@@ -2,9 +2,9 @@ import { useObjectCalculated } from "./objectCalculated";
2
2
  import { useObjectInstance } from "./objectInstance";
3
3
  import { useObjectRelated } from "./objectRelated";
4
4
  import { useObjectSubscription } from "./objectSubscription";
5
- import { effectScope, reactive, shallowReactive, shallowReadonly, toRef, watch } from "vue";
5
+ import { reactive, shallowReactive, shallowReadonly, toRef } from "vue";
6
6
 
7
- // Manages a chain of useObject functions, based on existence of keys in props: intendToRetrieve, relatedObjectRules, calculatedObjectRules
7
+ // Manages a chain of useObject* functions
8
8
  export const useObject = ({ props, functions }) => {
9
9
  const managed = shallowReactive({
10
10
  objectInstance: null,
@@ -12,99 +12,28 @@ export const useObject = ({ props, functions }) => {
12
12
  objectRelated: null,
13
13
  objectCalculated: null,
14
14
  });
15
- const es = effectScope();
16
15
 
17
16
  managed.objectInstance = useObjectInstance({
18
- crudArgs: toRef(props, "crudArgs"),
19
- id: toRef(props, "id"),
20
- retrieveArgs: toRef(props, "retrieveArgs"),
17
+ props,
21
18
  functions,
22
19
  });
23
-
24
- const intentPropsWatch = () => {
25
- es.run(() => {
26
- let nextState = managed.objectInstance?.state;
27
- // true or false, having a key is intent to use
28
- const hasIntendToRetrieve = "intendToRetrieve" in props;
29
- if (hasIntendToRetrieve && !managed.objectSubscription) {
30
- managed.objectSubscription = useObjectSubscription({
31
- objectInstance: managed.objectInstance,
32
- });
33
- managed.objectSubscription.state.intendToRetrieve = toRef(props, "intendToRetrieve");
34
- } else if (!hasIntendToRetrieve && managed.objectSubscription) {
35
- managed.objectSubscription.effectScope.stop();
36
- managed.objectSubscription = null;
37
- }
38
- const hasRelatedObjectRules = "relatedObjectRules" in props;
39
- if (hasRelatedObjectRules && !managed.objectRelated) {
40
- nextState = managed.objectSubscription?.state || nextState;
41
- managed.objectRelated = useObjectRelated({
42
- parentState: nextState,
43
- relatedObjectRules: toRef(props, "relatedObjectRules"),
44
- });
45
- } else if (!hasRelatedObjectRules && managed.objectRelated) {
46
- managed.objectRelated.effectScope.stop();
47
- managed.objectRelated = null;
48
- }
49
- const hasCalculatedObjectRules = "calculatedObjectRules" in props;
50
- if (hasCalculatedObjectRules && !managed.objectCalculated) {
51
- nextState = managed.objectRelated?.state || nextState;
52
- managed.objectCalculated = useObjectCalculated({
53
- parentState: nextState,
54
- calculatedObjectRules: toRef(props, "calculatedObjectRules"),
55
- });
56
- } else if (!hasCalculatedObjectRules && managed.objectCalculated) {
57
- managed.objectCalculated.effectScope.stop();
58
- managed.objectCalculated = null;
59
- }
60
- });
61
- };
62
-
63
- const exposedState = reactive({});
64
-
65
- es.run(() => {
66
- watch(
67
- [
68
- toRef(props, "intendToRetrieve"),
69
- toRef(props, "relatedObjectRules"),
70
- toRef(props, "calculatedObjectRules"),
71
- ],
72
- intentPropsWatch,
73
- { immediate: true }
74
- );
75
-
76
- const propertiesToRelay = [
77
- "loading",
78
- "error",
79
- "errored",
80
- "object",
81
- "running",
82
- "relatedObject",
83
- "calculatedObject",
84
- ];
85
- watch(
86
- () =>
87
- managed.objectCalculated?.state ||
88
- managed.objectRelated?.state ||
89
- managed.objectSubscription?.state ||
90
- managed.objectInstance.state,
91
- (newState, oldState) => {
92
- if (newState !== oldState && newState) {
93
- propertiesToRelay.forEach((x) => {
94
- exposedState[x] = toRef(newState, x);
95
- });
96
- }
97
- },
98
- {
99
- immediate: true,
100
- }
101
- );
20
+ managed.objectSubscription = useObjectSubscription({
21
+ objectInstance: managed.objectInstance,
22
+ props: reactive({
23
+ intendToSubscribe: toRef(props, "intendToSubscribe"),
24
+ intendToRetrieve: toRef(props, "intendToRetrieve"),
25
+ }),
102
26
  });
103
-
104
- return {
105
- // we manage the keys on both of these, so hands off the root.
27
+ managed.objectRelated = useObjectRelated({
28
+ parentState: managed.objectSubscription.state,
29
+ relatedObjectRules: toRef(props, "relatedObjectRules"),
30
+ });
31
+ managed.objectCalculated = useObjectCalculated({
32
+ parentState: managed.objectRelated.state,
33
+ calculatedObjectRules: toRef(props, "calculatedObjectRules"),
34
+ });
35
+ return shallowReactive({
106
36
  managed: shallowReadonly(managed),
107
- state: shallowReadonly(exposedState),
108
- effectScope: es,
109
- };
37
+ state: reactive(managed.objectCalculated.state),
38
+ });
110
39
  };
@@ -17,7 +17,30 @@ export function useObjectCalculated({
17
17
  parentState,
18
18
  calculatedObjectRules,
19
19
  calculatedObjectPropertyName = "calculatedObject", // NOT REACTIVE
20
- passThroughPropertyNames = ["relatedObject"], // NOT REACTIVE
20
+ passThroughPropertyNames = [
21
+ // instance
22
+ "crud",
23
+ "deleted",
24
+ "error",
25
+ "errored",
26
+ "id",
27
+ "loading",
28
+ "object",
29
+ "retrieveArgs",
30
+ // subscription
31
+ "intendToRetrieve",
32
+ "intendToSubscribe",
33
+ "subscribed",
34
+ "subscriptionError",
35
+ "subscriptionErrored",
36
+ "subscriptionLoading",
37
+ // related
38
+ "relatedObject",
39
+ "relatedObjectObjects",
40
+ "relatedObjectRules",
41
+ "relatedObjectWatchRunning",
42
+ "relatedRunning",
43
+ ], // NOT REACTIVE
21
44
  }) {
22
45
  const state = reactive({
23
46
  calculatedObjectRules,
@@ -36,11 +59,6 @@ export function useObjectCalculated({
36
59
  const es = effectScope();
37
60
 
38
61
  es.run(() => {
39
- state.loading = toRef(parentState, "loading");
40
- state.error = toRef(parentState, "error");
41
- state.errored = toRef(parentState, "errored");
42
- state.deleted = toRef(parentState, "deleted");
43
- state.object = toRef(parentState, "object");
44
62
  state[copn] = toRef(state, "calculatedObjectObjects");
45
63
  for (let key of passThroughPropertyNames) {
46
64
  state[key] = toRef(parentState, key);
@@ -91,7 +109,8 @@ export function useObjectCalculated({
91
109
  ],
92
110
  });
93
111
 
94
- state.running = computed(() => loadingCombine(watchesRunning.state.running, parentState.running));
112
+ state.calculatedRunning = toRef(watchesRunning.state, "running");
113
+ state.running = computed(() => loadingCombine(watchesRunning.state.running, parentState.relatedRunning));
95
114
 
96
115
  onScopeDispose(() => {
97
116
  for (const key in calculatedObjectEffectScopes) {
@@ -1,7 +1,7 @@
1
- import { addOrUpdateReactiveObject, assignReactiveObject } from "../utils/assignReactiveObject";
1
+ import { addOrUpdateReactiveObject, assignReactiveObject } from "../utils";
2
2
  import cloneDeep from "lodash-es/cloneDeep";
3
3
  import isFunction from "lodash-es/isFunction";
4
- import { effectScope, reactive } from "vue";
4
+ import { reactive, shallowReactive, toRef } from "vue";
5
5
 
6
6
  export class ObjectError extends Error {
7
7
  constructor(message) {
@@ -36,7 +36,7 @@ export function useObjectInstances(instanceArgs) {
36
36
  return instances;
37
37
  }
38
38
 
39
- export function useObjectInstance({ crudArgs, id, retrieveArgs, functions = {} }) {
39
+ export function useObjectInstance({ props, functions = {} }) {
40
40
  const state = reactive({
41
41
  crud: {
42
42
  args: {},
@@ -47,8 +47,8 @@ export function useObjectInstance({ crudArgs, id, retrieveArgs, functions = {} }
47
47
  delete: undefined,
48
48
  },
49
49
  object: {},
50
- id,
51
- retrieveArgs,
50
+ id: toRef(props, "id"),
51
+ retrieveArgs: toRef(props, "retrieveArgs"),
52
52
  loading: undefined,
53
53
  errored: false,
54
54
  error: null,
@@ -56,8 +56,8 @@ export function useObjectInstance({ crudArgs, id, retrieveArgs, functions = {} }
56
56
  });
57
57
  // prevent linking of all instances to the same default .args object
58
58
  Object.assign(state.crud, cloneDeep(defaultCrud));
59
- if (crudArgs) {
60
- addOrUpdateReactiveObject(state.crud.args, crudArgs);
59
+ if (props.crudArgs) {
60
+ addOrUpdateReactiveObject(state.crud.args, props.crudArgs);
61
61
  }
62
62
  for (const [key, value] of Object.entries(functions)) {
63
63
  if (isFunction(value) && key in state.crud) {
@@ -222,12 +222,7 @@ export function useObjectInstance({ crudArgs, id, retrieveArgs, functions = {} }
222
222
  state.error = null;
223
223
  }
224
224
 
225
- const es = effectScope();
226
-
227
- // we could have effects? let's keep the interface to keep our options open to add without major changes.
228
- es.run(() => {});
229
-
230
- return {
225
+ return shallowReactive({
231
226
  state,
232
227
  retrieve,
233
228
  create,
@@ -235,6 +230,5 @@ export function useObjectInstance({ crudArgs, id, retrieveArgs, functions = {} }
235
230
  patch,
236
231
  delete: deleteFn,
237
232
  clearError,
238
- effectScope: es,
239
- };
233
+ });
240
234
  }
@@ -1,4 +1,4 @@
1
- import { keyDiff, loadingCombine } from "../utils";
1
+ import { keyDiff } from "../utils";
2
2
  import { useWatchesRunning } from "./watchesRunning";
3
3
  import get from "lodash-es/get";
4
4
  import isArray from "lodash-es/isArray";
@@ -20,7 +20,24 @@ export function useObjectRelated({
20
20
  parentState,
21
21
  relatedObjectRules,
22
22
  relatedObjectPropertyName = "relatedObject", // NOT REACTIVE
23
- passThroughPropertyNames = ["calculatedObject"], // NOT REACTIVE
23
+ passThroughPropertyNames = [
24
+ // instance
25
+ "crud",
26
+ "deleted",
27
+ "error",
28
+ "errored",
29
+ "id",
30
+ "loading",
31
+ "object",
32
+ "retrieveArgs",
33
+ // subscription
34
+ "intendToRetrieve",
35
+ "intendToSubscribe",
36
+ "subscribed",
37
+ "subscriptionError",
38
+ "subscriptionErrored",
39
+ "subscriptionLoading",
40
+ ], // NOT REACTIVE
24
41
  }) {
25
42
  const state = reactive({
26
43
  relatedObjectRules,
@@ -38,11 +55,6 @@ export function useObjectRelated({
38
55
  const es = effectScope();
39
56
 
40
57
  es.run(() => {
41
- state.loading = toRef(parentState, "loading");
42
- state.error = toRef(parentState, "error");
43
- state.errored = toRef(parentState, "errored");
44
- state.deleted = toRef(parentState, "deleted");
45
- state.object = toRef(parentState, "object");
46
58
  state[ropn] = toRef(state, "relatedObjectObjects");
47
59
  for (let key of passThroughPropertyNames) {
48
60
  state[key] = toRef(parentState, key);
@@ -98,7 +110,7 @@ export function useObjectRelated({
98
110
  ],
99
111
  });
100
112
 
101
- state.running = computed(() => loadingCombine(watchesRunning.state.running, parentState.running));
113
+ state.relatedRunning = toRef(watchesRunning.state, "running");
102
114
 
103
115
  onScopeDispose(() => {
104
116
  for (const key in relatedObjectEffectScopes) {
@@ -1,7 +1,7 @@
1
1
  import { assignReactiveObject, loadingCombine } from "../utils";
2
2
  import { useCancellableIntent } from "./cancellableIntent";
3
3
  import { useObjectInstance } from "./objectInstance";
4
- import { computed, effectScope, reactive, toRef } from "vue";
4
+ import { computed, effectScope, reactive, shallowReactive, toRef } from "vue";
5
5
 
6
6
  export class ObjectSubscriptionError extends Error {
7
7
  constructor(message) {
@@ -26,18 +26,33 @@ export function useObjectSubscriptions(subscriptionArgs) {
26
26
  return subscriptions;
27
27
  }
28
28
 
29
- export function useObjectSubscription({ objectInstance, crudArgs, id, retrieveArgs }) {
30
- if (retrieveArgs && objectInstance) {
29
+ export function useObjectSubscription({
30
+ objectInstance,
31
+ props,
32
+ passThroughPropertyNames = [
33
+ // instance
34
+ "crud",
35
+ "deleted",
36
+ // "error",
37
+ // "errored",
38
+ "id",
39
+ // "loading",
40
+ "object",
41
+ "retrieveArgs",
42
+ ],
43
+ }) {
44
+ if (props?.retrieveArgs && objectInstance) {
31
45
  throw new ObjectSubscriptionError(
32
46
  "Cannot use retrieveArgs and objectInstance together, set retrieveArgs on objectInstance instead"
33
47
  );
34
48
  }
35
49
  if (!objectInstance) {
36
- objectInstance = useObjectInstance({ crudArgs, id, retrieveArgs });
50
+ objectInstance = useObjectInstance({ props });
37
51
  }
38
52
  if (!objectInstance.state.crud.subscribe) {
39
53
  objectInstance.state.crud.subscribe = defaultCrud.subscribe;
40
54
  }
55
+ const parentState = objectInstance.state;
41
56
  const state = reactive({
42
57
  subscriptionLoading: undefined,
43
58
  subscriptionErrored: false,
@@ -46,16 +61,19 @@ export function useObjectSubscription({ objectInstance, crudArgs, id, retrieveAr
46
61
  intendToSubscribe: false,
47
62
  intendToRetrieve: false,
48
63
  });
64
+ if ("intendToRetrieve" in props) {
65
+ state.intendToRetrieve = toRef(props, "intendToRetrieve");
66
+ }
49
67
 
50
68
  let subscribeIntent, retrieveIntent;
51
69
 
52
70
  function updateFromSubscription(data) {
53
- assignReactiveObject(state.object, data);
71
+ assignReactiveObject(parentState.object, data);
54
72
  }
55
73
 
56
74
  function deleteFromSubscription() {
57
75
  state.deleted = true;
58
- assignReactiveObject(state.object, {});
76
+ assignReactiveObject(parentState.object, {});
59
77
  }
60
78
 
61
79
  function publicSubscribe({ retrieve = true } = {}) {
@@ -82,9 +100,9 @@ export function useObjectSubscription({ objectInstance, crudArgs, id, retrieveAr
82
100
  state.subscriptionErrored = false;
83
101
  state.subscriptionError = null;
84
102
  let subscribePromise;
85
- subscribePromise = objectInstance.state.crud.subscribe({
86
- crudArgs: objectInstance.state.crud.args,
87
- id: objectInstance.state.id,
103
+ subscribePromise = parentState.crud.subscribe({
104
+ crudArgs: parentState.crud.args,
105
+ id: parentState.id,
88
106
  retrieveArgs: state.retrieveArgs,
89
107
  callback: (data, action) => {
90
108
  if (action === "delete") {
@@ -145,20 +163,20 @@ export function useObjectSubscription({ objectInstance, crudArgs, id, retrieveAr
145
163
  const es = effectScope();
146
164
 
147
165
  es.run(() => {
148
- state.loading = computed(() => loadingCombine(objectInstance.state.loading, state.subscriptionLoading));
149
- state.errored = computed(() => objectInstance.state.errored || state.subscriptionErrored);
150
- state.error = computed(() => objectInstance.state.error || state.subscriptionError);
166
+ state.loading = computed(() => loadingCombine(parentState.loading, state.subscriptionLoading));
167
+ state.errored = computed(() => parentState.errored || state.subscriptionErrored);
168
+ state.error = computed(() => parentState.error || state.subscriptionError);
151
169
 
152
- state.retrieveArgs = computed(() => objectInstance.state.retrieveArgs);
153
- state.object = toRef(objectInstance.state, "object");
154
- state.deleted = toRef(objectInstance.state, "deleted");
170
+ for (const key of passThroughPropertyNames) {
171
+ state[key] = toRef(parentState, key);
172
+ }
155
173
 
156
174
  subscribeIntent = useCancellableIntent({
157
175
  awaitableWithCancel: subscribe,
158
176
  watchArguments: reactive({
159
177
  intendToSubscribe: toRef(state, "intendToSubscribe"),
160
- listArgs: toRef(objectInstance.state, "id"),
161
- retrieveArgs: toRef(objectInstance.state, "retrieveArgs"),
178
+ listArgs: toRef(parentState, "id"),
179
+ retrieveArgs: toRef(parentState, "retrieveArgs"),
162
180
  }),
163
181
  clearActiveOnResolved: false,
164
182
  });
@@ -167,13 +185,13 @@ export function useObjectSubscription({ objectInstance, crudArgs, id, retrieveAr
167
185
  awaitableWithCancel: objectInstance.retrieve,
168
186
  watchArguments: reactive({
169
187
  intendToSubscribe: toRef(state, "intendToRetrieve"),
170
- listArgs: toRef(objectInstance.state, "id"),
171
- retrieveArgs: toRef(objectInstance.state, "retrieveArgs"),
188
+ listArgs: toRef(parentState, "id"),
189
+ retrieveArgs: toRef(parentState, "retrieveArgs"),
172
190
  }),
173
191
  });
174
192
  });
175
193
 
176
- return {
194
+ return shallowReactive({
177
195
  state,
178
196
  objectInstance,
179
197
  subscribeIntent,
@@ -184,5 +202,5 @@ export function useObjectSubscription({ objectInstance, crudArgs, id, retrieveAr
184
202
  deleteFromSubscription,
185
203
  clearError,
186
204
  effectScope: es,
187
- };
205
+ });
188
206
  }
@@ -1,6 +1,6 @@
1
1
  import { useListInstance } from "./listInstance";
2
2
 
3
- export function usePagedListInstance(useListInstanceArgs) {
3
+ export function usePagedListInstance({ keepOldPages = false, ...useListInstanceArgs }) {
4
4
  const listInstance = useListInstance(useListInstanceArgs);
5
5
 
6
6
  listInstance.state.totalRecords = 0;
@@ -8,8 +8,11 @@ export function usePagedListInstance(useListInstanceArgs) {
8
8
  listInstance.state.perPage = 0;
9
9
 
10
10
  listInstance.pageCallback = (newObjects, { totalRecords, totalPages, perPage }) => {
11
- // display one page at a time, clear the list
12
- listInstance.clearList();
11
+ // with keepOldPages, you are responsible for clearing the list as needed
12
+ if (!keepOldPages) {
13
+ // display one page at a time, clear the list
14
+ listInstance.clearList();
15
+ }
13
16
 
14
17
  listInstance.defaultPageCallback(newObjects);
15
18
  if (totalRecords !== undefined) {
@@ -0,0 +1,29 @@
1
+ import { keyDiff } from "./keyDiff";
2
+ import isEqual from "lodash-es/isEqual";
3
+ import { watch } from "vue";
4
+ import { deepUnref } from "vue-deepunref";
5
+
6
+ export const debugWatch = (target, name) => {
7
+ return watch(
8
+ target,
9
+ (newState, oldState) => {
10
+ console.log(`Watch triggered ${name}`);
11
+ const diff = keyDiff(Object.keys(newState || {}), Object.keys(oldState || {}));
12
+ for (const addedKey of Array.from(diff.addedKeys)) {
13
+ console.log(`${name} added key ${addedKey}`, deepUnref(newState[addedKey]));
14
+ }
15
+ for (const removedKey of Array.from(diff.removedKeys)) {
16
+ console.log(`${name} removed key ${removedKey}`, deepUnref(oldState[removedKey]));
17
+ }
18
+ for (const sameKey of Array.from(diff.sameKeys)) {
19
+ if (!isEqual(deepUnref(newState[sameKey]), deepUnref(oldState[sameKey]))) {
20
+ console.log(`${name} same key ${sameKey}`, newState[sameKey], oldState[sameKey]);
21
+ }
22
+ }
23
+ },
24
+ {
25
+ deep: true,
26
+ immediate: true,
27
+ }
28
+ );
29
+ };
package/utils/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./assignReactiveObject";
2
+ export * from "./debugWatch";
2
3
  export * from "./flattenPaths";
3
4
  export * from "./getFakeId";
4
5
  export * from "./keyDiff";