import { t as IamEngineTypes } from "../../engine.types-CKIkd4ez.js"; //#region src/invalidators/redis/index.d.ts /** Redis invalidator integration types. Type-only namespace - zero bundle cost. */ declare namespace IamRedisInvalidator { /** * Describes the minimum pub/sub surface needed by the Redis invalidator. * * Both ioredis and node-redis v4+ implement this shape; call sites stay * intentionally narrow to avoid pulling in either as a hard dependency. * Pass two clients - Redis requires a separate connection per subscriber. */ interface IPubSubLike { /** * Publishes the JSON payload on the given channel for every local admin * mutation. Synchronous return is fine; the caller does not await. * * @param channel - Names the channel to publish on. * @param message - Serialised JSON payload to broadcast. * @returns Whatever the underlying client returns; ignored by the invalidator. */ publish(channel: string, message: string): unknown; /** * Subscribes the handler to incoming messages on the channel. The factory * calls this once with the channel name and a raw-message handler. * * @param channel - Names the channel to subscribe to. * @param handler - Receives each raw message string from the channel. * @returns Void synchronously or a promise the caller may await on startup. */ subscribe(channel: string, handler: (message: string) => void): void | Promise; /** * Tears down the subscription. Engine calls this on `dispose()`. Optional - * passing a no-op stub is fine if your client manages connection lifecycle * out of band. * * @param channel - Names the channel to detach from. * @returns Void synchronously or a promise. */ unsubscribe?(channel: string): void | Promise; } /** Configures {@link createIamRedisInvalidator}. */ interface IConfig { /** Redis pub/sub adapter implementing {@link IPubSubLike}. */ client: IPubSubLike; /** * Channel name. Every engine subscribing to the same channel shares an * invalidate broadcast group. Defaults to `'duck-iam:invalidate'`. Use * {@link tenantId} for the common multi-tenant case instead of building * the channel name yourself. */ channel?: string; /** * CAVEAT-1: convenience helper for multi-tenant deployments. When set, * the effective channel becomes `duck-iam:invalidate:tenant:${tenantId}` * (or `${channel}:tenant:${tenantId}` if `channel` is also given). This * guarantees tenant isolation on a shared Redis instance - tenant A's * revoke cannot wipe tenant B's cache. * * Validates against `/^[A-Za-z0-9_-]{1,64}$/` to keep channel names * pub/sub-safe and prevent injection via attacker-controlled tenant * identifiers. * * @example * ```ts * createIamRedisInvalidator({ client, secret, tenantId: req.tenantSlug }) * ``` */ tenantId?: string; /** * Shared HMAC secret. When set, every published envelope is signed with * `HMAC-SHA256(secret, canonicalJSON(payload))` and incoming envelopes * without a verifying signature are dropped (silent - one `console.warn` * per channel on the first rejection). When `null` or omitted (default), * the invalidator falls back to legacy unsigned envelopes and warns once * at construction. Any party with PUBLISH rights to the channel can wipe * caches in that mode; set a secret in production. */ secret?: string | null; /** * Invoked when the underlying `client.publish(...)` throws. The publish * failure is non-fatal for the local engine (it already applied the * invalidation), but cross-instance invalidations are lost - wire this * to your alerting pipeline so a long-lived Redis outage does not * silently desync caches across nodes. */ onPublishError?: (err: Error, channel: string) => void; } } /** * Creates a cross-instance cache-invalidation broadcaster backed by Redis pub/sub. * * Delivery is at-least-once: every engine's local invalidate methods are * idempotent so re-applying the same event is safe. Filters self-published * events via an instance UUID embedded in the payload - without this guard * every local invalidate would echo back through the subscriber and re-clear * caches we just rebuilt. * * When `init.secret` is set, envelopes are signed with HMAC-SHA256 and * verified on receive with a 30-second replay window keyed off `payload.ts`. * Without a secret the invalidator falls back to legacy unsigned envelopes * (logged once); anyone with PUBLISH rights on the channel can then wipe * caches -> set a secret in production. * * @template TRole - Role identifier union the engine is parameterised over. * @param config - Supplies the client and optional channel; see {@link IamRedisInvalidator.IConfig}. * @returns An {@link IamEngineTypes.IInvalidator} bound to the configured channel. * @example * ```ts * import { createIamRedisInvalidator } from '@gentleduck/iam/invalidators/redis' * * const engine = new IamEngine({ * adapter, * invalidator: createIamRedisInvalidator({ * client: redisPubSub, * secret: process.env.IAM_INVALIDATE_SECRET, * }), * }) * ``` */ declare function createIamRedisInvalidator(config: IamRedisInvalidator.IConfig): IamEngineTypes.IInvalidator; //#endregion export { IamRedisInvalidator, createIamRedisInvalidator }; //# sourceMappingURL=index.d.ts.map