Skip to content

Commit

Permalink
Refactor replies
Browse files Browse the repository at this point in the history
  • Loading branch information
rygine committed Oct 16, 2023
1 parent 6d1d105 commit e9e8694
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 26 deletions.
91 changes: 66 additions & 25 deletions packages/react-sdk/src/helpers/caching/contentTypes/reply.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
import type { Reply } from "@xmtp/content-type-reply";
import { ReplyCodec, ContentTypeReply } from "@xmtp/content-type-reply";
import { ContentTypeId } from "@xmtp/xmtp-js";
import type { Dexie } from "dexie";
import type { Dexie, Table } from "dexie";
import { z } from "zod";
import type { CachedMessage } from "@/helpers/caching/messages";
import type {
CachedMessage,
CachedMessagesTable,
} from "@/helpers/caching/messages";
import type {
ContentTypeConfiguration,
ContentTypeMessageProcessor,
} from "../db";
import { getMessageByXmtpID, updateMessageMetadata } from "../messages";
import { getMessageByXmtpID } from "../messages";

const NAMESPACE = "replies";

export type CachedRepliesMetadata = string[];
export type CachedReply = {
id?: number;
referenceXmtpID: Reply["reference"];
xmtpID: string;
};

export type CachedReplyWithId = CachedReply & {
id: number;
};

export type CachedRepliesTable = Table<CachedReply, number>;

/**
* Add a reply to the metadata of a cached message
*
* Replies are stored as an array of XMTP message IDs in the metadata of
* the original message.
* Add a reply to the cache
*
* @param xmtpID XMTP message ID of the original message
* @param replyXmtpID XMTP message ID of the reply message
Expand All @@ -29,37 +39,60 @@ export const addReply = async (
replyXmtpID: string,
db: Dexie,
) => {
const message = await getMessageByXmtpID(xmtpID, db);
if (message) {
const replies = (message.metadata?.[NAMESPACE] ??
[]) as CachedRepliesMetadata;
const exists = replies.some((reply) => reply === replyXmtpID);
if (!exists) {
replies.push(replyXmtpID);
await updateMessageMetadata(message, NAMESPACE, replies, db);
}
}
const repliesTable = db.table("replies") as CachedRepliesTable;

const existing = await repliesTable
.where({
referenceXmtpID: xmtpID,
xmtpID: replyXmtpID,
})
.first();

return existing
? (existing as CachedReplyWithId).id
: repliesTable.add({
referenceXmtpID: xmtpID,
xmtpID: replyXmtpID,
});
};

/**
* Retrieve all replies to a cached message
*
* @param message Cached message
* @returns An array of XMTP message IDs
* @param db Database instance
* @returns An array of reply messages
*/
export const getReplies = (message: CachedMessage) => {
const metadata = message?.metadata?.[NAMESPACE] ?? [];
return metadata as CachedRepliesMetadata;
export const getReplies = async (message: CachedMessage, db: Dexie) => {
const repliesTable = db.table("replies") as CachedRepliesTable;
const replies = await repliesTable
.where({ referenceXmtpID: message.xmtpID })
.toArray();
if (replies.length > 0) {
const messagesTable = db.table("messages") as CachedMessagesTable;
const replyMessages = await messagesTable
.where("xmtpID")
.anyOf(replies.map((reply) => reply.xmtpID))
.sortBy("sentAt");
return replyMessages;
}
return [];
};

/**
* Check if a cached message has any replies
*
* @param message Cached message
* @param db Database instance
* @returns `true` if the message has any replies, `false` otherwise
*/
export const hasReply = (message: CachedMessage) =>
getReplies(message).length > 0;
export const hasReply = async (message: CachedMessage, db: Dexie) => {
const repliesTable = db.table("replies") as CachedRepliesTable;
const replies = await repliesTable
.where({ referenceXmtpID: message.xmtpID })
.toArray();
return replies.length > 0;
};

/**
* Get the original message from a reply message
Expand Down Expand Up @@ -123,7 +156,7 @@ export const processReply: ContentTypeMessageProcessor = async ({
) {
const reply = message.content as Reply;

// update replies metadata on the referenced message
// save the reply to cache
await addReply(reply.reference, message.xmtpID, db);

// save the message to cache
Expand All @@ -137,6 +170,14 @@ export const replyContentTypeConfig: ContentTypeConfiguration = {
processors: {
[ContentTypeReply.toString()]: [processReply],
},
schema: {
replies: `
++id,
[referenceXmtpID+xmtpID],
referenceXmtpID,
xmtpID
`,
},
validators: {
[ContentTypeReply.toString()]: isValidReplyContent,
},
Expand Down
22 changes: 22 additions & 0 deletions packages/react-sdk/src/hooks/useReplies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useLiveQuery } from "dexie-react-hooks";
import { useDb } from "./useDb";
import type { CachedMessage } from "@/helpers/caching/messages";
import { getReplies } from "@/helpers/caching/contentTypes/reply";

/**
* This hook returns cached replies to a message from the local cache
*/
export const useReplies = (message?: CachedMessage) => {
const { db } = useDb();

return (
useLiveQuery(async () => {
if (!message) return [];
try {
return await getReplies(message, db);
} catch {
return [];
}
}, [message]) ?? []
);
};
2 changes: 1 addition & 1 deletion packages/react-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export { useReactions } from "./hooks/useReactions";

// replies
export { useReply } from "./hooks/useReply";
export { useReplies } from "./hooks/useReplies";

// caching
export { getDbInstance, clearCache } from "./helpers/caching/db";
Expand Down Expand Up @@ -107,7 +108,6 @@ export {
} from "./helpers/caching/contentTypes/readReceipt";

// replies
export type { CachedRepliesMetadata } from "./helpers/caching/contentTypes/reply";
export {
getReplies,
getOriginalMessageFromReply,
Expand Down

0 comments on commit e9e8694

Please sign in to comment.