@arrai-innovations/reactive-helpers 21.0.1 → 21.1.2

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 (46) hide show
  1. package/README.md +11 -0
  2. package/config/commonCrud.js +12 -0
  3. package/config/listCrud.js +21 -6
  4. package/config/objectCrud.js +53 -11
  5. package/index.js +3 -0
  6. package/package.json +7 -5
  7. package/types/config/commonCrud.d.ts +8 -0
  8. package/types/config/listCrud.d.ts +14 -6
  9. package/types/config/objectCrud.d.ts +48 -10
  10. package/types/index.d.ts +3 -0
  11. package/types/tests/unit/utils/isReactiveTyped.spec.d.ts +1 -0
  12. package/types/tests/unit/utils/refIfReactive.spec.d.ts +1 -0
  13. package/types/tests/unit/utils/toRefsIfReactive.spec.d.ts +1 -0
  14. package/types/use/cancellableIntent.d.ts +16 -12
  15. package/types/use/error.d.ts +35 -7
  16. package/types/use/listCalculated.d.ts +2 -2
  17. package/types/use/listInstance.d.ts +22 -19
  18. package/types/use/listRelated.d.ts +6 -6
  19. package/types/use/listSubscription.d.ts +12 -3
  20. package/types/use/loading.d.ts +16 -7
  21. package/types/use/loadingError.d.ts +6 -2
  22. package/types/use/objectInstance.d.ts +38 -20
  23. package/types/use/objectSubscription.d.ts +22 -5
  24. package/types/use/proxyError.d.ts +21 -18
  25. package/types/use/proxyLoading.d.ts +18 -8
  26. package/types/use/proxyLoadingError.d.ts +20 -5
  27. package/types/utils/isReactiveTyped.d.ts +6 -0
  28. package/types/utils/refIfReactive.d.ts +2 -1
  29. package/types/utils/toRefsIfReactive.d.ts +14 -0
  30. package/use/cancellableIntent.js +8 -7
  31. package/use/error.js +22 -3
  32. package/use/listCalculated.js +1 -1
  33. package/use/listInstance.js +28 -26
  34. package/use/listRelated.js +3 -3
  35. package/use/listSubscription.js +102 -84
  36. package/use/loading.js +11 -3
  37. package/use/loadingError.js +3 -1
  38. package/use/objectInstance.js +77 -23
  39. package/use/objectSubscription.js +25 -26
  40. package/use/proxyError.js +28 -15
  41. package/use/proxyLoading.js +21 -8
  42. package/use/proxyLoadingError.js +33 -7
  43. package/utils/cancellableFetch.js +21 -1
  44. package/utils/isReactiveTyped.js +10 -0
  45. package/utils/refIfReactive.js +22 -2
  46. package/utils/toRefsIfReactive.js +28 -0
@@ -1,15 +1,25 @@
1
1
  /**
2
- * @typedef {Pick<import('./loading.js').LoadingStatus, "loading">} ReadonlyLoadingStatus
3
- * @typedef {import("vue").Ref<ReadonlyLoadingStatus>} RefLoadingStatus
4
- * @typedef {ReadonlyLoadingStatus | RefLoadingStatus} WatchableLoading
2
+ * @typedef {import('./loading.js').LoadingProperties | import("vue").Reactive<import('./loading.js').LoadingProperties>} WatchableLoading
3
+ * @typedef {import('vue').MaybeRef<WatchableLoading>} MaybeRefWatchableLoading
4
+ * @typedef {import('./loading.js').LoadingProperties} ReadonlyLoadingStatus
5
5
  */
6
6
  /**
7
7
  * A composable function for aggregating loading state across multiple sources.
8
8
  *
9
- * @param {WatchableLoading[]} loadings - The loading states to monitor.
9
+ * @param {import('vue').MaybeRef<MaybeRefWatchableLoading[]>} loadings - The loading states to monitor.
10
10
  * @returns {ReadonlyLoadingStatus} An object containing the aggregated loading field.
11
11
  */
