refactor queryRequest

This commit is contained in:
pa
2026-03-09 21:28:45 +09:00
parent c1a35223d4
commit 58b9bdc1c5
60 changed files with 1134 additions and 883 deletions

View File

@@ -4,6 +4,7 @@ import {
_entityCacheInternals,
fetchWithEntityPolicy,
patchAndRefetchActiveQuery,
patchUserFromEvent,
patchQueryDataWithRecency
} from '../entityCache';
import { queryClient } from '../client';
@@ -130,4 +131,54 @@ describe('entity query cache helpers', () => {
false
);
});
test('internal completeness guard requires params + entity identifier', () => {
expect(_entityCacheInternals.hasCompleteEntityData(undefined)).toBe(
false
);
expect(_entityCacheInternals.hasCompleteEntityData({})).toBe(false);
expect(
_entityCacheInternals.hasCompleteEntityData({
params: {}
})
).toBe(false);
expect(
_entityCacheInternals.hasCompleteEntityData({
params: { userId: 'usr_1' }
})
).toBe(true);
});
test('patchUserFromEvent skips placeholder cache entries', () => {
const queryKey = ['user', 'usr_1'];
queryClient.setQueryData(queryKey, {
params: {}
});
patchUserFromEvent({
id: 'usr_1',
displayName: 'Alice'
});
expect(queryClient.getQueryData(queryKey)).toEqual({
params: {}
});
});
test('patchUserFromEvent patches when query has complete data', () => {
const queryKey = ['user', 'usr_1'];
queryClient.setQueryData(queryKey, {
params: { userId: 'usr_1' },
ref: { id: 'usr_1', displayName: 'Old' },
json: { id: 'usr_1', displayName: 'Old' }
});
patchUserFromEvent({
id: 'usr_1',
displayName: 'New'
});
const data = queryClient.getQueryData(queryKey);
expect(data.ref.displayName).toBe('New');
});
});

View File

