update all QuantumKVCache fetchers to use consistent code styles

This commit is contained in:
Hazelnoot 2025-11-11 20:18:12 -05:00
parent fa1273a737
commit e5f78145d2
7 changed files with 334 additions and 187 deletions

View file

@ -185,7 +185,11 @@ export class CacheService implements OnApplicationShutdown {
this.userByIdCache = this.cacheManagementService.createQuantumKVCache('userById', {
lifetime: 1000 * 60 * 5, // 5m
fetcher: async (userId) => await this.usersRepository.findOneByOrFail({ id: userId }),
bulkFetcher: async (userIds) => await this.usersRepository.findBy({ id: In(userIds) }).then(us => us.map(u => [u.id, u])),
optionalFetcher: async (userId) => await this.usersRepository.findOneBy({ id: userId }),
bulkFetcher: async (userIds) => {
const users = await this.usersRepository.findBy({ id: In(userIds) });
return users.map(user => [user.id, user]);
},
});
this.nativeTokenCache = this.cacheManagementService.createQuantumKVCache('localUserByNativeToken', {
@ -198,6 +202,14 @@ export class CacheService implements OnApplicationShutdown {
.getOneOrFail() as { id: string };
return id;
},
optionalFetcher: async (token) => {
const result = await this.usersRepository
.createQueryBuilder('user')
.select('user.id')
.where({ token })
.getOne() as { id: string } | null;
return result?.id;
},
bulkFetcher: async (tokens) => {
const users = await this.usersRepository
.createQueryBuilder('user')
@ -205,7 +217,7 @@ export class CacheService implements OnApplicationShutdown {
.addSelect('user.token')
.where({ token: In(tokens) })
.getMany() as { id: string, token: string }[];
return users.map(u => [u.token, u.id]);
return users.map(user => [user.token, user.id]);
},
});
@ -223,224 +235,314 @@ export class CacheService implements OnApplicationShutdown {
.getOneOrFail();
return id;
},
// No bulk fetcher for this
optionalFetcher: async (acct) => {
const parsed = Acct.parse(acct);
const res = await this.usersRepository
.createQueryBuilder('user')
.select('user.id')
.where({
usernameLower: parsed.username.toLowerCase(),
host: parsed.host ?? IsNull(),
})
.getOne();
return res?.id;
},
// no bulkFetcher possible
});
this.userProfileCache = this.cacheManagementService.createQuantumKVCache('userProfile', {
lifetime: 1000 * 60 * 30, // 30m
fetcher: (key) => this.userProfilesRepository.findOneBy({ userId: key }),
bulkFetcher: userIds => this.userProfilesRepository.findBy({ userId: In(userIds) }).then(ps => ps.map(p => [p.userId, p])),
fetcher: async userId => await this.userProfilesRepository.findOneByOrFail({ userId }),
optionalFetcher: async userId => await this.userProfilesRepository.findOneBy({ userId }),
bulkFetcher: async userIds => {
const profiles = await this.userProfilesRepository.findBy({ userId: In(userIds) });
return profiles.map(profile => [profile.userId, profile]);
},
});
this.userMutingsCache = this.cacheManagementService.createQuantumKVCache<Set<string>>('userMutings', {
lifetime: 1000 * 60 * 30, // 3m (workaround for mute expiration)
fetcher: (key) => this.mutingsRepository.find({ where: { muterId: key }, select: ['muteeId'] }).then(xs => new Set(xs.map(x => x.muteeId))),
bulkFetcher: muterIds => this.mutingsRepository
.createQueryBuilder('muting')
.select('"muting"."muterId"', 'muterId')
.addSelect('array_agg("muting"."muteeId")', 'muteeIds')
.where({ muterId: In(muterIds) })
.andWhere(new Brackets(qb => qb
.orWhere({ expiresAt: IsNull() })
.orWhere({ expiresAt: MoreThan(this.timeService.date) })))
.groupBy('muting.muterId')
.getRawMany<{ muterId: string, muteeIds: string[] }>()
.then(ms => ms.map(m => [m.muterId, new Set(m.muteeIds)])),
fetcher: async muterId => {
const mutings = await this.mutingsRepository.find({ where: { muterId: muterId }, select: ['muteeId'] });
return new Set(mutings.map(muting => muting.muteeId));
},
// no optionalFetcher needed
bulkFetcher: async muterIds => {
const mutings = await this.mutingsRepository
.createQueryBuilder('muting')
.select('"muting"."muterId"', 'muterId')
.addSelect('array_agg("muting"."muteeId")', 'muteeIds')
.where({ muterId: In(muterIds) })
.andWhere(new Brackets(qb => qb
.orWhere({ expiresAt: IsNull() })
.orWhere({ expiresAt: MoreThan(this.timeService.date) })))
.groupBy('muting.muterId')
.getRawMany<{ muterId: string, muteeIds: string[] }>();
return mutings.map(muting => [muting.muterId, new Set(muting.muteeIds)]);
},
});
this.userMutedCache = this.cacheManagementService.createQuantumKVCache<Set<string>>('userMuted', {
lifetime: 1000 * 60 * 30, // 3m (workaround for mute expiration)
fetcher: (key) => this.mutingsRepository.find({ where: { muteeId: key }, select: ['muterId'] }).then(xs => new Set(xs.map(x => x.muterId))),
bulkFetcher: muteeIds => this.mutingsRepository
.createQueryBuilder('muting')
.select('"muting"."muteeId"', 'muteeId')
.addSelect('array_agg("muting"."muterId")', 'muterIds')
.where({ muteeId: In(muteeIds) })
.andWhere(new Brackets(qb => qb
.orWhere({ expiresAt: IsNull() })
.orWhere({ expiresAt: MoreThan(this.timeService.date) })))
.groupBy('muting.muteeId')
.getRawMany<{ muteeId: string, muterIds: string[] }>()
.then(ms => ms.map(m => [m.muteeId, new Set(m.muterIds)])),
fetcher: async muteeId => {
const mutings = await this.mutingsRepository.find({ where: { muteeId }, select: ['muterId'] });
return new Set(mutings.map(muting => muting.muterId));
},
// no optionalFetcher needed
bulkFetcher: async muteeIds => {
const mutings = await this.mutingsRepository
.createQueryBuilder('muting')
.select('"muting"."muteeId"', 'muteeId')
.addSelect('array_agg("muting"."muterId")', 'muterIds')
.where({ muteeId: In(muteeIds) })
.andWhere(new Brackets(qb => qb
.orWhere({ expiresAt: IsNull() })
.orWhere({ expiresAt: MoreThan(this.timeService.date) })))
.groupBy('muting.muteeId')
.getRawMany<{ muteeId: string, muterIds: string[] }>();
return mutings.map(muting => [muting.muteeId, new Set(muting.muterIds)]);
},
});
this.userBlockingCache = this.cacheManagementService.createQuantumKVCache<Set<string>>('userBlocking', {
lifetime: 1000 * 60 * 30, // 30m
fetcher: (key) => this.blockingsRepository.find({ where: { blockerId: key }, select: ['blockeeId'] }).then(xs => new Set(xs.map(x => x.blockeeId))),
bulkFetcher: blockerIds => this.blockingsRepository
.createQueryBuilder('blocking')
.select('"blocking"."blockerId"', 'blockerId')
.addSelect('array_agg("blocking"."blockeeId")', 'blockeeIds')
.where({ blockerId: In(blockerIds) })
.groupBy('blocking.blockerId')
.getRawMany<{ blockerId: string, blockeeIds: string[] }>()
.then(ms => ms.map(m => [m.blockerId, new Set(m.blockeeIds)])),
fetcher: async blockerId => {
const blockings = await this.blockingsRepository.find({ where: { blockerId }, select: ['blockeeId'] });
return new Set(blockings.map(blocking => blocking.blockeeId));
},
// no optionalFetcher needed
bulkFetcher: async blockerIds => {
const blockings = await this.blockingsRepository
.createQueryBuilder('blocking')
.select('"blocking"."blockerId"', 'blockerId')
.addSelect('array_agg("blocking"."blockeeId")', 'blockeeIds')
.where({ blockerId: In(blockerIds) })
.groupBy('blocking.blockerId')
.getRawMany<{ blockerId: string, blockeeIds: string[] }>();
return blockings.map(blocking => [blocking.blockerId, new Set(blocking.blockeeIds)]);
},
});
this.userBlockedCache = this.cacheManagementService.createQuantumKVCache<Set<string>>('userBlocked', {
lifetime: 1000 * 60 * 30, // 30m
fetcher: (key) => this.blockingsRepository.find({ where: { blockeeId: key }, select: ['blockerId'] }).then(xs => new Set(xs.map(x => x.blockerId))),
bulkFetcher: blockeeIds => this.blockingsRepository
.createQueryBuilder('blocking')
.select('"blocking"."blockeeId"', 'blockeeId')
.addSelect('array_agg("blocking"."blockerId")', 'blockerIds')
.where({ blockeeId: In(blockeeIds) })
.groupBy('blocking.blockeeId')
.getRawMany<{ blockeeId: string, blockerIds: string[] }>()
.then(ms => ms.map(m => [m.blockeeId, new Set(m.blockerIds)])),
fetcher: async blockeeId => {
const blockings = await this.blockingsRepository.find({ where: { blockeeId: blockeeId }, select: ['blockerId'] });
return new Set(blockings.map(blocking => blocking.blockerId));
},
// no optionalFetcher needed
bulkFetcher: async blockeeIds => {
const blockings = await this.blockingsRepository
.createQueryBuilder('blocking')
.select('"blocking"."blockeeId"', 'blockeeId')
.addSelect('array_agg("blocking"."blockerId")', 'blockerIds')
.where({ blockeeId: In(blockeeIds) })
.groupBy('blocking.blockeeId')
.getRawMany<{ blockeeId: string, blockerIds: string[] }>();
return blockings.map(blocking => [blocking.blockeeId, new Set(blocking.blockerIds)]);
},
});
this.userListMembershipsCache = this.cacheManagementService.createQuantumKVCache<Map<string, MiUserListMembership>>('userListMemberships', {
lifetime: 1000 * 60 * 30, // 30m
fetcher: async userId => await this.userListMembershipsRepository.findBy({ userId }).then(ms => new Map(ms.map(m => [m.userListId, m]))),
bulkFetcher: async userIds => await this.userListMembershipsRepository
.findBy({ userId: In(userIds) })
.then(ms => ms
.reduce((groups, m) => {
let listsForUser = groups.get(m.userId);
if (!listsForUser) {
listsForUser = new Map();
groups.set(m.userId, listsForUser);
}
listsForUser.set(m.userListId, m);
return groups;
}, new Map<string, Map<string, MiUserListMembership>>)),
fetcher: async userId => {
const memberships = await this.userListMembershipsRepository.findBy({ userId });
return new Map(memberships.map(membership => [membership.userListId, membership]));
},
// no optionalFetcher needed
bulkFetcher: async userIds => {
const groups = new Map<string, Map<string, MiUserListMembership>>;
const memberships = await this.userListMembershipsRepository.findBy({ userId: In(userIds) });
for (const membership of memberships) {
let listsForUser = groups.get(membership.userId);
if (!listsForUser) {
listsForUser = new Map();
groups.set(membership.userId, listsForUser);
}
listsForUser.set(membership.userListId, membership);
}
return groups;
},
});
this.listUserMembershipsCache = this.cacheManagementService.createQuantumKVCache<Map<string, MiUserListMembership>>('listUserMemberships', {
lifetime: 1000 * 60 * 30, // 30m
fetcher: async userListId => await this.userListMembershipsRepository.findBy({ userListId }).then(ms => new Map(ms.map(m => [m.userId, m]))),
bulkFetcher: async userListIds => await this.userListMembershipsRepository
.findBy({ userListId: In(userListIds) })
.then(ms => ms
.reduce((groups, m) => {
let usersForList = groups.get(m.userListId);
if (!usersForList) {
usersForList = new Map();
groups.set(m.userListId, usersForList);
}
usersForList.set(m.userId, m);
return groups;
}, new Map<string, Map<string, MiUserListMembership>>)),
fetcher: async userListId => {
const memberships = await this.userListMembershipsRepository.findBy({ userListId });
return new Map(memberships.map(membership => [membership.userId, membership]));
},
// no optionalFetcher needed
bulkFetcher: async userListIds => {
const memberships = await this.userListMembershipsRepository.findBy({ userListId: In(userListIds) });
const groups = new Map<string, Map<string, MiUserListMembership>>();
for (const membership of memberships) {
let usersForList = groups.get(membership.userListId);
if (!usersForList) {
usersForList = new Map();
groups.set(membership.userListId, usersForList);
}
usersForList.set(membership.userId, membership);
}
return groups;
},
});
this.userListFavoritesCache = cacheManagementService.createQuantumKVCache<Set<string>>('userListFavorites', {
lifetime: 1000 * 60 * 30, // 30m
fetcher: async userId => await this.userListFavoritesRepository.findBy({ userId }).then(fs => new Set(fs.map(f => f.userListId))),
bulkFetcher: async userIds => await this.userListFavoritesRepository
.createQueryBuilder('favorite')
.select('"favorite"."userId"', 'userId')
.addSelect('array_agg("favorite"."userListId")', 'userListIds')
.where({ userId: In(userIds) })
.groupBy('favorite.userId')
.getRawMany<{ userId: string, userListIds: string[] }>()
.then(fs => fs.map(f => [f.userId, new Set(f.userListIds)])),
fetcher: async userId => {
const favorites = await this.userListFavoritesRepository.find({ where: { userId }, select: ['userListId'] });
return new Set(favorites.map(favorites => favorites.userListId));
},
// no optionalFetcher needed
bulkFetcher: async userIds => {
const favorites = await this.userListFavoritesRepository
.createQueryBuilder('favorite')
.select('"favorite"."userId"', 'userId')
.addSelect('array_agg("favorite"."userListId")', 'userListIds')
.where({ userId: In(userIds) })
.groupBy('favorite.userId')
.getRawMany<{ userId: string, userListIds: string[] }>();
return favorites.map(favorite => [favorite.userId, new Set(favorite.userListIds)]);
},
});
this.listUserFavoritesCache = cacheManagementService.createQuantumKVCache<Set<string>>('listUserFavorites', {
lifetime: 1000 * 60 * 30, // 30m
fetcher: async userListId => await this.userListFavoritesRepository.findBy({ userListId }).then(fs => new Set(fs.map(f => f.userId))),
bulkFetcher: async userListIds => await this.userListFavoritesRepository
.createQueryBuilder('favorite')
.select('"favorite"."userListId"', 'userListId')
.addSelect('array_agg("favorite"."userId")', 'userIds')
.where({ userListId: In(userListIds) })
.groupBy('favorite.userListId')
.getRawMany<{ userListId: string, userIds: string[] }>()
.then(fs => fs.map(f => [f.userListId, new Set(f.userIds)])),
fetcher: async userListId => {
const favorites = await this.userListFavoritesRepository.find({ where: { userListId }, select: ['userId'] });
return new Set(favorites.map(favorite => favorite.userId));
},
// no optionalFetcher needed
bulkFetcher: async userListIds => {
const favorites = await this.userListFavoritesRepository
.createQueryBuilder('favorite')
.select('"favorite"."userListId"', 'userListId')
.addSelect('array_agg("favorite"."userId")', 'userIds')
.where({ userListId: In(userListIds) })
.groupBy('favorite.userListId')
.getRawMany<{ userListId: string, userIds: string[] }>();
return favorites.map(favorite => [favorite.userListId, new Set(favorite.userIds)]);
},
});
this.renoteMutingsCache = this.cacheManagementService.createQuantumKVCache<Set<string>>('renoteMutings', {
lifetime: 1000 * 60 * 30, // 30m
fetcher: (key) => this.renoteMutingsRepository.find({ where: { muterId: key }, select: ['muteeId'] }).then(xs => new Set(xs.map(x => x.muteeId))),
bulkFetcher: muterIds => this.renoteMutingsRepository
.createQueryBuilder('muting')
.select('"muting"."muterId"', 'muterId')
.addSelect('array_agg("muting"."muteeId")', 'muteeIds')
.where({ muterId: In(muterIds) })
.groupBy('muting.muterId')
.getRawMany<{ muterId: string, muteeIds: string[] }>()
.then(ms => ms.map(m => [m.muterId, new Set(m.muteeIds)])),
fetcher: async muterId => {
const mutings = await this.renoteMutingsRepository.find({ where: { muterId: muterId }, select: ['muteeId'] });
return new Set(mutings.map(muting => muting.muteeId));
},
// no optionalFetcher needed
bulkFetcher: async muterIds => {
const mutings = await this.renoteMutingsRepository
.createQueryBuilder('muting')
.select('"muting"."muterId"', 'muterId')
.addSelect('array_agg("muting"."muteeId")', 'muteeIds')
.where({ muterId: In(muterIds) })
.groupBy('muting.muterId')
.getRawMany<{ muterId: string, muteeIds: string[] }>();
return mutings.map(muting => [muting.muterId, new Set(muting.muteeIds)]);
},
});
this.threadMutingsCache = this.cacheManagementService.createQuantumKVCache<Set<string>>('threadMutings', {
lifetime: 1000 * 60 * 30, // 30m
fetcher: muterId => this.noteThreadMutingsRepository
.find({ where: { userId: muterId, isPostMute: false }, select: { threadId: true } })
.then(ms => new Set(ms.map(m => m.threadId))),
bulkFetcher: muterIds => this.noteThreadMutingsRepository
.createQueryBuilder('muting')
.select('"muting"."userId"', 'userId')
.addSelect('array_agg("muting"."threadId")', 'threadIds')
.groupBy('"muting"."userId"')
.where({ userId: In(muterIds), isPostMute: false })
.getRawMany<{ userId: string, threadIds: string[] }>()
.then(ms => ms.map(m => [m.userId, new Set(m.threadIds)])),
fetcher: async muterId => {
const mutings = await this.noteThreadMutingsRepository.find({ where: { userId: muterId, isPostMute: false }, select: { threadId: true } });
return new Set(mutings.map(muting => muting.threadId));
},
// no optionalFetcher needed
bulkFetcher: async muterIds => {
const mutings = await this.noteThreadMutingsRepository
.createQueryBuilder('muting')
.select('"muting"."userId"', 'userId')
.addSelect('array_agg("muting"."threadId")', 'threadIds')
.groupBy('"muting"."userId"')
.where({ userId: In(muterIds), isPostMute: false })
.getRawMany<{ userId: string, threadIds: string[] }>();
return mutings.map(muting => [muting.userId, new Set(muting.threadIds)]);
},
});
this.noteMutingsCache = this.cacheManagementService.createQuantumKVCache<Set<string>>('noteMutings', {
lifetime: 1000 * 60 * 30, // 30m
fetcher: muterId => this.noteThreadMutingsRepository
.find({ where: { userId: muterId, isPostMute: true }, select: { threadId: true } })
.then(ms => new Set(ms.map(m => m.threadId))),
bulkFetcher: muterIds => this.noteThreadMutingsRepository
.createQueryBuilder('muting')
.select('"muting"."userId"', 'userId')
.addSelect('array_agg("muting"."threadId")', 'threadIds')
.groupBy('"muting"."userId"')
.where({ userId: In(muterIds), isPostMute: true })
.getRawMany<{ userId: string, threadIds: string[] }>()
.then(ms => ms.map(m => [m.userId, new Set(m.threadIds)])),
fetcher: async muterId => {
const mutings = await this.noteThreadMutingsRepository.find({ where: { userId: muterId, isPostMute: true }, select: { threadId: true } });
return new Set(mutings.map(mutings => mutings.threadId));
},
// no optionalFetcher needed
bulkFetcher: async muterIds => {
const mutings = await this.noteThreadMutingsRepository
.createQueryBuilder('muting')
.select('"muting"."userId"', 'userId')
.addSelect('array_agg("muting"."threadId")', 'threadIds')
.groupBy('"muting"."userId"')
.where({ userId: In(muterIds), isPostMute: true })
.getRawMany<{ userId: string, threadIds: string[] }>();
return mutings.map(muting => [muting.userId, new Set(muting.threadIds)]);
},
});
this.userFollowingsCache = this.cacheManagementService.createQuantumKVCache<Map<string, Omit<MiFollowing, 'isFollowerHibernated'>>>('userFollowings', {
lifetime: 1000 * 60 * 30, // 30m
fetcher: (key) => this.followingsRepository.findBy({ followerId: key }).then(xs => new Map(xs.map(f => [f.followeeId, f]))),
bulkFetcher: followerIds => this.followingsRepository
.findBy({ followerId: In(followerIds) })
.then(fs => fs
.reduce((groups, f) => {
let group = groups.get(f.followerId);
if (!group) {
group = new Map();
groups.set(f.followerId, group);
}
group.set(f.followeeId, f);
return groups;
}, new Map<string, Map<string, Omit<MiFollowing, 'isFollowerHibernated'>>>)),
fetcher: async followerId => {
const followings = await this.followingsRepository.findBy({ followerId: followerId });
return new Map(followings.map(following => [following.followeeId, following]));
},
// no optionalFetcher needed
bulkFetcher: async followerIds => {
const groups = new Map<string, Map<string, Omit<MiFollowing, 'isFollowerHibernated'>>>();
const followings = await this.followingsRepository.findBy({ followerId: In(followerIds) });
for (const following of followings) {
let group = groups.get(following.followerId);
if (!group) {
group = new Map();
groups.set(following.followerId, group);
}
group.set(following.followeeId, following);
}
return groups;
},
});
this.userFollowersCache = this.cacheManagementService.createQuantumKVCache<Map<string, Omit<MiFollowing, 'isFollowerHibernated'>>>('userFollowers', {
lifetime: 1000 * 60 * 30, // 30m
fetcher: followeeId => this.followingsRepository.findBy({ followeeId: followeeId }).then(xs => new Map(xs.map(x => [x.followerId, x]))),
bulkFetcher: followeeIds => this.followingsRepository
.findBy({ followeeId: In(followeeIds) })
.then(fs => fs
.reduce((groups, f) => {
let group = groups.get(f.followeeId);
if (!group) {
group = new Map();
groups.set(f.followeeId, group);
}
group.set(f.followerId, f);
return groups;
}, new Map<string, Map<string, Omit<MiFollowing, 'isFollowerHibernated'>>>)),
fetcher: async followeeId => {
const followings = await this.followingsRepository.findBy({ followeeId: followeeId });
return new Map(followings.map(following => [following.followerId, following]));
},
// no optionalFetcher needed
bulkFetcher: async followeeIds => {
const groups = new Map<string, Map<string, Omit<MiFollowing, 'isFollowerHibernated'>>>();
const followings = await this.followingsRepository.findBy({ followeeId: In(followeeIds) });
for (const following of followings) {
let group = groups.get(following.followeeId);
if (!group) {
group = new Map();
groups.set(following.followeeId, group);
}
group.set(following.followerId, following);
}
return groups;
},
});
this.hibernatedUserCache = this.cacheManagementService.createQuantumKVCache<boolean>('hibernatedUsers', {
lifetime: 1000 * 60 * 30, // 30m
fetcher: async userId => {
const result = await this.usersRepository.findOne({
where: { id: userId },
select: { isHibernated: true },
});
const { isHibernated } = await this.usersRepository.findOneOrFail({ where: { id: userId }, select: { isHibernated: true } });
return isHibernated;
},
optionalFetcher: async userId => {
const result = await this.usersRepository.findOne({ where: { id: userId }, select: { isHibernated: true } });
return result?.isHibernated;
},
bulkFetcher: async userIds => {
const results = await this.usersRepository.find({
where: { id: In(userIds) },
select: { id: true, isHibernated: true },
});
const results = await this.usersRepository.find({ where: { id: In(userIds) }, select: { id: true, isHibernated: true } });
return results.map(({ id, isHibernated }) => [id, isHibernated]);
},
onChanged: async userIds => {
@ -467,8 +569,8 @@ export class CacheService implements OnApplicationShutdown {
for (const { id, isHibernated } of hibernations) {
const users = userObjects.get(id);
if (users) {
for (const u of users) {
u.isHibernated = isHibernated;
for (const user of users) {
user.isHibernated = isHibernated;
}
}
}
@ -480,11 +582,21 @@ export class CacheService implements OnApplicationShutdown {
this.userFollowingChannelsCache = this.cacheManagementService.createQuantumKVCache<Set<string>>('userFollowingChannels', {
lifetime: 1000 * 60 * 30, // 30m
fetcher: (key) => this.channelFollowingsRepository.find({
where: { followerId: key },
select: ['followeeId'],
}).then(xs => new Set(xs.map(x => x.followeeId))),
// TODO bulk fetcher
fetcher: async (followerId) => {
const followings = await this.channelFollowingsRepository.find({ where: { followerId: followerId }, select: ['followeeId'] });
return new Set(followings.map(following => following.followeeId));
},
// no optionalFetcher needed
bulkFetcher: async followerIds => {
const followings = await this.channelFollowingsRepository
.createQueryBuilder('following')
.select('"following"."followerId"', 'followerId')
.addSelect('array_agg("following"."followeeId")', 'followeeIds')
.where({ followerId: In(followerIds) })
.groupBy('following.followerId')
.getRawMany<{ followerId: string, followeeIds: string[] }>();
return followings.map(following => [following.followerId, new Set(following.followeeIds)]);
},
});
this.internalEventService.on('usersUpdated', this.onUserEvent);
@ -758,7 +870,7 @@ export class CacheService implements OnApplicationShutdown {
@bindThis
public async getHibernatedFollowers(followeeId: string): Promise<MiFollowing[]> {
const followers = await this.getFollowersWithHibernation(followeeId);
return followers.filter(f => f.isFollowerHibernated);
return followers.filter(follower => follower.isFollowerHibernated);
}
/**
@ -767,7 +879,7 @@ export class CacheService implements OnApplicationShutdown {
@bindThis
public async getNonHibernatedFollowers(followeeId: string): Promise<MiFollowing[]> {
const followers = await this.getFollowersWithHibernation(followeeId);
return followers.filter(f => !f.isFollowerHibernated);
return followers.filter(follower => !follower.isFollowerHibernated);
}
/**
@ -777,14 +889,14 @@ export class CacheService implements OnApplicationShutdown {
@bindThis
public async getFollowersWithHibernation(followeeId: string): Promise<MiFollowing[]> {
const followers = await this.userFollowersCache.fetch(followeeId);
const hibernations = await this.hibernatedUserCache.fetchMany(followers.keys()).then(fs => fs.reduce((map, f) => {
map.set(f[0], f[1]);
return map;
}, new Map<string, boolean>));
return Array.from(followers.values()).map(following => ({
...following,
isFollowerHibernated: hibernations.get(following.followerId) ?? false,
}));
const hibernations = new Map(await this.hibernatedUserCache.fetchMany(followers.keys()));
return followers
.values()
.map(following => ({
...following,
isFollowerHibernated: hibernations.get(following.followerId) ?? false,
}))
.toArray();
}
/**
@ -793,7 +905,7 @@ export class CacheService implements OnApplicationShutdown {
@bindThis
public async refreshFollowRelationsFor(userId: string): Promise<void> {
const followings = await this.userFollowingsCache.refresh(userId);
const followees = Array.from(followings.values()).map(f => f.followeeId);
const followees = followings.values().map(following => following.followeeId);
await this.userFollowersCache.deleteMany(followees);
}

View file

@ -115,13 +115,21 @@ export class CustomEmojiService {
this.emojisByIdCache = cacheManagementService.createQuantumKVCache<MiEmoji>('emojisById', {
lifetime: 1000 * 60 * 60, // 1h
fetcher: async (id) => await this.emojisRepository.findOneBy({ id }),
bulkFetcher: async (ids) => await this.emojisRepository.findBy({ id: In(ids) }).then(es => es.map(e => [e.id, e])),
fetcher: async (id) => await this.emojisRepository.findOneByOrFail({ id }),
optionalFetcher: async (id) => await this.emojisRepository.findOneBy({ id }),
bulkFetcher: async (ids) => {
const emojis = await this.emojisRepository.findBy({ id: In(ids) });
return emojis.map(emoji => [emoji.id, emoji]);
},
});
this.emojisByKeyCache = cacheManagementService.createQuantumKVCache<MiEmoji>('emojisByKey', {
lifetime: 1000 * 60 * 60, // 1h
fetcher: async (key) => {
const { host, name } = decodeEmojiKey(key);
return await this.emojisRepository.findOneByOrFail({ host: host ?? IsNull(), name });
},
optionalFetcher: async (key) => {
const { host, name } = decodeEmojiKey(key);
return await this.emojisRepository.findOneBy({ host: host ?? IsNull(), name });
},

View file

@ -63,10 +63,11 @@ export class FederatedInstanceService implements OnApplicationShutdown {
}
return instance;
},
// optionalFetcher not needed
bulkFetcher: async keys => {
const hosts = keys.map(key => this.utilityService.toPuny(key));
const instances = await this.instancesRepository.findBy({ host: In(hosts) });
return instances.map(i => [i.host, i]);
return instances.map(instance => [instance.host, instance]);
},
});

View file

@ -70,7 +70,9 @@ export class PushNotificationService {
) {
this.subscriptionsCache = cacheManagementService.createQuantumKVCache<MiSwSubscription[]>('userSwSubscriptions', {
lifetime: 1000 * 60 * 60 * 1, // 1h
fetcher: (key) => this.swSubscriptionsRepository.findBy({ userId: key }),
fetcher: async userId => await this.swSubscriptionsRepository.findBy({ userId }),
// optionalFetcher not needed
// bulkFetcher not needed
});
}

View file

@ -32,8 +32,12 @@ export class UserKeypairService implements OnApplicationShutdown {
) {
this.userKeypairCache = cacheManagementService.createQuantumKVCache('userKeypair', {
lifetime: 1000 * 60 * 60, // 1h
fetcher: async userId => await this.userKeypairsRepository.findOneBy({ userId }),
bulkFetcher: async userIds => await this.userKeypairsRepository.findBy({ userId: In(userIds) }).then(ks => ks.map(k => [k.userId, k])),
fetcher: async userId => await this.userKeypairsRepository.findOneByOrFail({ userId }),
optionalFetcher: async userId => await this.userKeypairsRepository.findOneBy({ userId }),
bulkFetcher: async userIds => {
const keypairs = await this.userKeypairsRepository.findBy({ userId: In(userIds) });
return keypairs.map(keypair => [keypair.userId, keypair]);
},
});
this.internalEventService.on('userChangeDeletedState', this.onUserDeleted);

View file

@ -61,8 +61,12 @@ export class UserListService implements OnModuleInit {
) {
this.userListsCache = cacheManagementService.createQuantumKVCache('userLists', {
lifetime: 1000 * 60 * 30, // 30m
fetcher: async id => await this.userListsRepository.findOneBy({ id }),
bulkFetcher: async ids => await this.userListsRepository.findBy({ id: In(ids) }).then(ls => ls.map(l => [l.id, l])),
fetcher: async id => await this.userListsRepository.findOneByOrFail({ id }),
optionalFetcher: async id => await this.userListsRepository.findOneBy({ id }),
bulkFetcher: async ids => {
const lists = await this.userListsRepository.findBy({ id: In(ids) });
return lists.map(list => [list.id, list]);
},
});
}

View file

@ -136,6 +136,14 @@ export class ApPersonService implements OnModuleInit {
.getOneOrFail() as { id: string };
return id;
},
optionalFetcher: async (uri) => {
const res = await this.usersRepository
.createQueryBuilder('user')
.select('user.id')
.where({ uri })
.getOne() as { id: string } | null;
return res?.id;
},
bulkFetcher: async (uris) => {
const users = await this.usersRepository
.createQueryBuilder('user')
@ -143,20 +151,28 @@ export class ApPersonService implements OnModuleInit {
.addSelect('user.uri')
.where({ uri: In(uris) })
.getMany() as { id: string, uri: string }[];
return users.map(u => [u.uri, u.id]);
return users.map(user => [user.uri, user.id]);
},
});
this.publicKeyByKeyIdCache = this.cacheManagementService.createQuantumKVCache<MiUserPublickey>('publicKeyByKeyId', {
lifetime: 1000 * 60 * 60 * 12, // 12h
fetcher: async (keyId) => await this.userPublickeysRepository.findOneBy({ keyId }),
bulkFetcher: async (keyIds) => await this.userPublickeysRepository.findBy({ keyId: In(keyIds) }).then(ks => ks.map(k => [k.keyId, k])),
fetcher: async (keyId) => await this.userPublickeysRepository.findOneByOrFail({ keyId }),
optionalFetcher: async (keyId) => await this.userPublickeysRepository.findOneBy({ keyId }),
bulkFetcher: async (keyIds) => {
const publicKeys = await this.userPublickeysRepository.findBy({ keyId: In(keyIds) });
return publicKeys.map(k => [k.keyId, k]);
},
});
this.publicKeyByUserIdCache = this.cacheManagementService.createQuantumKVCache<MiUserPublickey>('publicKeyByUserId', {
lifetime: 1000 * 60 * 60 * 12, // 12h
fetcher: async (userId) => await this.userPublickeysRepository.findOneBy({ userId }),
bulkFetcher: async (userIds) => await this.userPublickeysRepository.findBy({ userId: In(userIds) }).then(ks => ks.map(k => [k.userId, k])),
fetcher: async (userId) => await this.userPublickeysRepository.findOneByOrFail({ userId }),
optionalFetcher: async (userId) => await this.userPublickeysRepository.findOneBy({ userId }),
bulkFetcher: async (userIds) => {
const publicKeys = await this.userPublickeysRepository.findBy({ userId: In(userIds) });
return publicKeys.map(k => [k.userId, k]);
},
});
}