import { type DB } from '../db/client.js'; import { type BlockedNumberRow } from '../db/schema/blocklist.js'; export interface BlocklistOptions { /** * D1 binding OR a pre-built Drizzle client (any schema). v0.7+: * normalized internally. */ db: D1Database | DB; /** Cache TTL in ms. Default 5 minutes. Set 0 to disable caching. */ cacheTtlMs?: number; /** * v0.8: tenant-scope every block / check / list / cleanup operation. * Single-tenant deployments leave this unset (or null) — the IS NULL * filter preserves their behavior bit-for-bit. Multi-tenant deployments * pass the tenantId so a block at tenant A doesn't bleed into checks * at tenant B. Requires migration 017_blocklist_tenant.sql. */ tenantId?: string | null; } export interface BlockArgs { whatsapp: string; reason: string; blockedBy?: string | null; /** ISO datetime ("YYYY-MM-DD HH:MM:SS"). NULL = permanent. */ expiresAt?: string | null; notes?: string | null; } export interface ListBlockedOptions { /** When true (default), excludes expired rows. */ activeOnly?: boolean; limit?: number; } export declare class Blocklist { readonly db: DB; readonly cacheTtlMs: number; /** * Effective tenant scope for this Blocklist. Single-tenant deployments * see `''` (empty string) — matches the migration's NOT NULL default. * Multi-tenant deployments see the configured tenantId. */ readonly tenantId: string; private readonly cache; constructor({ db, cacheTtlMs, tenantId }: BlocklistOptions); /** * SQL fragment that scopes a query to this Blocklist's tenantId. * Centralized so isBlocked / list / cleanup stay consistent. */ private tenantScopeFilter; /** * Cache key includes the tenantId so blocks at tenant A don't mask * checks at tenant B sharing the same isolate. */ private cacheKey; /** * @returns true if `whatsapp` has an active (non-expired) block. * * Cache-first. On D1 error, returns false (fail-open). */ isBlocked(whatsapp: string): Promise; /** * Insert or overwrite a block. Latest reason / blockedBy / expiresAt / notes win; * `blocked_at` is refreshed on overwrite (matches bibliafala's behavior). */ block({ whatsapp, reason, blockedBy, expiresAt, notes }: BlockArgs): Promise; /** * Remove a block. Idempotent — returns true if a row was deleted, false otherwise. */ unblock(whatsapp: string): Promise; /** * List blocked numbers for admin UI / triage. Defaults to active only. */ listBlocked({ activeOnly, limit }?: ListBlockedOptions): Promise; /** * Sweep expired rows. Optional — `isBlocked()` ignores them automatically. * Returns count of deleted rows. */ cleanup(): Promise; /** Drops every cached decision. Useful in tests or after a bulk admin op. */ clearCache(): void; private cacheGet; private cachePut; private cacheBust; } //# sourceMappingURL=blocklist.d.ts.map