git » sdk » commit 3abba97

Pass the ChatMessage to the persistence layer

author Stephen Paul Weber
2026-03-16 02:55:17 UTC
committer Stephen Paul Weber
2026-03-16 13:49:15 UTC
parent 00d93f96985cf05efd56ccad73275d5d84d28baf

Pass the ChatMessage to the persistence layer

This way it doesn't need to look it up itself, and each layer can query
by id, timestamp, or other as needed without changing the API.

borogove/Chat.hx +19 -7
borogove/Persistence.hx +3 -3
borogove/persistence/Dummy.hx +3 -3
borogove/persistence/IDB.js +10 -21
borogove/persistence/Sqlite.hx +10 -22

diff --git a/borogove/Chat.hx b/borogove/Chat.hx
index f74e680..3c67453 100644
--- a/borogove/Chat.hx
+++ b/borogove/Chat.hx
@@ -867,7 +867,7 @@ abstract class Chat {
 	}
 
 	private function recomputeUnread(): Promise<Any> {
-		return persistence.getMessagesBefore(client.accountId(), chatId, null, null).then((messages) -> {
+		return persistence.getMessagesBefore(client.accountId(), chatId, null).then((messages) -> {
 			var i = messages.length;
 			while (--i >= 0) {
 				if (messages[i].serverId == readUpToId || !messages[i].isIncoming()) break;
@@ -965,7 +965,9 @@ class DirectChat extends Chat {
 
 	@HaxeCBridge.noemit // on superclass as abstract
 	public function getMessagesBefore(before: Null<ChatMessage>):Promise<Array<ChatMessage>> {
-		return persistence.getMessagesBefore(client.accountId(), chatId, before?.serverId ?? before?.localId, before?.timestamp).then((messages) ->
+		if (before.chatId() != chatId) throw "Cannot look before from a different chat";
+
+		return persistence.getMessagesBefore(client.accountId(), chatId, before).then((messages) ->
 			if (messages.length > 0) {
 				Promise.resolve(messages);
 			} else {
@@ -979,10 +981,12 @@ class DirectChat extends Chat {
 
 	@HaxeCBridge.noemit // on superclass as abstract
 	public function getMessagesAfter(after: Null<ChatMessage>):Promise<Array<ChatMessage>> {
+		if (after.chatId() != chatId) throw "Cannot look after from a different chat";
 		if (after != null && lastMessage != null && lastMessage.canReplace(after) && !syncing()) {
 			return Promise.resolve([]);
 		}
-		return persistence.getMessagesAfter(client.accountId(), chatId, after?.serverId ?? after?.localId, after?.timestamp).then((messages) ->
+
+		return persistence.getMessagesAfter(client.accountId(), chatId, after).then((messages) ->
 			if (messages.length > 0) {
 				Promise.resolve(messages);
 			} else {
@@ -996,8 +1000,10 @@ class DirectChat extends Chat {
 
 	@HaxeCBridge.noemit // on superclass as abstract
 	public function getMessagesAround(around: ChatMessage):Promise<Array<ChatMessage>> {
+		if (around.chatId() != chatId) throw "Cannot look around from a different chat";
+
 		// TODO: fetch more from MAM if nothing locally?
-		return persistence.getMessagesAround(client.accountId(), chatId, around.serverId ?? around.localId, around.timestamp);
+		return persistence.getMessagesAround(client.accountId(), around);
 	}
 
 	@:allow(borogove)
@@ -1632,7 +1638,9 @@ trace("XYZZY no MUC avatar locally matching so fetch vcard", chatId, avatarSha1H
 
 	@HaxeCBridge.noemit // on superclass as abstract
 	public function getMessagesBefore(before: Null<ChatMessage>):Promise<Array<ChatMessage>> {
-		return persistence.getMessagesBefore(client.accountId(), chatId, before?.serverId ?? before?.localId, before?.timestamp).then((messages) ->
+		if (before.chatId() != chatId) throw "Cannot look before from a different chat";
+
+		return persistence.getMessagesBefore(client.accountId(), chatId, before).then((messages) ->
 			if (messages.length > 0) {
 				Promise.resolve(messages);
 			} else {
@@ -1651,10 +1659,12 @@ trace("XYZZY no MUC avatar locally matching so fetch vcard", chatId, avatarSha1H
 
 	@HaxeCBridge.noemit // on superclass as abstract
 	public function getMessagesAfter(after: Null<ChatMessage>):Promise<Array<ChatMessage>> {
+		if (after.chatId() != chatId) throw "Cannot look after from a different chat";
 		if (after != null && lastMessage != null && lastMessage.canReplace(after) && !syncing()) {
 			return Promise.resolve([]);
 		}
-		return persistence.getMessagesAfter(client.accountId(), chatId, after?.serverId ?? after?.localId, after?.timestamp).then((messages) ->
+
+		return persistence.getMessagesAfter(client.accountId(), chatId, after).then((messages) ->
 			if (messages.length > 0) {
 				Promise.resolve(messages);
 			} else {
@@ -1673,8 +1683,10 @@ trace("XYZZY no MUC avatar locally matching so fetch vcard", chatId, avatarSha1H
 
 	@HaxeCBridge.noemit // on superclass as abstract
 	public function getMessagesAround(around: ChatMessage):Promise<Array<ChatMessage>> {
+		if (around.chatId() != chatId) throw "Cannot look around from a different chat";
+
 		// TODO: fetch more from MAM if nothing locally
-		return persistence.getMessagesAround(client.accountId(), chatId, around.serverId ?? around.localId, around.timestamp);
+		return persistence.getMessagesAround(client.accountId(), around);
 	}
 
 	@:allow(borogove)
diff --git a/borogove/Persistence.hx b/borogove/Persistence.hx
index 1ab8fbf..177ee67 100644
--- a/borogove/Persistence.hx
+++ b/borogove/Persistence.hx
@@ -33,9 +33,9 @@ interface Persistence {
 	public function updateMessage(accountId: String, message: ChatMessage):Void;
 	public function updateMessageStatus(accountId: String, localId: String, status:borogove.Message.MessageStatus, statusText: Null<String>): Promise<ChatMessage>;
 	public function getMessage(accountId: String, chatId: String, serverId: Null<String>, localId: Null<String>): Promise<Null<ChatMessage>>;
-	public function getMessagesBefore(accountId: String, chatId: String, beforeId: Null<String>, beforeTime: Null<String>): Promise<Array<ChatMessage>>;
-	public function getMessagesAfter(accountId: String, chatId: String, afterId: Null<String>, afterTime: Null<String>): Promise<Array<ChatMessage>>;
-	public function getMessagesAround(accountId: String, chatId: String, aroundId: Null<String>, aroundTime: Null<String>): Promise<Array<ChatMessage>>;
+	public function getMessagesBefore(accountId: String, chatId: String, before: Null<ChatMessage>): Promise<Array<ChatMessage>>;
+	public function getMessagesAfter(accountId: String, chatId: String, afterId: Null<ChatMessage>): Promise<Array<ChatMessage>>;
+	public function getMessagesAround(accountId: String, around: ChatMessage): Promise<Array<ChatMessage>>;
 	public function hasMedia(hashAlgorithm:String, hash:BytesData): Promise<Bool>;
 	public function storeMedia(mime:String, bytes:BytesData): Promise<Bool>;
 	public function removeMedia(hashAlgorithm:String, hash:BytesData):Void;
diff --git a/borogove/persistence/Dummy.hx b/borogove/persistence/Dummy.hx
index 7451752..50261ab 100644
--- a/borogove/persistence/Dummy.hx
+++ b/borogove/persistence/Dummy.hx
@@ -57,17 +57,17 @@ class Dummy implements Persistence {
 	}
 
 	@HaxeCBridge.noemit
-	public function getMessagesBefore(accountId: String, chatId: String, beforeId: Null<String>, beforeTime: Null<String>): Promise<Array<ChatMessage>> {
+	public function getMessagesBefore(accountId: String, chatId: String, before: Null<ChatMessage>): Promise<Array<ChatMessage>> {
 		return Promise.resolve([]);
 	}
 
 	@HaxeCBridge.noemit
-	public function getMessagesAfter(accountId: String, chatId: String, afterId: Null<String>, afterTime: Null<String>): Promise<Array<ChatMessage>> {
+	public function getMessagesAfter(accountId: String, chatId: String, after: Null<ChatMessage>): Promise<Array<ChatMessage>> {
 		return Promise.resolve([]);
 	}
 
 	@HaxeCBridge.noemit
-	public function getMessagesAround(accountId: String, chatId: String, aroundId: Null<String>, aroundTime: Null<String>): Promise<Array<ChatMessage>> {
+	public function getMessagesAround(accountId: String, around: Null<ChatMessage>): Promise<Array<ChatMessage>> {
 		return Promise.resolve([]);
 	}
 
diff --git a/borogove/persistence/IDB.js b/borogove/persistence/IDB.js
index 8abd974..c48c1df 100644
--- a/borogove/persistence/IDB.js
+++ b/borogove/persistence/IDB.js
@@ -531,47 +531,36 @@ export default async (dbname, media, tokenize, stemmer) => {
 			throw "Message not found: " + localId;
 		},
 
-		getMessagesBefore: async function(account, chatId, beforeId, beforeTime) {
-			// TODO: if beforeId is present but beforeTime is null, lookup time
-			const bound = beforeTime ? new Date(beforeTime) : [];
+		getMessagesBefore: async function(account, chatId, before) {
+			const bound = before ? new Date(before.timestamp) : [];
 			const tx = db.transaction(["messages"], "readonly");
 			const store = tx.objectStore("messages");
 			const cursor = store.index("chats").openCursor(
 				IDBKeyRange.bound([account, chatId], [account, chatId, bound]),
 				"prev"
 			);
-			const messages = await this.getMessagesFromCursor(cursor, beforeId, bound);
+			const messages = await this.getMessagesFromCursor(cursor, before?.serverId || before?.localId, bound);
 			return messages.reverse();
 		},
 
-		getMessagesAfter: async function(account, chatId, afterId, afterTime) {
-			// TODO: if afterId is present but afterTime is null, lookup time
-			const bound = afterTime ? [new Date(afterTime)] : [];
+		getMessagesAfter: async function(account, chatId, after) {
+			const bound = after ? [new Date(after.timestamp)] : [];
 			const tx = db.transaction(["messages"], "readonly");
 			const store = tx.objectStore("messages");
 			const cursor = store.index("chats").openCursor(
 				IDBKeyRange.bound([account, chatId, ...bound], [account, chatId, []]),
 				"next"
 			);
-			return this.getMessagesFromCursor(cursor, afterId, bound[0]);
+			return this.getMessagesFromCursor(cursor, after?.serverId || after?.localId, bound[0]);
 		},
 
-		getMessagesAround: async function(account, chatId, id, timeArg) {
-			if (!id && !timeArg) throw "Around what?";
-
-			const time = await (
-				timeArg ? Promise.resolve(timeArg) :
-					this.getMessage(account, chatId, id, null).then((m) =>
-						m ? m.timestamp : this.getMessage(account, chatId, null, id).then((m2) => m2?.timestamp)
-					)
-			);
-			if (!time) return [];
-
-			const before = this.getMessagesBefore(account, chatId, id, time);
+		getMessagesAround: async function(account, around) {
+			const chatId = around.chatId();
+			const before = this.getMessagesBefore(account, chatId, around);
 			const tx = db.transaction(["messages"], "readonly");
 			const store = tx.objectStore("messages");
 			const cursor = store.index("chats").openCursor(
-				IDBKeyRange.bound([account, chatId, new Date(time)], [account, chatId, []]),
+				IDBKeyRange.bound([account, chatId, new Date(around.timestamp)], [account, chatId, []]),
 				"next"
 			);
 			const aroundAndAfter = this.getMessagesFromCursor(cursor, null, null);
diff --git a/borogove/persistence/Sqlite.hx b/borogove/persistence/Sqlite.hx
index 5ecba83..a77c5e5 100644
--- a/borogove/persistence/Sqlite.hx
+++ b/borogove/persistence/Sqlite.hx
@@ -500,36 +500,24 @@ class Sqlite implements Persistence implements KeyValueStore {
 	}
 
 	@HaxeCBridge.noemit
-	public function getMessagesBefore(accountId: String, chatId: String, beforeId: Null<String>, beforeTime: Null<String>): Promise<Array<ChatMessage>> {
-		return getMessages(accountId, chatId, beforeTime, "<");
+	public function getMessagesBefore(accountId: String, chatId: String, before: Null<ChatMessage>): Promise<Array<ChatMessage>> {
+		return getMessages(accountId, chatId, before?.timestamp, "<");
 	}
 
 	@HaxeCBridge.noemit
-	public function getMessagesAfter(accountId: String, chatId: String, afterId: Null<String>, afterTime: Null<String>): Promise<Array<ChatMessage>> {
-		return getMessages(accountId, chatId, afterTime, ">");
+	public function getMessagesAfter(accountId: String, chatId: String, after: Null<ChatMessage>): Promise<Array<ChatMessage>> {
+		return getMessages(accountId, chatId, after?.timestamp, ">");
 	}
 
 	@HaxeCBridge.noemit
-	public function getMessagesAround(accountId: String, chatId: String, aroundId: Null<String>, aroundTime: Null<String>): Promise<Array<ChatMessage>> {
-		return (if (aroundTime == null) {
-			getMessage(accountId, chatId, aroundId, null).then(m ->
-				if (m != null) {
-					Promise.resolve(m.timestamp);
-				} else {
-					getMessage(accountId, chatId, null, aroundId).then(m -> m?.timestamp);
-				}
-			);
-		} else {
-			Promise.resolve(aroundTime);
-		}).then(aroundTime ->
-			thenshim.PromiseTools.all([
-				getMessages(accountId, chatId, aroundTime, "<"),
-				getMessages(accountId, chatId, aroundTime, ">=")
-			])
-		).then(results -> results.flatten());
+	public function getMessagesAround(accountId: String, around: ChatMessage): Promise<Array<ChatMessage>> {
+		final chatId = around.chatId();
+		return thenshim.PromiseTools.all([
+			getMessages(accountId, chatId, around.timestamp, "<"),
+			getMessages(accountId, chatId, around.timestamp, ">=")
+		]).then(results -> results.flatten());
 	}
 
-
 	private function getChatUnreadDetails(accountId: String, chat: Chat): Promise<{ chatId: String, message: ChatMessage, unreadCount: Int }> {
 		return db.exec(
 			"WITH subq as (SELECT ROWID as row, COALESCE(MAX(created_at), 0) as created_at FROM messages where account_id=? AND chat_id=? AND (mam_id=? OR direction=?)) SELECT chat_id AS chatId, stanza, direction, type, status, status_text, sender_id, mam_id, mam_by, sync_point, CASE WHEN (SELECT row FROM subq) IS NULL THEN COUNT(*) ELSE COUNT(*) - 1 END AS unreadCount, strftime('%FT%H:%M:%fZ', MAX(messages.created_at) / 1000.0, 'unixepoch') AS timestamp FROM messages WHERE account_id=? AND chat_id=? AND (stanza_id IS NULL OR stanza_id='' OR stanza_id=correction_id) AND (messages.created_at >= (SELECT created_at FROM subq) AND (messages.created_at <> (SELECT created_at FROM subq) OR messages.ROWID = (SELECT row FROM subq)))",