@@ -50,13 +50,6 @@ describe('query policy configuration', () => {
refetchOnWindowFocus: false
});
expect(entityQueryPolicies.instance).toMatchObject({
staleTime: 0,
gcTime: 10000,
retry: 0,
refetchOnWindowFocus: false
});
expect(entityQueryPolicies.friendList).toMatchObject({
staleTime: 20000,
gcTime: 90000,
@@ -104,9 +97,6 @@ describe('query policy configuration', () => {
expect(getEntityQueryPolicy('worldCollection')).toBe(
entityQueryPolicies.worldCollection
);
expect(getEntityQueryPolicy('instance')).toBe(
entityQueryPolicies.instance
);
expect(getEntityQueryPolicy('friendList')).toBe(
entityQueryPolicies.friendList
);

View File

@@ -15,6 +15,10 @@ const RECENCY_FIELDS = [
'createdAt'
];
/**
*
* @param data
*/
function getComparableEntity(data) {
if (!data || typeof data !== 'object') {
return null;
@@ -32,6 +36,10 @@ function getComparableEntity(data) {
return data;
}
/**
*
* @param value
*/
function parseTimestamp(value) {
if (typeof value === 'number' && Number.isFinite(value)) {
return value;
@@ -45,6 +53,10 @@ function parseTimestamp(value) {
return null;
}
/**
*
* @param data
*/
function getRecencyTimestamp(data) {
const comparable = getComparableEntity(data);
if (!comparable) {
@@ -61,6 +73,11 @@ function getRecencyTimestamp(data) {
return null;
}
/**
*
* @param currentData
* @param nextData
*/
function shouldReplaceCurrent(currentData, nextData) {
if (typeof currentData === 'undefined') {
return true;
@@ -80,6 +97,35 @@ function shouldReplaceCurrent(currentData, nextData) {
return true;
}
/**
*
* @param data
*/
function hasCompleteEntityData(data) {
if (!data || typeof data !== 'object') {
return false;
}
const params = data.params;
if (!params || typeof params !== 'object') {
return false;
}
const hasEntityId = Boolean(
data?.ref?.id ||
data?.json?.id ||
params.userId ||
params.avatarId ||
params.worldId ||
params.groupId ||
params.fileId ||
params.printId ||
params.inventoryId
);
return hasEntityId;
}
/**
* @param {{queryKey: unknown[], nextData: any}} options
*/
@@ -142,7 +188,8 @@ export async function patchAndRefetchActiveQuery({ queryKey, nextData }) {
export function patchUserFromEvent(ref) {
if (!ref?.id) return;
const queryKey = queryKeys.user(ref.id);
if (!queryClient.getQueryData(queryKey)) return;
const existing = queryClient.getQueryData(queryKey);
if (!hasCompleteEntityData(existing)) return;
patchQueryDataWithRecency({
queryKey,
nextData: {
@@ -160,7 +207,8 @@ export function patchUserFromEvent(ref) {
export function patchAvatarFromEvent(ref) {
if (!ref?.id) return;
const queryKey = queryKeys.avatar(ref.id);
if (!queryClient.getQueryData(queryKey)) return;
const existing = queryClient.getQueryData(queryKey);
if (!hasCompleteEntityData(existing)) return;
patchQueryDataWithRecency({
queryKey,
nextData: {
@@ -178,7 +226,8 @@ export function patchAvatarFromEvent(ref) {
export function patchWorldFromEvent(ref) {
if (!ref?.id) return;
const queryKey = queryKeys.world(ref.id);
if (!queryClient.getQueryData(queryKey)) return;
const existing = queryClient.getQueryData(queryKey);
if (!hasCompleteEntityData(existing)) return;
patchQueryDataWithRecency({
queryKey,
nextData: {
@@ -204,7 +253,8 @@ export function patchGroupFromEvent(ref) {
};
const keyFalse = queryKeys.group(ref.id, false);
if (queryClient.getQueryData(keyFalse)) {
const existingFalse = queryClient.getQueryData(keyFalse);
if (hasCompleteEntityData(existingFalse)) {
patchQueryDataWithRecency({
queryKey: keyFalse,
nextData
@@ -212,7 +262,8 @@ export function patchGroupFromEvent(ref) {
}
const keyTrue = queryKeys.group(ref.id, true);
if (queryClient.getQueryData(keyTrue)) {
const existingTrue = queryClient.getQueryData(keyTrue);
if (hasCompleteEntityData(existingTrue)) {
patchQueryDataWithRecency({
queryKey: keyTrue,
nextData
@@ -220,30 +271,8 @@ export function patchGroupFromEvent(ref) {
}
}
/**
* @param {object} ref
*/
export function patchInstanceFromEvent(ref) {
if (!ref?.id) return;
const [worldId, instanceId] = String(ref.id).split(':');
if (!worldId || !instanceId) return;
const queryKey = queryKeys.instance(worldId, instanceId);
if (!queryClient.getQueryData(queryKey)) return;
patchQueryDataWithRecency({
queryKey,
nextData: {
cache: false,
json: ref,
params: { worldId, instanceId },
ref
}
});
}
export const _entityCacheInternals = {
hasCompleteEntityData,
getRecencyTimestamp,
shouldReplaceCurrent
};

View File

@@ -1,6 +1,10 @@
export { queryClient } from './client';
export { queryKeys } from './keys';
export { entityQueryPolicies, getEntityQueryPolicy, toQueryOptions } from './policies';
export {
entityQueryPolicies,
getEntityQueryPolicy,
toQueryOptions
} from './policies';
export {
fetchWithEntityPolicy,
patchAndRefetchActiveQuery,
@@ -9,6 +13,5 @@ export {
patchAvatarFromEvent,
patchWorldFromEvent,
patchGroupFromEvent,
patchInstanceFromEvent,
refetchActiveEntityQuery
} from './entityCache';

View File

@@ -2,7 +2,11 @@ export const queryKeys = Object.freeze({
user: (userId) => ['user', userId],
avatar: (avatarId) => ['avatar', avatarId],
world: (worldId) => ['world', worldId],
group: (groupId, includeRoles = false) => ['group', groupId, Boolean(includeRoles)],
group: (groupId, includeRoles = false) => [
'group',
groupId,
Boolean(includeRoles)
],
groupPosts: ({ groupId, n = 100, offset = 0 } = {}) => [
'group',
groupId,
@@ -12,8 +16,19 @@ export const queryKeys = Object.freeze({
offset: Number(offset)
}
],
groupMember: ({ groupId, userId } = {}) => ['group', groupId, 'member', userId],
groupMembers: ({ groupId, n = 100, offset = 0, sort = '', roleId = '' } = {}) => [
groupMember: ({ groupId, userId } = {}) => [
'group',
groupId,
'member',
userId
],
groupMembers: ({
groupId,
n = 100,
offset = 0,
sort = '',
roleId = ''
} = {}) => [
'group',
groupId,
'members',
@@ -41,7 +56,6 @@ export const queryKeys = Object.freeze({
'calendarEvent',
eventId
],
instance: (worldId, instanceId) => ['instance', worldId, instanceId],
worldsByUser: ({
userId,
n = 50,
@@ -91,7 +105,13 @@ export const queryKeys = Object.freeze({
type: String(type || '')
}
],
favoriteWorlds: ({ n = 300, offset = 0, ownerId = '', userId = '', tag = '' } = {}) => [
favoriteWorlds: ({
n = 300,
offset = 0,
ownerId = '',
userId = '',
tag = ''
} = {}) => [
'favorite',
'worlds',
{
@@ -102,7 +122,13 @@ export const queryKeys = Object.freeze({
tag: String(tag || '')
}
],
favoriteAvatars: ({ n = 300, offset = 0, tag = '', ownerId = '', userId = '' } = {}) => [
favoriteAvatars: ({
n = 300,
offset = 0,
tag = '',
ownerId = '',
userId = ''
} = {}) => [
'favorite',
'avatars',
{
@@ -129,7 +155,12 @@ export const queryKeys = Object.freeze({
}
],
print: (printId) => ['gallery', 'print', printId],
inventoryItems: ({ n = 100, offset = 0, order = 'newest', types = '' } = {}) => [
inventoryItems: ({
n = 100,
offset = 0,
order = 'newest',
types = ''
} = {}) => [
'inventory',
'items',
{

View File

@@ -37,12 +37,6 @@ export const entityQueryPolicies = Object.freeze({
retry: 1,
refetchOnWindowFocus: false
}),
instance: Object.freeze({
staleTime: 0,
gcTime: 10 * SECOND,
retry: 0,
refetchOnWindowFocus: false
}),
friendList: Object.freeze({
staleTime: 20 * SECOND,
gcTime: 90 * SECOND,
@@ -76,7 +70,7 @@ export const entityQueryPolicies = Object.freeze({
});
/**
* @param {'user'|'avatar'|'world'|'group'|'groupCollection'|'worldCollection'|'instance'|'friendList'|'favoriteCollection'|'galleryCollection'|'inventoryCollection'|'fileObject'} entity
* @param {'user'|'avatar'|'world'|'group'|'groupCollection'|'worldCollection'|'friendList'|'favoriteCollection'|'galleryCollection'|'inventoryCollection'|'fileObject'} entity
* @returns {{staleTime: number, gcTime: number, retry: number, refetchOnWindowFocus: boolean}}
*/
export function getEntityQueryPolicy(entity) {

View File

@@ -1,9 +1,14 @@
import { useQuery } from '@tanstack/vue-query';
import { avatarRequest, groupRequest, instanceRequest, userRequest, worldRequest } from '../api';
import { queryKeys } from './keys';
import { avatarRequest, groupRequest, userRequest, worldRequest } from '../api';
import { entityQueryPolicies, toQueryOptions } from './policies';
import { queryKeys } from './keys';
/**
*
* @param userId
* @param options
*/
export function useUserQuery(userId, options = {}) {
return useQuery({
...options,
@@ -14,6 +19,11 @@ export function useUserQuery(userId, options = {}) {
});
}
/**
*
* @param avatarId
* @param options
*/
export function useAvatarQuery(avatarId, options = {}) {
return useQuery({
...options,
@@ -24,6 +34,11 @@ export function useAvatarQuery(avatarId, options = {}) {
});
}
/**
*
* @param worldId
* @param options
*/
export function useWorldQuery(worldId, options = {}) {
return useQuery({
...options,
@@ -34,6 +49,12 @@ export function useWorldQuery(worldId, options = {}) {
});
}
/**
*
* @param groupId
* @param includeRoles
* @param options
*/
export function useGroupQuery(groupId, includeRoles = false, options = {}) {
return useQuery({
...options,
@@ -43,13 +64,3 @@ export function useGroupQuery(groupId, includeRoles = false, options = {}) {
...toQueryOptions(entityQueryPolicies.group)
});
}
export function useInstanceQuery(worldId, instanceId, options = {}) {
return useQuery({
...options,
queryKey: queryKeys.instance(worldId, instanceId),
queryFn: () => instanceRequest.getInstance({ worldId, instanceId }),
enabled: Boolean(worldId && instanceId),
...toQueryOptions(entityQueryPolicies.instance)
});
}