Skip to content

Commit f3f1a4c

Browse files
committed
feat(errors): consolidate errors in service
1 parent c37f0fe commit f3f1a4c

13 files changed

+126
-9
lines changed

src/components/AppComponent.vue

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { RouterView, useRouter } from 'vue-router';
55
66
import { NavbarComponent } from '~/components/common';
77
import GridBackground from '~/components/common/background/GridBackground.vue';
8+
import DebugProvider from '~/components/common/debug/DebugProvider.vue';
89
import PageLoading from '~/components/common/loading/PageLoading.vue';
910
import IconChevronLeft from '~/components/icons/IconChevronLeft.vue';
1011
import IconClose from '~/components/icons/IconClose.vue';
@@ -105,6 +106,7 @@ const onBack = () => {
105106
</NDrawer>
106107
</RouterView>
107108
</aside>
109+
<DebugProvider />
108110
</RouterView>
109111
</template>
110112

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script lang="ts" setup>
2+
import { onMounted, ref } from 'vue';
3+
4+
import { ErrorService } from '~/services/error.service';
5+
import { TraktService } from '~/services/trakt.service';
6+
7+
const debugProvider = ref<HTMLDivElement>();
8+
9+
onMounted(() => {
10+
console.info('DebugProvider mounted', debugProvider.value);
11+
Object.defineProperty(debugProvider.value, 'trakt', {
12+
value: TraktService,
13+
});
14+
Object.defineProperty(debugProvider.value, 'error', {
15+
value: ErrorService,
16+
});
17+
});
18+
</script>
19+
20+
<template>
21+
<div id="debug-provider" ref="debugProvider" hidden />
22+
</template>

src/services/error.service.ts

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { isReactive, isRef, toRaw } from 'vue';
2+
3+
import type { RecursiveRecord } from '@dvcol/common-utils/common';
4+
5+
const toRawDeep = (obj: RecursiveRecord): RecursiveRecord => {
6+
return Object.fromEntries(
7+
Object.entries(obj).map(([key, value]) => {
8+
if (Object.values(value).some(v => isRef(v) || isReactive(v))) return [key, toRawDeep(value)];
9+
return [key, toRaw(value)];
10+
}),
11+
);
12+
};
13+
14+
export class ErrorService {
15+
private static _dictionaries: RecursiveRecord = {};
16+
private static _errors: Array<{ date: Date; error: unknown; meta: RecursiveRecord }> = [];
17+
18+
static retention = 30;
19+
20+
static get dictionaries() {
21+
return toRawDeep(this._dictionaries);
22+
}
23+
24+
static get errors() {
25+
return this._errors;
26+
}
27+
28+
static get lastError() {
29+
return this._errors[this._errors.length - 1];
30+
}
31+
32+
static registerDictionary(name: string, dictionary: RecursiveRecord) {
33+
if (this._dictionaries[name]) throw new Error(`Dictionary ${name} already exists`);
34+
this._dictionaries[name] = dictionary;
35+
}
36+
37+
static registerError(error: unknown, ...rest: unknown[]) {
38+
if (this._errors.length >= this.retention) this._errors.shift();
39+
this._errors.push({ date: new Date(), error, meta: { ...rest } });
40+
}
41+
42+
static since(date: Date) {
43+
return this._errors.filter(({ date: errorDate }) => errorDate > date);
44+
}
45+
}

src/services/trakt.service.ts

+12
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import type { TvdbApiResponse } from '@dvcol/tvdb-http-client/models';
4242
import type { ProgressItem } from '~/models/progress.model';
4343
import type { SettingsAuth, UserSetting } from '~/models/trakt-service.model';
4444

45+
import { ErrorService } from '~/services/error.service';
4546
import { LoadingBarService } from '~/services/loading-bar.service';
4647
import { tmdbUsedApi } from '~/services/tmdb.used.api';
4748
import { traktUsedApi } from '~/services/trakt-used.api';
@@ -186,6 +187,7 @@ export class TraktService {
186187
LoadingBarService.finish();
187188
} catch (error) {
188189
LoadingBarService.error();
190+
ErrorService.registerError(error);
189191
} finally {
190192
clearTimeout(timeout);
191193
}
@@ -207,6 +209,11 @@ export class TraktService {
207209

208210
this.tvdbClient.onCall(async call => {
209211
logger.debug('TvdbClient.onCall', call);
212+
try {
213+
await call.query;
214+
} catch (error) {
215+
ErrorService.registerError(error);
216+
}
210217
});
211218

212219
this.tmdbClient.onAuthChange(async _auth => {
@@ -215,6 +222,11 @@ export class TraktService {
215222

216223
this.tmdbClient.onCall(async call => {
217224
logger.debug('TmdbClient.onCall', call);
225+
try {
226+
await call.query;
227+
} catch (error) {
228+
ErrorService.registerError(error);
229+
}
218230
});
219231
}
220232

src/stores/data/calendar.store.ts

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { TraktCalendarMovie, TraktCalendarQuery, TraktCalendarShow } from '
55

66
import { type ListScrollItem, type ListScrollItemTag, ListScrollItemType } from '~/models/list-scroll.model';
77

8+
import { ErrorService } from '~/services/error.service';
89
import { NotificationService } from '~/services/notification.service';
910
import { TraktService } from '~/services/trakt.service';
1011
import { logger } from '~/stores/settings/log.store';
@@ -58,6 +59,7 @@ export const useCalendarStore = defineStore(CalendarStoreConstants.Store, () =>
5859
const filter = ref('');
5960

6061
const calendarErrors = reactive<ErrorDictionary>({});
62+
ErrorService.registerDictionary('calendar', calendarErrors);
6163

6264
const clearState = (date: Date = new Date()) => {
6365
calendar.value = [];

src/stores/data/history.store.ts

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { TraktClientPagination, TraktHistory, TraktHistoryGetQuery } from '
55

66
import type { ErrorDictionary } from '~/utils/retry.utils';
77

8+
import { ErrorService } from '~/services/error.service';
89
import { NotificationService } from '~/services/notification.service';
910
import { TraktService } from '~/services/trakt.service';
1011
import { logger } from '~/stores/settings/log.store';
@@ -31,6 +32,7 @@ export const useHistoryStore = defineStore(HistoryStoreConstants.Store, () => {
3132
const threshold = ref(10);
3233

3334
const historyErrors = reactive<ErrorDictionary>({});
35+
ErrorService.registerDictionary('history', historyErrors);
3436

3537
const saveState = async () =>
3638
storage.local.set(HistoryStoreConstants.Store, {

src/stores/data/image.store.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { computed, reactive, type Ref, ref } from 'vue';
55

66
import type { TmdbConfiguration, TmdbImage } from '@dvcol/tmdb-http-client/models';
77

8+
import { ErrorService } from '~/services/error.service';
89
import { TraktService } from '~/services/trakt.service';
910
import { logger } from '~/stores/settings/log.store';
1011
import { localCache, storage } from '~/utils/browser/browser-storage.utils';
@@ -61,7 +62,7 @@ type ImagePayload = {
6162
profiles?: TmdbImage[]; // profiles
6263
};
6364

64-
const EmptyImageStore = {
65+
const emptyImageStore = {
6566
movie: {},
6667
show: {},
6768
season: {},
@@ -89,9 +90,10 @@ const localArrayMax = (
8990

9091
export const useImageStore = defineStore(ImageStoreConstants.Store, () => {
9192
const tmdbConfig = ref<TmdbConfiguration>();
92-
const images = reactive<ImageStore>(EmptyImageStore);
93+
const images = reactive<ImageStore>(emptyImageStore);
9394

94-
const imageErrors = reactive<ImageStoreErrors>(EmptyImageStore);
95+
const imageErrors = reactive<Partial<ImageStoreErrors>>({});
96+
ErrorService.registerDictionary('image', imageErrors);
9597

9698
const saveState = debounce(
9799
(_images = images) =>
@@ -137,14 +139,15 @@ export const useImageStore = defineStore(ImageStoreConstants.Store, () => {
137139
const queueRequest = async ({ key, type }: { key: string; type: ImageQuery['type'] }, request: () => Promise<ImagePayload>) => {
138140
try {
139141
if (!(key in queue)) queue[key] = request();
140-
delete imageErrors[type][key];
142+
const response = await queue[key];
143+
delete imageErrors[type]?.[key];
144+
return response;
141145
} catch (error) {
142146
logger.error('Failed to queue image request', { key, type });
143147
if (!imageErrors[type]) imageErrors[type] = {};
144-
imageErrors[type][key] = ErrorCount.fromDictionary(imageErrors[type], key, error);
148+
imageErrors[type]![key] = ErrorCount.fromDictionary(imageErrors[type]!, key, error);
145149
throw error;
146150
}
147-
return queue[key];
148151
};
149152

150153
const getKeyAndType = ({ id, season, episode, type }: ImageQuery): { key: string; type: ImageQuery['type'] } => {

src/stores/data/list.store.ts

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import IconGrid from '~/components/icons/IconGrid.vue';
2020
import IconHeart from '~/components/icons/IconHeart.vue';
2121
import IconList from '~/components/icons/IconList.vue';
2222
import { ListScrollItemType } from '~/models/list-scroll.model';
23+
import { ErrorService } from '~/services/error.service';
2324
import { NotificationService } from '~/services/notification.service';
2425
import { TraktService } from '~/services/trakt.service';
2526
import { useActivityStore } from '~/stores/data/activity.store';
@@ -96,6 +97,7 @@ export const useListsStore = defineStore(ListsStoreConstants.Store, () => {
9697
const activeList = ref<ListEntity>(DefaultLists.Watchlist);
9798

9899
const listsErrors = reactive<ErrorDictionary>({});
100+
ErrorService.registerDictionary('lists', listsErrors);
99101

100102
const saveState = async () =>
101103
storage.local.set(ListsStoreConstants.Store, {
@@ -229,6 +231,7 @@ export const useListStore = defineStore(ListStoreConstants.Store, () => {
229231
const listDictionaryLoading = reactive<ListDictionaryLoading>({});
230232

231233
const listErrors = reactive<ListErrorDictionary>({});
234+
ErrorService.registerDictionary('list', listErrors);
232235

233236
const saveState = async () => storage.local.set(ListStoreConstants.LocalPageSize, pageSize.value);
234237
const restoreState = async () => {

src/stores/data/movie.store.ts

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { computed, reactive, ref, watch } from 'vue';
44

55
import type { TraktMovieExtended } from '@dvcol/trakt-http-client/models';
66

7+
import { ErrorService } from '~/services/error.service';
78
import { NotificationService } from '~/services/notification.service';
89
import { TraktService } from '~/services/trakt.service';
910
import { logger } from '~/stores/settings/log.store';
@@ -27,6 +28,7 @@ export const useMovieStore = defineStore('data.movie', () => {
2728
const loadingCollected = ref(false);
2829

2930
const movieErrors = reactive<ErrorDictionary>({});
31+
ErrorService.registerDictionary('movie', movieErrors);
3032

3133
const clearProgressState = () => {
3234
clearProxy(moviesWatched);

src/stores/data/person.store.ts

+3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import type { TraktPersonExtended } from '@dvcol/trakt-http-client/models';
66

77
import type { ErrorDictionary } from '~/utils/retry.utils';
88

9+
import { ErrorService } from '~/services/error.service';
910
import { NotificationService } from '~/services/notification.service';
11+
1012
import { TraktService } from '~/services/trakt.service';
1113
import { logger } from '~/stores/settings/log.store';
1214
import { ErrorCount } from '~/utils/retry.utils';
@@ -20,6 +22,7 @@ export const usePersonStore = defineStore('data.person', () => {
2022
const loading = reactive<LoadingDictionary>({});
2123

2224
const peopleErrors = reactive<ErrorDictionary>({});
25+
ErrorService.registerDictionary('person', peopleErrors);
2326

2427
const clearState = () => {
2528
clearProxy(people);

src/stores/data/search.store.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import type { TraktClientPagination, TraktSearch, TraktSearchResult, TraktSearch
77
import type { ErrorDictionary } from '~/utils/retry.utils';
88

99
import { type ListScrollItem, ListScrollItemType } from '~/models/list-scroll.model';
10-
10+
import { ErrorService } from '~/services/error.service';
1111
import { NotificationService } from '~/services/notification.service';
12+
1213
import { TraktService } from '~/services/trakt.service';
1314
import { logger } from '~/stores/settings/log.store';
1415
import { storage } from '~/utils/browser/browser-storage.utils';
@@ -37,6 +38,7 @@ export const useSearchStore = defineStore(SearchStoreConstants.Store, () => {
3738
const search = ref('');
3839

3940
const searchErrors = reactive<ErrorDictionary>({});
41+
ErrorService.registerDictionary('search', searchErrors);
4042

4143
const pageSize = ref(100);
4244
const pagination = ref<TraktClientPagination>();

src/stores/data/show.store.ts

+10
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
} from '@dvcol/trakt-http-client/models';
1414

1515
import { type ShowProgress, ShowProgressType } from '~/models/list-scroll.model';
16+
import { ErrorService } from '~/services/error.service';
1617
import { NotificationService } from '~/services/notification.service';
1718
import { TraktService } from '~/services/trakt.service';
1819
import { logger } from '~/stores/settings/log.store';
@@ -92,6 +93,15 @@ export const useShowStore = defineStore('data.show', () => {
9293
const showWatchedProgressError = reactive<ErrorDictionary>({});
9394
const showCollectionProgressError = reactive<ErrorDictionary>({});
9495

96+
ErrorService.registerDictionary('show', {
97+
shows: showsError,
98+
seasons: showsSeasonsError,
99+
seasonEpisodes: showsSeasonEpisodesError,
100+
episodes: showsEpisodesError,
101+
watched: showWatchedProgressError,
102+
collected: showCollectionProgressError,
103+
});
104+
95105
const clearProgressState = () => {
96106
clearProxy(showsWatchedProgress);
97107
clearProxy(showsCollectionProgress);

src/utils/retry.utils.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,21 @@ export type ErrorDictionary = Record<ErrorDictionaryKey, ErrorCount>;
66
export class ErrorCount {
77
last: Date;
88
count: number;
9-
error?: unknown;
9+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- necessary for unknown error types
10+
error?: any;
1011

1112
constructor(last: Date, count: number, error?: unknown) {
1213
this.last = last;
1314
this.count = count;
14-
this.error = error;
15+
16+
if (error instanceof Response) {
17+
this.error = { response: error.clone(), content: 'pending' };
18+
this.error.response.json().then((json: unknown) => {
19+
this.error.content = json;
20+
});
21+
} else {
22+
this.error = error;
23+
}
1524
}
1625

1726
static fromDictionary<T extends ErrorDictionary>(dictionary: T, key: ErrorDictionaryKey, error?: unknown) {

0 commit comments

Comments
 (0)