import type { BrandedString, Brand } from '../utils/brand.ts';
import type { Logger } from '../log/index.ts';
import type { JSONSerializable } from '../utils/json.ts';
import type { KeetaAnchorQueueRunOptions } from './common.js';
export type KeetaAnchorQueueRequest<QueueRequest> = QueueRequest;
export type KeetaAnchorQueueRequestID = BrandedString<'KeetaAnchorQueueID'>;
export type KeetaAnchorQueueWorkerID = Brand<number, 'KeetaAnchorQueueWorkerID'>;
export type KeetaAnchorQueueStatus = 'pending' | 'processing' | 'completed' | 'failed_temporarily' | 'failed_permanently' | 'stuck' | 'aborted' | 'moved' | '@internal';
declare const keetaAnchorPipeableQueueStatuses: readonly ["completed", "failed_permanently"];
export type KeetaAnchorPipeableQueueStatus = Extract<KeetaAnchorQueueStatus, typeof keetaAnchorPipeableQueueStatuses[number]>;
export type KeetaAnchorQueueEntry<QueueRequest, QueueResult> = {
    /**
     * The Job ID
     */
    id: KeetaAnchorQueueRequestID;
    /**
     * Idempotent IDs from a previous stage
     */
    idempotentKeys?: Set<KeetaAnchorQueueRequestID> | undefined;
    request: KeetaAnchorQueueRequest<QueueRequest>;
    output: QueueResult | null;
    lastError: string | null;
    status: KeetaAnchorQueueStatus;
    created: Date;
    updated: Date;
    worker: KeetaAnchorQueueWorkerID | null;
    failures: number;
};
/**
 * Extra information to provide to a request when adding an entry to the queue
 */
export type KeetaAnchorQueueEntryExtra = {
    [key in 'idempotentKeys' | 'id' | 'status']?: (key extends 'id' ? KeetaAnchorQueueRequestID | string : KeetaAnchorQueueEntry<never, never>[key]) | undefined;
};
export type KeetaAnchorQueueFilter = {
    /**
     * Only return entries with this status
     */
    status?: KeetaAnchorQueueStatus;
    /**
     * Only return entries last updated before this date
     */
    updatedBefore?: Date;
    /**
     * Limit the number of entries returned
     */
    limit?: number;
};
export type KeetaAnchorQueueCommonOptions = {
    logger?: Logger | undefined;
    id?: string | undefined;
};
export type KeetaAnchorQueueRunnerOptions = KeetaAnchorQueueCommonOptions & {
    /**
     * If specified, then multiple workers can be used to process this queue
     * in parallel by splitting the work among the workers.
     *
     * By default, only a single worker will process the queue (count=1, id=0)
     */
    workers?: {
        count: number;
        id: number;
    } | undefined;
};
export type KeetaAnchorQueueStorageOptions = KeetaAnchorQueueCommonOptions & {
    path?: string[] | undefined;
};
export type KeetaAnchorQueueEntryAncillaryData<QueueResult> = {
    /**
     * The previous status of the entry -- if the entry is not currently in this status,
     * the status update will fail
     */
    oldStatus?: KeetaAnchorQueueStatus | undefined;
    /**
     * The worker ID performing the status update
     */
    by?: KeetaAnchorQueueWorkerID | undefined;
    /**
     * The output data to store with the entry
     */
    output?: QueueResult | null | undefined;
    /**
     * An error message to store with the entry
     */
    error?: string | undefined;
};
export type KeetaAnchorQueueStorageDriverConstructor<QueueRequest extends JSONSerializable, QueueResult extends JSONSerializable> = new (options?: KeetaAnchorQueueStorageOptions) => KeetaAnchorQueueStorageDriver<QueueRequest, QueueResult>;
export interface KeetaAnchorQueueStorageDriver<QueueRequest extends JSONSerializable, QueueResult extends JSONSerializable> {
    /**
     * An ID for this instance of the storage driver
     */
    readonly id: string;
    /**
     * The name of the storage driver
     */
    readonly name: string;
    /**
     * The partition ID for this instance of the storage driver
     *
     * This is used to divide a single storage backend into multiple
     * independent queues.
     *
     * The root partition is an empty array, and each element is
     * a hierarchical partition name.
     */
    readonly path: string[];
    /**
     * Enqueue an item to be processed by the queue
     *
     * It will be inserted into the queue as a 'pending' entry
     *
     * @param request The request to enqueue
     * @param id Optional ID to use for the entry -- if not provided, a new
     *           ID will be generated.  If the ID is already in use then
     *           nothing will be added.
     * @returns The ID for the newly created pending entry
     */
    add: (request: KeetaAnchorQueueRequest<QueueRequest>, info?: KeetaAnchorQueueEntryExtra) => Promise<KeetaAnchorQueueRequestID>;
    /**
     * Update the status of an entry in the queue
     *
     * If the status is "failed_temporarily", the failure count will be incremented
     *
     * @param id The entry ID to update
     * @param status The new status of the entry
     * @param ancillary Optional ancillary data for the status update
     * @returns void
     */
    setStatus: (id: KeetaAnchorQueueRequestID, status: KeetaAnchorQueueStatus, ancillary?: KeetaAnchorQueueEntryAncillaryData<QueueResult>) => Promise<void>;
    /**
     * Get entries from storage with an optional filter
     *
     * @param filter The filter to apply (optional)
     * @returns An array of entries matching the criteria
     */
    query(filter?: KeetaAnchorQueueFilter): Promise<KeetaAnchorQueueEntry<QueueRequest, QueueResult>[]>;
    /**
     * Get a single entry from storage by ID
     *
     * @param id The ID of the entry to retrieve
     * @returns The entry if found, or null if not found
     */
    get(id: KeetaAnchorQueueRequestID): Promise<KeetaAnchorQueueEntry<QueueRequest, QueueResult> | null>;
    /**
     * Perform maintenance tasks on the storage driver
     * (e.g. cleaning up old entries, etc)
     *
     * @returns void
     */
    maintain?: () => Promise<void>;
    /**
     * Create a partitioned view of the queue
     *
     * @param partitionID The partition ID to use
     * @returns A new storage driver instance for the partition
     */
    partition: (path: string) => Promise<KeetaAnchorQueueStorageDriver<QueueRequest, QueueResult>>;
    /**
     * Close the storage driver and release any resources
     */
    destroy(): Promise<void>;
    [Symbol.asyncDispose](): Promise<void>;
}
/**
 * An in-memory implementation of the KeetaAnchorQueueStorageDriver
 */
