Skip to content
← 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 · TypeScript
import { 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 }