diff --git a/src/api/queryRequest.js b/src/api/queryRequest.js index e1fefab5..603566d1 100644 --- a/src/api/queryRequest.js +++ b/src/api/queryRequest.js @@ -42,21 +42,11 @@ const registry = Object.freeze({ policy: entityQueryPolicies.group, queryFn: (params) => groupRequest.getGroup(params) }, - groupPosts: { - key: (params) => queryKeys.groupPosts(params), - policy: entityQueryPolicies.groupCollection, - queryFn: (params) => groupRequest.getGroupPosts(params) - }, groupMember: { key: (params) => queryKeys.groupMember(params), policy: entityQueryPolicies.groupCollection, queryFn: (params) => groupRequest.getGroupMember(params) }, - groupMembers: { - key: (params) => queryKeys.groupMembers(params), - policy: entityQueryPolicies.groupCollection, - queryFn: (params) => groupRequest.getGroupMembers(params) - }, groupGallery: { key: (params) => queryKeys.groupGallery(params), policy: entityQueryPolicies.groupCollection, @@ -67,21 +57,6 @@ const registry = Object.freeze({ policy: entityQueryPolicies.groupCollection, queryFn: (params) => groupRequest.getGroupCalendar(params.groupId) }, - groupCalendars: { - key: (params) => queryKeys.groupCalendars(params), - policy: entityQueryPolicies.groupCalendarCollection, - queryFn: (params) => groupRequest.getGroupCalendars(params) - }, - followingGroupCalendars: { - key: (params) => queryKeys.followingGroupCalendars(params), - policy: entityQueryPolicies.groupFollowingCalendarCollection, - queryFn: (params) => groupRequest.getFollowingGroupCalendars(params) - }, - featuredGroupCalendars: { - key: (params) => queryKeys.featuredGroupCalendars(params), - policy: entityQueryPolicies.groupFeaturedCalendarCollection, - queryFn: (params) => groupRequest.getFeaturedGroupCalendars(params) - }, groupCalendarEvent: { key: (params) => queryKeys.groupCalendarEvent(params), policy: entityQueryPolicies.groupCalendarEvent, @@ -92,71 +67,16 @@ const registry = Object.freeze({ policy: entityQueryPolicies.avatarGallery, queryFn: (params) => avatarRequest.getAvatarGallery(params.avatarId) }, - friends: { - key: (params) => queryKeys.friends(params), - policy: entityQueryPolicies.friendList, - queryFn: (params) => friendRequest.getFriends(params) - }, favoriteLimits: { key: () => queryKeys.favoriteLimits(), - policy: entityQueryPolicies.favoriteCollection, + policy: entityQueryPolicies.favoriteLimits, queryFn: () => favoriteRequest.getFavoriteLimits() }, - favorites: { - key: (params) => queryKeys.favorites(params), - policy: entityQueryPolicies.favoriteCollection, - queryFn: (params) => favoriteRequest.getFavorites(params) - }, - favoriteGroups: { - key: (params) => queryKeys.favoriteGroups(params), - policy: entityQueryPolicies.favoriteCollection, - queryFn: (params) => favoriteRequest.getFavoriteGroups(params) - }, - favoriteWorlds: { - key: (params) => queryKeys.favoriteWorlds(params), - policy: entityQueryPolicies.favoriteCollection, - queryFn: (params) => favoriteRequest.getFavoriteWorlds(params) - }, - favoriteAvatars: { - key: (params) => queryKeys.favoriteAvatars(params), - policy: entityQueryPolicies.favoriteCollection, - queryFn: (params) => favoriteRequest.getFavoriteAvatars(params) - }, - galleryFiles: { - key: (params) => queryKeys.galleryFiles(params), - policy: entityQueryPolicies.galleryCollection, - queryFn: (params) => vrcPlusIconRequest.getFileList(params) - }, - prints: { - key: (params) => queryKeys.prints(params), - policy: entityQueryPolicies.galleryCollection, - queryFn: (params) => vrcPlusImageRequest.getPrints(params) - }, - print: { - key: (params) => queryKeys.print(params.printId), - policy: entityQueryPolicies.galleryCollection, - queryFn: (params) => vrcPlusImageRequest.getPrint(params) - }, userInventoryItem: { key: (params) => queryKeys.userInventoryItem(params), policy: entityQueryPolicies.inventoryCollection, queryFn: (params) => inventoryRequest.getUserInventoryItem(params) }, - inventoryItem: { - key: (params) => queryKeys.inventoryItem(params.inventoryId), - policy: entityQueryPolicies.inventoryObject, - queryFn: (params) => inventoryRequest.getInventoryItem(params) - }, - inventoryItems: { - key: (params) => queryKeys.inventoryItems(params), - policy: entityQueryPolicies.inventoryCollection, - queryFn: (params) => inventoryRequest.getInventoryItems(params) - }, - inventoryTemplate: { - key: (params) => queryKeys.inventoryTemplate(params.inventoryTemplateId), - policy: entityQueryPolicies.inventoryObject, - queryFn: (params) => inventoryRequest.getInventoryTemplate(params) - }, fileAnalysis: { key: (params) => queryKeys.fileAnalysis(params), policy: entityQueryPolicies.fileAnalysis, @@ -181,6 +101,21 @@ const registry = Object.freeze({ key: (params) => queryKeys.file(params.fileId), policy: entityQueryPolicies.fileObject, queryFn: (params) => miscRequest.getFile(params) + }, + avatarStyles: { + key: () => queryKeys.avatarStyles(), + policy: entityQueryPolicies.avatarStyles, + queryFn: () => avatarRequest.getAvailableAvatarStyles() + }, + representedGroup: { + key: (params) => queryKeys.representedGroup(params.userId), + policy: entityQueryPolicies.representedGroup, + queryFn: (params) => groupRequest.getRepresentedGroup(params) + }, + vrchatCredits: { + key: () => queryKeys.vrchatCredits(), + policy: entityQueryPolicies.vrchatCredits, + queryFn: () => miscRequest.getVRChatCredits() } }); diff --git a/src/components/dialogs/AvatarDialog/SetAvatarStylesDialog.vue b/src/components/dialogs/AvatarDialog/SetAvatarStylesDialog.vue index 7be27e7b..5f100ff6 100644 --- a/src/components/dialogs/AvatarDialog/SetAvatarStylesDialog.vue +++ b/src/components/dialogs/AvatarDialog/SetAvatarStylesDialog.vue @@ -90,8 +90,8 @@ import { watch } from 'vue'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../ui/select'; + import { avatarRequest, queryRequest } from '../../../api'; import { arraysMatch } from '../../../shared/utils'; - import { avatarRequest } from '../../../api'; import { useAvatarStore } from '../../../stores'; const props = defineProps({ @@ -133,7 +133,7 @@ */ async function getAvatarStyles() { try { - const ref = await avatarRequest.getAvailableAvatarStyles(); + const ref = await queryRequest.fetch('avatarStyles'); const styles = []; const stylesMap = new Map(); for (const style of ref.json) { diff --git a/src/components/dialogs/GroupDialog/GroupDialog.vue b/src/components/dialogs/GroupDialog/GroupDialog.vue index 0f164625..f3f7025b 100644 --- a/src/components/dialogs/GroupDialog/GroupDialog.vue +++ b/src/components/dialogs/GroupDialog/GroupDialog.vue @@ -408,6 +408,7 @@ } from '../../../shared/utils'; import { useGalleryStore, useGroupStore, useModalStore, useUserStore } from '../../../stores'; import { groupRequest, queryRequest } from '../../../api'; + import { queryKeys, refetchActiveEntityQuery } from '../../../queries'; import { Badge } from '../../ui/badge'; import { formatJsonVars } from '../../../shared/utils/base/ui'; @@ -497,8 +498,6 @@ } ); - - /** * * @param groupId @@ -536,6 +535,7 @@ }); getGroupDialogGroup(groupId); } + refetchActiveEntityQuery(queryKeys.representedGroup(currentUser.value.id)); }); } @@ -604,8 +604,6 @@ * @param gallery */ - - /** * * @param id diff --git a/src/components/dialogs/UserDialog/UserDialogFavoriteWorldsTab.vue b/src/components/dialogs/UserDialog/UserDialogFavoriteWorldsTab.vue index f3fd4861..fe94cc54 100644 --- a/src/components/dialogs/UserDialog/UserDialogFavoriteWorldsTab.vue +++ b/src/components/dialogs/UserDialog/UserDialogFavoriteWorldsTab.vue @@ -76,7 +76,7 @@ import DeprecationAlert from '@/components/DeprecationAlert.vue'; import { useFavoriteStore, useUserStore, useWorldStore } from '../../../stores'; - import { queryRequest } from '../../../api'; + import { favoriteRequest } from '../../../api'; const { t } = useI18n(); @@ -121,7 +121,7 @@ favoriteWorldsTab.value = '0'; userDialog.value.userFavoriteWorlds = []; const worldLists = []; - const groupArgs = await queryRequest.fetch('favoriteGroups', { + const groupArgs = await favoriteRequest.getFavoriteGroups({ ownerId: userId, n: 100, offset: 0 @@ -145,7 +145,7 @@ tag: list.name }; try { - const args = await queryRequest.fetch('favoriteWorlds', params); + const args = await favoriteRequest.getFavoriteWorlds(params); handleFavoriteWorldList(args); return [list.displayName, list.visibility, args.json]; } catch (err) { diff --git a/src/components/dialogs/UserDialog/UserDialogInfoTab.vue b/src/components/dialogs/UserDialog/UserDialogInfoTab.vue index 8f2c73cf..3b707881 100644 --- a/src/components/dialogs/UserDialog/UserDialogInfoTab.vue +++ b/src/components/dialogs/UserDialog/UserDialogInfoTab.vue @@ -475,7 +475,6 @@ import { storeToRefs } from 'pinia'; import { toast } from 'vue-sonner'; import { useI18n } from 'vue-i18n'; - import InstanceActionBar from '../../InstanceActionBar.vue'; import { copyToClipboard, @@ -502,7 +501,9 @@ useUserStore, useWorldStore } from '../../../stores'; - import { miscRequest, userRequest } from '../../../api'; + import { queryRequest, userRequest } from '../../../api'; + + import InstanceActionBar from '../../InstanceActionBar.vue'; const EditNoteAndMemoDialog = defineAsyncComponent(() => import('./EditNoteAndMemoDialog.vue')); @@ -681,7 +682,7 @@ * */ function getVRChatCredits() { - miscRequest.getVRChatCredits().then((args) => (vrchatCredit.value = args.json?.balance)); + queryRequest.fetch('vrchatCredits').then((args) => (vrchatCredit.value = args.json?.balance)); } defineExpose({ diff --git a/src/components/dialogs/UserDialog/__tests__/useUserDialogCommands.test.js b/src/components/dialogs/UserDialog/__tests__/useUserDialogCommands.test.js index f12b9877..1081c0d2 100644 --- a/src/components/dialogs/UserDialog/__tests__/useUserDialogCommands.test.js +++ b/src/components/dialogs/UserDialog/__tests__/useUserDialogCommands.test.js @@ -25,11 +25,7 @@ vi.mock('../../../../api', () => ({ sendPlayerModeration: vi.fn(), deletePlayerModeration: vi.fn() }, - worldRequest: { - getCachedWorld: vi.fn(() => - Promise.resolve({ ref: { name: 'TestWorld' } }) - ) - } + worldRequest: {} })); vi.mock('../../../../shared/utils', () => ({ diff --git a/src/queries/__tests__/keys.test.js b/src/queries/__tests__/keys.test.js index d696106e..4dfea07a 100644 --- a/src/queries/__tests__/keys.test.js +++ b/src/queries/__tests__/keys.test.js @@ -3,25 +3,6 @@ import { describe, expect, test } from 'vitest'; import { queryKeys } from '../keys'; describe('query key shapes', () => { - test('favorite world keys include owner and tag dimensions', () => { - const a = queryKeys.favoriteWorlds({ - n: 100, - offset: 0, - ownerId: 'usr_1', - userId: 'usr_1', - tag: 'worlds1' - }); - const b = queryKeys.favoriteWorlds({ - n: 100, - offset: 0, - ownerId: 'usr_2', - userId: 'usr_2', - tag: 'worlds1' - }); - - expect(a).not.toEqual(b); - }); - test('world list keys include query option discriminator', () => { const base = { userId: 'usr_me', @@ -42,22 +23,119 @@ describe('query key shapes', () => { expect(defaultKey).not.toEqual(featuredKey); }); - test('group member list keys include sort and role dimensions', () => { - const everyone = queryKeys.groupMembers({ + test('groupCalendarEvent key includes groupId and eventId', () => { + const a = queryKeys.groupCalendarEvent({ groupId: 'grp_1', - n: 100, - offset: 0, - sort: 'joinedAt:desc', - roleId: '' + eventId: 'evt_1' }); - const roleScoped = queryKeys.groupMembers({ + const b = queryKeys.groupCalendarEvent({ groupId: 'grp_1', - n: 100, - offset: 0, - sort: 'joinedAt:desc', - roleId: 'grol_1' + eventId: 'evt_2' }); - expect(everyone).not.toEqual(roleScoped); + expect(a).not.toEqual(b); + expect(a).toEqual(['group', 'grp_1', 'calendarEvent', 'evt_1']); + }); + + test('userInventoryItem key scopes by both userId and inventoryId', () => { + const a = queryKeys.userInventoryItem({ + inventoryId: 'inv_1', + userId: 'usr_1' + }); + const b = queryKeys.userInventoryItem({ + inventoryId: 'inv_1', + userId: 'usr_2' + }); + + expect(a).not.toEqual(b); + }); + + test('mutualCounts key is unique per userId', () => { + const a = queryKeys.mutualCounts('usr_1'); + const b = queryKeys.mutualCounts('usr_2'); + + expect(a).not.toEqual(b); + expect(a).toEqual(['user', 'usr_1', 'mutualCounts']); + }); + + test('representedGroup key is unique per userId', () => { + const a = queryKeys.representedGroup('usr_1'); + const b = queryKeys.representedGroup('usr_2'); + + expect(a).not.toEqual(b); + expect(a).toEqual(['user', 'usr_1', 'representedGroup']); + }); + + test('avatarStyles returns a stable singleton key', () => { + expect(queryKeys.avatarStyles()).toEqual(['avatar', 'styles']); + expect(queryKeys.avatarStyles()).toEqual(queryKeys.avatarStyles()); + }); + + test('vrchatCredits returns a stable singleton key', () => { + expect(queryKeys.vrchatCredits()).toEqual(['credits']); + }); + + test('visits returns a stable singleton key', () => { + expect(queryKeys.visits()).toEqual(['visits']); + }); + + test('favoriteLimits returns a stable singleton key', () => { + expect(queryKeys.favoriteLimits()).toEqual(['favorite', 'limits']); + }); + + test('groupMember key includes both groupId and userId', () => { + const key = queryKeys.groupMember({ + groupId: 'grp_1', + userId: 'usr_1' + }); + + expect(key).toEqual(['group', 'grp_1', 'member', 'usr_1']); + }); + + test('group key differentiates includeRoles flag', () => { + const withRoles = queryKeys.group('grp_1', true); + const withoutRoles = queryKeys.group('grp_1', false); + + expect(withRoles).not.toEqual(withoutRoles); + }); + + test('worldPersistData key scopes by worldId', () => { + const a = queryKeys.worldPersistData('wrld_1'); + const b = queryKeys.worldPersistData('wrld_2'); + + expect(a).not.toEqual(b); + expect(a).toEqual(['world', 'wrld_1', 'persistData']); + }); + + test('fileAnalysis key differentiates version and variant', () => { + const a = queryKeys.fileAnalysis({ + fileId: 'file_1', + version: 1, + variant: 'default' + }); + const b = queryKeys.fileAnalysis({ + fileId: 'file_1', + version: 2, + variant: 'default' + }); + const c = queryKeys.fileAnalysis({ + fileId: 'file_1', + version: 1, + variant: 'hd' + }); + + expect(a).not.toEqual(b); + expect(a).not.toEqual(c); + }); + + test('worldsByUser key coerces numeric params consistently', () => { + const a = queryKeys.worldsByUser({ + userId: 'usr_1', + n: '50', + offset: '0' + }); + const b = queryKeys.worldsByUser({ userId: 'usr_1', n: 50, offset: 0 }); + + expect(a).toEqual(b); }); }); diff --git a/src/queries/__tests__/policies.test.js b/src/queries/__tests__/policies.test.js index d5703783..948eca7f 100644 --- a/src/queries/__tests__/policies.test.js +++ b/src/queries/__tests__/policies.test.js @@ -1,12 +1,9 @@ import { describe, expect, test } from 'vitest'; -import { - entityQueryPolicies, - toQueryOptions -} from '../policies'; +import { entityQueryPolicies, toQueryOptions } from '../policies'; describe('query policy configuration', () => { - test('matches the finalized cache strategy', () => { + test('core entity policies have correct stale/gc times', () => { expect(entityQueryPolicies.user).toMatchObject({ staleTime: 20000, gcTime: 90000, @@ -34,64 +31,37 @@ describe('query policy configuration', () => { retry: 1, refetchOnWindowFocus: false }); + }); + test('group sub-resource policies', () => { expect(entityQueryPolicies.groupCollection).toMatchObject({ staleTime: 60000, gcTime: 300000, retry: 1, refetchOnWindowFocus: false }); - expect(entityQueryPolicies.groupCalendarCollection).toMatchObject({ - staleTime: 120000, - gcTime: 600000, - retry: 1, - refetchOnWindowFocus: false - }); - expect( - entityQueryPolicies.groupFollowingCalendarCollection - ).toMatchObject({ - staleTime: 60000, - gcTime: 300000, - retry: 1, - refetchOnWindowFocus: false - }); - expect(entityQueryPolicies.groupFeaturedCalendarCollection).toMatchObject({ - staleTime: 300000, - gcTime: 900000, - retry: 1, - refetchOnWindowFocus: false - }); + expect(entityQueryPolicies.groupCalendarEvent).toMatchObject({ staleTime: 120000, gcTime: 600000, retry: 1, refetchOnWindowFocus: false }); + }); + test('world collection policy', () => { expect(entityQueryPolicies.worldCollection).toMatchObject({ staleTime: 60000, gcTime: 300000, retry: 1, refetchOnWindowFocus: false }); + }); - expect(entityQueryPolicies.friendList).toMatchObject({ - staleTime: 20000, - gcTime: 90000, - retry: 1, - refetchOnWindowFocus: false - }); - - expect(entityQueryPolicies.favoriteCollection).toMatchObject({ - staleTime: 60000, - gcTime: 300000, - retry: 1, - refetchOnWindowFocus: false - }); - - expect(entityQueryPolicies.galleryCollection).toMatchObject({ - staleTime: 60000, - gcTime: 300000, + test('favorite and inventory policies', () => { + expect(entityQueryPolicies.favoriteLimits).toMatchObject({ + staleTime: 600000, + gcTime: 1800000, retry: 1, refetchOnWindowFocus: false }); @@ -102,42 +72,28 @@ describe('query policy configuration', () => { retry: 1, refetchOnWindowFocus: false }); - expect(entityQueryPolicies.inventoryObject).toMatchObject({ - staleTime: 60000, - gcTime: 300000, - retry: 1, - refetchOnWindowFocus: false - }); + }); + + test('avatar gallery policy has shorter staleTime than avatar entity', () => { expect(entityQueryPolicies.avatarGallery).toMatchObject({ staleTime: 30000, gcTime: 120000, retry: 1, refetchOnWindowFocus: false }); + + expect(entityQueryPolicies.avatarGallery.staleTime).toBeLessThan( + entityQueryPolicies.avatar.staleTime + ); + }); + + test('file-related policies', () => { expect(entityQueryPolicies.fileAnalysis).toMatchObject({ staleTime: 120000, gcTime: 600000, retry: 1, refetchOnWindowFocus: false }); - expect(entityQueryPolicies.worldPersistData).toMatchObject({ - staleTime: 120000, - gcTime: 600000, - retry: 1, - refetchOnWindowFocus: false - }); - expect(entityQueryPolicies.mutualCounts).toMatchObject({ - staleTime: 120000, - gcTime: 600000, - retry: 1, - refetchOnWindowFocus: false - }); - expect(entityQueryPolicies.visits).toMatchObject({ - staleTime: 300000, - gcTime: 900000, - retry: 1, - refetchOnWindowFocus: false - }); expect(entityQueryPolicies.fileObject).toMatchObject({ staleTime: 60000, @@ -147,6 +103,63 @@ describe('query policy configuration', () => { }); }); + test('world persist data policy', () => { + expect(entityQueryPolicies.worldPersistData).toMatchObject({ + staleTime: 120000, + gcTime: 600000, + retry: 1, + refetchOnWindowFocus: false + }); + }); + + test('user relation policies (mutualCounts, representedGroup)', () => { + expect(entityQueryPolicies.mutualCounts).toMatchObject({ + staleTime: 120000, + gcTime: 600000, + retry: 1, + refetchOnWindowFocus: false + }); + + expect(entityQueryPolicies.representedGroup).toMatchObject({ + staleTime: 60000, + gcTime: 300000, + retry: 1, + refetchOnWindowFocus: false + }); + }); + + test('visits policy has longer staleTime for slow-changing data', () => { + expect(entityQueryPolicies.visits).toMatchObject({ + staleTime: 300000, + gcTime: 900000, + retry: 1, + refetchOnWindowFocus: false + }); + }); + + test('avatarStyles policy has very long staleTime for static config data', () => { + expect(entityQueryPolicies.avatarStyles).toMatchObject({ + staleTime: 600000, + gcTime: 3600000, + retry: 1, + refetchOnWindowFocus: false + }); + + // Should outlive visits (which is already long-lived) + expect(entityQueryPolicies.avatarStyles.staleTime).toBeGreaterThan( + entityQueryPolicies.visits.staleTime + ); + }); + + test('vrchatCredits policy has moderate staleTime for balance data', () => { + expect(entityQueryPolicies.vrchatCredits).toMatchObject({ + staleTime: 120000, + gcTime: 600000, + retry: 1, + refetchOnWindowFocus: false + }); + }); + test('normalizes policy values to query options', () => { const options = toQueryOptions(entityQueryPolicies.group); @@ -157,4 +170,34 @@ describe('query policy configuration', () => { refetchOnWindowFocus: false }); }); + + test('toQueryOptions returns only the four query option fields', () => { + const options = toQueryOptions(entityQueryPolicies.user); + const keys = Object.keys(options); + + expect(keys).toEqual([ + 'staleTime', + 'gcTime', + 'retry', + 'refetchOnWindowFocus' + ]); + }); + + test('all policies are frozen and immutable', () => { + for (const [, policy] of Object.entries(entityQueryPolicies)) { + expect(Object.isFrozen(policy)).toBe(true); + } + }); + + test('all policies have refetchOnWindowFocus disabled', () => { + for (const policy of Object.values(entityQueryPolicies)) { + expect(policy.refetchOnWindowFocus).toBe(false); + } + }); + + test('gcTime is always greater than staleTime for all policies', () => { + for (const [, policy] of Object.entries(entityQueryPolicies)) { + expect(policy.gcTime).toBeGreaterThan(policy.staleTime); + } + }); }); diff --git a/src/queries/keys.js b/src/queries/keys.js index c34e396c..1cea10d3 100644 --- a/src/queries/keys.js +++ b/src/queries/keys.js @@ -7,38 +7,12 @@ export const queryKeys = Object.freeze({ groupId, Boolean(includeRoles) ], - groupPosts: ({ groupId, n = 100, offset = 0 } = {}) => [ - 'group', - groupId, - 'posts', - { - n: Number(n), - offset: Number(offset) - } - ], groupMember: ({ groupId, userId } = {}) => [ 'group', groupId, 'member', userId ], - groupMembers: ({ - groupId, - n = 100, - offset = 0, - sort = '', - roleId = '' - } = {}) => [ - 'group', - groupId, - 'members', - { - n: Number(n), - offset: Number(offset), - sort: String(sort || ''), - roleId: String(roleId || '') - } - ], groupGallery: ({ groupId, galleryId, n = 100, offset = 0 } = {}) => [ 'group', groupId, @@ -50,35 +24,6 @@ export const queryKeys = Object.freeze({ } ], groupCalendar: (groupId) => ['group', groupId, 'calendar'], - groupCalendars: ({ n = 100, offset = 0, date = '' } = {}) => [ - 'group', - 'calendar', - { - n: Number(n), - offset: Number(offset), - date: String(date || '') - } - ], - followingGroupCalendars: ({ n = 100, offset = 0, date = '' } = {}) => [ - 'group', - 'calendar', - 'following', - { - n: Number(n), - offset: Number(offset), - date: String(date || '') - } - ], - featuredGroupCalendars: ({ n = 100, offset = 0, date = '' } = {}) => [ - 'group', - 'calendar', - 'featured', - { - n: Number(n), - offset: Number(offset), - date: String(date || '') - } - ], groupCalendarEvent: ({ groupId, eventId } = {}) => [ 'group', groupId, @@ -109,109 +54,13 @@ export const queryKeys = Object.freeze({ option: String(option || '') } ], - friends: ({ offline = false, n = 50, offset = 0 } = {}) => [ - 'friends', - { - offline: Boolean(offline), - n: Number(n), - offset: Number(offset) - } - ], favoriteLimits: () => ['favorite', 'limits'], - favorites: ({ n = 300, offset = 0 } = {}) => [ - 'favorite', - 'items', - { - n: Number(n), - offset: Number(offset) - } - ], - favoriteGroups: ({ n = 50, offset = 0, type = '' } = {}) => [ - 'favorite', - 'groups', - { - n: Number(n), - offset: Number(offset), - type: String(type || '') - } - ], - favoriteWorlds: ({ - n = 300, - offset = 0, - ownerId = '', - userId = '', - tag = '' - } = {}) => [ - 'favorite', - 'worlds', - { - n: Number(n), - offset: Number(offset), - ownerId: String(ownerId || ''), - userId: String(userId || ''), - tag: String(tag || '') - } - ], - favoriteAvatars: ({ - n = 300, - offset = 0, - tag = '', - ownerId = '', - userId = '' - } = {}) => [ - 'favorite', - 'avatars', - { - n: Number(n), - offset: Number(offset), - tag: String(tag || ''), - ownerId: String(ownerId || ''), - userId: String(userId || '') - } - ], - galleryFiles: ({ tag = '', n = 100 } = {}) => [ - 'gallery', - 'files', - { - tag: String(tag || ''), - n: Number(n) - } - ], - prints: ({ n = 100 } = {}) => [ - 'gallery', - 'prints', - { - n: Number(n) - } - ], - print: (printId) => ['gallery', 'print', printId], - inventoryItems: ({ - n = 100, - offset = 0, - order = 'newest', - types = '' - } = {}) => [ - 'inventory', - 'items', - { - n: Number(n), - offset: Number(offset), - order: String(order || 'newest'), - types: String(types || '') - } - ], userInventoryItem: ({ inventoryId, userId }) => [ 'inventory', 'item', userId, inventoryId ], - inventoryItem: (inventoryId) => ['inventory', 'item', inventoryId], - inventoryTemplate: (inventoryTemplateId) => [ - 'inventory', - 'template', - inventoryTemplateId - ], fileAnalysis: ({ fileId, version, variant } = {}) => [ 'analysis', fileId, @@ -221,5 +70,8 @@ export const queryKeys = Object.freeze({ worldPersistData: (worldId) => ['world', worldId, 'persistData'], mutualCounts: (userId) => ['user', userId, 'mutualCounts'], visits: () => ['visits'], - file: (fileId) => ['file', fileId] + file: (fileId) => ['file', fileId], + avatarStyles: () => ['avatar', 'styles'], + representedGroup: (userId) => ['user', userId, 'representedGroup'], + vrchatCredits: () => ['credits'] }); diff --git a/src/queries/policies.js b/src/queries/policies.js index 5f052ae6..443eb5ab 100644 --- a/src/queries/policies.js +++ b/src/queries/policies.js @@ -31,24 +31,6 @@ export const entityQueryPolicies = Object.freeze({ retry: 1, refetchOnWindowFocus: false }), - groupCalendarCollection: Object.freeze({ - staleTime: 120 * SECOND, - gcTime: 600 * SECOND, - retry: 1, - refetchOnWindowFocus: false - }), - groupFollowingCalendarCollection: Object.freeze({ - staleTime: 60 * SECOND, - gcTime: 300 * SECOND, - retry: 1, - refetchOnWindowFocus: false - }), - groupFeaturedCalendarCollection: Object.freeze({ - staleTime: 300 * SECOND, - gcTime: 900 * SECOND, - retry: 1, - refetchOnWindowFocus: false - }), groupCalendarEvent: Object.freeze({ staleTime: 120 * SECOND, gcTime: 600 * SECOND, @@ -61,21 +43,9 @@ export const entityQueryPolicies = Object.freeze({ retry: 1, refetchOnWindowFocus: false }), - friendList: Object.freeze({ - staleTime: 20 * SECOND, - gcTime: 90 * SECOND, - retry: 1, - refetchOnWindowFocus: false - }), - favoriteCollection: Object.freeze({ - staleTime: 60 * SECOND, - gcTime: 300 * SECOND, - retry: 1, - refetchOnWindowFocus: false - }), - galleryCollection: Object.freeze({ - staleTime: 60 * SECOND, - gcTime: 300 * SECOND, + favoriteLimits: Object.freeze({ + staleTime: 600 * SECOND, + gcTime: 1800 * SECOND, retry: 1, refetchOnWindowFocus: false }), @@ -85,12 +55,6 @@ export const entityQueryPolicies = Object.freeze({ retry: 1, refetchOnWindowFocus: false }), - inventoryObject: Object.freeze({ - staleTime: 60 * SECOND, - gcTime: 300 * SECOND, - retry: 1, - refetchOnWindowFocus: false - }), avatarGallery: Object.freeze({ staleTime: 30 * SECOND, gcTime: 120 * SECOND, @@ -126,6 +90,24 @@ export const entityQueryPolicies = Object.freeze({ gcTime: 300 * SECOND, retry: 1, refetchOnWindowFocus: false + }), + avatarStyles: Object.freeze({ + staleTime: 600 * SECOND, + gcTime: 3600 * SECOND, + retry: 1, + refetchOnWindowFocus: false + }), + representedGroup: Object.freeze({ + staleTime: 60 * SECOND, + gcTime: 300 * SECOND, + retry: 1, + refetchOnWindowFocus: false + }), + vrchatCredits: Object.freeze({ + staleTime: 120 * SECOND, + gcTime: 600 * SECOND, + retry: 1, + refetchOnWindowFocus: false }) }); diff --git a/src/stores/favorite.js b/src/stores/favorite.js index d573e330..5b0e595e 100644 --- a/src/stores/favorite.js +++ b/src/stores/favorite.js @@ -600,7 +600,7 @@ export const useFavoriteStore = defineStore('Favorite', () => { } isFavoriteGroupLoading.value = true; processBulk({ - fn: favoriteRequest.getCachedFavoriteGroups, + fn: (params) => favoriteRequest.getFavoriteGroups(params), N: -1, params: { n: 50, @@ -781,7 +781,7 @@ export const useFavoriteStore = defineStore('Favorite', () => { } let newFavoriteSortOrder = []; processBulk({ - fn: (params) => queryRequest.fetch('favorites', params), + fn: (params) => favoriteRequest.getFavorites(params), N: -1, params: { n: 300, @@ -884,7 +884,7 @@ export const useFavoriteStore = defineStore('Favorite', () => { offset: 0, tag }; - const args = await queryRequest.fetch('favoriteAvatars', params); + const args = await favoriteRequest.getFavoriteAvatars(params); handleFavoriteAvatarList(args); } @@ -893,8 +893,8 @@ export const useFavoriteStore = defineStore('Favorite', () => { */ function refreshFavoriteItems() { const types = { - world: [0, favoriteRequest.getCachedFavoriteWorlds], - avatar: [0, favoriteRequest.getCachedFavoriteAvatars] + world: [0, (params) => favoriteRequest.getFavoriteWorlds(params)], + avatar: [0, (params) => favoriteRequest.getFavoriteAvatars(params)] }; const tags = []; for (const ref of cachedFavorites.values()) { diff --git a/src/stores/friend.js b/src/stores/friend.js index 92b5f192..e9811152 100644 --- a/src/stores/friend.js +++ b/src/stores/friend.js @@ -16,7 +16,7 @@ import { isRealInstance, migrateMemos } from '../shared/utils'; -import { friendRequest, queryRequest, userRequest } from '../api'; +import { friendRequest, userRequest } from '../api'; import { AppDebug } from '../service/appConfig'; import { createFriendPresenceCoordinator } from './coordinators/friendPresenceCoordinator'; import { createFriendRelationshipCoordinator } from './coordinators/friendRelationshipCoordinator'; @@ -567,7 +567,7 @@ export const useFriendStore = defineStore('Friend', () => { async function fetchPage(offset) { const result = await executeWithBackoff( async () => { - const { json } = await queryRequest.fetch('friends', { + const { json } = await friendRequest.getFriends({ ...args, n: PAGE_SIZE, offset diff --git a/src/stores/gallery.js b/src/stores/gallery.js index 2af9238e..aa12cdf5 100644 --- a/src/stores/gallery.js +++ b/src/stores/gallery.js @@ -9,7 +9,12 @@ import { getPrintLocalDate, openExternalLink } from '../shared/utils'; -import { queryRequest, vrcPlusImageRequest } from '../api'; +import { + inventoryRequest, + queryRequest, + vrcPlusIconRequest, + vrcPlusImageRequest +} from '../api'; import { AppDebug } from '../service/appConfig'; import { handleImageUploadInput } from '../shared/utils/imageUpload'; import { router } from '../plugin/router'; @@ -157,8 +162,8 @@ export const useGalleryStore = defineStore('Gallery', () => { n: 100, tag: 'gallery' }; - queryRequest - .fetch('galleryFiles', params) + vrcPlusIconRequest + .getFileList(params) .then((args) => handleFilesList(args)) .catch((error) => { console.error('Error fetching gallery files:', error); @@ -177,8 +182,8 @@ export const useGalleryStore = defineStore('Gallery', () => { n: 100, tag: 'icon' }; - queryRequest - .fetch('galleryFiles', params) + vrcPlusIconRequest + .getFileList(params) .then((args) => handleFilesList(args)) .catch((error) => { console.error('Error fetching VRC Plus icons:', error); @@ -229,8 +234,8 @@ export const useGalleryStore = defineStore('Gallery', () => { n: 100, tag: 'sticker' }; - queryRequest - .fetch('galleryFiles', params) + vrcPlusIconRequest + .getFileList(params) .then((args) => handleFilesList(args)) .catch((error) => { console.error('Error fetching stickers:', error); @@ -304,7 +309,7 @@ export const useGalleryStore = defineStore('Gallery', () => { n: 100 }; try { - const args = await queryRequest.fetch('prints', params); + const args = await vrcPlusImageRequest.getPrints(params); args.json.sort((a, b) => { return ( new Date(b.timestamp).getTime() - @@ -349,7 +354,7 @@ export const useGalleryStore = defineStore('Gallery', () => { * @param printId */ async function trySavePrintToFile(printId) { - const args = await queryRequest.fetch('print', { printId }); + const args = await vrcPlusImageRequest.getPrint({ printId }); const imageUrl = args.json?.files?.image; if (!imageUrl) { console.error('Print image URL is missing', args); @@ -402,8 +407,8 @@ export const useGalleryStore = defineStore('Gallery', () => { n: 100, tag: 'emoji' }; - queryRequest - .fetch('galleryFiles', params) + vrcPlusIconRequest + .getFileList(params) .then((args) => handleFilesList(args)) .catch((error) => { console.error('Error fetching emojis:', error); @@ -428,7 +433,7 @@ export const useGalleryStore = defineStore('Gallery', () => { try { for (let i = 0; i < 100; i++) { params.offset = i * params.n; - const args = await queryRequest.fetch('inventoryItems', params); + const args = await inventoryRequest.getInventoryItems(params); for (const item of args.json.data) { advancedSettingsStore.currentUserInventory.set( item.id, diff --git a/src/stores/group.js b/src/stores/group.js index 6e73fcbc..b842c7dc 100644 --- a/src/stores/group.js +++ b/src/stores/group.js @@ -417,7 +417,7 @@ export const useGroupStore = defineStore('Group', () => { let total = Infinity; let pages = 0; do { - const args = await queryRequest.fetch('groupPosts', { + const args = await groupRequest.getGroupPosts({ groupId: params.groupId, n, offset diff --git a/src/stores/user.js b/src/stores/user.js index adbda1c2..60a23472 100644 --- a/src/stores/user.js +++ b/src/stores/user.js @@ -868,8 +868,8 @@ export const useUserStore = defineStore('User', () => { } }); } - groupRequest - .getRepresentedGroup({ userId }) + queryRequest + .fetch('representedGroup', { userId }) .then((args1) => { groupStore.handleGroupRepresented(args1); }); diff --git a/src/views/Sidebar/components/__tests__/FriendsSidebar.test.js b/src/views/Sidebar/components/__tests__/FriendsSidebar.test.js index a3197678..85c633f8 100644 --- a/src/views/Sidebar/components/__tests__/FriendsSidebar.test.js +++ b/src/views/Sidebar/components/__tests__/FriendsSidebar.test.js @@ -48,7 +48,9 @@ const mocks = vi.hoisted(() => ({ localFriendFavorites: { value: {} } }, locationStore: { - lastLocation: { value: { location: 'wrld_home:123', friendList: new Map() } }, + lastLocation: { + value: { location: 'wrld_home:123', friendList: new Map() } + }, lastLocationDestination: { value: '' } }, gameStore: { @@ -62,9 +64,7 @@ const mocks = vi.hoisted(() => ({ sendRequestInvite: vi.fn().mockResolvedValue({}), sendInvite: vi.fn().mockResolvedValue({}) }, - worldRequest: { - getCachedWorld: vi.fn().mockResolvedValue({ ref: { name: 'World' } }) - }, + worldRequest: {}, instanceRequest: { selfInvite: vi.fn().mockResolvedValue({}) }, @@ -162,7 +162,8 @@ vi.mock('../../../../components/ui/context-menu', () => ({ ContextMenuItem: { emits: ['click'], props: ['disabled'], - template: '' + template: + '' }, ContextMenuSeparator: { template: '
' }, ContextMenuSub: { template: '
' }, @@ -277,7 +278,10 @@ describe('FriendsSidebar.vue', () => { test('renders same-instance section when grouping is enabled', async () => { mocks.appearanceStore.isSidebarGroupByInstance.value = true; mocks.friendStore.friendsInSameInstance.value = [ - [makeFriend('usr_a', 'wrld_same:1'), makeFriend('usr_b', 'wrld_same:1')] + [ + makeFriend('usr_a', 'wrld_same:1'), + makeFriend('usr_b', 'wrld_same:1') + ] ]; const wrapper = mount(FriendsSidebar); diff --git a/src/views/Tools/dialogs/GroupCalendarDialog.vue b/src/views/Tools/dialogs/GroupCalendarDialog.vue index 8e8bf355..9a1220e8 100644 --- a/src/views/Tools/dialogs/GroupCalendarDialog.vue +++ b/src/views/Tools/dialogs/GroupCalendarDialog.vue @@ -106,8 +106,8 @@ import { formatDateFilter, getGroupName, replaceBioSymbols } from '../../../shared/utils'; import { Switch } from '../../../components/ui/switch'; + import { groupRequest } from '../../../api'; import { processBulk } from '../../../service/request'; - import { queryRequest } from '../../../api'; import { useGroupStore } from '../../../stores'; import GroupCalendarEventCard from '../components/GroupCalendarEventCard.vue'; @@ -355,7 +355,7 @@ calendar.value = []; try { await processBulk({ - fn: (bulkParams) => queryRequest.fetch('groupCalendars', bulkParams), + fn: (bulkParams) => groupRequest.getGroupCalendars(bulkParams), N: -1, params: { n: 100, @@ -384,7 +384,7 @@ followingCalendar.value = []; try { await processBulk({ - fn: (bulkParams) => queryRequest.fetch('followingGroupCalendars', bulkParams), + fn: (bulkParams) => groupRequest.getFollowingGroupCalendars(bulkParams), N: -1, params: { n: 100, @@ -411,7 +411,7 @@ featuredCalendar.value = []; try { await processBulk({ - fn: (bulkParams) => queryRequest.fetch('featuredGroupCalendars', bulkParams), + fn: (bulkParams) => groupRequest.getFeaturedGroupCalendars(bulkParams), N: -1, params: { n: 100,