@arrai-innovations/reactive-helpers 18.0.2 → 19.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/README.md +7 -1
- package/config/commonCrud.js +75 -0
- package/config/listCrud.js +57 -56
- package/config/objectCrud.js +110 -77
- package/index.js +6 -1
- package/package.json +3 -2
- package/types/config/commonCrud.d.ts +19 -0
- package/types/config/commonCrud.d.ts.map +1 -0
- package/types/config/listCrud.d.ts +44 -39
- package/types/config/listCrud.d.ts.map +1 -1
- package/types/config/objectCrud.d.ts +102 -40
- package/types/config/objectCrud.d.ts.map +1 -1
- package/types/index.d.ts +6 -1
- package/types/use/cancellableIntent.d.ts +2 -13
- package/types/use/cancellableIntent.d.ts.map +1 -1
- package/types/use/listInstance.d.ts +10 -10
- package/types/use/listInstance.d.ts.map +1 -1
- package/types/use/loadingError.d.ts +30 -6
- package/types/use/loadingError.d.ts.map +1 -1
- package/types/use/objectInstance.d.ts +83 -31
- package/types/use/objectInstance.d.ts.map +1 -1
- package/types/use/objectSubscription.d.ts +33 -29
- package/types/use/objectSubscription.d.ts.map +1 -1
- package/types/use/proxyLoadingError.d.ts +8 -23
- package/types/use/proxyLoadingError.d.ts.map +1 -1
- package/types/utils/cancellableFetch.d.ts +12 -0
- package/types/utils/cancellableFetch.d.ts.map +1 -0
- package/types/utils/cancellablePromise.d.ts +40 -0
- package/types/utils/cancellablePromise.d.ts.map +1 -0
- package/types/utils/deepUnref.d.ts +26 -0
- package/types/utils/deepUnref.d.ts.map +1 -0
- package/types/utils/refIfReactive.d.ts +2 -0
- package/types/utils/refIfReactive.d.ts.map +1 -0
- package/types/utils/unwrapNested.d.ts +2 -0
- package/types/utils/unwrapNested.d.ts.map +1 -0
- package/use/cancellableIntent.js +8 -13
- package/use/listInstance.js +67 -68
- package/use/listSearch.js +1 -1
- package/use/loadingError.js +30 -6
- package/use/objectInstance.js +228 -128
- package/use/objectSubscription.js +81 -82
- package/use/proxyLoadingError.js +14 -27
- package/use/search.js +1 -1
- package/utils/cancellableFetch.js +22 -0
- package/utils/cancellablePromise.js +70 -0
- package/utils/classes.js +5 -5
- package/utils/deepUnref.js +28 -0
- package/utils/refIfReactive.js +18 -0
- package/utils/unwrapNested.js +25 -0
package/use/listInstance.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { getListCrud } from "../config/listCrud.js";
|
|
1
|
+
import { defaultListCrud, getListCrud } from "../config/listCrud.js";
|
|
2
2
|
import { assignReactiveObject } from "../utils/assignReactiveObject.js";
|
|
3
3
|
import { getFakePk } from "../utils/getFakePk.js";
|
|
4
4
|
import { useLoadingError } from "./loadingError.js";
|
|
5
5
|
import inspect from "browser-util-inspect";
|
|
6
|
-
import { computed, effectScope, nextTick, reactive, readonly, ref, toRef, unref, watch } from "vue";
|
|
6
|
+
import { computed, effectScope, nextTick, reactive, readonly, ref, shallowReactive, toRef, unref, watch } from "vue";
|
|
7
|
+
import { CancellablePromise, wrapMaybeCancellable } from "../utils/cancellablePromise.js";
|
|
8
|
+
import { refIfReactive } from "../utils/refIfReactive.js";
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* A composable function for managing a list of objects.
|
|
@@ -52,13 +54,13 @@ export class ListInstanceError extends Error {
|
|
|
52
54
|
* @typedef {object} ListInstanceOptions
|
|
53
55
|
* @property {import('vue').UnwrapNestedRefs<ListInstanceProps>} props - The props for the list instance.
|
|
54
56
|
* @property {object} [functions] - Default implementation are used as set by `setListCrud`.
|
|
55
|
-
* @property {import('../config/listCrud.js').
|
|
57
|
+
* @property {import('../config/listCrud.js').CrudListFn} [functions.list] - Provide the implementation for the list
|
|
56
58
|
* function.
|
|
57
|
-
* @property {import('../config/listCrud.js').
|
|
59
|
+
* @property {import('../config/listCrud.js').CrudBulkDeleteFn} [functions.bulkDelete] - Provide the implementation for the bulkDelete
|
|
58
60
|
* function.
|
|
59
|
-
* @property {import('../config/listCrud.js').
|
|
61
|
+
* @property {import('../config/listCrud.js').CrudExecuteActionFn} [functions.executeAction] - Provide the implementation for the executeAction
|
|
60
62
|
* function.
|
|
61
|
-
* @property {import('../config/listCrud.js').
|
|
63
|
+
* @property {import('../config/listCrud.js').CrudListSubscribeFn} [functions.subscribe] - Provide the implementation for the
|
|
62
64
|
* subscribe function.
|
|
63
65
|
* @property {boolean} keepOldPages - If true, pages will not be cleared when defaultPageCallback is called.
|
|
64
66
|
*/
|
|
@@ -117,7 +119,7 @@ export class ListInstanceError extends Error {
|
|
|
117
119
|
* @property {(objectId: string) => void} deleteListObject - Deletes an object from the list by pk.
|
|
118
120
|
* @property {() => void} clearList - Clears all objects and errors from the list.
|
|
119
121
|
* @property {() => string} getFakePk - Generates a unique fake pk for use within the list.
|
|
120
|
-
* @property {() =>
|
|
122
|
+
* @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.
|
|
121
123
|
* @property {() => Promise<boolean>} bulkDelete - Initiates a bulk delete operation on all objects in the list, returning a promise to a boolean indicating success.
|
|
122
124
|
* @property {() => Promise<object|string|false>} executeAction - Initiates an action on all objects in the list, returning the response, or false if the action failed.
|
|
123
125
|
*/
|
|
@@ -206,16 +208,22 @@ export function useListInstances(listInstanceArgs) {
|
|
|
206
208
|
*/
|
|
207
209
|
export function useListInstance({ props, functions = {}, keepOldPages }) {
|
|
208
210
|
if (!props) {
|
|
209
|
-
throw new ListInstanceError(
|
|
211
|
+
throw new ListInstanceError("useListInstance requires props", "missing-props");
|
|
210
212
|
}
|
|
211
213
|
if (keepOldPages === undefined) {
|
|
212
|
-
throw new ListInstanceError(
|
|
214
|
+
throw new ListInstanceError("useListInstance requires keepOldPages", "missing-keepOldPages");
|
|
213
215
|
}
|
|
216
|
+
if (!props.pkKey) {
|
|
217
|
+
throw new ListInstanceError("useListInstance requires pkKey.", "missing-pkKey");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const es = effectScope();
|
|
214
221
|
|
|
215
222
|
// ### touching the _objectsMap or _objectsProxy directly will not trigger reactivity ###
|
|
216
223
|
const _objectsMap = new Map(); // maps are ordered, if you don't clear lists, you need to insert pages in order.
|
|
217
224
|
|
|
218
225
|
// ### touching the _objectsMap or _objectsProxy directly will not trigger reactivity ###
|
|
226
|
+
// noinspection JSValidateTypes
|
|
219
227
|
/** @type {{[key: string]: ListObject}} */
|
|
220
228
|
// @ts-ignore - we are using a proxy to make this map behave like an object for reactivity
|
|
221
229
|
const _objectsProxy = new Proxy(_objectsMap, {
|
|
@@ -250,7 +258,7 @@ export function useListInstance({ props, functions = {}, keepOldPages }) {
|
|
|
250
258
|
}
|
|
251
259
|
: Reflect.getOwnPropertyDescriptor(target, p); // we can't report target properties as non-existent re: proxy invariants
|
|
252
260
|
},
|
|
253
|
-
// things introspect us thing we are a map, we need to pretend to be
|
|
261
|
+
// things introspect us thing we are a map, we need to pretend to be an object
|
|
254
262
|
getPrototypeOf() {
|
|
255
263
|
return Object.prototype;
|
|
256
264
|
},
|
|
@@ -262,31 +270,28 @@ export function useListInstance({ props, functions = {}, keepOldPages }) {
|
|
|
262
270
|
|
|
263
271
|
// ### touching the _objectsMap or _objectsProxy directly will not trigger reactivity ###
|
|
264
272
|
const state = reactive({
|
|
265
|
-
crud: {
|
|
266
|
-
args: {},
|
|
267
|
-
list:
|
|
268
|
-
bulkDelete:
|
|
269
|
-
executeAction:
|
|
270
|
-
},
|
|
271
|
-
pkKey:
|
|
272
|
-
retrieveArgs:
|
|
273
|
-
listArgs:
|
|
273
|
+
crud: shallowReactive({
|
|
274
|
+
args: reactive({}),
|
|
275
|
+
list: defaultListCrud.list,
|
|
276
|
+
bulkDelete: defaultListCrud.bulkDelete,
|
|
277
|
+
executeAction: defaultListCrud.executeAction,
|
|
278
|
+
}),
|
|
279
|
+
pkKey: refIfReactive(props, "pkKey"),
|
|
280
|
+
retrieveArgs: refIfReactive(props, "retrieveArgs", {}),
|
|
281
|
+
listArgs: refIfReactive(props, "listArgs", {}),
|
|
274
282
|
/** @type {{[key: string]: ListObject}} */
|
|
275
283
|
objects: /** @type {{[key: string]: ListObject}} */ _objectsProxy,
|
|
276
284
|
running: false,
|
|
277
285
|
loading: loadingError.loading,
|
|
278
286
|
errored: loadingError.errored,
|
|
279
287
|
error: loadingError.error,
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
/** @type {import('vue').ComputedRef<ListObject[]>|undefined} */
|
|
283
|
-
objectsInOrder: undefined,
|
|
288
|
+
order: es.run(() => computed(() => Object.keys(state.objects))),
|
|
289
|
+
objectsInOrder: es.run(() => computed(() => objectsInOrderRefs.value.map((ref) => unref(ref)))),
|
|
284
290
|
});
|
|
285
|
-
const es = effectScope();
|
|
286
291
|
|
|
287
292
|
getListCrud(state.crud, { props, functions });
|
|
288
293
|
|
|
289
|
-
const defaultPageCallback = (newObjects) => {
|
|
294
|
+
const defaultPageCallback = (/** @type {ListObject[]} */ newObjects) => {
|
|
290
295
|
// with keepOldPages, you are responsible for clearing the list as needed
|
|
291
296
|
if (!keepOldPages) {
|
|
292
297
|
// display one page at a time, clear the list
|
|
@@ -301,7 +306,7 @@ export function useListInstance({ props, functions = {}, keepOldPages }) {
|
|
|
301
306
|
});
|
|
302
307
|
};
|
|
303
308
|
|
|
304
|
-
/** @type {{[key: string]: import('
|
|
309
|
+
/** @type {{[key: string]: import('../utils/cancellablePromise.js').MaybeCancellablePromise<boolean>|null}} */
|
|
305
310
|
const promises = {
|
|
306
311
|
list: null,
|
|
307
312
|
};
|
|
@@ -323,7 +328,7 @@ export function useListInstance({ props, functions = {}, keepOldPages }) {
|
|
|
323
328
|
loadingError.clearError();
|
|
324
329
|
return Promise.resolve(true);
|
|
325
330
|
})
|
|
326
|
-
.catch((error) => {
|
|
331
|
+
.catch((/** @type {Error} */ error) => {
|
|
327
332
|
loadingError.setError(error);
|
|
328
333
|
return Promise.resolve(false);
|
|
329
334
|
})
|
|
@@ -344,19 +349,25 @@ export function useListInstance({ props, functions = {}, keepOldPages }) {
|
|
|
344
349
|
pks: Object.keys(state.objects).map(Number),
|
|
345
350
|
pkKey: state.pkKey,
|
|
346
351
|
})
|
|
347
|
-
.then((responseData) => {
|
|
352
|
+
.then((/** @type {object|string} */ responseData) => {
|
|
348
353
|
loadingError.clearError();
|
|
349
354
|
return Promise.resolve(responseData);
|
|
350
355
|
})
|
|
351
|
-
.catch((error) => {
|
|
356
|
+
.catch((/** @type {Error} */ error) => {
|
|
352
357
|
loadingError.setError(error);
|
|
353
|
-
return Promise.resolve(
|
|
358
|
+
return Promise.resolve(null);
|
|
354
359
|
})
|
|
355
360
|
.finally(() => {
|
|
356
361
|
loadingError.clearLoading();
|
|
357
362
|
});
|
|
358
363
|
}
|
|
359
364
|
|
|
365
|
+
/**
|
|
366
|
+
* Fetches a list of objects from the server, using the configured CRUD operations and arguments.
|
|
367
|
+
*
|
|
368
|
+
* @throws {ListInstanceError} If the list is already loading.
|
|
369
|
+
* @returns {import('../utils/cancellablePromise.js').MaybeCancellablePromise<boolean|never>} A promise that resolves to true if the list was successfully retrieved, or false if an error occurred.
|
|
370
|
+
*/
|
|
360
371
|
function list() {
|
|
361
372
|
// this function cannot be async, or the resulting promise will lose its .cancel() method
|
|
362
373
|
if (promises.list) {
|
|
@@ -364,13 +375,8 @@ export function useListInstance({ props, functions = {}, keepOldPages }) {
|
|
|
364
375
|
return promises.list;
|
|
365
376
|
}
|
|
366
377
|
if (state.loading) {
|
|
367
|
-
return
|
|
378
|
+
return CancellablePromise.reject(new ListInstanceError("already loading.", "already-loading"));
|
|
368
379
|
}
|
|
369
|
-
let returnPromiseResolve;
|
|
370
|
-
// @ts-ignore - we are setting this in the promise
|
|
371
|
-
promises.list = /** @type {import('./cancellableIntent.js').CancellablePromise} */ new Promise(
|
|
372
|
-
(resolve) => (returnPromiseResolve = resolve)
|
|
373
|
-
);
|
|
374
380
|
loadingError.clearError();
|
|
375
381
|
loadingError.setLoading();
|
|
376
382
|
const isCancelled = ref(false);
|
|
@@ -382,35 +388,31 @@ export function useListInstance({ props, functions = {}, keepOldPages }) {
|
|
|
382
388
|
pageCallback: returnedObject.pageCallback,
|
|
383
389
|
isCancelled: readonly(isCancelled),
|
|
384
390
|
});
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
loadingError.clearLoading();
|
|
407
|
-
returnPromiseResolve(resolveState);
|
|
408
|
-
promises.list = null;
|
|
409
|
-
});
|
|
391
|
+
promises.list = wrapMaybeCancellable(
|
|
392
|
+
listPromise
|
|
393
|
+
.then(() => {
|
|
394
|
+
return true;
|
|
395
|
+
})
|
|
396
|
+
.catch((/** @type {Error} */ error) => {
|
|
397
|
+
loadingError.setError(error);
|
|
398
|
+
return false;
|
|
399
|
+
})
|
|
400
|
+
.finally(() => {
|
|
401
|
+
loadingError.clearLoading();
|
|
402
|
+
promises.list = null;
|
|
403
|
+
}),
|
|
404
|
+
listPromise.cancel
|
|
405
|
+
? async (/** @type {any} */ reason) => {
|
|
406
|
+
isCancelled.value = true;
|
|
407
|
+
await listPromise.cancel?.(reason);
|
|
408
|
+
loadingError.clearLoading();
|
|
409
|
+
}
|
|
410
|
+
: undefined
|
|
411
|
+
);
|
|
410
412
|
return promises.list;
|
|
411
413
|
}
|
|
412
414
|
|
|
413
|
-
function addListObject(object) {
|
|
415
|
+
function addListObject(/** @type {ListObject} */ object) {
|
|
414
416
|
if (!object[state.pkKey]) {
|
|
415
417
|
throw new ListInstanceError(
|
|
416
418
|
`addListObject: object missing pk(${state.pkKey}).\n${inspect(object)}`,
|
|
@@ -429,7 +431,7 @@ export function useListInstance({ props, functions = {}, keepOldPages }) {
|
|
|
429
431
|
}
|
|
430
432
|
}
|
|
431
433
|
|
|
432
|
-
function updateListObject(object) {
|
|
434
|
+
function updateListObject(/** @type {ListObject} */ object) {
|
|
433
435
|
if (!object[state.pkKey]) {
|
|
434
436
|
throw new ListInstanceError(
|
|
435
437
|
`updateListObject: object missing pk(${state.pkKey}).\n${inspect(object)}`,
|
|
@@ -448,7 +450,7 @@ export function useListInstance({ props, functions = {}, keepOldPages }) {
|
|
|
448
450
|
}
|
|
449
451
|
}
|
|
450
452
|
|
|
451
|
-
function deleteListObject(pk) {
|
|
453
|
+
function deleteListObject(/** @type {string} */ pk) {
|
|
452
454
|
if (!(pk in state.objects)) {
|
|
453
455
|
throw new ListInstanceError(
|
|
454
456
|
`deleteListObject: list missing object for removal by pk(${state.pkKey}): ${inspect(pk)}`,
|
|
@@ -479,6 +481,7 @@ export function useListInstance({ props, functions = {}, keepOldPages }) {
|
|
|
479
481
|
toRef(state, "objects"),
|
|
480
482
|
() => {
|
|
481
483
|
objectsInOrderRefs.value = Object.values(state.objects).map((object) => ref(object));
|
|
484
|
+
// noinspection JSIgnoredPromiseFromCall
|
|
482
485
|
nextTick(() => {
|
|
483
486
|
if (state.running) {
|
|
484
487
|
state.running = false;
|
|
@@ -490,10 +493,6 @@ export function useListInstance({ props, functions = {}, keepOldPages }) {
|
|
|
490
493
|
deep: true,
|
|
491
494
|
}
|
|
492
495
|
);
|
|
493
|
-
// @ts-ignore - we want the computed in the explicit effect scope, tsc is mad that we are 'changing' the type
|
|
494
|
-
state.objectsInOrder = computed(() => objectsInOrderRefs.value.map((ref) => unref(ref)));
|
|
495
|
-
// @ts-ignore - we want the computed in the explicit effect scope, tsc is mad that we are 'changing' the type
|
|
496
|
-
state.order = computed(() => Object.keys(state.objects));
|
|
497
496
|
});
|
|
498
497
|
|
|
499
498
|
// This isn't a direct return because we want the live returnedObject.pageCallback in list()
|
package/use/listSearch.js
CHANGED
|
@@ -15,7 +15,7 @@ import { useSearch } from "./search.js";
|
|
|
15
15
|
import get from "lodash-es/get.js";
|
|
16
16
|
import isEqual from "lodash-es/isEqual.js";
|
|
17
17
|
import { computed, effectScope, onScopeDispose, reactive, readonly, ref, toRef, unref, watch } from "vue";
|
|
18
|
-
import { deepUnref } from "
|
|
18
|
+
import { deepUnref } from "../utils/deepUnref.js";
|
|
19
19
|
import { assignReactiveObject } from "../utils/assignReactiveObject.js";
|
|
20
20
|
import { loadingCombine } from "../utils/loadingCombine.js";
|
|
21
21
|
|
package/use/loadingError.js
CHANGED
|
@@ -8,13 +8,37 @@ import { readonly, ref } from "vue";
|
|
|
8
8
|
* @typedef {() => void} ClearErrorFn - Clear the error state.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {import("vue").Ref<boolean|undefined>} LoadingRef
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {import("vue").Ref<Error|null>} ErrorRef
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {import("vue").Ref<boolean>} ErroredRef
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Readonly<LoadingRef>} LoadingReadonlyRef
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {Readonly<ErrorRef>} ErrorReadonlyRef
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {Readonly<ErroredRef>} ErroredReadonlyRef
|
|
33
|
+
*/
|
|
34
|
+
|
|
11
35
|
/**
|
|
12
36
|
* The common API for loading and error states.
|
|
13
37
|
*
|
|
14
38
|
* @typedef {object} LoadingErrorStatus
|
|
15
|
-
* @property {
|
|
16
|
-
* @property {
|
|
17
|
-
* @property {
|
|
39
|
+
* @property {LoadingReadonlyRef} loading - Whether the component is loading.
|
|
40
|
+
* @property {ErrorReadonlyRef} error - The error that occurred.
|
|
41
|
+
* @property {ErroredReadonlyRef} errored - Whether an error has occurred.
|
|
18
42
|
* @property {ClearErrorFn} clearError - Clear the error state.
|
|
19
43
|
*/
|
|
20
44
|
|
|
@@ -39,11 +63,11 @@ import { readonly, ref } from "vue";
|
|
|
39
63
|
* @returns {LoadingError} - An object containing reactive fields and actions for loading and error states.
|
|
40
64
|
*/
|
|
41
65
|
export function useLoadingError() {
|
|
42
|
-
/** @type {
|
|
66
|
+
/** @type {LoadingRef} */
|
|
43
67
|
const loading = ref(undefined);
|
|
44
|
-
/** @type {
|
|
68
|
+
/** @type {ErrorRef} */
|
|
45
69
|
const error = ref(null);
|
|
46
|
-
/** @type {
|
|
70
|
+
/** @type {ErroredRef} */
|
|
47
71
|
const errored = ref(false);
|
|
48
72
|
|
|
49
73
|
return {
|