perf: server navigation

This commit is contained in:
2026-05-18 19:38:08 +02:00
parent 0152ed9dd2
commit afb64520ed
12 changed files with 212 additions and 90 deletions

View File

@@ -66,31 +66,32 @@ export class BrowserDatabaseService {
}
/**
* Retrieve messages for a room, sorted oldest-first.
* Retrieve the latest messages for a room, sorted oldest-first for display.
* @param roomId - Target room.
* @param limit - Maximum number of messages to return.
* @param offset - Number of messages to skip (for pagination).
* @param offset - Number of newer messages to skip (for pagination).
*/
async getMessages(roomId: string, limit = 100, offset = 0): Promise<Message[]> {
const allRoomMessages = await this.getAllFromIndex<Message>(
STORE_MESSAGES, 'roomId', roomId
);
const sortedMessages = allRoomMessages.sort((first, second) => first.timestamp - second.timestamp);
const endIndex = Math.max(sortedMessages.length - offset, 0);
const startIndex = Math.max(endIndex - limit, 0);
const messages = sortedMessages.slice(startIndex, endIndex);
return allRoomMessages
.sort((first, second) => first.timestamp - second.timestamp)
.slice(offset, offset + limit)
.map((message) => this.normaliseMessage(message));
return this.hydrateMessages(messages);
}
async getMessagesSince(roomId: string, sinceTimestamp: number): Promise<Message[]> {
const allRoomMessages = await this.getAllFromIndex<Message>(
STORE_MESSAGES, 'roomId', roomId
);
return allRoomMessages
const messages = allRoomMessages
.filter((message) => message.timestamp > sinceTimestamp)
.sort((first, second) => first.timestamp - second.timestamp)
.map((message) => this.normaliseMessage(message));
.sort((first, second) => first.timestamp - second.timestamp);
return this.hydrateMessages(messages);
}
/** Delete a message by its ID. */
@@ -112,7 +113,11 @@ export class BrowserDatabaseService {
async getMessageById(messageId: string): Promise<Message | null> {
const message = await this.get<Message>(STORE_MESSAGES, messageId);
return message ? this.normaliseMessage(message) : null;
if (!message) {
return null;
}
return (await this.hydrateMessages([message]))[0] ?? null;
}
/** Remove every message belonging to a room. */
@@ -520,6 +525,47 @@ export class BrowserDatabaseService {
await this.awaitTransaction(transaction);
}
private async hydrateMessages(messages: Message[]): Promise<Message[]> {
if (messages.length === 0) {
return [];
}
const reactionsByMessageId = await this.loadReactionsForMessages(messages.map((message) => message.id));
return messages.map((message) => this.normaliseMessage({
...message,
reactions: reactionsByMessageId.get(message.id) ?? message.reactions ?? []
}));
}
private async loadReactionsForMessages(messageIds: readonly string[]): Promise<Map<string, Reaction[]>> {
const messageIdSet = new Set(messageIds.filter((messageId) => messageId.trim().length > 0));
const reactionsByMessageId = new Map<string, Reaction[]>();
if (messageIdSet.size === 0) {
return reactionsByMessageId;
}
const allReactions = await this.getAll<Reaction>(STORE_REACTIONS);
for (const reaction of allReactions) {
if (!messageIdSet.has(reaction.messageId)) {
continue;
}
const reactions = reactionsByMessageId.get(reaction.messageId) ?? [];
reactions.push(reaction);
reactionsByMessageId.set(reaction.messageId, reactions);
}
for (const reactions of reactionsByMessageId.values()) {
reactions.sort((first, second) => first.timestamp - second.timestamp);
}
return reactionsByMessageId;
}
private normaliseMessage(message: Message): Message {
if (message.content === DELETED_MESSAGE_CONTENT) {
return { ...message,