kuamail thread mutation transport updates

This commit is contained in:
Kavi 2026-04-23 13:37:31 -04:00
parent cc0740d158
commit fc7420eb8f
5 changed files with 31 additions and 11 deletions

View File

@ -58,9 +58,12 @@ export type SyncRequest = {
mutations: Mutation[];
};
export type MutationTargetKind = 'thread' | 'message' | 'draft';
export type Mutation = {
id: string;
op: 'flag' | 'move' | 'delete' | 'send' | 'draft_save' | 'draft_delete';
target_kind: MutationTargetKind;
target_id: string;
payload: Record<string, unknown>;
};

View File

@ -25,19 +25,19 @@ export function MessageReader({ threadId }: Props) {
function handleArchive() {
if (!accountId) return;
queueMutation(accountId, 'move', threadId, { folder_special_use: 'archive' });
queueMutation(accountId, 'move', 'thread', threadId, { folder_special_use: 'archive' });
selectThread(null);
}
function handleDelete() {
if (!accountId) return;
queueMutation(accountId, 'move', threadId, { folder_special_use: 'trash' });
queueMutation(accountId, 'move', 'thread', threadId, { folder_special_use: 'trash' });
selectThread(null);
}
function handleMarkUnread() {
if (!accountId) return;
queueMutation(accountId, 'flag', threadId, { seen: false });
queueMutation(accountId, 'flag', 'thread', threadId, { seen: false });
}
return (

View File

@ -44,7 +44,7 @@ export function ThreadList({ folderId }: Props) {
const handleStar = useCallback((e: React.MouseEvent, thread: Thread) => {
e.stopPropagation();
if (!accountId) return;
queueMutation(accountId, 'flag', thread.id, { flagged: !thread.has_flagged });
queueMutation(accountId, 'flag', 'thread', thread.id, { flagged: !thread.has_flagged });
}, [accountId]);
const handleCheck = useCallback((e: React.MouseEvent, threadId: string) => {
@ -54,17 +54,17 @@ export function ThreadList({ folderId }: Props) {
const handleArchive = useCallback((_: React.MouseEvent, thread: Thread) => {
if (!accountId) return;
queueMutation(accountId, 'move', thread.id, { folder_special_use: 'archive' });
queueMutation(accountId, 'move', 'thread', thread.id, { folder_special_use: 'archive' });
}, [accountId]);
const handleDelete = useCallback((_: React.MouseEvent, thread: Thread) => {
if (!accountId) return;
queueMutation(accountId, 'move', thread.id, { folder_special_use: 'trash' });
queueMutation(accountId, 'move', 'thread', thread.id, { folder_special_use: 'trash' });
}, [accountId]);
const handleMarkRead = useCallback((_: React.MouseEvent, thread: Thread) => {
if (!accountId) return;
queueMutation(accountId, 'flag', thread.id, { seen: thread.unread_count > 0 });
queueMutation(accountId, 'flag', 'thread', thread.id, { seen: true });
}, [accountId]);
const handleSnooze = useCallback((_: React.MouseEvent, _thread: Thread) => {
@ -74,9 +74,9 @@ export function ThreadList({ folderId }: Props) {
const bulkAction = (op: 'archive' | 'trash' | 'read') => {
if (!accountId) return;
for (const id of checkedThreadIds) {
if (op === 'archive') queueMutation(accountId, 'move', id, { folder_special_use: 'archive' });
else if (op === 'trash') queueMutation(accountId, 'move', id, { folder_special_use: 'trash' });
else if (op === 'read') queueMutation(accountId, 'flag', id, { seen: true });
if (op === 'archive') queueMutation(accountId, 'move', 'thread', id, { folder_special_use: 'archive' });
else if (op === 'trash') queueMutation(accountId, 'move', 'thread', id, { folder_special_use: 'trash' });
else if (op === 'read') queueMutation(accountId, 'flag', 'thread', id, { seen: true });
}
clearChecks();
};

View File

@ -15,6 +15,7 @@ type PendingMutation = {
id: string;
account_id: string;
op: string;
target_kind: 'thread' | 'message' | 'draft';
target_id: string;
payload: unknown;
created_at: string;
@ -37,6 +38,19 @@ class MailDB extends Dexie {
syncState: 'account_id',
pendingMutations: 'id, account_id, created_at',
});
this.version(2)
.stores({
threads: 'id, folder_id, account_id, last_message_at',
messages: 'id, thread_id, account_id, date_sent, folder_id',
folders: 'id, account_id, imap_path',
syncState: 'account_id',
pendingMutations: 'id, account_id, target_kind, created_at',
})
.upgrade((tx) =>
tx.table('pendingMutations').toCollection().modify((mutation) => {
if (!mutation.target_kind) mutation.target_kind = 'thread';
})
);
}
}

View File

@ -1,5 +1,5 @@
import { db } from '@/db/dexie';
import { api } from '@/api/client';
import { api, type MutationTargetKind } from '@/api/client';
import { monotonicFactory } from 'ulid';
import { connectSSE, onSSEEvent } from './sse';
@ -48,6 +48,7 @@ export async function pullChanges(accountId: string) {
mutations: pendingMutations.map((m) => ({
id: m.id,
op: m.op as never,
target_kind: m.target_kind,
target_id: m.target_id,
payload: m.payload as Record<string, unknown>,
})),
@ -91,6 +92,7 @@ export async function pullChanges(accountId: string) {
export async function queueMutation(
accountId: string,
op: string,
targetKind: MutationTargetKind,
targetId: string,
payload: Record<string, unknown>
) {
@ -98,6 +100,7 @@ export async function queueMutation(
id: ulid(),
account_id: accountId,
op,
target_kind: targetKind,
target_id: targetId,
payload,
created_at: new Date().toISOString(),