export declare class KeetaAnchorQueueStorageDriverMemory<QueueRequest extends JSONSerializable = JSONSerializable, QueueResult extends JSONSerializable = JSONSerializable> implements KeetaAnchorQueueStorageDriver<QueueRequest, QueueResult> {
    protected queueStorage: {
        [path: string]: KeetaAnchorQueueEntry<QueueRequest, QueueResult>[];
    };
    protected readonly logger?: Logger | undefined;
    protected partitionCounter: number;
    private destroyed;
    readonly name: string;
    readonly id: string;
    readonly path: string[];
    constructor(options?: KeetaAnchorQueueStorageOptions);
    protected clone(options?: Partial<KeetaAnchorQueueStorageOptions>): KeetaAnchorQueueStorageDriver<QueueRequest, QueueResult>;
    protected get queue(): KeetaAnchorQueueEntry<QueueRequest, QueueResult>[];
    protected methodLogger(method: string): Logger | undefined;
    private checkDestroyed;
    add(request: KeetaAnchorQueueRequest<QueueRequest>, info?: KeetaAnchorQueueEntryExtra): Promise<KeetaAnchorQueueRequestID>;
    setStatus(id: KeetaAnchorQueueRequestID, status: KeetaAnchorQueueStatus, ancillary?: KeetaAnchorQueueEntryAncillaryData<QueueResult>): Promise<void>;
    get(id: KeetaAnchorQueueRequestID): Promise<KeetaAnchorQueueEntry<QueueRequest, QueueResult> | null>;
    query(filter?: KeetaAnchorQueueFilter): Promise<KeetaAnchorQueueEntry<QueueRequest, QueueResult>[]>;
    partition(path: string): Promise<KeetaAnchorQueueStorageDriver<QueueRequest, QueueResult>>;
    destroy(): Promise<void>;
    [Symbol.asyncDispose](): Promise<void>;
}
interface AdditionalPipeOptions {
    /**
     * If true, call maintain/run on this target when maintaining/running this runner
     * Defaults to true
     */
    exclusiveTarget?: boolean;
}
export interface KeetaAnchorQueueRunnerConfigurationObject {
    maxRetries: number;
    processTimeout: number;
    batchSize: number;
    retryDelay: number;
    stuckMultiplier: number;
}
/**
 * A Queue Runner and Request Translator for processing entries in a queue
 *
 * The queue runner is responsible for pulling entries from the queue,
 * processing them, and updating their status in the queue.  As well
 * as moving jobs between queues by piping the output of one runner
 * to another.  Additionally, maintenance tasks such as re-queuing
 * failed jobs and marking stuck jobs are also handled by the runner.
 *
 * This is an abstract base class that must be extended to provide
 * the actual processing logic as well as the encoding and decoding
 * for requests and responses.
 */
