/** * HTTP client for the Attestry batch ingest endpoint. * * Contract: * - **Never throws** to the caller. The exporter runs in the * customer's request path; an uncaught throw here would surface as * an unhandled rejection on Node and crash long-running workers. * Every failure mode resolves with `{ ok: false, ... }` and is * surfaced via the configured logger. * - **Bounded retries.** Three attempts on transient errors (network, * 5xx, 429) with capped exponential backoff (250ms, 750ms, 2_250ms) * + 50ms full jitter. Gives up after the third attempt rather than * spinning indefinitely against a permanently-broken server. * - **Honors `Retry-After`.** On 429 / 503 with a Retry-After header * we use the server's value (capped at 60s) instead of our own * backoff. Hammering a server that just told us to slow down is * the canonical way to escalate from "rate-limited" to "blocked". * - **No retries on 4xx (other than 429).** A 400/422 means the body * is malformed; replaying it cannot succeed. We log and drop. * - **AbortController timeout.** The native `fetch` has no built-in * request timeout; without one a hung server holds the queue * forever. Default 10s. * - **http:// guard.** Construction warns if `apiUrl` is HTTP and * not localhost โ€” the API key would otherwise traverse the network * in cleartext. We don't *reject* HTTP because dev environments * legitimately use it; we surface the warning so it gets caught * in code review. * * Why fetch instead of a richer client (axios/got)? `LIBRARY_LOCK.md` * pins this package to native fetch โ€” adding axios would be a new * top-level dep across the kernel. fetch (Node 18+) is good enough * for a single-endpoint POST with a JSON body. */ import type { AttestryExporterConfig, DecisionInput, ExporterLogger } from "./types.js"; interface BatchSendResult { ok: boolean; status: number; /** Number of records the server accepted (best-effort, may be 0). */ inserted: number; /** Number of records the server rejected. */ failed: number; /** Error class for logging, never propagated. */ error?: string; } /** Wrap a logger call so a misbehaved customer logger that throws can't * bubble up and crash the customer's process. The exporter contract * is "never throws" โ€” that contract is only as strong as our weakest * side-effect. */ export declare function safeLog(logger: ExporterLogger, level: "warn" | "error", message: string, meta?: Record): void; export declare class AttestryClient { private readonly config; private readonly logger; private readonly fetchTimeoutMs; constructor(config: AttestryExporterConfig); recordDecisionsBatch(decisions: DecisionInput[]): Promise; private sendOnce; private backoff; } /** * Parse an HTTP `Retry-After` header value. Per RFC 7231 ยง7.1.3 the * value is either an HTTP date or a delta-seconds integer. Returns * milliseconds-from-now, or `null` if unparseable / negative. */ export declare function parseRetryAfter(raw: string | null): number | null; /** True if URL is http:// and not pointing at localhost / 127.0.0.1 / ::1. */ export declare function isInsecureUrl(rawUrl: string): boolean; /** Strip query strings and userinfo from a URL before logging. */ export declare function redactUrl(rawUrl: string): string; export declare const __test__: { RETRYABLE_STATUS: Set; DEFAULT_RETRIES: number; DEFAULT_TIMEOUT_MS: number; RETRY_AFTER_CAP_MS: number; }; export {}; //# sourceMappingURL=client.d.ts.map