bt-core-app 0.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.
Files changed (53) hide show
  1. package/.vscode/extensions.json +3 -0
  2. package/README.md +45 -0
  3. package/index.html +13 -0
  4. package/package.json +39 -0
  5. package/public/vite.svg +1 -0
  6. package/src/assets/vue.svg +1 -0
  7. package/src/components/BT-Btn.vue +40 -0
  8. package/src/components/BT-Col.vue +36 -0
  9. package/src/components/BT-Span.vue +21 -0
  10. package/src/components/Dialog-Confirm.vue +47 -0
  11. package/src/components/Dialog-Select-Date.vue +89 -0
  12. package/src/components/Dialog-Select.vue +140 -0
  13. package/src/components/Dialog-Text.vue +59 -0
  14. package/src/composables/actions-tracker.ts +99 -0
  15. package/src/composables/actions.ts +354 -0
  16. package/src/composables/api.ts +471 -0
  17. package/src/composables/auth.ts +382 -0
  18. package/src/composables/cosmetics.ts +178 -0
  19. package/src/composables/csv.ts +198 -0
  20. package/src/composables/dates.ts +79 -0
  21. package/src/composables/demo.ts +25 -0
  22. package/src/composables/dialogs.ts +115 -0
  23. package/src/composables/document-meta.ts +40 -0
  24. package/src/composables/draggable.ts +189 -0
  25. package/src/composables/filters.ts +256 -0
  26. package/src/composables/forage.ts +49 -0
  27. package/src/composables/helpers.ts +694 -0
  28. package/src/composables/id.ts +20 -0
  29. package/src/composables/list.ts +601 -0
  30. package/src/composables/navigation.ts +214 -0
  31. package/src/composables/presets.ts +20 -0
  32. package/src/composables/pwa.ts +89 -0
  33. package/src/composables/resizable.ts +382 -0
  34. package/src/composables/rules.ts +40 -0
  35. package/src/composables/stores.ts +797 -0
  36. package/src/composables/track.ts +55 -0
  37. package/src/composables/urls.ts +11 -0
  38. package/src/core.ts +92 -0
  39. package/src/index.ts +16 -0
  40. package/src/types.ts +13 -0
  41. package/src/useApi.ts +68 -0
  42. package/src/vite-env.d.ts +1 -0
  43. package/test/api.test.ts +84 -0
  44. package/test/forage.test.ts +31 -0
  45. package/test/helpers.test.ts +231 -0
  46. package/test/navigation.test.ts +99 -0
  47. package/test/stores-last-update.test.ts +138 -0
  48. package/test/stores-session.test.ts +118 -0
  49. package/test/track.test.ts +29 -0
  50. package/test/utils.ts +15 -0
  51. package/tsconfig.json +33 -0
  52. package/tsconfig.node.json +11 -0
  53. package/vite.config.ts +19 -0