export declare abstract class KeetaAnchorQueueRunner<UserRequest = unknown, UserResult = unknown, QueueRequest extends JSONSerializable = JSONSerializable, QueueResult extends JSONSerializable = JSONSerializable> {
    /**
     * The queue this runner is responsible for running
     */
    private readonly queue;
    /**
     * The logger we should use for logging anything
     */
    private readonly logger?;
    /**
     * The processor function to use for processing entries
     */
    protected abstract processor(entry: KeetaAnchorQueueEntry<UserRequest, UserResult>): Promise<{
        status: KeetaAnchorQueueStatus;
        output: UserResult | null;
        error?: string | undefined;
    }>;
    /**
     * The processor for stuck jobs (optional)
     */
    protected processorStuck?(entry: KeetaAnchorQueueEntry<UserRequest, UserResult>): Promise<{
        status: KeetaAnchorQueueStatus;
        output: UserResult | null;
        error?: string | undefined;
    }>;
    /**
     * The processor for aborted jobs (optional)
     */
    protected processorAborted?(entry: KeetaAnchorQueueEntry<UserRequest, UserResult>): Promise<{
        status: KeetaAnchorQueueStatus;
        output: UserResult | null;
        error?: string | undefined;
    }>;
    /**
     * Worker configuration
     */
    private readonly workers;
    private readonly workerID;
    /**
     * Pipes to other runners we have registered
     */
    private readonly pipes;
    /**
     * Initialization promise
     */
    private initializePromise;
    /**
     * Configuration for this queue
     */
    /**
     * The maximum number of times to retry a failed job before giving up
     * and marking it as permanently failed
     */
    protected maxRetries: number;
    /**
     * The amount of time to allow a job to run before considering it
     * timed out and marking it as aborted, the `aborted` processor
     * (if provided) will be responsible for determining what to do with
     * the job at that point (e.g. retry it, mark it as failed, etc)
     * because some work from the job may have been completed
     * before the timeout was reached -- it could even still be on-going
     * after the timeout is reached, so the `aborted` processor should be
     * prepared to handle that situation as well.
     */
    protected processTimeout: number;
    /**
     * When piping a batch of jobs to another runner, this is the number
     * of jobs to include in each batch (max).
     */
    protected batchSize: number;
    /**
     * The amount of time to wait before retrying a failed job -- this should
     * be long enough to allow any transient issues to be resolved (e.g. a downstream
     * service to come back up, etc) but not so long that it causes excessive
     * delays in processing.
     *
     * Keep in mind that after the {@link maxRetries} is reached, the job will
     * be marked as permanently failed, so if the retry delay is too low then
     * it could cause jobs to be marked as permanently failed before transient
     * issues have a chance to be resolved.
     */
    protected get retryDelay(): number;
    protected set retryDelay(value: number);
    private internalRetryDelay?;
    /**
     * The number of {@link processTimeout} intervals to wait before
     * considering a job to be stuck -- this is for jobs that are
     * still in the 'processing' state but have not updated their
     * timestamp in a long time, which likely indicates that the worker
     * processing the job has died or is otherwise no longer making
     * progress on the job.
     *
     * Like the `aborted` status, the `stuck` status means the job is in
     * an indeterminate state where it may have done some of the work of
     * the processor but we don't know how much (if any) of it was
     * completed, so the `stuck` processor should be prepared to handle
     * that situation.  It is unlikely that the job is still being actively
     * processed by a worker at this point, but it is possible, so the
     * `stuck` processor should be prepared to handle that as well.
     */
    protected get stuckMultiplier(): number;
    protected set stuckMultiplier(value: number);
    private internalStuckMultiplier?;
    /**
     * How many runners can process this queue in parallel
     */
    protected maxRunners?: number;
    private readonly runnerLockKey;
    /**
     * The ID of this runner for diagnostic purposes
     */
    readonly id: string;
    constructor(config: {
        queue: KeetaAnchorQueueStorageDriver<QueueRequest, QueueResult>;
    } & KeetaAnchorQueueRunnerOptions);
    private initialize;
    protected setConfiguration(parameters: Partial<KeetaAnchorQueueRunnerConfigurationObject>): void;
    private methodLogger;
    protected abstract decodeRequest(request: QueueRequest): UserRequest;
    protected abstract decodeResponse(response: QueueResult | null): UserResult | null;
    protected abstract encodeRequest(request: UserRequest): QueueRequest;
    protected abstract encodeResponse(response: UserResult | null): QueueResult | null;
    protected decodeEntry(entry: KeetaAnchorQueueEntry<QueueRequest, QueueResult>): KeetaAnchorQueueEntry<UserRequest, UserResult>;
    /**
     * Enqueue an item to be processed by the queue
     */
    add(request: UserRequest, info?: KeetaAnchorQueueEntryExtra): Promise<KeetaAnchorQueueRequestID>;
    /**
     * Get a single entry from storage by ID
     */
    get(id: KeetaAnchorQueueRequestID): Promise<KeetaAnchorQueueEntry<UserRequest, UserResult> | null>;
    /**
     * Get entries from storage with an optional filter
     */
    query(filter?: KeetaAnchorQueueFilter): Promise<KeetaAnchorQueueEntry<UserRequest, UserResult>[]>;
    /**
     * Set the status of an entry in the queue
     */
    setStatus(id: KeetaAnchorQueueRequestID, status: KeetaAnchorQueueStatus, ancillary?: KeetaAnchorQueueEntryAncillaryData<UserResult>): Promise<void>;
    /**
     * Checks to see if the queue is runnable
     */
    runnable(): Promise<boolean>;
    private updateRunnerLockTimestamp;
    private getRunnerLock;
    private maintainRunnerLock;
    /**
     * Run the queue processor
     *
     * Processes up to `batchSize` entries from the queue and returns
     * true if there may be more work to do, or false if the queue
     * is empty.
     *
     * @param options Optional run options
     */
    run(options?: KeetaAnchorQueueRunOptions): Promise<boolean>;
    private markStuckRequestsAsStuck;
    private requeueFailedRequests;
    private moveCompletedToNextStage;
    maintain(): Promise<void>;
    /**
     * Pipe the the completed entries of this runner to another runner
     */
    pipe<T1, T2 extends JSONSerializable>(target: KeetaAnchorQueueRunner<UserResult, T1, QueueResult, T2>, options?: AdditionalPipeOptions): typeof target;
    pipeFailed<T1, T2 extends JSONSerializable>(target: KeetaAnchorQueueRunner<UserRequest, T1, QueueResult, T2>, options?: AdditionalPipeOptions): typeof target;
    /**
     * Pipe batches of completed entries from this runner to another runner
     */
    pipeBatch<T1, T2 extends JSONSerializable>(target: KeetaAnchorQueueRunner<UserResult[], T1, JSONSerializable, T2>, maxBatchSize?: number, minBatchSize?: number, options?: AdditionalPipeOptions): typeof target;
    pipeBatchFailed<T1, T2 extends JSONSerializable>(target: KeetaAnchorQueueRunner<UserRequest[], T1, JSONSerializable, T2>, maxBatchSize?: number, minBatchSize?: number, options?: AdditionalPipeOptions): typeof target;
    destroy(): Promise<void>;
    [Symbol.asyncDispose](): Promise<void>;
}
/**
 * A KeetaAnchorQueueRunner for use when you want to process already
 * JSON-serializable data without any encoding/decoding needed
 */
