@arrai-innovations/reactive-helpers 11.0.5 → 11.2.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": "11.0.5",
3
+ "version": "11.2.0",
4
4
  "description": "VueJS 3 utility composition functions to help manipulate objects and lists.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  import { useListFilters, useListSort } from "../../../use/index.js";
2
2
  import { doAwaitNot } from "../../../utils/watches.js";
3
- import { ref, unref, reactive } from "vue";
3
+ import { reactive, ref, unref } from "vue";
4
4
  import { deepUnref } from "vue-deepunref";
5
5
 
6
6
  describe("use/listFilter", () => {
@@ -122,6 +122,10 @@ describe("use/listFilter", () => {
122
122
  const filter = useListFilter({
123
123
  parentState: listInstance.state,
124
124
  });
125
+ await doAwaitNot({
126
+ obj: filter.state,
127
+ prop: "running",
128
+ });
125
129
  expect(filter.state.objects).toEqual(listInstance.state.objects);
126
130
  });
127
131
  describe("useListFilter operates on parentState modified by useListSort", () => {
@@ -373,10 +377,6 @@ describe("use/listFilter", () => {
373
377
  parentState: filter1.state,
374
378
  allowedFilter: filter2AllowedFilter,
375
379
  });
376
- await doAwaitNot({
377
- obj: filter1.state,
378
- prop: "running",
379
- });
380
380
  await doAwaitNot({
381
381
  obj: filter2.state,
382
382
  prop: "running",
@@ -386,10 +386,6 @@ describe("use/listFilter", () => {
386
386
  expect(filter2.state.objects).toEqual({});
387
387
  expect(filter2.state.order).toEqual([]);
388
388
  list.addListObject({ id: 1, name: "one", has_things: true, has_stuff: true, has_other_stuff: true });
389
- await doAwaitNot({
390
- obj: filter1.state,
391
- prop: "running",
392
- });
393
389
  await doAwaitNot({
394
390
  obj: filter2.state,
395
391
  prop: "running",
@@ -403,10 +399,6 @@ describe("use/listFilter", () => {
403
399
  });
404
400
  expect(filter2.state.order).toEqual(["1"]);
405
401
  list.addListObject({ id: 2, name: "two", has_things: true, has_stuff: true, has_other_stuff: false });
406
- await doAwaitNot({
407
- obj: filter1.state,
408
- prop: "running",
409
- });
410
402
  await doAwaitNot({
411
403
  obj: filter2.state,
412
404
  prop: "running",
@@ -421,10 +413,6 @@ describe("use/listFilter", () => {
421
413
  });
422
414
  expect(filter2.state.order).toEqual(["1"]);
423
415
  list.addListObject({ id: 3, name: "three", has_things: true, has_stuff: false, has_other_stuff: true });
424
- await doAwaitNot({
425
- obj: filter1.state,
426
- prop: "running",
427
- });
428
416
  await doAwaitNot({
429
417
  obj: filter2.state,
430
418
  prop: "running",
@@ -439,10 +427,6 @@ describe("use/listFilter", () => {
439
427
  });
440
428
  expect(filter2.state.order).toEqual(["1"]);
441
429
  list.addListObject({ id: 4, name: "four", has_things: true, has_stuff: false, has_other_stuff: false });
442
- await doAwaitNot({
443
- obj: filter1.state,
444
- prop: "running",
445
- });
446
430
  await doAwaitNot({
447
431
  obj: filter2.state,
448
432
  prop: "running",
@@ -458,10 +442,6 @@ describe("use/listFilter", () => {
458
442
  expect(filter2.state.order).toEqual(["1"]);
459
443
  list.state.objects[1].has_stuff = false;
460
444
  list.state.objects[2].has_other_stuff = true;
461
- await doAwaitNot({
462
- obj: filter1.state,
463
- prop: "running",
464
- });
465
445
  await doAwaitNot({
466
446
  obj: filter2.state,
467
447
  prop: "running",
@@ -478,10 +458,6 @@ describe("use/listFilter", () => {
478
458
  list.addListObject({ id: 6, name: "six", has_things: true, has_stuff: true, has_other_stuff: false });
479
459
  list.addListObject({ id: 7, name: "seven", has_things: false, has_stuff: true, has_other_stuff: true });
480
460
  list.addListObject({ id: 8, name: "eight", has_things: true, has_stuff: true, has_other_stuff: true });
481
- await doAwaitNot({
482
- obj: filter1.state,
483
- prop: "running",
484
- });
485
461
  await doAwaitNot({
486
462
  obj: filter2.state,
487
463
  prop: "running",
@@ -3,7 +3,7 @@ import { expectErrorToBeNull } from "../expectHelpers.js";
3
3
  import flushPromises from "flush-promises";
4
4
  import keyBy from "lodash-es/keyBy.js";
5
5
  import { inspect } from "util";
6
- import { isReactive, nextTick, reactive } from "vue";
6
+ import { isReactive, nextTick, reactive, isRef, isReadonly } from "vue";
7
7
 
8
8
  afterAll(() => {
9
9
  vi.restoreAllMocks();
@@ -16,6 +16,7 @@ describe("use/listInstance.spec.js", function () {
16
16
  const listCrud = await import("../../../config/listCrud.js");
17
17
  const imported = await import("../../../use/listInstance");
18
18
  globalList = vi.fn();
19
+ globalList.cancel = vi.fn();
19
20
  listCrud.setListCrud({
20
21
  list: globalList,
21
22
  args: { stream: "test_stream" },
@@ -128,6 +129,7 @@ describe("use/listInstance.spec.js", function () {
128
129
  listArgs: { user: 1 },
129
130
  retrieveArgs: { fields: fields },
130
131
  pageCallback: passedPageCallback,
132
+ isCancelled: expect.any(Object), // ref
131
133
  });
132
134
  expect(globalList).toHaveBeenCalledTimes(1);
133
135
  });
@@ -153,6 +155,7 @@ describe("use/listInstance.spec.js", function () {
153
155
  listArgs: { user: 1 },
154
156
  retrieveArgs: { fields: fields },
155
157
  pageCallback: expect.any(Function),
158
+ isCancelled: expect.any(Object), // ref
156
159
  });
157
160
  expect(globalList).toHaveBeenCalledTimes(1);
158
161
  expectErrorToBeNull(listInstance.state.error);
@@ -207,6 +210,7 @@ describe("use/listInstance.spec.js", function () {
207
210
  listArgs: { user: 1 },
208
211
  retrieveArgs: { fields: fields },
209
212
  pageCallback: passedPageCallback,
213
+ isCancelled: expect.any(Object), // ref
210
214
  });
211
215
  expect(globalList).toHaveBeenCalledTimes(1);
212
216
  });
@@ -272,6 +276,7 @@ describe("use/listInstance.spec.js", function () {
272
276
  listArgs: { user: 1 },
273
277
  retrieveArgs: { fields: fields },
274
278
  pageCallback: passedPageCallback,
279
+ isCancelled: expect.any(Object), // ref
275
280
  });
276
281
  expect(globalList).toHaveBeenCalledTimes(1);
277
282
  });
@@ -318,6 +323,7 @@ describe("use/listInstance.spec.js", function () {
318
323
  listArgs: { user: 1 },
319
324
  retrieveArgs: { fields: fields },
320
325
  pageCallback: passedPageCallback,
326
+ isCancelled: expect.any(Object), // ref
321
327
  });
322
328
  expect(globalList).toHaveBeenCalledTimes(1);
323
329
 
@@ -381,7 +387,7 @@ describe("use/listInstance.spec.js", function () {
381
387
  listInstance.addListObject(listObject);
382
388
  expect(() => listInstance.addListObject({ listObject })).toThrowError(ListError);
383
389
  });
384
- it("succeeded", function () {
390
+ it("succeeded", async function () {
385
391
  const listInstance = useListInstance({ props: {} });
386
392
  const newId = listInstance.getFakeId();
387
393
  listObject.id = newId;
@@ -389,6 +395,18 @@ describe("use/listInstance.spec.js", function () {
389
395
  expect(listInstance.state.objects[newId]).toEqual(listObject);
390
396
  const reactiveProxy = listInstance.state.objects[newId];
391
397
  expect(isReactive(reactiveProxy)).toBe(true);
398
+ expect(reactiveProxy.id).toBe(newId);
399
+ expect(reactiveProxy.__str__).toBe("nvm");
400
+ expect(reactiveProxy.name).toBe("nvm");
401
+ expect(listInstance.state.objectsInOrder).toEqual([]);
402
+ // order updates immediately due to not being proxied through objectsInOrderRefs
403
+ expect(listInstance.state.order).toStrictEqual([newId.toString()]);
404
+ await doAwaitNot({
405
+ obj: listInstance.state,
406
+ prop: "running",
407
+ });
408
+ expect(listInstance.state.objectsInOrder).toEqual([reactiveProxy]);
409
+ expect(listInstance.state.order).toEqual([newId.toString()]);
392
410
  });
393
411
  });
394
412
  describe("updateListObject", function () {
@@ -401,7 +419,7 @@ describe("use/listInstance.spec.js", function () {
401
419
  });
402
420
  it("succeeds", async function () {
403
421
  const listInstance = useListInstance({
404
- props: { listArgs: { user: 1 }, retrieveArgs: { fields: fields } },
422
+ props: {},
405
423
  });
406
424
  let crudListResolve;
407
425
  const crudListPromise = new Promise((resolve) => {
@@ -418,10 +436,70 @@ describe("use/listInstance.spec.js", function () {
418
436
  passedPageCallback(crudListResolvedPage1);
419
437
  crudListResolve();
420
438
 
439
+ await doAwaitNot({
440
+ obj: listInstance.state,
441
+ prop: "running",
442
+ });
443
+
421
444
  let updateObject = listInstance.state.objects["1"];
422
445
  updateObject.name = "updated";
423
446
  listInstance.updateListObject(updateObject);
424
447
  expect(listInstance.state.objects["1"]).toEqual(updateObject);
448
+ expect(listInstance.state.objectsInOrder[0].name).toBe("updated");
449
+ await doAwaitNot({
450
+ obj: listInstance.state,
451
+ prop: "running",
452
+ });
453
+ expect(listInstance.state.objectsInOrder[0].name).toBe("updated");
454
+ });
455
+ });
456
+ describe("deleteListObject", function () {
457
+ it("errors", function () {
458
+ const listInstance = useListInstance({ props: {} });
459
+ expect(() => listInstance.deleteListObject(-50002000)).toThrowError(ListError);
460
+ });
461
+ it("succeeds", async function () {
462
+ const listInstance = useListInstance({
463
+ props: {},
464
+ });
465
+ let crudListResolve;
466
+ const crudListPromise = new Promise((resolve) => {
467
+ crudListResolve = resolve;
468
+ });
469
+ let passedPageCallback;
470
+ globalList.mockImplementation(({ pageCallback }) => {
471
+ passedPageCallback = pageCallback;
472
+ return crudListPromise;
473
+ });
474
+
475
+ listInstance.list();
476
+
477
+ passedPageCallback(crudListResolvedPage1);
478
+ crudListResolve();
479
+
480
+ await doAwaitNot({
481
+ obj: listInstance.state,
482
+ prop: "running",
483
+ });
484
+
485
+ listInstance.deleteListObject(1);
486
+ expect(listInstance.state.objects["1"]).toBeUndefined();
487
+ expect(listInstance.state.objectsInOrder).toStrictEqual([
488
+ undefined,
489
+ crudListResolvedObjects1["2"],
490
+ crudListResolvedObjects1["3"],
491
+ ]);
492
+ // order updates immediately due to not being proxied through objectsInOrderRefs
493
+ expect(listInstance.state.order).toStrictEqual(["2", "3"]);
494
+ await doAwaitNot({
495
+ obj: listInstance.state,
496
+ prop: "running",
497
+ });
498
+ expect(listInstance.state.objectsInOrder).toEqual([
499
+ crudListResolvedObjects1["2"],
500
+ crudListResolvedObjects1["3"],
501
+ ]);
502
+ expect(listInstance.state.order).toEqual(["2", "3"]);
425
503
  });
426
504
  });
427
505
  describe("getFakeId", function () {
@@ -488,4 +566,39 @@ describe("use/listInstance.spec.js", function () {
488
566
  expect(listInstance.state.order).toEqual(crudListResolvedPage3.map((e) => e.id.toString()));
489
567
  expect(listInstance.state.objectsInOrder).toEqual(crudListResolvedPage3);
490
568
  });
569
+ describe("list promise cancellation", function () {
570
+ it("cancels and sets isCancelled", async function () {
571
+ let myListFnCancelResolve, passedIsCancelled;
572
+ const myListFn = vi.fn().mockImplementation(({ isCancelled }) => {
573
+ passedIsCancelled = isCancelled;
574
+ const promise = new Promise(() => {});
575
+ promise.cancel = async () => {
576
+ await new Promise((resolve) => {
577
+ myListFnCancelResolve = resolve;
578
+ });
579
+ };
580
+ return promise;
581
+ });
582
+ const listInstance = useListInstance({
583
+ props: {},
584
+ functions: {
585
+ list: myListFn,
586
+ },
587
+ });
588
+
589
+ const cancelablePromise = listInstance.list();
590
+
591
+ expect(passedIsCancelled).toBeTruthy();
592
+ expect(isRef(passedIsCancelled)).toBe(true);
593
+ expect(isReadonly(passedIsCancelled)).toBe(true);
594
+ expect(passedIsCancelled.value).toBe(false);
595
+ expect(cancelablePromise.cancel).toBeTruthy();
596
+
597
+ const cancelPromise = cancelablePromise.cancel();
598
+ myListFnCancelResolve();
599
+ await cancelPromise;
600
+
601
+ expect(passedIsCancelled.value).toBe(true);
602
+ });
603
+ });
491
604
  });
@@ -1,4 +1,4 @@
1
- import { doAwaitTimeout, doAwaitNot } from "../../../utils/index.js";
1
+ import { doAwaitNot, doAwaitTimeout } from "../../../utils/index.js";
2
2
  import { reactive, ref } from "vue";
3
3
  import { deepUnref } from "vue-deepunref";
4
4
 
@@ -13,8 +13,7 @@ describe("use/useListSort", () => {
13
13
  useListFilter,
14
14
  useListSort,
15
15
  useListSorts,
16
- setListSortDefaultOptions,
17
- AwaitNot;
16
+ setListSortDefaultOptions;
18
17
  const contactsResolved = [
19
18
  {
20
19
  id: 15,
@@ -65,20 +64,17 @@ describe("use/useListSort", () => {
65
64
  setListSortDefaultOptions({
66
65
  sortThrottleWait: 0,
67
66
  });
68
- const importedUtils = await import("../../../utils");
69
- AwaitNot = importedUtils.AwaitNot;
70
67
  });
71
68
 
72
69
  afterEach(() => vi.resetAllMocks());
73
70
 
74
71
  const waitForListSort = async (listSort) => {
75
- const anr = new AwaitNot({
72
+ await doAwaitNot({
76
73
  obj: listSort.state,
77
74
  prop: "running",
78
75
  timeout: 2000,
76
+ couldAlreadyBeFalse: false,
79
77
  });
80
- anr.start();
81
- await anr.promise;
82
78
  };
83
79
 
84
80
  it("generates initial values from inputs", () => {
@@ -106,6 +102,10 @@ describe("use/useListSort", () => {
106
102
  for (const contact of contactsResolved) {
107
103
  listInstance.addListObject(contact);
108
104
  }
105
+ await doAwaitNot({
106
+ obj: listInstance.state,
107
+ prop: "running",
108
+ });
109
109
  const listSort = useListSort({ parentState: listInstance.state, orderByRules });
110
110
  // sorts immediately
111
111
  expect(listSort.state.order).toEqual(testOrder1);
@@ -142,7 +142,6 @@ describe("use/listSubscription.spec.js", function () {
142
142
  obj: listSubscription.subscribeIntent.state,
143
143
  prop: "active",
144
144
  });
145
- expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledWith();
146
145
  expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledTimes(1);
147
146
  expect(returnValue).toBe(true);
148
147
 
@@ -288,7 +287,6 @@ describe("use/listSubscription.spec.js", function () {
288
287
  crudSubscribeResolvable[0].cancel.resolve(true);
289
288
  const returnValue = await unsubscribe;
290
289
  await poll(() => !listSubscription.state.subscribed);
291
- expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledWith();
292
290
  expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledTimes(1);
293
291
  expect(returnValue).toBe(true);
294
292
  expect(listSubscription.state.subscribed).toBe(false);
@@ -322,7 +320,6 @@ describe("use/listSubscription.spec.js", function () {
322
320
  crudSubscribeResolvable[0].cancel.resolve(true);
323
321
  const returnValue = await unsubscribePromise;
324
322
  await poll(() => !listSubscription.state.subscribed);
325
- expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledWith();
326
323
  expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledTimes(1);
327
324
  expect(returnValue).toBe(true);
328
325
  expect(listSubscription.state.subscribed).toBe(false);
@@ -382,7 +379,6 @@ describe("use/listSubscription.spec.js", function () {
382
379
  crudSubscribeResolvable[0].cancel.resolve(true);
383
380
  const returnValue = await unsubscribePromise;
384
381
  await poll(() => !listSubscription.state.subscribed);
385
- expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledWith();
386
382
  expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledTimes(1);
387
383
  expect(returnValue).toBe(true);
388
384
  expect(listSubscription.state.subscribed).toBe(false);
@@ -455,7 +451,6 @@ describe("use/listSubscription.spec.js", function () {
455
451
  crudSubscribeResolvable[0].cancel.resolve(true);
456
452
  const returnValue = await unsubscribePromise;
457
453
  await poll(() => !listSubscription.state.subscribed);
458
- expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledWith();
459
454
  expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledTimes(1);
460
455
  expect(returnValue).toBe(true);
461
456
  expect(listSubscription.state.subscribed).toBe(false);
@@ -508,7 +503,6 @@ describe("use/listSubscription.spec.js", function () {
508
503
  await crudSubscribeResolvable[1].resolve();
509
504
  await crudListResolvable[1].resolve();
510
505
  await poll(() => listSubscription.state.subscribed);
511
- expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledWith();
512
506
  expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledTimes(1);
513
507
 
514
508
  expect(crudSubscribe).toHaveBeenCalledTimes(2);
@@ -518,7 +512,6 @@ describe("use/listSubscription.spec.js", function () {
518
512
  const returnValue = await unsubscribePromise;
519
513
  expect(returnValue).toBe(true);
520
514
  await poll(() => !listSubscription.state.subscribed);
521
- expect(crudSubscribeResolvable[1].promise.cancel).toHaveBeenCalledWith();
522
515
  expect(crudSubscribeResolvable[1].promise.cancel).toHaveBeenCalledTimes(1);
523
516
  expect(listSubscription.state.subscribed).toBe(false);
524
517
  });
@@ -54,7 +54,7 @@ export function useCancellableIntent({
54
54
  return false;
55
55
  }
56
56
 
57
- const doIntentWatch = async () => {
57
+ const doIntentWatch = () => {
58
58
  state.errored = false;
59
59
  state.error = null;
60
60
  if (state.activeCount === undefined) {
@@ -127,13 +127,13 @@ export function useCancellableIntent({
127
127
  }
128
128
  };
129
129
 
130
- const guardWatch = async () => {
130
+ const guardWatch = () => {
131
131
  if (delayedWatch) {
132
132
  // if all guards are false, run the watch
133
133
  if (Object.values(guardArguments).every((x) => !x)) {
134
134
  const myDelayedWatch = delayedWatch;
135
135
  delayedWatch = null;
136
- await myDelayedWatch();
136
+ myDelayedWatch();
137
137
  }
138
138
  }
139
139
  };
@@ -1,12 +1,13 @@
1
- import { keyDiff, loadingCombine, difference } from "../utils/index.js";
1
+ import { difference, keyDiff, loadingCombine } from "../utils/index.js";
2
+ import { proxyRunning } from "../utils/proxyRunning.js";
2
3
  import {
3
- listRelatedStateKeys,
4
- listSubscriptionStateKeys,
5
- listInstanceStateKeys,
6
- listFilterStateKeys,
7
4
  listCalculatedStateKeys,
8
- listSortStateKeys,
5
+ listFilterStateKeys,
6
+ listInstanceStateKeys,
7
+ listRelatedStateKeys,
9
8
  listSearchStateKeys,
9
+ listSortStateKeys,
10
+ listSubscriptionStateKeys,
10
11
  } from "./listKeys.js";
11
12
  import { useWatchesRunning } from "./watchesRunning.js";
12
13
  import isEmpty from "lodash-es/isEmpty.js";
@@ -145,7 +146,9 @@ export function useListCalculated({ parentState, calculatedObjectsRules }) {
145
146
  });
146
147
 
147
148
  state.calculatedRunning = toRef(watchesRunning.state, "running");
148
- state.running = computed(() => loadingCombine(watchesRunning.state.running, parentState.running));
149
+ const parentRunning = ref(undefined);
150
+ proxyRunning(parentState, "running", parentRunning);
151
+ state.running = computed(() => loadingCombine(watchesRunning.state.running, parentRunning.value));
149
152
 
150
153
  onScopeDispose(() => {
151
154
  for (const objectKey of Object.keys(calculatedObjectsEffectScopes)) {
package/use/listFilter.js CHANGED
@@ -1,15 +1,16 @@
1
1
  import { assignReactiveObject, difference, loadingCombine } from "../utils/index.js";
2
2
  import { keyDiff } from "../utils/keyDiff.js";
3
+ import { proxyRunning } from "../utils/proxyRunning.js";
3
4
  import {
4
- listSortStateKeys,
5
- listFilterStateKeys,
6
- listRelatedStateKeys,
7
5
  listCalculatedStateKeys,
8
- listSubscriptionStateKeys,
6
+ listFilterStateKeys,
9
7
  listInstanceStateKeys,
8
+ listRelatedStateKeys,
10
9
  listSearchStateKeys,
10
+ listSortStateKeys,
11
+ listSubscriptionStateKeys,
11
12
  } from "./listKeys.js";
12
- import { effectScope, reactive, toRef, watch, unref, computed, nextTick } from "vue";
13
+ import { computed, effectScope, nextTick, reactive, ref, toRef, unref, watch } from "vue";
13
14
 
14
15
  const parentStateKeys = difference(
15
16
  new Set([
@@ -71,9 +72,6 @@ export function useListFilter({ parentState, allowedFilter, excludedFilter }) {
71
72
  previousExcludedFilter = null;
72
73
 
73
74
  const objectsWatch = () => {
74
- if (parentState.running) {
75
- return;
76
- }
77
75
  state.objectsWatchRunning = true;
78
76
  const allowedOrExcludedFilterChanged =
79
77
  allowedFilter !== previousAllowedFilter || excludedFilter !== previousExcludedFilter;
@@ -155,8 +153,10 @@ export function useListFilter({ parentState, allowedFilter, excludedFilter }) {
155
153
  state[key] = toRef(parentState, key);
156
154
  }
157
155
 
156
+ const parentRunning = ref(undefined);
157
+ proxyRunning(parentState, "running", parentRunning);
158
158
  state.running = computed(() => {
159
- return loadingCombine(parentState.running, state.objectsWatchRunning, state.resultsWatchRunning);
159
+ return loadingCombine(parentRunning.value, state.objectsWatchRunning, state.resultsWatchRunning);
160
160
  });
161
161
 
162
162
  watch(toRef(state, "inResults"), resultsWatch, { deep: true });
@@ -2,7 +2,7 @@ import { getListCrud } from "../config/listCrud.js";
2
2
  import { assignReactiveObject } from "../utils/assignReactiveObject.js";
3
3
  import { getFakeId } from "../utils/getFakeId.js";
4
4
  import inspect from "browser-util-inspect";
5
- import { effectScope, reactive, toRef, watch, computed, unref } from "vue";
5
+ import { effectScope, reactive, toRef, watch, computed, unref, ref, readonly } from "vue";
6
6
 
7
7
  export class ListError extends Error {
8
8
  constructor(message, code) {
@@ -100,6 +100,9 @@ export function useListInstance({ props, functions = {}, keepOldPages = false })
100
100
  },
101
101
  set(target, prop, value) {
102
102
  target.set(prop, value); // map.set() returns the map, we don't need that
103
+ if (!state.running) {
104
+ state.running = true;
105
+ }
103
106
  return true;
104
107
  },
105
108
  ownKeys(target) {
@@ -135,6 +138,7 @@ export function useListInstance({ props, functions = {}, keepOldPages = false })
135
138
  listArgs: toRef(props, "listArgs"),
136
139
  objects: _objectsProxy,
137
140
  loading: undefined,
141
+ running: false,
138
142
  errored: false,
139
143
  error: null,
140
144
  order: [],
@@ -178,16 +182,19 @@ export function useListInstance({ props, functions = {}, keepOldPages = false })
178
182
  state.loading = true;
179
183
  state.errored = false;
180
184
  state.error = null;
185
+ const isCancelled = ref(false);
181
186
  const listPromise = state.crud.list({
182
187
  crudArgs: state.crud.args,
183
188
  retrieveArgs: state.retrieveArgs,
184
189
  listArgs: state.listArgs,
185
190
  pageCallback: returnedObject.pageCallback,
191
+ isCancelled: readonly(isCancelled),
186
192
  });
187
193
  let resolveState = false;
188
194
  if (listPromise.cancel) {
189
195
  promises.list.cancel = async () => {
190
196
  let promise = listPromise.cancel();
197
+ isCancelled.value = true;
191
198
  if (promise) {
192
199
  await promise;
193
200
  }
@@ -218,6 +225,9 @@ export function useListInstance({ props, functions = {}, keepOldPages = false })
218
225
  throw new ListError(`addListObject: list already has object for id: ${inspect(object.id)}`, "duplicate-id");
219
226
  }
220
227
  state.objects[object.id] = object;
228
+ if (!state.running) {
229
+ state.running = true;
230
+ }
221
231
  }
222
232
 
223
233
  function updateListObject(object) {
@@ -231,6 +241,9 @@ export function useListInstance({ props, functions = {}, keepOldPages = false })
231
241
  );
232
242
  }
233
243
  assignReactiveObject(state.objects[object.id], object);
244
+ if (!state.running) {
245
+ state.running = true;
246
+ }
234
247
  }
235
248
 
236
249
  function deleteListObject(objectId) {
@@ -241,6 +254,9 @@ export function useListInstance({ props, functions = {}, keepOldPages = false })
241
254
  );
242
255
  }
243
256
  delete state.objects[objectId];
257
+ if (!state.running) {
258
+ state.running = true;
259
+ }
244
260
  }
245
261
 
246
262
  function clearList() {
@@ -265,6 +281,9 @@ export function useListInstance({ props, functions = {}, keepOldPages = false })
265
281
  state.objectsInOrderRefs,
266
282
  Object.keys(state.objects).map((id) => toRef(state.objects, id))
267
283
  );
284
+ if (state.running) {
285
+ state.running = false;
286
+ }
268
287
  },
269
288
  {
270
289
  immediate: true,
package/use/listKeys.js CHANGED
@@ -4,6 +4,7 @@ export const listInstanceStateKeys = [
4
4
  "listArgs",
5
5
  "objects",
6
6
  "loading",
7
+ "running",
7
8
  "errored",
8
9
  "error",
9
10
  "objectsInOrder",
@@ -1,15 +1,16 @@
1
1
  import { keyDiff } from "../utils/keyDiff.js";
2
2
  import { loadingCombine } from "../utils/loadingCombine.js";
3
+ import { proxyRunning } from "../utils/proxyRunning.js";
3
4
  import { getObjectRelatedByKey } from "../utils/relatedCalculatedHelpers.js";
4
5
  import { difference } from "../utils/set.js";
5
6
  import {
6
- listSubscriptionStateKeys,
7
- listInstanceStateKeys,
8
- listRelatedStateKeys,
9
7
  listCalculatedStateKeys,
10
- listSortStateKeys,
11
8
  listFilterStateKeys,
9
+ listInstanceStateKeys,
10
+ listRelatedStateKeys,
12
11
  listSearchStateKeys,
12
+ listSortStateKeys,
13
+ listSubscriptionStateKeys,
13
14
  } from "./listKeys.js";
14
15
  import { useWatchesRunning } from "./watchesRunning.js";
15
16
  import get from "lodash-es/get.js";
@@ -17,7 +18,7 @@ import identity from "lodash-es/identity.js";
17
18
  import isArray from "lodash-es/isArray.js";
18
19
  import isEmpty from "lodash-es/isEmpty.js";
19
20
  import isUndefined from "lodash-es/isUndefined.js";
20
- import { computed, effectScope, onScopeDispose, reactive, toRef, unref, watch } from "vue";
21
+ import { computed, effectScope, onScopeDispose, reactive, ref, toRef, unref, watch } from "vue";
21
22
 
22
23
  const parentStateKeys = difference(
23
24
  new Set([
@@ -198,7 +199,9 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
198
199
  });
199
200
 
200
201
  state.relatedRunning = toRef(watchesRunning.state, "running");
201
- state.running = computed(() => loadingCombine(watchesRunning.state.running, parentState.running));
202
+ const parentRunning = ref(undefined);
203
+ proxyRunning(parentState, "running", parentRunning);
204
+ state.running = computed(() => loadingCombine(watchesRunning.state.running, parentRunning.value));
202
205
 
203
206
  onScopeDispose(() => {
204
207
  for (const objectKey of Object.keys(relatedObjectsEffectScopes)) {
package/use/listSearch.js CHANGED
@@ -1,18 +1,19 @@
1
- import { difference, loadingCombine, assignReactiveObject, keyDiff } from "../utils/index.js";
1
+ import { assignReactiveObject, difference, keyDiff, loadingCombine } from "../utils/index.js";
2
+ import { proxyRunning } from "../utils/proxyRunning.js";
2
3
  import { getObjectRelatedCalculatedByKey } from "../utils/relatedCalculatedHelpers.js";
3
4
  import {
4
- listInstanceStateKeys,
5
- listSubscriptionStateKeys,
6
- listRelatedStateKeys,
7
5
  listCalculatedStateKeys,
8
- listSortStateKeys,
9
6
  listFilterStateKeys,
7
+ listInstanceStateKeys,
8
+ listRelatedStateKeys,
10
9
  listSearchStateKeys,
10
+ listSortStateKeys,
11
+ listSubscriptionStateKeys,
11
12
  } from "./listKeys.js";
12
13
  import { useSearch } from "./search.js";
13
14
  import get from "lodash-es/get.js";
14
15
  import isEqual from "lodash-es/isEqual.js";
15
- import { reactive, effectScope, toRef, computed, watch, unref, onScopeDispose } from "vue";
16
+ import { computed, effectScope, onScopeDispose, reactive, ref, toRef, unref, watch } from "vue";
16
17
  import { deepUnref } from "vue-deepunref";
17
18
 
18
19
  const parentStateKeys = difference(
@@ -304,8 +305,10 @@ export function useListSearch({ parentState, props, throttle = 500, showAllWhenE
304
305
  textSearchIndex.state.search = toRef(state, "textSearchValue");
305
306
  textSearchIndex.events.addEventListener("newIndex", indexWasCleared);
306
307
  state.searched = toRef(() => textSearchIndex.state.searched);
308
+ const parentRunning = ref(undefined);
309
+ proxyRunning(parentState, "running", parentRunning);
307
310
  state.running = computed(() => {
308
- return loadingCombine(parentState.running, state.newSearchComputeds, textSearchIndex.state.running);
311
+ return loadingCombine(parentRunning.value, state.newSearchComputeds, textSearchIndex.state.running);
309
312
  });
310
313
  state.objectsInOrder = computed(() => state.objectsInOrderRefs.map((ref) => unref(ref)));
311
314
 
package/use/listSort.js CHANGED
@@ -1,12 +1,13 @@
1
- import { assignReactiveObject, keyDiff, loadingCombine, difference } from "../utils/index.js";
1
+ import { assignReactiveObject, difference, keyDiff, loadingCombine } from "../utils/index.js";
2
+ import { proxyRunning } from "../utils/proxyRunning.js";
2
3
  import {
3
- listSortStateKeys,
4
- listFilterStateKeys,
5
- listRelatedStateKeys,
6
4
  listCalculatedStateKeys,
7
- listSubscriptionStateKeys,
5
+ listFilterStateKeys,
8
6
  listInstanceStateKeys,
7
+ listRelatedStateKeys,
9
8
  listSearchStateKeys,
9
+ listSortStateKeys,
10
+ listSubscriptionStateKeys,
10
11
  } from "./listKeys.js";
11
12
  import { useWatchesRunning } from "./watchesRunning.js";
12
13
  import get from "lodash-es/get.js";
@@ -17,7 +18,7 @@ import isNull from "lodash-es/isNull.js";
17
18
  import isUndefined from "lodash-es/isUndefined.js";
18
19
  import throttle from "lodash-es/throttle.js";
19
20
  import zip from "lodash-es/zip.js";
20
- import { effectScope, reactive, toRef, unref, watch, computed } from "vue";
21
+ import { computed, effectScope, reactive, ref, toRef, unref, watch } from "vue";
21
22
 
22
23
  const collator = new Intl.Collator(undefined, { numeric: true });
23
24
 
@@ -251,8 +252,10 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
251
252
 
252
253
  state.objectsInOrder = computed(() => state.objectsInOrderRefs.map((e) => unref(e)));
253
254
  state.sortRunning = computed(() => loadingCombine(watchesRunning.state.running, state.outstandingEffects));
255
+ const parentRunning = ref(undefined);
256
+ proxyRunning(parentState, "running", parentRunning);
254
257
  state.running = computed(() =>
255
- loadingCombine(watchesRunning.state.running, state.outstandingEffects, parentState.running)
258
+ loadingCombine(watchesRunning.state.running, state.outstandingEffects, parentRunning.value)
256
259
  );
257
260
  });
258
261
 
@@ -1,3 +1,4 @@
1
+ import { proxyRunning } from "../utils/index.js";
1
2
  import { keyDiff } from "../utils/keyDiff.js";
2
3
  import { loadingCombine } from "../utils/loadingCombine.js";
3
4
  import { objectInstanceStateKeys } from "./objectInstance.js";
@@ -5,7 +6,7 @@ import { objectRelatedStateKeys } from "./objectRelated.js";
5
6
  import { objectSubscriptionStateKeys } from "./objectSubscription.js";
6
7
  import { useWatchesRunning } from "./watchesRunning.js";
7
8
  import isEmpty from "lodash-es/isEmpty.js";
8
- import { computed, effectScope, onScopeDispose, reactive, toRef, watch } from "vue";
9
+ import { computed, effectScope, onScopeDispose, reactive, ref, toRef, watch } from "vue";
9
10
 
10
11
  export const objectCalculatedStateKeys = [
11
12
  "calculatedObject",
@@ -99,7 +100,9 @@ export function useObjectCalculated({ parentState, calculatedObjectRules }) {
99
100
  });
100
101
 
101
102
  state.calculatedRunning = toRef(watchesRunning.state, "running");
102
- state.running = computed(() => loadingCombine(watchesRunning.state.running, parentState.running));
103
+ const parentRunning = ref(undefined);
104
+ proxyRunning(parentState, "running", parentRunning);
105
+ state.running = computed(() => loadingCombine(watchesRunning.state.running, parentRunning));
103
106
 
104
107
  onScopeDispose(() => {
105
108
  for (const key in calculatedObjectEffectScopes) {
@@ -1,4 +1,4 @@
1
- import { loadingCombine } from "../utils/index.js";
1
+ import { loadingCombine, proxyRunning } from "../utils/index.js";
2
2
  import { keyDiff } from "../utils/keyDiff.js";
3
3
  import { objectInstanceStateKeys } from "./objectInstance.js";
4
4
  import { objectSubscriptionStateKeys } from "./objectSubscription.js";
@@ -9,7 +9,7 @@ import isArray from "lodash-es/isArray.js";
9
9
  import isEmpty from "lodash-es/isEmpty.js";
10
10
  import isEqual from "lodash-es/isEqual.js";
11
11
  import isUndefined from "lodash-es/isUndefined.js";
12
- import { computed, effectScope, onScopeDispose, reactive, toRef, unref, watch } from "vue";
12
+ import { computed, effectScope, onScopeDispose, reactive, ref, toRef, unref, watch } from "vue";
13
13
 
14
14
  export const objectRelatedStateKeys = [
15
15
  "relatedObject",
@@ -129,7 +129,9 @@ export function useObjectRelated({ parentState, relatedObjectRules }) {
129
129
  });
130
130
 
131
131
  state.relatedRunning = toRef(watchesRunning.state, "running");
132
- state.running = computed(() => loadingCombine(watchesRunning.state.running, parentState.running));
132
+ const parentRunning = ref(undefined);
133
+ proxyRunning(parentState, "running", parentRunning);
134
+ state.running = computed(() => loadingCombine(watchesRunning.state.running, parentRunning));
133
135
 
134
136
  onScopeDispose(() => {
135
137
  for (const key in relatedObjectEffectScopes) {
package/utils/index.js CHANGED
@@ -8,6 +8,7 @@ export * from "./getFakeId.js";
8
8
  export * from "./keyDiff.js";
9
9
  export * from "./lifecycleDebug.js";
10
10
  export * from "./loadingCombine.js";
11
+ export * from "./proxyRunning.js";
11
12
  export * from "./relatedCalculatedHelpers.js";
12
13
  export * from "./set.js";
13
14
  export * from "./transformWalk.js";
@@ -0,0 +1,26 @@
1
+ import { nextTick, toRef, watch } from "vue";
2
+
3
+ export function proxyRunning(parentState, parentStateProp, state, prop) {
4
+ // we want you to have been running false for two ticks before we stop you, but we want you to start running true immediately
5
+ // the point of this is to reduce running flicker where the stack has running states awaiting other running states
6
+ let falseTickCount = 0;
7
+ const checkStillFalse = () => {
8
+ if (falseTickCount > 1) {
9
+ state[prop] = false;
10
+ falseTickCount = 0;
11
+ } else if (parentState[parentStateProp] === false) {
12
+ falseTickCount++;
13
+ nextTick(checkStillFalse);
14
+ } else {
15
+ falseTickCount = 0;
16
+ }
17
+ };
18
+ watch(toRef(parentState, parentStateProp), (running) => {
19
+ if (running && state[prop] !== running) {
20
+ state[prop] = running;
21
+ } else if (!running && state[prop] !== running) {
22
+ nextTick(checkStillFalse);
23
+ }
24
+ });
25
+ state[prop] = parentState[parentStateProp];
26
+ }
package/utils/watches.js CHANGED
@@ -5,8 +5,11 @@ export class ImmediateStopWatch {
5
5
  constructor() {}
6
6
 
7
7
  // watchFuncArgs are for immediate call only.
8
- start(watchSources, watchFunc, watchFuncArgs = []) {
9
- this.stopWatch = watch(watchSources, watchFunc);
8
+ start(watchSources, watchFunc, watchFuncArgs = [], watchOptions = {}) {
9
+ if (watchOptions.immediate) {
10
+ throw new Error("ImmediateStopWatch is always immediate.");
11
+ }
12
+ this.stopWatch = watch(watchSources, watchFunc, watchOptions);
10
13
  watchFunc(...watchFuncArgs);
11
14
  }
12
15