12
- export function useProxyLoading(loadings: WatchableLoading[]): ReadonlyLoadingStatus;
13
- export type ReadonlyLoadingStatus = Pick<import("./loading.js").LoadingStatus, "loading">;
14
- export type RefLoadingStatus = import("vue").Ref<ReadonlyLoadingStatus>;
15
- export type WatchableLoading = ReadonlyLoadingStatus | RefLoadingStatus;
12
+ export function useProxyLoading(loadings: import("vue").MaybeRef<MaybeRefWatchableLoading[]>): ReadonlyLoadingStatus;
13
+ /**
14
+ * Adapt an object with reactive loading state into a WatchableLoading shape.
15
+ * Accepts either an object with a `state` property or an object that already exposes `loading`.
16
+ *
17
+ * @param {import('vue').MaybeRef<{ state: WatchableLoading } | WatchableLoading>} source - The source object to adapt.
18
+ * @returns {WatchableLoading} - The adapted WatchableLoading object.
19
+ */
20
+ export function asWatchableLoading(source: import("vue").MaybeRef<{
21
+ state: WatchableLoading;
22
+ } | WatchableLoading>): WatchableLoading;
23
+ export type WatchableLoading = import("./loading.js").LoadingProperties | import("vue").Reactive<import("./loading.js").LoadingProperties>;
24
+ export type MaybeRefWatchableLoading = import("vue").MaybeRef<WatchableLoading>;
25
+ export type ReadonlyLoadingStatus = import("./loading.js").LoadingProperties;
@@ -1,13 +1,28 @@
1
1
  /**
2
2
  * @typedef {import('./proxyLoading.js').WatchableLoading & import('./proxyError.js').WatchableError} WatchableLoadingError
3
- * @typedef {import('./proxyLoading.js').ReadonlyLoadingStatus & import('./proxyError.js').ReadonlyErrorStatus} ProxyLoadingError
3
+ * @typedef {import('vue').MaybeRef<WatchableLoadingError>} MaybeRefWatchableLoadingError
4
+ * @typedef {import('./loading.js').LoadingProperties & import('./error.js').ReadonlyErrorStatus} ProxyLoadingError
4
5
  */
5
6
  /**
6
- * A composable function combining aggregated loading and error state.
7
+ * A composable function combining aggregated loading and error state. Use `asWatchableLoadingError` to convert <List|Object><Instance|Subscription> to WatchableLoadingError.
7
8
  *
8
- * @param {WatchableLoadingError[]} loadingErrors - The loading and error states to monitor.
9
+ * @param {import('vue').MaybeRef<MaybeRefWatchableLoadingError[]>} loadingErrors - The loading and error states to monitor.
9
10
  * @returns {ProxyLoadingError} - An object containing aggregated reactive fields and actions for both loading and error state.
10
11
  */
11
- export function useProxyLoadingError(loadingErrors: WatchableLoadingError[]): ProxyLoadingError;
12
+ export function useProxyLoadingError(loadingErrors: import("vue").MaybeRef<MaybeRefWatchableLoadingError[]>): ProxyLoadingError;
13
+ /**
14
+ * @typedef {{ state: import('vue').Reactive<import('./error.js').ErrorProperties> } & import('./error.js').ErrorReadOnlyFunctions} SeparateStateLoadingError
15
+ */
16
+ /**
17
+ * Adapt an object that exposes loading/error state and clearError into a WatchableLoadingError shape.
18
+ *
19
+ * @param {import('vue').MaybeRef<SeparateStateLoadingError | WatchableLoadingError>} source - The source object to adapt.
20
+ * @returns {WatchableLoadingError} - The adapted WatchableLoadingError object.
21
+ */
22
+ export function asWatchableLoadingError(source: import("vue").MaybeRef<SeparateStateLoadingError | WatchableLoadingError>): WatchableLoadingError;
12
23
  export type WatchableLoadingError = import("./proxyLoading.js").WatchableLoading & import("./proxyError.js").WatchableError;
