import type { DrainInputQueueOptions } from "./serverScripts"; import type { InputQueueItem } from "./serverScripts"; export const MAX_INPUT_QUEUE_LENGTH = 100; export type StoredInputQueueEntry = { id: string; queue: string; payload: unknown; enqueuedAt: number; }; export type InputQueueStore = Map>; type EnqueueOptions = { store: InputQueueStore; connectionId: string; queue: string; payload: unknown; onQueueActivity?: (queueName: string) => void; }; export function enqueueInput({ store, connectionId, queue, payload, onQueueActivity, }: EnqueueOptions) { const queueName = typeof queue === "string" && queue.trim().length > 0 ? queue.trim() : "default"; const entry: StoredInputQueueEntry = { id: crypto.randomUUID(), queue: queueName, payload, enqueuedAt: Date.now(), }; const connectionQueues = store.get(connectionId) ?? new Map(); const queueEntries = connectionQueues.get(queueName) ?? []; queueEntries.push(entry); if (queueEntries.length > MAX_INPUT_QUEUE_LENGTH) { queueEntries.splice(0, queueEntries.length - MAX_INPUT_QUEUE_LENGTH); } connectionQueues.set(queueName, queueEntries); store.set(connectionId, connectionQueues); onQueueActivity?.(queueName); } export function drainInputQueue( store: InputQueueStore, options?: DrainInputQueueOptions ): InputQueueItem[] { const { connectionId, queue, maxEntries } = options ?? {}; const connectionIds = connectionId ? [connectionId] : Array.from(store.keys()); const limit = maxEntries !== undefined ? Math.max(0, Math.floor(maxEntries)) : undefined; let remaining = limit ?? Number.POSITIVE_INFINITY; const drained: InputQueueItem[] = []; for (const id of connectionIds) { if (remaining <= 0) break; const perConnection = store.get(id); if (!perConnection) continue; const queueNames = queue ? [queue] : Array.from(perConnection.keys()); for (const queueName of queueNames) { if (remaining <= 0) break; const entries = perConnection.get(queueName); if (!entries?.length) continue; const take = Math.min(entries.length, remaining); const removed = entries.splice(0, take); drained.push( ...removed.map((entry) => ({ ...entry, connectionId: id, })) ); remaining -= take; if (entries.length === 0) { perConnection.delete(queueName); } else { perConnection.set(queueName, entries); } } if (perConnection.size === 0) { store.delete(id); } } return drained; } export function clearConnectionQueue(store: InputQueueStore, connectionId: string) { store.delete(connectionId); }