import { DateTime } from 'luxon'; import * as msg from './state/remote/messages.svelte.js'; import * as api from './apiServer.js'; import * as md from './markdown.js'; class SendToConversation { constructor(conversation, body) { this.conversation = conversation; this.body = body; this.at = DateTime.now(); } toSkeleton(sender) { return { id: null, at: this.at, conversation: this.conversation, sender, body: this.body, }; } async send() { return await api.retry(() => api.sendToConversation(this.conversation, this.body)); } } class DeleteMessage { constructor(messageId) { this.messageId = messageId; } async send() { return await api.retry(() => api.deleteMessage(this.messageId)); } } class CreateConversation { constructor(name) { this.name = name; } async send() { return await api.retry(() => api.createConversation(this.name)); } } export class Outbox { pending = $state([]); messages = $derived(this.pending.filter((operation) => operation instanceof SendToConversation)); deleted = $derived(this.pending.filter((operation) => operation instanceof DeleteMessage)); static empty() { return new Outbox([]); } constructor(pending) { this.pending = pending; } enqueue(operation) { this.pending.push(operation); this.start(); } createConversation(name) { this.enqueue(new CreateConversation(name)); } sendToConversation(conversationId, body) { this.enqueue(new SendToConversation(conversationId, body)); } deleteMessage(messageId) { this.enqueue(new DeleteMessage(messageId)); } start() { if (this.sending) { return; } // This is a promise transform primarily to keep the management of `this.sending` in one place, // rather than spreading it across multiple methods. this.sending = this.drain().finally(() => { this.sending = null; // If we encounter an exception processing the pending queue, it may have an operation left // in it. If so, start over. The exception will still propagate out (though since nothing // ever awaits the promise from this.sending, it'll ultimately leak out to the browser // anyways). if (this.pending.length > 0) { this.start(); } }); } async drain() { while (this.pending.length > 0) { const operation = this.pending[0]; try { await operation.send(); } finally { this.pending.shift(); } } } }