13
- export type ProxyLoadingError = import("./proxyLoading.js").ReadonlyLoadingStatus & import("./proxyError.js").ReadonlyErrorStatus;
24
+ export type MaybeRefWatchableLoadingError = import("vue").MaybeRef<WatchableLoadingError>;
25
+ export type ProxyLoadingError = import("./loading.js").LoadingProperties & import("./error.js").ReadonlyErrorStatus;
26
+ export type SeparateStateLoadingError = {
27
+ state: import("vue").Reactive<import("./error.js").ErrorProperties>;
28
+ } & import("./error.js").ErrorReadOnlyFunctions;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @template {object} T
3
+ * @param {T | import('vue').Reactive<T>} v - The value to check.
4
+ * @returns {v is import('vue').Reactive<T>} - True if the value is reactive, false otherwise.
5
+ */
6
+ export function isReactiveTyped<T extends unknown>(v: T | import("vue").Reactive<T>): v is import("vue").Reactive<T>;
@@ -1 +1,2 @@
1
- export function refIfReactive<T, K extends keyof T>(source: (T & object) | undefined | null, property: K, defaultValue?: T[K]): import("vue").ComputedRef<T[K]> | import("vue").Ref<T[K]>;
1
+ export function refIfReactive<T, K extends keyof T>(source: (T & object) | undefined | null, property: K, defaultValue?: T[K] | undefined): import("vue").ComputedRef<T[K] | undefined> | import("vue").Ref<T[K] | undefined>;
2
+ export function pkRefIfReactive(source: object | undefined | null, property?: string, defaultValue?: import("../config/commonCrud.js").Pk | null): import("vue").ComputedRef<import("../config/commonCrud.js").Pk | undefined>;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @template {object} T
3
+ * @overload
4
+ * @param {import('vue').Reactive<T>} source - The source reactive object.
5
+ * @returns {import('vue').ToRefs<T>} - The refs of the reactive object.
6
+ */
7
+ export function toRefsIfReactive<T extends unknown>(source: import("vue").Reactive<T>): import("vue").ToRefs<T>;
8
+ /**
9
+ * @template {object} T
10
+ * @overload
11
+ * @param {T} source - The source object.
12
+ * @returns {T} - The original object.
13
+ */
14
+ export function toRefsIfReactive<T extends unknown>(source: T): T;
@@ -34,7 +34,7 @@ export class CancellableIntentError extends Error {
34
34
  */
35
35
 
36
36
  /**
37
- * @typedef {object} CancellableIntentRawState - The raw state of the cancellable intent.
37
+ * @typedef {object} CancellableIntentMyState - The raw state of the cancellable intent.
38
38
  * @property {import('vue').ComputedRef<boolean>|undefined} active - Whether there are active intents.
39
39
  * @property {import('vue').ComputedRef<boolean>|undefined} resolving - Whether there are resolving intents.
40
40
  * @property {boolean} clearActiveOnResolved - Whether to clear the active state when the promise resolves.
@@ -44,10 +44,11 @@ export class CancellableIntentError extends Error {
44
44
  */
45
45
 
46
46
  /**
47
- * @typedef {import("vue").Reactive<
48
- * CancellableIntentRawState &
49
- * Pick<import('./error.js').ErrorStatus, 'error' | 'errored'>
50
- * >} CancellableIntentState - The state of the cancellable intent.
47
+ * @typedef {CancellableIntentMyState & import('./error.js').ErrorProperties} CancellableIntentRawState - The raw state of the cancellable intent.
48
+ */
49
+
50
+ /**
51
+ * @typedef {import("vue").Reactive<CancellableIntentRawState>} CancellableIntentState - The state of the cancellable intent.
51
52
  */
52
53
 
53
54
  /**
@@ -63,7 +64,7 @@ export class CancellableIntentError extends Error {
63
64
  */
64
65
 
65
66
  /**
66
- * @typedef {(runTracking: CommonRunTracking) => import('../utils/cancellablePromise.js').MaybeCancellablePromise<void>} AwaitableWithCancel - A function that returns a promise that can be cancelled.
67
+ * @typedef {(runTracking: CommonRunTracking) => import('../utils/cancellablePromise.js').MaybeCancellablePromise<unknown>} AwaitableWithCancel - A function that returns a promise that can be cancelled. The return value of the promise is not used.
67
68
  */
68
69
 
69
70
  /**
@@ -95,7 +96,7 @@ export class CancellableIntentError extends Error {
95
96
  */
96
97
 
97
98
  /**
98
- * @typedef {MyCancellableIntent & Pick<import('./error.js').ErrorStatus, "clearError">} CancellableIntent - The instance of the cancellable intent.
99
+ * @typedef {MyCancellableIntent & import('./error.js').ErrorReadOnlyFunctions} CancellableIntent - The instance of the cancellable intent.
99
100
  */
100
101
 
101
102
  /**
package/use/error.js CHANGED
@@ -9,15 +9,34 @@ import { readonly, ref } from "vue";
9
9
  */
10
10
 
11
11
  /**
12
- * The error state API.
13
- *
14
- * @typedef {object} ErrorStatus
12
+ * @typedef {object} ErrorProperties
15
13
  * @property {ErrorReadonlyRef} error - The error that occurred.
16
14
  * @property {ErroredReadonlyRef} errored - Whether an error has occurred.
15
+ */
16
+
17
+ /**
18
+ * @typedef {object} ErrorFunctions
17
19
  * @property {(error: Error) => void} setError - Set the error state.
18
20
  * @property {ClearErrorFn} clearError - Clear the error state.
19
21
  */
20
22
 
23
+ /**
24
+ * Proxies can still clear errors but cannot set them directly.
25
+ *
26
+ * @typedef {object} ErrorReadOnlyFunctions
27
+ * @property {ClearErrorFn} clearError - Clear the error state.
28
+ */
29
+
30
+ /**
31
+ * @typedef {ErrorProperties & ErrorReadOnlyFunctions} ReadonlyErrorStatus
32
+ */
33
+
34
+ /**
35
+ * The error state API.
36
+ *
37
+ * @typedef {ErrorProperties & ErrorFunctions} ErrorStatus
38
+ */
39
+
21
40
  /**
22
41
  * A composable function for managing error state.
23
42
  *
@@ -35,7 +35,7 @@ import { computed, effectScope, nextTick, reactive, ref, toRef, toRefs, unref, w
35
35
  * The raw state for a list calculated.
36
36
  *
37
37
  * @typedef {object} ListCalculatedRawState - The raw state for a list calculated property.
38
- * @property {{[pk: string]: {[rule: string]: import('vue').ComputedRef<any>}}} calculatedObjects - The calculated objects.
38
+ * @property {{[pk: import('../config/commonCrud.js').Pk]: {[rule: string]: import('vue').ComputedRef<any>}}} calculatedObjects - The calculated objects.
39
39
  * @property {ListCalculatedRules} calculatedObjectsRules - The rules for the calculated objects.
40
40
  * @property {boolean} calculatedObjectsParentStateObjectsWatchRunning - Whether the parent state objects watch is running.
41
41
  * @property {boolean} calculatedObjectsWatchRunning - Whether the calculated objects watch is running.
@@ -59,7 +59,7 @@ export class ListInstanceError extends Error {
59
59
  /**
60
60
  * The objects by pk.
61
61
  *
62
- * @typedef {{[pk: string]: import('../use/objectInstance.js').ExistingCrudObject}} ObjectsByPk
62
+ * @typedef {{[pk: import('../config/commonCrud.js').Pk]: import('../use/objectInstance.js').ExistingCrudObject}} ObjectsByPk
63
63
  */
64
64
 
65
65
  /**
@@ -71,7 +71,7 @@ export class ListInstanceError extends Error {
71
71
  /**
72
72
  * The order of the objects in the list.
73
73
  *
74
- * @typedef {import('vue').ComputedRef<string[]>} ListOrder
74
+ * @typedef {import('vue').ComputedRef<import('../config/commonCrud.js').Pk[]>} ListOrder
75
75
  */
76
76
 
77
77
  /**
@@ -84,7 +84,7 @@ export class ListInstanceError extends Error {
84
84
  */
85
85
 
86
86
  /**
87
- * @typedef {Map<string, import('vue').Reactive<import('../use/objectInstance.js').ExistingCrudObject>>} ObjectsMap
87
+ * @typedef {Map<import('../config/commonCrud.js').Pk, import('vue').Reactive<import('../use/objectInstance.js').ExistingCrudObject>>} ObjectsMap
88
88
  */
89
89
 
90
90
  /**
@@ -156,13 +156,13 @@ export class ListInstanceError extends Error {
156
156
  * @property {PushObjectsFn} pushObjects - Customizable callback for handling new objects per page.
157
157
  * @property {(object: import('../use/objectInstance.js').ExistingCrudObject) => void} addListObject - Adds an object to the list.
158
158
  * @property {(object: import('../use/objectInstance.js').ExistingCrudObject) => void} updateListObject - Updates an object in the list.
159
- * @property {(objectId: string) => void} deleteListObject - Deletes an object from the list by pk.
159
+ * @property {(objectId: import('../config/commonCrud.js').PkInput) => void} deleteListObject - Deletes an object from the list by pk.
160
160
  * @property {(options?: ClearListOptions) => void} clearList - Clears the list objects and optionally keeps pagination, totals,
161
161
  * or error state.
162
- * @property {() => string} getFakePk - Generates a unique fake pk for use within the list.
163
- * @property {() => import('../utils/cancellablePromise.js').MaybeCancellablePromise<boolean|never>} list - Initiates a fetch to retrieve objects according to the CRUD configuration, returning a promise to a boolean indicating success.
164
- * @property {(args: {pks?: string[]}) => Promise<boolean>} bulkDelete - Deletes objects from the list by pk, returning a promise to a boolean indicating success.
165
- * @property {() => Promise<object|string|false>} executeAction - Initiates an action on all objects in the list, returning the response, or false if the action failed.
162
+ * @property {() => import('../config/commonCrud.js').Pk} getFakePk - Generates a unique fake pk for use within the list.
163
+ * @property {(args?: import('../config/listCrud.js').AdditionalListArgs) => import('../utils/cancellablePromise.js').MaybeCancellablePromise<boolean|never>} list - Initiates a fetch to retrieve objects according to the CRUD configuration, returning a promise to a boolean indicating success.
164
+ * @property {(args?: {pks?: import('../config/commonCrud.js').Pk[]} & import('../config/listCrud.js').AdditionalListArgs) => Promise<boolean>} bulkDelete - Deletes objects from the list by pk, returning a promise to a boolean indicating success.
165
+ * @property {(args: {action: string, pks?: import('../config/commonCrud.js').Pk[]} & import('../config/listCrud.js').AdditionalListArgs) => Promise<object|string|boolean|null>} executeAction - Initiates an action on all objects in the list, returning the response, or null if the action failed.
166
166
  * @property {(info: PaginateInfo) => void} setPaginateInfo - The method to update pagination information.
167
167
  * @property {(total: ColumnTotals) => void} setColumnTotals - The method to update column totals.
168
168
  */
@@ -262,7 +262,7 @@ export function useListInstance({ props, handlers = {} }) {
262
262
 
263
263
  const [_objectsProxy, _objectsMapProxy] = es.run(() => {
264
264
  // ### do not use this directly, because we proxy `set` to make sure that values are reactive ###
265
- /** @type {import('vue').Reactive<Map<string, import('../use/objectInstance.js').ExistingCrudObject>>} */
265
+ /** @type {import('vue').Reactive<Map<import('../config/commonCrud.js').Pk, import('../use/objectInstance.js').ExistingCrudObject>>} */
266
266
  const _objectsMap = shallowReactive(new Map()); // maps are ordered, if you don't clear lists, you need to insert pages in order.
267
267
 
268
268
  // ### this is a proxy to make the map behave like an object for reactivity ###
@@ -417,8 +417,7 @@ export function useListInstance({ props, handlers = {} }) {
417
417
  setColumnTotals: (total) => {
418
418
  assignReactiveObject(state.columnTotals, total || {});
419
419
  },
420
- list: (args) => {
421
- const { runId, isCurrentRun } = args || {};
420
+ list: (args = {}) => {
422
421
  // this function cannot be async, or the resulting promise will lose its .cancel() method
423
422
  if (promises.list) {
424
423
  // if a retrieve is already in progress, return the existing promise
@@ -433,6 +432,7 @@ export function useListInstance({ props, handlers = {} }) {
433
432
  let listPromise = null;
434
433
  try {
435
434
  const listCrudArgs = {
435
+ ...args,
436
436
  target: state.crud.args,
437
437
  pkKey: state.pkKey,
438
438
  params: state.params,
@@ -442,10 +442,6 @@ export function useListInstance({ props, handlers = {} }) {
442
442
  setPaginateInfo: self.setPaginateInfo,
443
443
  setColumnTotals: self.setColumnTotals,
444
444
  };
445
- if (runId) {
446
- listCrudArgs.runId = runId;
447
- listCrudArgs.isCurrentRun = isCurrentRun;
448
- }
449
445
  listPromise = state.crud.list(listCrudArgs);
450
446
  } catch (e) {
451
447
  loadingError.setError(e);
@@ -475,7 +471,7 @@ export function useListInstance({ props, handlers = {} }) {
475
471
  );
476
472
  return promises.list;
477
473
  },
478
- bulkDelete: ({ pks } = {}) => {
474
+ bulkDelete: ({ pks, ...additionalArgs } = {}) => {
479
475
  if (state.loading) {
480
476
  return Promise.reject(new ListInstanceError("already loading.", "already-loading"));
481
477
  }
@@ -486,6 +482,7 @@ export function useListInstance({ props, handlers = {} }) {
486
482
  loadingError.clearError();
487
483
  return state.crud
488
484
  .bulkDelete({
485
+ ...additionalArgs,
489
486
  target: state.crud.args,
490
487
  pks,
491
488
  pkKey: state.pkKey,
@@ -503,7 +500,7 @@ export function useListInstance({ props, handlers = {} }) {
503
500
  loadingError.clearLoading();
504
501
  });
505
502
  },
506
- executeAction: ({ pks, action }) => {
503
+ executeAction: ({ pks, action, ...additionalArgs }) => {
507
504
  if (state.loading) {
508
505
  return Promise.reject(new ListInstanceError("already loading.", "already-loading"));
509
506
  }
@@ -514,8 +511,9 @@ export function useListInstance({ props, handlers = {} }) {
514
511
  loadingError.clearError();
515
512
  return state.crud
516
513
  .executeAction({
517
- action,
514
+ ...additionalArgs,
518
515
  target: state.crud.args,
516
+ action,
519
517
  pks,
520
518
  pkKey: state.pkKey,
521
519
  })
@@ -532,36 +530,39 @@ export function useListInstance({ props, handlers = {} }) {
532
530
  });
533
531
  },
534
532
  addListObject: (object) => {
533
+ const pk = String(object[state.pkKey]);
535
534
  if (!object[state.pkKey]) {
536
535
  throw new ListInstanceError(
537
536
  `addListObject: object missing pk(${state.pkKey}).\n${inspect(object)}`,
538
537
  "missing-pk"
539
538
  );
540
539
  }
541
- if (object[state.pkKey] in state.objects) {
540
+ if (pk in state.objects) {
542
541
  throw new ListInstanceError(
543
- `addListObject: list already has object for pk(${state.pkKey}): ${inspect(object[state.pkKey])}`,
542
+ `addListObject: list already has object for pk(${state.pkKey}): ${inspect(pk)}`,
544
543
  "duplicate-pk"
545
544
  );
546
545
  }
547
- state.objects[object[state.pkKey]] = object;
546
+ state.objects[pk] = object;
548
547
  },
549
548
  updateListObject: (object) => {
549
+ const pk = String(object[state.pkKey]);
550
550
  if (!object[state.pkKey]) {
551
551
  throw new ListInstanceError(
552
552
  `updateListObject: object missing pk(${state.pkKey}).\n${inspect(object)}`,
553
553
  "missing-pk"
554
554
  );
555
555
  }
556
- if (!(object[state.pkKey] in state.objects)) {
556
+ if (!(pk in state.objects)) {
557
557
  throw new ListInstanceError(
558
- `updateListObject: list missing object for update by pk(${state.pkKey}): ${inspect(object[state.pkKey])}`,
558
+ `updateListObject: list missing object for update by pk(${state.pkKey}): ${inspect(pk)}`,
559
559
  "missing-object"
560
560
  );
561
561
  }
562
- assignReactiveObject(state.objects[object[state.pkKey]], object);
562
+ assignReactiveObject(state.objects[pk], object);
563
563
  },
564
- deleteListObject: (pk) => {
564
+ deleteListObject: (pkInput) => {
565
+ const pk = String(pkInput);
565
566
  if (!(pk in state.objects)) {
566
567
  throw new ListInstanceError(
567
568
  `deleteListObject: list missing object for removal by pk(${state.pkKey}): ${inspect(pk)}`,
@@ -588,7 +589,8 @@ export function useListInstance({ props, handlers = {} }) {
588
589
  getFakePk: () => getFakePk(state.objects, state.pkKey),
589
590
  pushObjects: (newObjects) => {
590
591
  newObjects.forEach((newObject) => {
591
- if (newObject[state.pkKey] in state.objects) {
592
+ const pk = String(newObject[state.pkKey]);
593
+ if (pk in state.objects) {
592
594
  self.updateListObject.call(this, newObject);
593
595
  } else {
594
596
  self.addListObject.call(this, newObject);
@@ -41,18 +41,18 @@ import { computed, effectScope, nextTick, onScopeDispose, reactive, ref, toRef,
41
41
  *
42
42
  * @typedef {object} ListRelatedRawState
43
43
  * @property {{
44
- * [pk: string]: {
44
+ * [pk: import('../config/commonCrud.js').Pk]: {
45
45
  * [rule: string]: import('vue').ComputedRef<any>,
46
46
  * },
47
47
  * }} relatedObjects - Stores computed references to related objects, allowing for dynamic access based on object pk and specific rules.
48
48
  * @property {ListRelatedRules} relatedObjectsRules - Defines the rules for establishing relationships, such as foreign key links and sorting orders.
49
49
  * @property {{
50
- * [pk: string]: {
50
+ * [pk: import('../config/commonCrud.js').Pk]: {
51
51
  * [rule: string]: import('vue').ComputedRef<[object, string]>,
52
52
  * },
53
53
  * }} objAndKeyForPkAndRule - Maps each object pk and rule to a tuple consisting of the related object and its respective key, facilitating direct data manipulation.
54
54
  * @property {{
55
- * [pk: string]: {
55
+ * [pk: import('../config/commonCrud.js').Pk]: {
56
56
  * [rule: string]: import('vue').ComputedRef<any>,
57
57
  * },
58
58
  * }} fkForPkAndRule - Maintains computed references to the foreign keys for each object pk and rule, crucial for navigating complex data relationships.
@@ -5,8 +5,8 @@ import inspect from "browser-util-inspect";
5
5
  import cloneDeep from "lodash-es/cloneDeep.js";
6
6
  import isEmpty from "lodash-es/isEmpty.js";
7
7
  import isObject from "lodash-es/isObject.js";
8
- import { reactive, toRef, toRefs } from "vue";
9
- import { useProxyLoadingError } from "./proxyLoadingError.js";
8
+ import { reactive, readonly, ref, toRef, toRefs } from "vue";
9
+ import { asWatchableLoadingError, useProxyLoadingError } from "./proxyLoadingError.js";
10
10
  import { refIfReactive } from "../utils/refIfReactive.js";
11
11
 
12
12
  /**
@@ -89,12 +89,16 @@ export class ListSubscriptionError extends Error {
89
89
  * @typedef {ListSubscriptionFunctions & ListSubscriptionProperties} ListSubscription
90
90
  */
91
91
 
92
+ /**
93
+ * @typedef {object} ListSubscriptionOwnOptions
94
+ * @property {import("./listInstance.js").ListInstance} [listInstance] - A list instance to use instead of creating one.
95
+ */
96
+
92
97
  /**
93
98
  * Defines the settings required to establish a list subscription, detailing how list instances should handle updates
94
99
  * and subscriptions based on the given properties.
95
100
  *
96
- * @typedef {object & import("./listInstance.js").ListInstanceOptions} ListSubscriptionOptions
97
- * @property {import("./listInstance.js").ListInstance} listInstance - A list instance to use instead of creating one.
101
+ * @typedef {import("./listInstance.js").ListInstanceOptions & ListSubscriptionOwnOptions} ListSubscriptionOptions
98
102
  */
99
103
 
100
104
  /**
@@ -182,7 +186,7 @@ export function useListSubscription({ listInstance, props, handlers }) {
182
186
  const parentState = listInstance.state;
183
187
 
184
188
  const loadingError = useLoadingError();
185
- const proxyLoadingError = useProxyLoadingError([loadingError, parentState]);
189
+ const proxyLoadingError = useProxyLoadingError([loadingError, asWatchableLoadingError(listInstance)]);
186
190
  // stand alone in order to avoid circular dependency between state & intents.
187
191
  const intendToList = refIfReactive(props, "intendToList", false);
188
192
  const intendToSubscribe = refIfReactive(props, "intendToSubscribe", false);
@@ -208,95 +212,109 @@ export function useListSubscription({ listInstance, props, handlers }) {
208
212
  const subscribeIntent = useCancellableIntent({
209
213
  awaitableWithCancel: ({ runId, isCurrentRun }) => {
210
214
  // this function cannot be async, or the resulting promise will lose its .cancel() method
215
+ const isCancelled = ref(false);
211
216
  loadingError.setLoading();
212
217
  let subscribePromise, catchPromise;
213
218
  try {
214
- subscribePromise = parentState.crud.subscribe({
215
- runId,
216
- isCurrentRun,
217
- target: cloneDeep(parentState.crud.args),
218
- pkKey: parentState.pkKey,
219
- params: cloneDeep(parentState.params),
220
- applyObjectEvent: (
221
- /** @type {import('./objectInstance.js').ExistingCrudObject} */ data,
222
- /** @type {"create"|"update"|"delete"} */ action
223
- ) => {
224
- if (!data || (isObject(data) && isEmpty(data))) {
225
- throw new ListSubscriptionError(
226
- `got update with no data (${inspect(data)}), action: ${action}`,
227
- "empty-data"
228
- );
229
- }
230
- switch (action) {
231
- case "delete":
232
- try {
233
- listInstance.deleteListObject(data);
234
- } catch (err) {
235
- if (err.name === "ListInstanceError" && err.code === "missing-object") {
236
- console.warn(
237
- `deleteFromSubscription: delete for pk(${listInstance.state.pkKey}) not in objects (${inspect(data)}).`
219
+ subscribePromise = /** @type {import('../utils/cancellablePromise.js').CancellablePromise<void>} */ (
220
+ parentState.crud.subscribe({
221
+ runId,
222
+ isCurrentRun,
223
+ target: cloneDeep(parentState.crud.args),
224
+ pkKey: parentState.pkKey,
225
+ params: cloneDeep(parentState.params),
226
+ applyObjectEvent: (
227
+ /** @type {import('./objectInstance.js').ExistingCrudObject} */ data,
228
+ /** @type {"create"|"update"|"delete"} */ action
229
+ ) => {
230
+ if (!data || (isObject(data) && isEmpty(data))) {
231
+ throw new ListSubscriptionError(
232
+ `got update with no data (${inspect(data)}), action: ${action}`,
233
+ "empty-data"
234
+ );
235
+ }
236
+ switch (action) {
237
+ case "delete":
238
+ try {
239
+ const pkValue = isObject(data) ? data[parentState.pkKey] : data;
240
+ listInstance.deleteListObject(pkValue);
241
+ } catch (err) {
242
+ if (err.name === "ListInstanceError" && err.code === "missing-object") {
243
+ console.warn(
244
+ `deleteFromSubscription: delete for pk(${parentState.pkKey}) not in objects (${inspect(data)}).`
245
+ );
246
+ return;
247
+ }
248
+ throw err;
249
+ }
250
+ break;
251
+ case "create":
252
+ if (!data[parentState.pkKey]) {
253
+ throw new ListSubscriptionError(
254
+ `addFromSubscription: data missing pk(${parentState.pkKey}).\n${inspect(data)}`,
255
+ "missing-pk"
238
256
  );
239
- return;
240
257
  }
241
- throw err;
242
- }
243
- break;
244
- case "create":
245
- if (!data[listInstance.state.pkKey]) {
246
- throw new ListSubscriptionError(
247
- `addFromSubscription: data missing pk(${listInstance.state.pkKey}).\n${inspect(data)}`,
248
- "missing-pk"
249
- );
250
- }
251
- try {
252
- listInstance.addListObject(data);
253
- } catch (err) {
254
- if (err.name === "ListInstanceError" && err.code === "duplicate-pk") {
255
- console.warn(
256
- `addFromSubscription: add for pk(${listInstance.state.pkKey}) already in objects (${data[listInstance.state.pkKey]}).`
258
+ try {
259
+ listInstance.addListObject(data);
260
+ } catch (err) {
261
+ if (err.name === "ListInstanceError" && err.code === "duplicate-pk") {
262
+ console.warn(
263
+ `addFromSubscription: add for pk(${parentState.pkKey}) already in objects (${data[parentState.pkKey]}).`
264
+ );
265
+ return;
266
+ }
267
+ throw err;
268
+ }
269
+ break;
270
+ case "update":
271
+ if (!data[parentState.pkKey]) {
272
+ throw new ListSubscriptionError(
273
+ `updateFromSubscription: data missing pk(${parentState.pkKey}).\n${inspect(data)}`,
274
+ "missing-pk"
257
275
  );
258
- return;
259
276
  }
260
- throw err;
261
- }
262
- break;
263
- case "update":
264
- if (!data[listInstance.state.pkKey]) {
277
+ try {
278
+ listInstance.updateListObject(data);
279
+ } catch (err) {
280
+ if (err.name === "ListInstanceError" && err.code === "missing-object") {
281
+ console.warn(
282
+ `updateFromSubscription: update for pk(${parentState.pkKey}) not in objects (${data[parentState.pkKey]}).`
283
+ );
284
+ return;
285
+ }
286
+ throw err;
287
+ }
288
+ break;
289
+ default:
265
290
  throw new ListSubscriptionError(
266
- `updateFromSubscription: data missing pk(${listInstance.state.pkKey}).\n${inspect(data)}`,
267
- "missing-pk"
291
+ `got update for unknown action: ${action}\n${inspect(data)}`,
292
+ "unknown-action"
268
293
  );
269
- }
270
- try {
271
- listInstance.updateListObject(data);
272
- } catch (err) {
273
- if (err.name === "ListInstanceError" && err.code === "missing-object") {
274
- console.warn(
275
- `updateFromSubscription: update for pk(${listInstance.state.pkKey}) not in objects (${data[listInstance.state.pkKey]}).`
276
- );
277
- return;
278
- }
279
- throw err;
280
- }
281
- break;
282
- default:
283
- throw new ListSubscriptionError(
284
- `got update for unknown action: ${action}\n${inspect(data)}`,
285
- "unknown-action"
286
- );
287
- }
288
- },
289
- });
290
- // catching makes a new promise, we need to make sure the cancel method lives on.
291
- catchPromise = subscribePromise
292
- .catch((/** @type {Error} */ err) => {
293
- console.error(err);
294
- loadingError.setError(err);
294
+ }
295
+ },
296
+ isCancelled: readonly(isCancelled),
295
297
  })
296
- .finally(() => {
297
- loadingError.clearLoading();
298
- });
299
- catchPromise.cancel = subscribePromise.cancel.bind(subscribePromise);
298
+ );
299
+ // catching makes a new promise, we need to make sure the cancel method lives on.
300
+ catchPromise = /** @type {import('../utils/cancellablePromise.js').CancellablePromise<void>} */ (
301
+ subscribePromise
302
+ .catch((/** @type {Error} */ err) => {
303
+ console.error(err);
304
+ loadingError.setError(err);
305
+ })
306
+ .finally(() => {
307
+ loadingError.clearLoading();
308
+ })
309
+ );
310
+ if (subscribePromise.cancel) {
311
+ const originalCancel = subscribePromise.cancel.bind(subscribePromise);
312
+ const cancelWithFlag = async (/** @type {any} */ reason) => {
313
+ isCancelled.value = true;
314
+ return originalCancel(reason);
315
+ };
316
+ catchPromise.cancel = cancelWithFlag;
317
+ }
300
318
  return catchPromise;
301
319
  } catch (err) {
302
320
  console.error(err);