@arrai-innovations/reactive-helpers 18.0.2 → 18.1.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/config/listCrud.js +1 -1
- package/config/objectCrud.js +129 -47
- package/index.js +3 -0
- package/package.json +2 -2
- package/types/config/listCrud.d.ts +1 -1
- package/types/config/objectCrud.d.ts +99 -33
- package/types/config/objectCrud.d.ts.map +1 -1
- package/types/index.d.ts +3 -0
- package/types/use/cancellableIntent.d.ts +2 -13
- package/types/use/cancellableIntent.d.ts.map +1 -1
- package/types/use/listInstance.d.ts +2 -2
- 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/use/cancellableIntent.js +8 -13
- package/use/listInstance.js +39 -40
- package/use/listSearch.js +1 -1
- package/use/loadingError.js +30 -6
- package/use/objectInstance.js +225 -125
- 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/use/objectInstance.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { getObjectCrud } from "../config/objectCrud.js";
|
|
1
|
+
import { defaultCrud, getObjectCrud } from "../config/objectCrud.js";
|
|
2
2
|
import { assignReactiveObject } from "../utils/assignReactiveObject.js";
|
|
3
3
|
import { useLoadingError } from "./loadingError.js";
|
|
4
|
-
import { reactive, toRef } from "vue";
|
|
4
|
+
import { reactive, readonly, ref, shallowReactive, toRef } from "vue";
|
|
5
|
+
import { wrapMaybeCancellable } from "../utils/cancellablePromise.js";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* A composition function to manage create, retrieve, update, delete, and patch operations.
|
|
@@ -12,7 +13,15 @@ import { reactive, toRef } from "vue";
|
|
|
12
13
|
/**
|
|
13
14
|
* The object being managed by the instance. Empty object is the default.
|
|
14
15
|
*
|
|
15
|
-
* @typedef {{pkKey: string, [key: string]: any}
|
|
16
|
+
* @typedef {{pkKey: string, [key: string]: any}} ExistingCrudObject
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {{[key: string]: any}} NewCrudObject
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {ExistingCrudObject|NewCrudObject} CrudObject
|
|
16
25
|
*/
|
|
17
26
|
|
|
18
27
|
/**
|
|
@@ -20,7 +29,7 @@ import { reactive, toRef } from "vue";
|
|
|
20
29
|
*
|
|
21
30
|
* @typedef {object} ObjectInstanceOptions
|
|
22
31
|
* @property {import('vue').UnwrapNestedRefs<ObjectInstanceRawProps>} props - The reactive configuration object.
|
|
23
|
-
* @property {import('../config/objectCrud.js').ObjectCrudFunctions} functions - An object of custom crud functions to use instead of the defaults.
|
|
32
|
+
* @property {import('../config/objectCrud.js').ObjectCrudFunctions} [functions] - An object of custom crud functions to use instead of the defaults.
|
|
24
33
|
*/
|
|
25
34
|
|
|
26
35
|
/**
|
|
@@ -33,37 +42,48 @@ import { reactive, toRef } from "vue";
|
|
|
33
42
|
* @property {import('../config/objectCrud.js').ObjectCrudArgs} crudArgs - The arguments to be passed to the crud functions.
|
|
34
43
|
*/
|
|
35
44
|
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {object} ObjectInstanceRawStateCrud
|
|
47
|
+
* @property {import('vue').Reactive<import('../config/objectCrud.js').ObjectCrudArgsArgs|{}>} args - The arguments to be passed to the crud functions.
|
|
48
|
+
* @property {import('../config/objectCrud.js').CrudCreateFn} create - The create function.
|
|
49
|
+
* @property {import('../config/objectCrud.js').CrudRetrieveFn} retrieve - The retrieve function.
|
|
50
|
+
* @property {import('../config/objectCrud.js').CrudUpdateFn} update - The update function.
|
|
51
|
+
* @property {import('../config/objectCrud.js').CrudPatchFn} patch - The patch function.
|
|
52
|
+
* @property {import('../config/objectCrud.js').CrudDeleteFn} delete - The delete function.
|
|
53
|
+
* @property {import('../config/objectCrud.js').CrudSubscribeFn} subscribe - The subscribe function.
|
|
54
|
+
*/
|
|
55
|
+
|
|
36
56
|
/**
|
|
37
57
|
* The raw state of the object instance.
|
|
38
58
|
*
|
|
39
59
|
* @typedef {object} ObjectInstanceRawState
|
|
40
|
-
* @property {import('
|
|
41
|
-
* @property {string} pk - The pk of the object.
|
|
42
|
-
* @property {string} pkKey - The pk key of the object.
|
|
43
|
-
* @property {
|
|
44
|
-
* @property {CrudObject} object - The object.
|
|
60
|
+
* @property {import('vue').ShallowReactive<ObjectInstanceRawStateCrud>} crud - The crud functions.
|
|
61
|
+
* @property {import('vue').Ref<string|undefined>} pk - The pk of the object.
|
|
62
|
+
* @property {import('vue').Ref<string|undefined>} pkKey - The pk key of the object.
|
|
63
|
+
* @property {import('vue').Ref<{[key:string]: any}>} retrieveArgs - The arguments to be passed to the retrieve function.
|
|
64
|
+
* @property {import('vue').Reactive<CrudObject>} object - The object.
|
|
45
65
|
* @property {Readonly<import('vue').Ref<boolean>>} loading - Whether the object is loading.
|
|
46
66
|
* @property {Readonly<import('vue').Ref<boolean>>} errored - Whether the object errored.
|
|
47
67
|
* @property {Readonly<import('vue').Ref<Error|null>>} error - The error.
|
|
48
|
-
* @property {
|
|
68
|
+
* @property {boolean} deleted - Whether the object is deleted.
|
|
49
69
|
*/
|
|
50
70
|
|
|
51
71
|
/**
|
|
52
72
|
* Manages a reactive state of an object including its CRUD status, loading states, and any operational errors.
|
|
53
73
|
* Reactivity ensures that any changes in state immediately reflect in the UI components that depend on this state.
|
|
54
74
|
*
|
|
55
|
-
* @typedef {import('vue').
|
|
75
|
+
* @typedef {import('vue').Reactive<ObjectInstanceRawState>} ObjectInstanceState
|
|
56
76
|
*/
|
|
57
77
|
|
|
58
78
|
/**
|
|
59
79
|
* The functions available on the object instance.
|
|
60
80
|
*
|
|
61
81
|
* @typedef {object} ObjectInstanceFunctions
|
|
62
|
-
* @property {(args: { object: object }) =>
|
|
63
|
-
* @property {() =>
|
|
64
|
-
* @property {(args: { object:
|
|
65
|
-
* @property {() =>
|
|
66
|
-
* @property {(args: { partialObject:
|
|
82
|
+
* @property {(args: { object: object }) => import('../utils/cancellablePromise.js').MaybeCancellablePromise<boolean>} create - Called to turn the current object into a new object on the server.
|
|
83
|
+
* @property {() => import('../utils/cancellablePromise.js').MaybeCancellablePromise<boolean>} retrieve - Called to retrieve the current object by pk from the server.
|
|
84
|
+
* @property {(args: { object: ExistingCrudObject }) => import('../utils/cancellablePromise.js').MaybeCancellablePromise<boolean>} update - Called to update the current object on the server.
|
|
85
|
+
* @property {() => import('../utils/cancellablePromise.js').MaybeCancellablePromise<boolean>} delete - Called to delete the current object on the server.
|
|
86
|
+
* @property {(args: { partialObject: ExistingCrudObject }) => import('../utils/cancellablePromise.js').MaybeCancellablePromise<boolean>} patch - Called to patch the current object on the server.
|
|
67
87
|
* @property {import('./loadingError.js').ClearErrorFn} clearError - Called to clear the error state.
|
|
68
88
|
* @property {() => void} clear - Called to clear the object state.
|
|
69
89
|
*/
|
|
@@ -189,17 +209,22 @@ export function useObjectInstance({ props, functions = {} }) {
|
|
|
189
209
|
const loadingError = useLoadingError();
|
|
190
210
|
/** @type {ObjectInstanceState} */
|
|
191
211
|
const state = reactive(
|
|
192
|
-
/** @type {ObjectInstanceRawState} */
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
212
|
+
/** @type {ObjectInstanceRawState} */
|
|
213
|
+
{
|
|
214
|
+
// function typing support is a lot nicer with shallow reactive
|
|
215
|
+
crud: shallowReactive(
|
|
216
|
+
/** @type {ObjectInstanceRawStateCrud} */
|
|
217
|
+
{
|
|
218
|
+
args: reactive({}),
|
|
219
|
+
create: defaultCrud.create,
|
|
220
|
+
retrieve: defaultCrud.retrieve,
|
|
221
|
+
update: defaultCrud.update,
|
|
222
|
+
delete: defaultCrud.delete,
|
|
223
|
+
patch: defaultCrud.patch,
|
|
224
|
+
subscribe: defaultCrud.subscribe,
|
|
225
|
+
}
|
|
226
|
+
),
|
|
227
|
+
object: reactive({}),
|
|
203
228
|
pk: toRef(props, "pk"),
|
|
204
229
|
pkKey: toRef(props, "pkKey"),
|
|
205
230
|
retrieveArgs: toRef(props, "retrieveArgs"),
|
|
@@ -218,6 +243,9 @@ export function useObjectInstance({ props, functions = {} }) {
|
|
|
218
243
|
retrieve: null,
|
|
219
244
|
};
|
|
220
245
|
|
|
246
|
+
/**
|
|
247
|
+
* @returns {import('../utils/cancellablePromise.js').MaybeCancellablePromise<boolean|never>} - A promise that resolves to true if the object was retrieved successfully, or false if there was an error.
|
|
248
|
+
*/
|
|
221
249
|
function retrieve() {
|
|
222
250
|
// this function cannot be async, or the resulting promise will lose its .cancel() method
|
|
223
251
|
if (promises.retrieve) {
|
|
@@ -225,136 +253,208 @@ export function useObjectInstance({ props, functions = {} }) {
|
|
|
225
253
|
return promises.retrieve;
|
|
226
254
|
}
|
|
227
255
|
if (state.loading) {
|
|
228
|
-
// if another operation is already in progress,
|
|
229
|
-
|
|
256
|
+
// if another operation is already in progress, throw an error
|
|
257
|
+
// we throw because we want devs to see this error in the console
|
|
258
|
+
// state.error should be for user facing errors, or unknown errors
|
|
259
|
+
throw new ObjectError("already loading.", "already-loading");
|
|
230
260
|
}
|
|
231
261
|
loadingError.setLoading();
|
|
232
262
|
loadingError.clearError();
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
263
|
+
const isCancelled = ref(false);
|
|
264
|
+
const retrievePromise = state.crud.retrieve({
|
|
265
|
+
crudArgs: state.crud.args,
|
|
266
|
+
pk: state.pk,
|
|
267
|
+
retrieveArgs: state.retrieveArgs,
|
|
268
|
+
pkKey: state.pkKey,
|
|
269
|
+
isCancelled: readonly(isCancelled),
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
promises.retrieve = wrapMaybeCancellable(
|
|
273
|
+
retrievePromise
|
|
274
|
+
.then((/** @type {ExistingCrudObject} */ object) => {
|
|
275
|
+
assignReactiveObject(state.object, object);
|
|
276
|
+
return true;
|
|
277
|
+
})
|
|
278
|
+
.catch((/** @type {Error} */ error) => {
|
|
279
|
+
loadingError.setError(error);
|
|
280
|
+
return false;
|
|
281
|
+
})
|
|
282
|
+
.finally(() => {
|
|
283
|
+
loadingError.clearLoading();
|
|
284
|
+
promises.retrieve = null;
|
|
285
|
+
}),
|
|
286
|
+
retrievePromise.cancel
|
|
287
|
+
? async (/** @type {any} */ reason) => {
|
|
288
|
+
isCancelled.value = true;
|
|
289
|
+
await retrievePromise.cancel?.(reason);
|
|
290
|
+
loadingError.clearLoading();
|
|
291
|
+
}
|
|
292
|
+
: undefined
|
|
293
|
+
);
|
|
294
|
+
|
|
252
295
|
return promises.retrieve;
|
|
253
296
|
}
|
|
254
297
|
|
|
255
|
-
|
|
298
|
+
function create({ object }) {
|
|
299
|
+
// this function cannot be async, or the resulting promise will lose its .cancel() method
|
|
256
300
|
if (state.loading) {
|
|
301
|
+
// we throw because we want devs to see this error in the console
|
|
302
|
+
// state.error should be for user facing errors, or unknown errors
|
|
257
303
|
throw new ObjectError("already loading.", "already-loading");
|
|
258
304
|
}
|
|
259
305
|
loadingError.setLoading();
|
|
260
306
|
loadingError.clearError();
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
307
|
+
const isCancelled = ref(false);
|
|
308
|
+
const createPromise = state.crud.create({
|
|
309
|
+
crudArgs: state.crud.args,
|
|
310
|
+
object,
|
|
311
|
+
retrieveArgs: state.retrieveArgs,
|
|
312
|
+
pkKey: state.pkKey,
|
|
313
|
+
isCancelled: readonly(isCancelled),
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
return wrapMaybeCancellable(
|
|
317
|
+
createPromise
|
|
318
|
+
.then((/** @type {ExistingCrudObject} */ object) => {
|
|
319
|
+
assignReactiveObject(state.object, object);
|
|
320
|
+
return true;
|
|
321
|
+
})
|
|
322
|
+
.catch((/** @type {Error} */ error) => {
|
|
323
|
+
loadingError.setError(error);
|
|
324
|
+
return false;
|
|
325
|
+
})
|
|
326
|
+
.finally(() => {
|
|
327
|
+
loadingError.clearLoading();
|
|
328
|
+
}),
|
|
329
|
+
createPromise.cancel
|
|
330
|
+
? async (/** @type {any} */ reason) => {
|
|
331
|
+
isCancelled.value = true;
|
|
332
|
+
await createPromise.cancel?.(reason);
|
|
333
|
+
loadingError.clearLoading();
|
|
334
|
+
}
|
|
335
|
+
: undefined
|
|
336
|
+
);
|
|
279
337
|
}
|
|
280
338
|
|
|
281
|
-
|
|
339
|
+
function update({ object }) {
|
|
340
|
+
// this function cannot be async, or the resulting promise will lose its .cancel() method
|
|
282
341
|
if (state.loading) {
|
|
342
|
+
// we throw because we want devs to see this error in the console
|
|
343
|
+
// state.error should be for user facing errors, or unknown errors
|
|
283
344
|
throw new ObjectError("already loading.", "already-loading");
|
|
284
345
|
}
|
|
285
346
|
loadingError.setLoading();
|
|
286
347
|
loadingError.clearError();
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
348
|
+
const isCancelled = ref(false);
|
|
349
|
+
const updatePromise = state.crud.update({
|
|
350
|
+
crudArgs: state.crud.args,
|
|
351
|
+
object,
|
|
352
|
+
retrieveArgs: state.retrieveArgs,
|
|
353
|
+
pkKey: state.pkKey,
|
|
354
|
+
isCancelled: readonly(isCancelled),
|
|
355
|
+
});
|
|
356
|
+
return wrapMaybeCancellable(
|
|
357
|
+
updatePromise
|
|
358
|
+
.then((/** @type {ExistingCrudObject} */ object) => {
|
|
359
|
+
assignReactiveObject(state.object, object);
|
|
360
|
+
return true;
|
|
361
|
+
})
|
|
362
|
+
.catch((/** @type {Error} */ error) => {
|
|
363
|
+
loadingError.setError(error);
|
|
364
|
+
return false;
|
|
365
|
+
})
|
|
366
|
+
.finally(() => {
|
|
367
|
+
loadingError.clearLoading();
|
|
368
|
+
}),
|
|
369
|
+
updatePromise.cancel
|
|
370
|
+
? async (/** @type {any} */ reason) => {
|
|
371
|
+
isCancelled.value = true;
|
|
372
|
+
await updatePromise.cancel?.(reason);
|
|
373
|
+
loadingError.clearLoading();
|
|
374
|
+
}
|
|
375
|
+
: undefined
|
|
376
|
+
);
|
|
305
377
|
}
|
|
306
378
|
|
|
307
|
-
|
|
379
|
+
function patch({ partialObject }) {
|
|
380
|
+
// this function cannot be async, or the resulting promise will lose its .cancel() method
|
|
308
381
|
if (state.loading) {
|
|
382
|
+
// we throw because we want devs to see this error in the console
|
|
383
|
+
// state.error should be for user facing errors, or unknown errors
|
|
309
384
|
throw new ObjectError("already loading.", "already-loading");
|
|
310
385
|
}
|
|
311
386
|
loadingError.setLoading();
|
|
312
387
|
loadingError.clearError();
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
388
|
+
const isCancelled = ref(false);
|
|
389
|
+
const patchPromise = state.crud.patch({
|
|
390
|
+
crudArgs: state.crud.args,
|
|
391
|
+
pk: state.pk,
|
|
392
|
+
pkKey: state.pkKey,
|
|
393
|
+
partialObject,
|
|
394
|
+
retrieveArgs: state.retrieveArgs,
|
|
395
|
+
isCancelled: readonly(isCancelled),
|
|
396
|
+
});
|
|
397
|
+
return wrapMaybeCancellable(
|
|
398
|
+
patchPromise
|
|
399
|
+
.then((/** @type {ExistingCrudObject} */ object) => {
|
|
400
|
+
assignReactiveObject(state.object, object);
|
|
401
|
+
return true;
|
|
402
|
+
})
|
|
403
|
+
.catch((/** @type {Error} */ error) => {
|
|
404
|
+
loadingError.setError(error);
|
|
405
|
+
return false;
|
|
406
|
+
})
|
|
407
|
+
.finally(() => {
|
|
408
|
+
loadingError.clearLoading();
|
|
409
|
+
}),
|
|
410
|
+
patchPromise.cancel
|
|
411
|
+
? async (/** @type {any} */ reason) => {
|
|
412
|
+
isCancelled.value = true;
|
|
413
|
+
await patchPromise.cancel?.(reason);
|
|
414
|
+
loadingError.clearLoading();
|
|
415
|
+
}
|
|
416
|
+
: undefined
|
|
417
|
+
);
|
|
332
418
|
}
|
|
333
419
|
|
|
334
|
-
|
|
420
|
+
function deleteFn() {
|
|
421
|
+
// this function cannot be async, or the resulting promise will lose its .cancel() method
|
|
335
422
|
if (state.loading) {
|
|
423
|
+
// we throw because we want devs to see this error in the console
|
|
424
|
+
// state.error should be for user facing errors, or unknown errors
|
|
336
425
|
throw new ObjectError("already loading.", "already-loading");
|
|
337
426
|
}
|
|
338
427
|
loadingError.setLoading();
|
|
339
428
|
loadingError.clearError();
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
429
|
+
const isCancelled = ref(false);
|
|
430
|
+
const deletePromise = state.crud.delete({
|
|
431
|
+
crudArgs: state.crud.args,
|
|
432
|
+
pk: state.pk,
|
|
433
|
+
pkKey: state.pkKey,
|
|
434
|
+
isCancelled: readonly(isCancelled),
|
|
435
|
+
});
|
|
436
|
+
return wrapMaybeCancellable(
|
|
437
|
+
deletePromise
|
|
438
|
+
.then(() => {
|
|
439
|
+
state.deleted = true;
|
|
440
|
+
assignReactiveObject(state.object, {});
|
|
441
|
+
return true;
|
|
442
|
+
})
|
|
443
|
+
.catch((/** @type {Error} */ error) => {
|
|
444
|
+
loadingError.setError(error);
|
|
445
|
+
return false;
|
|
446
|
+
})
|
|
447
|
+
.finally(() => {
|
|
448
|
+
loadingError.clearLoading();
|
|
449
|
+
}),
|
|
450
|
+
deletePromise.cancel
|
|
451
|
+
? async (/** @type {any} */ reason) => {
|
|
452
|
+
isCancelled.value = true;
|
|
453
|
+
await deletePromise.cancel?.(reason);
|
|
454
|
+
loadingError.clearLoading();
|
|
455
|
+
}
|
|
456
|
+
: undefined
|
|
457
|
+
);
|
|
358
458
|
}
|
|
359
459
|
|
|
360
460
|
function clear() {
|