← Public packages
@kody/mailroom
Offline-first PWA for your Kody mailbox: sent and received threads, copy-prompt for agents, no send.
src/thread.ts
56 lines · 1.7 KB · TypeScriptimport { getMailMessage, listMailbox } from './mail.ts'
import { messageWhen, threadKey } from './threads.ts'
import type { MailMessage } from './types.ts'
async function mapPool<T, R>(
items: Array<T>,
limit: number,
fn: (item: T) => Promise<R>,
): Promise<Array<R>> {
const out: Array<R> = []
let index = 0
async function worker() {
while (index < items.length) {
const current = index
index += 1
out[current] = await fn(items[current] as T)
}
}
const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker())
await Promise.all(workers)
return out
}
/**
* Load every stored message in a mailbox thread, oldest first, with bodies.
*
* @example
* import getThread from 'kody:@kody/mailroom/thread'
*
* const thread = await getThread({ threadId: '…' })
*/
export default async function getThread(input: {
threadId: string
}): Promise<{
id: string
subject: string
messages: Array<MailMessage>
} | null> {
const threadId = String(input.threadId || '').trim()
if (!threadId) throw new Error('threadId is required.')
const listed = await listMailbox(100)
const members = listed.filter((message) => threadKey(message) === threadId)
const ids = members.length ? members.map((message) => message.id) : [threadId]
const loaded = await mapPool(ids, 4, (id) => getMailMessage(id))
const messages = loaded.filter((message): message is MailMessage => Boolean(message))
if (!messages.length) return null
messages.sort((a, b) => messageWhen(a).localeCompare(messageWhen(b)))
const latest = messages[messages.length - 1]
return {
id: threadId,
subject: String(latest?.subject || messages[0]?.subject || '').trim() || '(no subject)',
messages,
}
}
export { getThread }