@@ -0,0 +1,797 @@
1
+ import { defineStore, StoreDefinition } from 'pinia'
2
+ import { ref, type Ref } from 'vue'
3
+ import { type PathOptions, type BTApi } from '@/composables/api'
4
+ import { DateTime } from 'luxon'
5
+ import { toValue } from 'vue'
6
+ import { appendUrl, getMinDateString } from '@/composables/helpers'
7
+ import { firstBy } from 'thenby'
8
+ import { useLocalDb } from '@/composables/forage'
9
+ import { BaseModel } from '@/types'
10
+ import { BTAuth } from './auth'
11
+
12
+ export type StoreMode = 'whole-last-updated' | 'partial-last-updated' | 'session'
13
+ export type StorageMode = 'session' | 'local-cache'
14
+
15
+ export interface LocalMeta {
16
+ storedOn: string //days
17
+ isDetailed?: boolean
18
+ earliestData?: string
19
+ lastUpdate?: string
20
+ }
21
+
22
+ export interface LocallyStoredItem<T> {
23
+ meta: LocalMeta,
24
+ data: T,
25
+ count?: number,
26
+ filters?: string[]
27
+ }
28
+
29
+ export interface StoreGetAllReturn<T> {
30
+ data: T[],
31
+ count?: number,
32
+ filters?: string[]
33
+ }
34
+
35
+ export interface StoreGetReturn<T> {
36
+ data: T
37
+ }
38
+
39
+ export interface BTStore extends StoreDefinition<any, {}, {},
40
+ {
41
+ deleteItem: (dOptions: PathOptions) => Promise<string | undefined>
42
+ get: <T extends BaseModel>(dOptions: PathOptions) => Promise<StoreGetReturn<T>>
43
+ getAll: <T extends BaseModel>(dOptions: PathOptions) => Promise<StoreGetAllReturn<T>>
44
+ patch: <T extends BaseModel>(dOptions: PathOptions) => Promise<T | undefined>
45
+ post: <T extends BaseModel>(dOptions: PathOptions) => Promise<T | undefined>
46
+ restore: <T extends BaseModel>(dOptions: PathOptions) => Promise<T | undefined>
47
+ }> {}
48
+
49
+ export interface ApiError {
50
+ message: string
51
+ }
52
+
53
+ interface UseStoreOptions {
54
+ /**ideally required. Otherwise will struggle to find url path */
55
+ api?: BTApi
56
+ /**ideally required. For setting unique local storage keys */
57
+ auth?: BTAuth
58
+ /**build a query. Overrides the default */
59
+ builderQuery?: (params: any) => string
60
+ /**overrides the default */
61
+ buildUrl?: (path: PathOptions) => string
62
+ /**particularly what store syle to use */
63
+ storeMode: StoreMode
64
+ /**whether to store data locally or only for the duration of the session */
65
+ storageMode: StorageMode
66
+ /**the name of this store */
67
+ storeName: string
68
+ }
69
+
70
+ export interface CreateStoreBuilderOptions {
71
+ /**ideally required. Otherwise will struggle to find url path */
72
+ api?: BTApi
73
+ /**ideally required. For setting unique local storage keys */
74
+ auth?: BTAuth
75
+ /**build a query. Overrides the default */
76
+ builderQuery?: (params: any) => string
77
+ /**overrides the default */
78
+ buildUrl?: (path: PathOptions) => string
79
+ }
80
+
81
+ export interface CreateStoreOptions {
82
+ /**particularly what store syle to use */
83
+ storeMode: StoreMode
84
+ /**whether to store data locally or only for the duration of the session */
85
+ storageMode: StorageMode
86
+ /**the name of this store */
87
+ storeName: string
88
+ }
89
+
90
+ export function createStoreBuilder(options: CreateStoreBuilderOptions): (opt: CreateStoreOptions) => BTStore {
91
+ return (opt: CreateStoreOptions) => {
92
+ return createStore({
93
+ ...options,
94
+ ...opt
95
+ })
96
+ }
97
+ }
98
+
99
+ export function createStore(options: UseStoreOptions): BTStore {
100
+ if (options.storeMode == 'whole-last-updated') {
101
+ if (options.api == null) throw new Error('Must supply an api object to use store')
102
+
103
+ return createWholeLastUpdateStore(options)
104
+ }
105
+ else if (options.storeMode == 'partial-last-updated') {
106
+ if (options.api == null) throw new Error('Must supply an api object to use store')
107
+
108
+ return createWholeLastUpdateStore(options)
109
+ }
110
+ else { //if (options.storeMode == 'session') {
111
+ return createSessionStore(options)
112
+ }
113
+ }
114
+
115
+ const defaultPathBuilder = (path: PathOptions) => {
116
+ let url: string | undefined = toValue(path.url) ?? undefined
117
+
118
+ if (path.additionalUrl != null) {
119
+ if (url == null)
120
+ url = path.additionalUrl
121
+ else
122
+ url = appendUrl(url, path.additionalUrl)
123
+ }
124
+
125
+ if (path.id != null) {
126
+ if (url?.includes('{id}'))
127
+ url = url.replaceAll('{id}', path.id)
128
+ else
129
+ url = appendUrl(url, path.id)
130
+ }
131
+
132
+ if (path.params != null) {
133
+ url ??= ''
134
+ let query = new URLSearchParams()
135
+ let entries = Object.entries(path.params).sort(firstBy(x => x[0]))
136
+ entries.forEach(entry => {
137
+ if (entry[1] != null)
138
+ query.append(entry[0], entry[1].toString())
139
+ })
140
+
141
+ url = `${url}?${query.toString()}`
142
+ }
143
+
144
+ path.finalUrl = url
145
+
146
+ return path.finalUrl ?? ''
147
+ }
148
+
149
+ interface UseSessionStoreOptions {
150
+ /**ideally required. Otherwise will struggle to find url path */
151
+ api?: BTApi
152
+ /**ideally required. For setting unique local storage keys */
153
+ auth?: BTAuth
154
+ /**whether to store data locally or only for the duration of the session */
155
+ storageMode: 'session' | 'local-cache'
156
+ /**build a query. Overrides the default */
157
+ buildQuery?: (params: any) => string
158
+ /**overrides the default */
159
+ buildUrl?: (path: PathOptions) => string
160
+ /**the name of this store */
161
+ storeName: string
162
+ }
163
+
164
+ export function createSessionStore(options: UseSessionStoreOptions): BTStore {
165
+ return defineStore(options.storeName, () => {
166
+ const currentTimeStampDays = DateTime.utc().toSeconds() / 86400
167
+ const searchMemory: Ref<any> = ref({})
168
+ const promiseMemory: Ref<any> = ref({})
169
+ const cacheLocally = options.storageMode == 'local-cache'
170
+
171
+ const buildPath = options.buildUrl ?? options.api?.buildUrl ?? defaultPathBuilder
172
+ // const authData = useAuthData()
173
+
174
+ function getKey(dOptions: PathOptions) {
175
+ return `${options.storeName}_${options.auth?.credentials.userID ?? 'no-user-id'}_${dOptions.id ?? dOptions.data?.id ?? 'no-item-id'}`
176
+ }
177
+
178
+ async function getAll<T extends BaseModel>(dOptions: PathOptions): Promise<StoreGetAllReturn<T>> {
179
+ dOptions.additionalUrl ??= '/getAll'
180
+
181
+ const path = buildPath(dOptions)
182
+ const key = `${options.storeName}_${options.auth?.credentials.userID ?? 'no-user-id'}_${path}`
183
+ const refresh = dOptions.refresh
184
+
185
+ if (!refresh && searchMemory.value[key] !== undefined)
186
+ return searchMemory.value[key] ?? null
187
+
188
+ if (!refresh && cacheLocally == true) {
189
+ //attempt to get locally
190
+ const localRes = await useLocalDb().getItem<LocallyStoredItem<T[]>>(key)
191
+
192
+ if (localRes != null &&
193
+ (options.api == null || parseFloat(localRes.meta.storedOn) > (currentTimeStampDays - 7))) {
194
+ searchMemory.value[key] = localRes
195
+ return localRes
196
+ }
197
+ }
198
+
199
+ //nothing exists in session so far so load from api
200
+ if (options.api == null) {
201
+ return searchMemory.value[key]
202
+ }
203
+
204
+ try {
205
+ let apiPrm = promiseMemory.value[key]
206
+
207
+ if (apiPrm == null) {
208
+ apiPrm = new Promise<StoreGetAllReturn<T> | undefined>(async (resolve, reject) => {
209
+ try {
210
+ let res = await options.api?.getAll<StoreGetAllReturn<T>>(dOptions)
211
+
212
+ if (cacheLocally == true) {
213
+ await useLocalDb().setItem(key, {
214
+ meta: { storedOn: currentTimeStampDays },
215
+ ...res
216
+ })
217
+ }
218
+
219
+ resolve(res)
220
+ }
221
+ catch (err) {
222
+ reject(err)
223
+ }
224
+ finally {
225
+ //remove promise
226
+ delete promiseMemory.value[key]
227
+ }
228
+ })
229
+
230
+ promiseMemory.value[key] = apiPrm
231
+ }
232
+
233
+ const apiRes = await apiPrm
234
+
235
+ searchMemory.value[key] = apiRes
236
+ return apiRes
237
+ }
238
+ catch (err) {
239
+ let e = err as ApiError
240
+ throw new Error(e.message ?? 'Problem')
241
+ }
242
+ }
243
+
244
+ async function get<T extends BaseModel>(dOptions: PathOptions): Promise<StoreGetReturn<T>> {
245
+ dOptions.additionalUrl ??= '/get'
246
+
247
+ const key = getKey(dOptions)
248
+ const refresh = dOptions.refresh
249
+
250
+ if (!refresh && searchMemory.value[key] !== undefined)
251
+ return searchMemory.value[key] ?? []
252
+
253
+ if (!refresh && cacheLocally == true ) {
254
+ //attempt to get locally
255
+ const localRes = await useLocalDb().getItem<LocallyStoredItem<T>>(key)
256
+
257
+ if (localRes != null &&
258
+ (options.api == null || parseFloat(localRes.meta.storedOn) > (currentTimeStampDays - 7))) {
259
+ searchMemory.value[key] = localRes
260
+ return localRes
261
+ }
262
+ }
263
+
264
+ //nothing exists in session so far so load from api
265
+ if (options.api == null) {
266
+ return searchMemory.value[key]
267
+ }
268
+
269
+ try {
270
+ let apiPrm = promiseMemory.value[key]
271
+
272
+ if (apiPrm == null) {
273
+ apiPrm = new Promise(async (resolve, reject) => {
274
+ try {
275
+ let res = await options.api?.get<LocallyStoredItem<T>>(dOptions)
276
+
277
+ if (cacheLocally == true) {
278
+ await useLocalDb().setItem(key, {
279
+ meta: { storedOn: currentTimeStampDays },
280
+ ...res
281
+ })
282
+ }
283
+
284
+ resolve(res)
285
+ }
286
+ catch (err) {
287
+ reject(err)
288
+ }
289
+ finally {
290
+ //remove promise
291
+ delete promiseMemory.value[key]
292
+ }
293
+ })
294
+
295
+ promiseMemory.value[key] = apiPrm
296
+ }
297
+
298
+ const apiRes = await apiPrm
299
+ searchMemory.value[key] = apiRes
300
+ return apiRes
301
+ }
302
+ catch (err) {
303
+ let e = err as ApiError
304
+ throw new Error(e.message ?? 'Problem')
305
+ }
306
+ }
307
+
308
+ async function patch<T extends BaseModel>(dOptions: PathOptions): Promise<T | undefined> {
309
+ dOptions.additionalUrl ??= '/patch'
310
+
311
+ const key = getKey(dOptions)
312
+ let patchedObject: any
313
+
314
+ //patch api
315
+ if (options.api != null) {
316
+ //do not bother saving the promise
317
+ try {
318
+ let apiRes = await options.api.patch<StoreGetReturn<T>>(dOptions)
319
+ patchedObject = apiRes?.data
320
+ }
321
+ catch (err) {
322
+ let e = err as ApiError
323
+ throw new Error(e.message ?? 'Patch Problem')
324
+ }
325
+ }
326
+ else {
327
+ patchedObject = dOptions.data
328
+ }
329
+
330
+ //update local versions
331
+ if (patchedObject != null) {
332
+ //update keyed search memory
333
+ const existingItem = searchMemory.value[key]
334
+ if (existingItem != null) {
335
+ existingItem.data = { ...existingItem.data, ...patchedObject }
336
+
337
+ //override local cache
338
+ if (cacheLocally == true) {
339
+ await useLocalDb().setItem(key, {
340
+ meta: { storedOn: currentTimeStampDays },
341
+ ...existingItem
342
+ })
343
+ }
344
+ }
345
+
346
+ //update other session stored items
347
+ if (patchedObject.id != null && patchedObject.rowVersion != null) {
348
+ let memoryItems = Object.entries(searchMemory.value)
349
+ memoryItems.forEach(entry => {
350
+ let entryVal = entry[1] as any
351
+ entryVal = entryVal.data
352
+ if (entryVal != null) {
353
+ if (Array.isArray(entryVal)) {
354
+ for (let i = 0; i < entryVal.length; i++) {
355
+ const listItem = entryVal[i];
356
+ if (listItem.id == patchedObject.id) {
357
+ entryVal.splice(i, 1, { ...listItem, ...patchedObject})
358
+ }
359
+ }
360
+ }
361
+ else {
362
+ if (entryVal.id == patchedObject.id) {
363
+ searchMemory.value[entry[0]].data = { ...entryVal, ...patchedObject}
364
+ }
365
+ }
366
+ }
367
+ })
368
+ }
369
+ }
370
+
371
+ return patchedObject
372
+ }
373
+
374
+ async function post<T extends BaseModel>(dOptions: PathOptions): Promise<T | undefined> {
375
+ dOptions.additionalUrl ??= '/post'
376
+
377
+ const key = getKey(dOptions)
378
+ let postedObject: any
379
+
380
+ //patch api
381
+ if (options.api != null) {
382
+ //do not bother saving the promise
383
+ try {
384
+ let apiRes = await options.api.post<StoreGetReturn<T>>(dOptions)
385
+ postedObject = apiRes?.data
386
+ }
387
+ catch (err) {
388
+ let e = err as ApiError
389
+ throw new Error(e.message ?? 'Patch Problem')
390
+ }
391
+ }
392
+ else {
393
+ postedObject = dOptions.data
394
+ }
395
+
396
+ //update local versions
397
+ searchMemory.value[key] = {
398
+ meta: { storedOn: currentTimeStampDays },
399
+ data: postedObject
400
+ }
401
+
402
+ if (cacheLocally == true) {
403
+ await useLocalDb().setItem(key, searchMemory.value[key])
404
+ }
405
+
406
+ return postedObject
407
+ }
408
+
409
+ async function deleteItem(dOptions: PathOptions): Promise<string | undefined> {
410
+ dOptions.additionalUrl ??= '/delete'
411
+ const key = getKey(dOptions)
412
+
413
+ //delete api
414
+ if (options.api != null) {
415
+ //do not bother saving the promise
416
+ try {
417
+ let res = await options.api.deleteItem(dOptions)
418
+ if (res != null)
419
+ return res
420
+ }
421
+ catch (err) {
422
+ let e = err as ApiError
423
+ throw new Error(e.message ?? 'Patch Problem')
424
+ }
425
+ }
426
+
427
+ //delete local memory
428
+ delete searchMemory.value[key]
429
+
430
+ if (cacheLocally == true) {
431
+ await useLocalDb().removeItem(key)
432
+ }
433
+
434
+ return undefined
435
+ }
436
+
437
+ async function restore<T extends BaseModel>(dOptions: PathOptions): Promise<T | undefined> {
438
+ dOptions.additionalUrl ??= `/patch/restore?id=${dOptions.data.id}`
439
+
440
+ buildPath(dOptions)
441
+
442
+ //patch api
443
+ if (options.api != null) {
444
+ //do not bother saving the promise
445
+ try {
446
+ let apiRes = await options.api.patch<StoreGetReturn<T>>(dOptions)
447
+ return apiRes?.data
448
+ }
449
+ catch (err) {
450
+ let e = err as ApiError
451
+ throw new Error(e.message ?? 'Patch Problem')
452
+ }
453
+ }
454
+
455
+ return undefined
456
+ }
457
+
458
+ return {
459
+ deleteItem,
460
+ get,
461
+ getAll,
462
+ patch,
463
+ post,
464
+ restore,
465
+ searchMemory //mainly for testing
466
+ }
467
+ })
468
+ }
469
+
470
+ interface UseWholeLastUpdateStoreOptions {
471
+ /**ideally required. Otherwise will struggle to find url path */
472
+ api?: BTApi
473
+ /**ideally required. For setting unique local storage keys */
474
+ auth?: BTAuth
475
+ storageMode: 'session' | 'local-cache'
476
+ storeName: string
477
+ }
478
+
479
+ export function createWholeLastUpdateStore(options: UseWholeLastUpdateStoreOptions): BTStore {
480
+ return defineStore(options.storeName, () => {
481
+ const dataItems: Ref<any[] | undefined> = ref()
482
+ const count = ref(0)
483
+ const currentTimeStampHours = DateTime.utc().toSeconds() / 3600
484
+ const filters: Ref<string[] | undefined> = ref()
485
+ const meta: Ref<LocalMeta | undefined> = ref()
486
+ const promiseMemory: Ref<any> = ref({})
487
+ const cacheLocally = options.storageMode == 'local-cache'
488
+
489
+ function getKey() {
490
+ return `${options.storeName}_${options.auth?.credentials.userID ?? 'no-user-id'}`
491
+ }
492
+
493
+ async function trySaveToLocalCache() {
494
+ if (cacheLocally == true) {
495
+ //save locally
496
+ await useLocalDb().setItem(getKey(), {
497
+ meta: meta.value,
498
+ data: dataItems.value,
499
+ count: count.value,
500
+ filters: []
501
+ })
502
+ }
503
+ }
504
+
505
+ function createRefreshPromise<T extends BaseModel>(dOptions: PathOptions): Promise<StoreGetAllReturn<T>> {
506
+ const key = getKey()
507
+
508
+ if (promiseMemory.value[key])
509
+ return promiseMemory.value[key]
510
+
511
+ promiseMemory.value[key] = new Promise<StoreGetAllReturn<T>>(async (resolve, reject) => {
512
+ try {
513
+ let res = await options.api?.getAll<StoreGetAllReturn<T>>({
514
+ additionalUrl: '/getAll',
515
+ nav: dOptions.nav,
516
+ params: {
517
+ lastUpdate: meta.value?.lastUpdate ?? getMinDateString()
518
+ }
519
+ })
520
+
521
+ if (res == null) {
522
+ reject(res)
523
+ }
524
+ else {
525
+ //update
526
+ dataItems.value ??= []
527
+ res.data.forEach(serverItem => {
528
+ const existingInd = dataItems.value!.findIndex(x => x.id == serverItem.id)
529
+ if (existingInd >= 0)
530
+ dataItems.value?.splice(existingInd, 1, serverItem)
531
+ else
532
+ dataItems.value?.push(serverItem)
533
+ })
534
+
535
+ count.value = dataItems.value.length
536
+ meta.value = {
537
+ lastUpdate: DateTime.utc().toString(),
538
+ storedOn: currentTimeStampHours.toString()
539
+ }
540
+
541
+ await trySaveToLocalCache()
542
+
543
+ resolve(res)
544
+ }
545
+ }
546
+ catch (err) {
547
+ reject(err)
548
+ }
549
+ finally {
550
+ //remove promise
551
+ delete promiseMemory.value[key]
552
+ }
553
+ })
554
+
555
+ return promiseMemory.value[key]
556
+ }
557
+
558
+ async function getAll<T extends BaseModel>(dOptions: PathOptions): Promise<StoreGetAllReturn<T>> {
559
+ const key = getKey()
560
+ const refresh = dOptions.refresh
561
+ dOptions.nav ??= options.storeName
562
+
563
+ if (!refresh && dataItems.value != null) {
564
+ return {
565
+ count: dataItems.value?.length,
566
+ data: dataItems.value,
567
+ filters: []
568
+ }
569
+ }
570
+
571
+ if (!refresh && cacheLocally == true) {
572
+ //retrieve from local cache
573
+ const localRes = await useLocalDb().getItem<LocallyStoredItem<T[]>>(key)
574
+
575
+ if (localRes != null &&
576
+ parseFloat(localRes.meta.storedOn) > (currentTimeStampHours - 12)) {
577
+ dataItems.value = localRes.data
578
+ count.value = localRes.data.length
579
+ filters.value = localRes.filters ?? []
580
+ meta.value = localRes.meta
581
+
582
+ return {
583
+ count: dataItems.value?.length,
584
+ data: dataItems.value,
585
+ filters: []
586
+ }
587
+ }
588
+ }
589
+
590
+ //nothing exists in session so far so load from api
591
+ if (options.api == null) {
592
+ return {
593
+ count: dataItems.value?.length,
594
+ data: dataItems.value ?? [],
595
+ filters: filters.value
596
+ }
597
+ }
598
+ else {
599
+ dataItems.value ??= []
600
+ }
601
+
602
+ try {
603
+ return await createRefreshPromise(dOptions)
604
+ }
605
+ catch (err) {
606
+ let e = err as ApiError
607
+ throw new Error(e.message ?? 'Problem')
608
+ }
609
+ }
610
+
611
+ async function get<T extends BaseModel>(dOptions: PathOptions): Promise<StoreGetReturn<T>> {
612
+ const key = getKey()
613
+ const refresh = dOptions.refresh
614
+ dOptions.nav ??= options.storeName
615
+
616
+ if (!refresh && dataItems.value != null) {
617
+ return {
618
+ data: dataItems.value.find(x => x.id == dOptions.id)
619
+ }
620
+ }
621
+
622
+ if (!refresh && cacheLocally == true) {
623
+ //retrieve from local cache
624
+ const localRes = await useLocalDb().getItem<LocallyStoredItem<T[]>>(key)
625
+
626
+ if (localRes != null &&
627
+ parseFloat(localRes.meta.storedOn) > (currentTimeStampHours - 12)) {
628
+ dataItems.value = localRes.data
629
+ count.value = localRes.data.length
630
+ filters.value = localRes.filters ?? []
631
+ meta.value = localRes.meta
632
+
633
+ let existingItem = dataItems.value.find(x => x.id == dOptions.id)
634
+
635
+ if (existingItem != null)
636
+ return { data: existingItem }
637
+ }
638
+ }
639
+
640
+ if (options.api == null) {
641
+ return {
642
+ data: dataItems.value?.find(x => x.id == dOptions.id)
643
+ }
644
+ }
645
+ else {
646
+ dataItems.value ??= []
647
+ }
648
+
649
+ try {
650
+ await createRefreshPromise<T>(dOptions)
651
+
652
+ return { data: dataItems.value?.find(x => x.id == dOptions.id) }
653
+ }
654
+ catch (err) {
655
+ let e = err as ApiError
656
+ throw new Error(e.message ?? 'Problem')
657
+ }
658
+ }
659
+
660
+ async function patch<T extends BaseModel>(dOptions: PathOptions): Promise<T | undefined> {
661
+ dOptions.additionalUrl ??= '/patch'
662
+ dOptions.nav ??= options.storeName
663
+
664
+ let patchedObject: any
665
+
666
+ //patch api
667
+ if (options.api != null) {
668
+ //do not bother saving the promise
669
+ try {
670
+ let apiRes = await options.api.patch<StoreGetReturn<T>>(dOptions)
671
+ patchedObject = { ...dOptions.data, ...apiRes?.data }
672
+ }
673
+ catch (err) {
674
+ let e = err as ApiError
675
+ throw new Error(e.message ?? 'Patch Problem')
676
+ }
677
+ }
678
+ else {
679
+ patchedObject = dOptions.data
680
+ }
681
+
682
+ if (patchedObject != null) {
683
+ let existingInd = dataItems.value?.findIndex(x => x.id == patchedObject.id)
684
+ if (existingInd != null && existingInd >= 0) {
685
+ dataItems.value ??= []
686
+ dataItems.value?.splice(existingInd, 1, patchedObject)
687
+ await trySaveToLocalCache()
688
+ }
689
+ }
690
+
691
+ return dataItems.value?.find(x => x.id == (dOptions.id ?? dOptions.data.id))
692
+ }
693
+
694
+ async function post<T extends BaseModel>(dOptions: PathOptions): Promise<T | undefined> {
695
+ dOptions.additionalUrl ??= '/post'
696
+ dOptions.nav ??= options.storeName
697
+
698
+ let postedObject: any
699
+
700
+ //post api
701
+ if (options.api != null) {
702
+ //do not bother saving the promise
703
+ try {
704
+ let apiRes = await options.api.post<StoreGetReturn<T>>(dOptions)
705
+ postedObject = apiRes?.data
706
+ }
707
+ catch (err) {
708
+ let e = err as ApiError
709
+ throw new Error(e.message ?? 'Patch Problem')
710
+ }
711
+ }
712
+ else {
713
+ postedObject = dOptions.data
714
+ }
715
+
716
+ if (postedObject != null) {
717
+ dataItems.value ??= []
718
+ dataItems.value?.unshift(postedObject)
719
+ count.value += 1
720
+ await trySaveToLocalCache()
721
+ }
722
+
723
+ return dataItems.value?.find(x => x.id == (dOptions.id ?? dOptions.data.id))
724
+ }
725
+
726
+ async function deleteItem(dOptions: PathOptions): Promise<string | undefined> {
727
+ dOptions.additionalUrl ??= '/delete'
728
+ dOptions.nav ??= options.storeName
729
+
730
+ //delete api
731
+ if (options.api != null) {
732
+ //do not bother saving the promise
733
+ try {
734
+ let res = await options.api.deleteItem(dOptions)
735
+ if (res != null)
736
+ return res
737
+ }
738
+ catch (err) {
739
+ let e = err as ApiError
740
+ throw new Error(e.message ?? 'Patch Problem')
741
+ }
742
+ }
743
+
744
+ const id = dOptions.id ?? dOptions.data.id
745
+ if (id != null) {
746
+ let existingInd = dataItems.value?.findIndex(x => x.id == id)
747
+ if (existingInd != null && existingInd >= 0) {
748
+ dataItems.value ??= []
749
+ dataItems.value?.splice(existingInd, 1)
750
+ await trySaveToLocalCache()
751
+ }
752
+ }
753
+
754
+ return undefined
755
+ }
756
+
757
+ async function restore<T extends BaseModel>(dOptions: PathOptions): Promise<T | undefined> {
758
+ dOptions.additionalUrl ??= `/patch/restore?id=${dOptions.data.id}`
759
+ dOptions.nav ??= options.storeName
760
+
761
+ //patch api
762
+ if (options.api != null) {
763
+ //do not bother saving the promise
764
+ try {
765
+ await options.api.patch<StoreGetReturn<T>>(dOptions)
766
+ // let apiRes = await options.api.patch<StoreGetReturn<T>>(dOptions)
767
+ // return apiRes?.data
768
+ }
769
+ catch (err) {
770
+ let e = err as ApiError
771
+ throw new Error(e.message ?? 'Patch Problem')
772
+ }
773
+ }
774
+
775
+ //refresh
776
+ try {
777
+ dOptions.additionalUrl = '/getAll'
778
+ await createRefreshPromise<any>(dOptions)
779
+ }
780
+ catch (err) {
781
+ let e = err as ApiError
782
+ throw new Error(e.message ?? 'Problem')
783
+ }
784
+
785
+ return dataItems.value?.find(x => x.id == (dOptions.id ?? dOptions.data.id))
786
+ }
787
+
788
+ return {
789
+ deleteItem,
790
+ get,
791
+ getAll,
792
+ patch,
793
+ post,
794
+ restore
795
+ }
796
+ })
797
+ }