From 0d509d38c4930eb3bda1e4630fe16787ec774e74 Mon Sep 17 00:00:00 2001 From: Jacob Schlecht Date: Sun, 9 Aug 2026 20:19:50 -0600 Subject: [PATCH 1/3] refactor: Add ability to hydrate outside batch() Signed-off-by: Jacob Schlecht --- src/classes/Server.ts | 93 ++++++++--------------- src/collections/Collection.ts | 17 +++++ src/collections/ServerMemberCollection.ts | 32 ++++++++ src/collections/UserCollection.ts | 30 +++++++- src/storage/ObjectStorage.ts | 22 ++++++ 5 files changed, 131 insertions(+), 63 deletions(-) diff --git a/src/classes/Server.ts b/src/classes/Server.ts index e510de65..4ff841ca 100644 --- a/src/classes/Server.ts +++ b/src/classes/Server.ts @@ -22,6 +22,8 @@ import { decodeTime } from "ulid"; import type { ServerCollection } from "../collections/ServerCollection.js"; import { hydrate } from "../hydration/index.js"; import type { ServerFlags } from "../hydration/server.js"; +import { HydratedServerMember } from "../hydration/serverMember.js"; +import { HydratedUser } from "../hydration/user.js"; import { bitwiseAndEq, calculatePermission, @@ -675,10 +677,7 @@ export class Server { #synced: undefined | "partial" | "full"; - async syncMembers( - excludeOffline?: boolean, - excludeOfflineUserCap?: number, - ): Promise { + async syncMembers(excludeOffline?: boolean): Promise { if (this.#synced && (this.#synced === "full" || excludeOffline)) return; const data = await this.#collection.client.api.get( @@ -686,68 +685,38 @@ export class Server { { exclude_offline: excludeOffline }, ); - batch(() => { - if (excludeOffline && excludeOfflineUserCap) { - // quick fix to cap users - let count = 0; - - for ( - let i = 0; - i < data.users.length && count < excludeOfflineUserCap; - i++ - ) { - const user = data.users[i]; - if (user.online && data.members[i].roles?.length) { - this.#collection.client.users.getOrCreate(user._id, user); - this.#collection.client.serverMembers.getOrCreate( - data.members[i]._id, - data.members[i], - ); - - count++; - } - } + const newUsers: HydratedUser[] = []; + const newServerMembers: HydratedServerMember[] = []; - for ( - let i = 0; - i < data.users.length && count < excludeOfflineUserCap; - i++ - ) { - const user = data.users[i]; - if (user.online && !data.members[i].roles?.length) { - this.#collection.client.users.getOrCreate(user._id, user); - this.#collection.client.serverMembers.getOrCreate( - data.members[i]._id, - data.members[i], - ); - - count++; - } - } - // end quick fix - } else if (excludeOffline) { - for (let i = 0; i < data.users.length; i++) { - const user = data.users[i]; - if (user.online) { - this.#collection.client.users.getOrCreate(user._id, user); - this.#collection.client.serverMembers.getOrCreate( - data.members[i]._id, - data.members[i], - ); - } + for (let i = 0; i < data.users.length; i++) { + const user = data.users[i]; + if (!excludeOffline || user.online) { + const newUser = this.#collection.client.users.hydrateIfNotHas( + user._id, + user, + ); + if (newUser) { + newUsers.push(newUser); } - } else { - for (let i = 0; i < data.users.length; i++) { - this.#collection.client.users.getOrCreate( - data.users[i]._id, - data.users[i], - ); - this.#collection.client.serverMembers.getOrCreate( - data.members[i]._id, - data.members[i], - ); + const newMember = this.#collection.client.serverMembers.hydrateIfNotHas( + data.members[i]._id, + data.members[i], + ); + if (newMember) { + newServerMembers.push(newMember); } } + } + + batch(() => { + for (const newUser of newUsers) { + this.#collection.client.users.addHydratedUser(newUser); + } + for (const newServerMember of newServerMembers) { + this.#collection.client.serverMembers.addHydratedServerMember( + newServerMember, + ); + } }); } diff --git a/src/collections/Collection.ts b/src/collections/Collection.ts index 153daeee..8457640b 100644 --- a/src/collections/Collection.ts +++ b/src/collections/Collection.ts @@ -175,6 +175,23 @@ export abstract class StoreCollection extends Collection { this.#objects.set(id, instance); } + /** + * Hydrate a new instance of an object. This function does not add the object to the collection. + * @param id Id + * @param type Type + * @param instance Instance + * @param context Context + * @param data Data + */ + protected hydrate(type: keyof Hydrators, context: unknown, data: object): V { + return this.#storage.hydrateOnly(type, context, data); + } + + protected add(id: string, instance: T, hydrated: V) { + this.#storage.add(id, hydrated); + this.#objects.set(id, instance); + } + /** * Check whether an object is partially defined * @param id Id diff --git a/src/collections/ServerMemberCollection.ts b/src/collections/ServerMemberCollection.ts index a559979d..37c54a40 100644 --- a/src/collections/ServerMemberCollection.ts +++ b/src/collections/ServerMemberCollection.ts @@ -97,4 +97,36 @@ export class ServerMemberCollection extends ClassCollection< return instance; } } + + /** + * Hydrate a new server member if it is not in the collection yet. This + * function does not add the server member to the store, make sure you call + * ServerMemberCollection.addHydratedServerMember afterwards. This function + * is particularly useful when adding many server members asynchronously. See + * Server.syncMembers for an example of this in use. + * @param id The ID of the server member + * @param data The API object for a server member + * @returns The HydratedServerMember, or undefined if the user is in the collection + */ + hydrateIfNotHas( + id: MemberCompositeKey, + data: Member, + ): HydratedServerMember | undefined { + if (this.hasByKey(id) && !this.isPartialByKey(id)) { + return; + } else { + return this.hydrate("serverMember", this.client, data); + } + } + + /** + * Add a pre-hydrated server member to this collection. + * @param user A hydrated server member + * @returns The server member instance + */ + addHydratedServerMember(member: HydratedServerMember): ServerMember { + const instance = new ServerMember(this, member.id); + this.add(member.id.server + member.id.user, instance, member); + return instance; + } } diff --git a/src/collections/UserCollection.ts b/src/collections/UserCollection.ts index a5e70740..c6bdfa9a 100644 --- a/src/collections/UserCollection.ts +++ b/src/collections/UserCollection.ts @@ -42,7 +42,6 @@ export class UserCollection extends ClassCollection { * Get or create * @param id Id * @param data Data - * @param isNew Whether this object is new */ getOrCreate(id: string, data: APIUser): User { if (this.has(id) && !this.isPartial(id)) { @@ -70,4 +69,33 @@ export class UserCollection extends ClassCollection { return instance; } } + + /** + * Hydrate a new user if it is not in the collection yet. This function does + * not add the user to the store, make sure you call + * UserCollection.addHydratedUser afterwards. This function is particularly + * useful when adding many users asynchronously. See Server.syncMembers for + * an example of this in use. + * @param id The ID of the user + * @param data The API object for a user + * @returns The HydratedUser, or undefined if the user is in the collection + */ + hydrateIfNotHas(id: string, data: APIUser): HydratedUser | undefined { + if (this.has(id) && !this.isPartial(id)) { + return; + } else { + return this.hydrate("user", this.client, data); + } + } + + /** + * Add a pre-hydrated user to this collection. + * @param user A hydrated user + * @returns The user instance + */ + addHydratedUser(user: HydratedUser): User { + const instance = new User(this, user.id); + this.add(user.id, instance, user); + return instance; + } } diff --git a/src/storage/ObjectStorage.ts b/src/storage/ObjectStorage.ts index 91dd50ea..65cbe33e 100644 --- a/src/storage/ObjectStorage.ts +++ b/src/storage/ObjectStorage.ts @@ -47,4 +47,26 @@ export class ObjectStorage { this.set(id, hydrate(type, data as never, context, true) as T); } } + + /** + * Hydrate some data without putting it into storage. Call add afterwards to + * add the hydrated value into storage. + * @param type Hydration type + * @param context Context + * @param data Input Data + * @returns The hydrated type + */ + hydrateOnly(type: keyof Hydrators, context: unknown, data: object): T { + data = { partial: false, ...data }; + return hydrate(type, data as never, context, true) as T; + } + + /** + * Add a previously hydrated object to the storage + * @param id ID + * @param toAdd Hydrated object + */ + add(id: string, toAdd: T) { + this.set(id, toAdd); + } } From 071c36ea273c89f969b696562c816c6811a15fc0 Mon Sep 17 00:00:00 2001 From: Jacob Schlecht Date: Mon, 10 Aug 2026 17:26:22 -0600 Subject: [PATCH 2/3] chore: Update javadoc for these new functions to use link Signed-off-by: Jacob Schlecht --- src/collections/Collection.ts | 6 ++++++ src/collections/ServerMemberCollection.ts | 4 ++-- src/collections/UserCollection.ts | 2 +- src/storage/ObjectStorage.ts | 4 ++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/collections/Collection.ts b/src/collections/Collection.ts index 8457640b..65bc2bcf 100644 --- a/src/collections/Collection.ts +++ b/src/collections/Collection.ts @@ -187,6 +187,12 @@ export abstract class StoreCollection extends Collection { return this.#storage.hydrateOnly(type, context, data); } + /** + * Add a previously hydrated object to the collection + * @param id Id + * @param instance Instance + * @param hydrated Hydrated + */ protected add(id: string, instance: T, hydrated: V) { this.#storage.add(id, hydrated); this.#objects.set(id, instance); diff --git a/src/collections/ServerMemberCollection.ts b/src/collections/ServerMemberCollection.ts index 37c54a40..4d28f4a7 100644 --- a/src/collections/ServerMemberCollection.ts +++ b/src/collections/ServerMemberCollection.ts @@ -101,8 +101,8 @@ export class ServerMemberCollection extends ClassCollection< /** * Hydrate a new server member if it is not in the collection yet. This * function does not add the server member to the store, make sure you call - * ServerMemberCollection.addHydratedServerMember afterwards. This function - * is particularly useful when adding many server members asynchronously. See + * {@link addHydratedServerMember} afterwards. This function is particularly + * useful when adding many server members asynchronously. See * Server.syncMembers for an example of this in use. * @param id The ID of the server member * @param data The API object for a server member diff --git a/src/collections/UserCollection.ts b/src/collections/UserCollection.ts index c6bdfa9a..c327e211 100644 --- a/src/collections/UserCollection.ts +++ b/src/collections/UserCollection.ts @@ -73,7 +73,7 @@ export class UserCollection extends ClassCollection { /** * Hydrate a new user if it is not in the collection yet. This function does * not add the user to the store, make sure you call - * UserCollection.addHydratedUser afterwards. This function is particularly + * {@link addHydratedUser} afterwards. This function is particularly * useful when adding many users asynchronously. See Server.syncMembers for * an example of this in use. * @param id The ID of the user diff --git a/src/storage/ObjectStorage.ts b/src/storage/ObjectStorage.ts index 65cbe33e..1367d0f4 100644 --- a/src/storage/ObjectStorage.ts +++ b/src/storage/ObjectStorage.ts @@ -49,8 +49,8 @@ export class ObjectStorage { } /** - * Hydrate some data without putting it into storage. Call add afterwards to - * add the hydrated value into storage. + * Hydrate some data without putting it into storage. Call {@link add} + * afterwards to add the hydrated value into storage. * @param type Hydration type * @param context Context * @param data Input Data From a86d34adaf5b6f9a5789b243dc355b8db30f3d45 Mon Sep 17 00:00:00 2001 From: Jacob Schlecht Date: Mon, 10 Aug 2026 17:27:19 -0600 Subject: [PATCH 3/3] chore: Fix this comment Signed-off-by: Jacob Schlecht --- src/collections/UserCollection.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/collections/UserCollection.ts b/src/collections/UserCollection.ts index c327e211..e99d3822 100644 --- a/src/collections/UserCollection.ts +++ b/src/collections/UserCollection.ts @@ -72,10 +72,9 @@ export class UserCollection extends ClassCollection { /** * Hydrate a new user if it is not in the collection yet. This function does - * not add the user to the store, make sure you call - * {@link addHydratedUser} afterwards. This function is particularly - * useful when adding many users asynchronously. See Server.syncMembers for - * an example of this in use. + * not add the user to the store, make sure you call {@link addHydratedUser} + * afterwards. This function is particularly useful when adding many users + * asynchronously. See Server.syncMembers for an example of this in use. * @param id The ID of the user * @param data The API object for a user * @returns The HydratedUser, or undefined if the user is in the collection