import type { DB } from '../db/client.js'; import type { InboundEnvelope } from '../types.js'; export interface D1QueueOptions { db: DB; debounceSeconds?: number; maxAttempts?: number; staleMinutes?: number; maxPerInvocation?: number; retryDelaySeconds?: number; cleanupAfterDays?: number; /** * Multi-tenant scoping (v0.6+). When set, `enqueue` writes the tenantId * to the row and `claimBatch` only picks rows for THIS tenant. Single-tenant * deployments leave this unset (or null) — the IS NULL filter preserves * pre-v0.6 behavior bit-for-bit. Set this to the same string `Agent` uses * for `tenantId` so the registry's per-tenant Agents stay isolated. */ tenantId?: string | null; } export interface QueueRow { id: number; message_id: string | null; whatsapp: string; payload: string; status: 'pending' | 'processing' | 'done' | 'failed'; attempts: number; scheduled_at: string; created_at: string; started_at: string | null; completed_at: string | null; error_message: string | null; } export interface BatchInfo { envelope: InboundEnvelope; rows: QueueRow[]; combinedText: string; whatsapp: string; } export type BatchHandler = (info: BatchInfo) => Promise; export declare class D1CoalesceQueue { readonly db: DB; readonly debounceSeconds: number; readonly maxAttempts: number; readonly staleMinutes: number; readonly maxPerInvocation: number; readonly retryDelaySeconds: number; readonly cleanupAfterDays: number; readonly tenantId: string | null; constructor({ db, debounceSeconds, maxAttempts, staleMinutes, maxPerInvocation, retryDelaySeconds, cleanupAfterDays, tenantId, }: D1QueueOptions); enqueue(envelope: InboundEnvelope): Promise; /** * SQL fragment that scopes a query to this queue's tenantId. Single-tenant * deployments (tenantId === null) filter by `tenant_id IS NULL`; tenant * deployments filter by `tenant_id = ?`. Centralized so enqueue, claim, * recover, and cleanup stay consistent. */ private tenantScopeFilter; /** * Process all pending users' batches. * * `opts.parallel` (v0.14): fan out claim+dispatch across N users at a * time via Promise.allSettled. Chunks run sequentially so the Worker's * subrequest budget doesn't get tripped on a large backlog. Default 1 * preserves pre-v0.14 sequential behavior exactly. * * The webhook path should now use `processBatchForUser(whatsapp, ...)` * (v0.14) directly, keeping each user's dispatch in its own Worker * invocation. This method is for the cron path that drains stragglers. */ processAll(handler: BatchHandler, opts?: { parallel?: number; }): Promise; processNextBatch(handler: BatchHandler): Promise; /** * v0.14: process ONLY the given user's pending batch. * * Right for the webhook path: each inbound Meta webhook calls this with * the sender's whatsapp, isolating that user's dispatch in its own * Worker invocation with its own subrequest / wall-clock budget. A slow * free-tier user's classifier cascade no longer blocks other subscribers * waiting behind them in the queue. * * Documented same-user race (rare in practice, no data corruption): two * inbound webhooks for the same user within the debounce window can each * claim a disjoint set of rows and dispatch two replies. The 3s debounce * makes this unlikely — if it becomes a problem, add a per-user KV lock. */ processBatchForUser(whatsapp: string, handler: BatchHandler): Promise; /** * v0.14: distinct whatsapp numbers with pending rows due now (this tenant only). * Public so a cron can fan out `processBatchForUser` calls itself. */ listPendingUsers(): Promise; /** * v0.14: atomic UPDATE...RETURNING scoped to one user. Public so the * cron parallel fan-out and tests can compose against a known user. */ claimBatchForUser(whatsapp: string): Promise; claimBatch(): Promise; completeBatch(ids: number[]): Promise; failBatch(ids: number[], errorMessage: string): Promise; recoverStale(): Promise; cleanup(): Promise; } /** * Combine the text bodies of multiple buffered messages into one prompt-friendly string. * Audio / interactive messages contribute placeholders/titles so the LLM has context. */ export declare function combineText(rows: QueueRow[]): string; //# sourceMappingURL=d1_coalesce_queue.d.ts.map