/** * Durable post-commit reactions. Planning runs inside the authoritative push * transaction and may only produce bounded data. Delivery runs later under a * lease and is at-least-once, so handlers receive the stable idempotency key. */ import type { SyncularServerEvents } from './events.js'; import type { DurableJsonValue, NewReaction, ServerStorage } from './storage.js'; import type { CommitValidationReader, ValidateCommitOperation } from './validate.js'; export declare const MAX_REACTIONS_PER_COMMIT = 100; export declare const MAX_REACTION_PAYLOAD_BYTES: number; export declare const MAX_REACTION_FAILURE_DETAILS_BYTES: number; export declare const DEFAULT_REACTION_MAX_ATTEMPTS = 10; export declare const DEFAULT_REACTION_LEASE_MS = 30000; export declare const DEFAULT_REACTION_INITIAL_BACKOFF_MS = 1000; export declare const DEFAULT_REACTION_MAX_BACKOFF_MS: number; export interface ReactionRetentionPolicy { /** Keep completed rows for at least this duration (default 30 days). */ readonly completedRetentionMs: number; /** Keep dead letters for at least this duration (default 90 days). */ readonly deadLetterRetentionMs: number; /** Maximum terminal rows removed by one pass (default 1000). */ readonly batchSize: number; } export declare const DEFAULT_REACTION_RETENTION: ReactionRetentionPolicy; export type ReactionTypeMap = Readonly>; export interface ReactionPlan { /** Unique within the source client commit. */ readonly key: string; readonly type: string; readonly version: number; readonly payload: DurableJsonValue; readonly maxAttempts?: number; } export type PlannedReaction = { [Type in keyof Reactions & string]: { /** Unique within the source client commit. */ readonly key: string; readonly type: Type; readonly version: number; readonly payload: Reactions[Type]; readonly maxAttempts?: number; }; }[keyof Reactions & string]; export interface ReactionPlannerInput { readonly clientId: string; readonly clientCommitId: string; readonly actorId: string; readonly partition: string; readonly operations: readonly ValidateCommitOperation[]; /** Candidate-state reads from the still-open authoritative transaction. */ readonly read: CommitValidationReader; } /** * A pure planner over an accepted candidate commit. It may read candidate * state and return durable data. It must not execute user-visible side effects. */ export type ReactionPlanner = (input: ReactionPlannerInput) => readonly PlannedReaction[] | Promise[]>; /** Erased planner shape stored on non-generic server configuration. */ export type AnyReactionPlanner = (input: ReactionPlannerInput) => readonly ReactionPlan[] | Promise; export interface ReactionHandlerInput { readonly partition: string; readonly idempotencyKey: string; readonly type: string; readonly version: number; readonly payload: Payload; readonly attempt: number; readonly maxAttempts: number; readonly sourceClientId: string; readonly sourceClientCommitId: string; readonly sourceCommitSeq: number; /** Extend a long-running handler's lease; throws after ownership is lost. */ readonly extendLease: () => Promise; } export type ReactionHandler = (input: ReactionHandlerInput) => void | Promise; export type ReactionHandlers = { readonly [Type in keyof Reactions & string]: ReactionHandler; }; /** Stable handler idempotency key for one planned item in a client commit. */ export declare function reactionIdempotencyKey(partition: string, clientId: string, clientCommitId: string, plannerKey: string): string; export interface PreparedReaction { readonly idempotencyKey: string; readonly type: string; readonly version: number; readonly payload: DurableJsonValue; readonly maxAttempts: number; } /** Internal push seam, exported for focused planner tests and custom hosts. */ export declare function prepareReactions(planner: AnyReactionPlanner, input: ReactionPlannerInput): Promise; declare class ReactionDeliveryError extends Error { readonly code: string; readonly details?: { readonly [key: string]: DurableJsonValue; }; constructor(name: string, code: string, details?: { readonly [key: string]: DurableJsonValue; }); } /** A handler failure that should be retried until its attempt limit. */ export declare class RetryableReactionError extends ReactionDeliveryError { constructor(code: string, details?: { readonly [key: string]: DurableJsonValue; }); } /** A handler failure that should be dead-lettered immediately. */ export declare class PermanentReactionError extends ReactionDeliveryError { constructor(code: string, details?: { readonly [key: string]: DurableJsonValue; }); } export interface ReactionRunnerOptions { readonly storage: ServerStorage; readonly partition: string; readonly workerId: string; readonly handlers: ReactionHandlers; readonly events?: SyncularServerEvents; readonly clock?: () => number; readonly leaseDurationMs?: number; readonly batchSize?: number; readonly initialBackoffMs?: number; readonly maxBackoffMs?: number; } export interface ReactionRunResult { readonly claimed: number; readonly completed: number; readonly retried: number; readonly deadLettered: number; /** Lease ownership changed before this worker could persist its outcome. */ readonly lostLeases: number; } export interface PruneReactionsOptions { readonly storage: ServerStorage; readonly partition: string; readonly nowMs: number; readonly retention?: Partial; readonly events?: SyncularServerEvents; } export interface ReactionPruneResult { readonly completedBeforeMs: number; readonly deadLetterBeforeMs: number; readonly removedCompleted: number; readonly removedDeadLetter: number; /** True when the bounded pass filled its batch and another pass may help. */ readonly mayHaveMore: boolean; } /** Host-driven worker. Call `runOnce` from the host scheduler or queue wake. */ export declare class ReactionRunner { #private; constructor(options: ReactionRunnerOptions); runOnce(): Promise; } /** Explicit operator action for a dead-lettered reaction. */ export declare function retryDeadLetterReaction(options: { readonly storage: ServerStorage; readonly partition: string; readonly idempotencyKey: string; readonly nowMs?: number; }): Promise; /** Delete one bounded batch of aged completed and dead-lettered rows. */ export declare function pruneReactions(options: PruneReactionsOptions): Promise; /** Helper used by the push path after commit sequence allocation. */ export declare function toNewReactions(prepared: readonly PreparedReaction[], source: { readonly clientId: string; readonly clientCommitId: string; readonly commitSeq: number; readonly createdAtMs: number; }): NewReaction[]; export {};