/** * Store middleware — composable interceptors for NoydbStore. * * ```ts * const resilient = wrapStore( * dynamo({ table: 'myapp' }), * withRetry({ maxRetries: 3 }), * withLogging({ level: 'debug' }), * withCache({ ttlMs: 60_000 }), * ) * ``` * * Each middleware is `(next: NoydbStore) => NoydbStore`. They compose * left-to-right: first middleware is outermost (processes requests first, * responses last). * * @module */ import type { NoydbStore } from '../kernel/types.js'; /** * A store middleware function. * * Takes the next store in the chain and returns a wrapped store. Middlewares * compose left-to-right via `wrapStore()`: the first argument is outermost * (first to intercept requests, last to process responses). * * ```ts * const mw: StoreMiddleware = (next) => ({ * ...next, * async get(vault, collection, id) { * console.log('get', id) * return next.get(vault, collection, id) * }, * }) * ``` */ export type StoreMiddleware = (next: NoydbStore) => NoydbStore; /** * Wrap a store with one or more middlewares. Middlewares compose left-to-right. */ export declare function wrapStore(store: NoydbStore, ...middlewares: StoreMiddleware[]): NoydbStore; /** Options for `withRetry()`. */ export interface RetryOptions { /** Maximum retry attempts. Default: 3. */ maxRetries?: number; /** Base backoff delay in ms. Default: 500. */ backoffMs?: number; /** Jitter factor (0-1). Adds random delay up to `backoffMs * jitter`. Default: 0.3. */ jitter?: number; /** Only retry on these error codes. Default: retry all errors. */ retryOn?: string[]; } /** * Middleware that retries failed store operations with exponential backoff * and optional jitter. Useful for transient network errors on DynamoDB/S3. * * ```ts * wrapStore(dynamo({ table: 'myapp' }), withRetry({ maxRetries: 5, retryOn: ['NETWORK_ERROR'] })) * ``` */ export declare function withRetry(opts?: RetryOptions): StoreMiddleware; /** Log level for `withLogging()`. Maps to standard console method names. */ export type LogLevel = 'debug' | 'info' | 'warn' | 'error'; /** Options for `withLogging()`. */ export interface LoggingOptions { /** Minimum log level. Default: 'info'. */ level?: LogLevel; /** Custom logger. Default: console. */ logger?: { debug(msg: string, ...args: unknown[]): void; info(msg: string, ...args: unknown[]): void; warn(msg: string, ...args: unknown[]): void; error(msg: string, ...args: unknown[]): void; }; /** Log the data payload (envelope contents). Default: false (privacy). */ logData?: boolean; } /** * Middleware that logs every store operation with its method name, arguments, * and elapsed duration. Privacy-safe by default: envelope payloads are not * logged unless `logData: true` is set. */ export declare function withLogging(opts?: LoggingOptions): StoreMiddleware; /** * Data emitted to `MetricsOptions.onOperation` after every store call. * * Carries method name, vault/collection/id context, elapsed duration, * and success/failure status. Wire this into your metrics pipeline * (DataDog, Prometheus, CloudWatch) to get per-operation latency histograms. */ export interface StoreOperation { method: 'get' | 'put' | 'delete' | 'list' | 'loadAll' | 'saveAll'; vault: string; collection?: string; id?: string; durationMs: number; success: boolean; error?: Error; } /** Options for `withMetrics()`. */ export interface MetricsOptions { /** Called after every store operation. */ onOperation: (op: StoreOperation) => void; } /** * Middleware that calls `onOperation` after every store method with timing * and success/failure data. Designed for low-overhead integration with * metrics systems — the callback is synchronous and fire-and-forget. */ export declare function withMetrics(opts: MetricsOptions): StoreMiddleware; /** * Options for `withCircuitBreaker()`. * * The circuit breaker moves through three states: * - `closed`: normal operation. * - `open`: store is failing; all calls return fallback values immediately. * - `half-open`: one probe call after `resetTimeoutMs` — success closes, failure re-opens. */ export interface CircuitBreakerOptions { /** Number of consecutive failures before opening the circuit. Default: 5. */ failureThreshold?: number; /** Time in ms before attempting to half-open the circuit. Default: 30_000. */ resetTimeoutMs?: number; /** Called when the circuit opens (store becomes unavailable). */ onOpen?: () => void; /** Called when the circuit closes (store recovers). */ onClose?: () => void; } /** * Middleware that implements the circuit-breaker pattern. * * When the wrapped store fails `failureThreshold` consecutive times, the * circuit opens: subsequent calls return safe fallback values (`null`, `[]`, * `{}`) without hitting the store. After `resetTimeoutMs` the circuit * half-opens and allows one probe — success closes the circuit, failure * keeps it open. Pair with `withRetry` to handle transient errors before * they trip the circuit. */ export declare function withCircuitBreaker(opts?: CircuitBreakerOptions): StoreMiddleware; /** * Options for `withCache()`. * * The cache is a read-through LRU that caches individual record fetches * (`get`). Writes (`put`, `delete`) invalidate the relevant cache entry * immediately. `list`, `loadAll`, and `saveAll` bypass the cache. * * Named `StoreCacheOptions` to distinguish from `CacheOptions` in * `@noy-db/hub/collection`, which controls the in-memory decrypted-record LRU. */ export interface StoreCacheOptions { /** Maximum cached entries. Default: 500. */ maxEntries?: number; /** Cache TTL in ms. Default: 60_000 (1 minute). 0 = no expiry. */ ttlMs?: number; } /** * Middleware that adds a read-through LRU cache for `get()` calls. * * Reduces latency for frequently-read records (e.g. lookup tables, user * profiles) by serving repeat reads from memory. Because NOYDB records are * encrypted at rest, caching envelopes is safe — the cache holds ciphertext, * not plaintext. For write-heavy workloads, the cache provides little benefit * and should be omitted to avoid the invalidation overhead. */ export declare function withCache(opts?: StoreCacheOptions): StoreMiddleware; export interface HealthCheckOptions { /** Ping interval in ms. Default: 30_000. */ checkIntervalMs?: number; /** Suspend after N consecutive ping failures. Default: 3. */ suspendAfterFailures?: number; /** Resume after N consecutive ping successes. Default: 1. */ resumeAfterSuccess?: number; /** Called when the store is auto-suspended. */ onSuspend?: () => void; /** Called when the store is auto-resumed. */ onResume?: () => void; /** * Custom health check. Default: calls `store.ping()` if available, * otherwise attempts a `list()` on a sentinel collection. */ check?: () => Promise; } /** * Auto-suspends a store when health checks fail, auto-resumes when they recover. * * When suspended, `get` returns null, `put`/`delete` are no-ops, `list` returns []. * This is identical to the `NullStore` behavior from `routeStore.suspend()`. */ export declare function withHealthCheck(opts?: HealthCheckOptions): StoreMiddleware;