export declare abstract class KeetaAnchorQueueRunnerJSON<UserRequest extends JSONSerializable = JSONSerializable, UserResult extends JSONSerializable = JSONSerializable> extends KeetaAnchorQueueRunner<UserRequest, UserResult, JSONSerializable, JSONSerializable> {
    protected decodeRequest(request: JSONSerializable): UserRequest;
    protected decodeResponse(response: JSONSerializable | null): UserResult | null;
    protected encodeRequest(request: JSONSerializable): JSONSerializable;
    protected encodeResponse(response: JSONSerializable | null): JSONSerializable | null;
}
/**
 * A KeetaAnchorQueueRunnerJSON that takes a processor function
 * in the constructor -- this is mainly useful for testing
 */
export declare class KeetaAnchorQueueRunnerJSONConfigProc<UserRequest extends JSONSerializable = JSONSerializable, UserResult extends JSONSerializable = JSONSerializable> extends KeetaAnchorQueueRunnerJSON<UserRequest, UserResult> {
    protected readonly processor: KeetaAnchorQueueRunner<UserRequest, UserResult>['processor'];
    constructor(config: ConstructorParameters<typeof KeetaAnchorQueueRunner>[0] & {
        processor: KeetaAnchorQueueRunner<UserRequest, UserResult>['processor'];
        processorStuck?: KeetaAnchorQueueRunner<UserRequest, UserResult>['processorStuck'] | undefined;
        processorAborted?: KeetaAnchorQueueRunner<UserRequest, UserResult>['processorAborted'] | undefined;
    } & Partial<KeetaAnchorQueueRunnerConfigurationObject>);
}
export {};
//# sourceMappingURL=index.d.ts.map