refactor: Add ability to hydrate outside batch() (#186)

* refactor: Add ability to hydrate outside batch()

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* chore: Update javadoc for these new functions to use link

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* chore: Fix this comment

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

---------

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>
This commit is contained in:
Jacob Schlecht
2026-08-10 17:29:07 -06:00
committed by GitHub
parent d9a7d919e7
commit ee0a9803c5
5 changed files with 138 additions and 65 deletions
+33 -64
View File
@@ -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<void> {
async syncMembers(excludeOffline?: boolean): Promise<void> {
if (this.#synced && (this.#synced === "full" || excludeOffline)) return;
const data = await this.#collection.client.api.get(
@@ -686,67 +685,37 @@ export class Server {
{ exclude_offline: excludeOffline },
);
const newUsers: HydratedUser[] = [];
const newServerMembers: HydratedServerMember[] = [];
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);
}
const newMember = this.#collection.client.serverMembers.hydrateIfNotHas(
data.members[i]._id,
data.members[i],
);
if (newMember) {
newServerMembers.push(newMember);
}
}
}
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++;
}
}
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],
);
}
}
} 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],
);
}
for (const newUser of newUsers) {
this.#collection.client.users.addHydratedUser(newUser);
}
for (const newServerMember of newServerMembers) {
this.#collection.client.serverMembers.addHydratedServerMember(
newServerMember,
);
}
});
}
+23
View File
@@ -175,6 +175,29 @@ export abstract class StoreCollection<T, V> extends Collection<T> {
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);
}
/**
* 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);
}
/**
* Check whether an object is partially defined
* @param id Id
+32
View File
@@ -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
* {@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
* @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;
}
}
+28 -1
View File
@@ -42,7 +42,6 @@ export class UserCollection extends ClassCollection<User, HydratedUser> {
* 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,32 @@ export class UserCollection extends ClassCollection<User, HydratedUser> {
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 {@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
*/
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;
}
}
+22
View File
@@ -47,4 +47,26 @@ export class ObjectStorage<T> {
this.set(id, hydrate(type, data as never, context, true) as T);
}
}
/**
* 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
* @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);
}
}