feat: add reusable jobqueue library
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
216
src/types.ts
Normal file
216
src/types.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
export type JsonPrimitive = string | number | boolean | null;
|
||||
export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
|
||||
export type Awaitable<T> = T | Promise<T>;
|
||||
export type JobData = Record<string, JsonValue>;
|
||||
export type JobStatus =
|
||||
| 'pending'
|
||||
| 'active'
|
||||
| 'completed'
|
||||
| 'failed'
|
||||
| 'cancelled'
|
||||
| 'stale';
|
||||
export type JobPhaseStatus = 'pending' | 'active' | 'completed' | 'failed' | 'cancelled';
|
||||
export type RetryBackoffStrategy = 'fixed' | 'linear' | 'exponential';
|
||||
export type ErrorDisposition = 'recoverable' | 'fatal';
|
||||
|
||||
export interface JobPhaseState {
|
||||
name: string;
|
||||
status: JobPhaseStatus;
|
||||
progress: number;
|
||||
message: string | null;
|
||||
startedAt: string | null;
|
||||
completedAt: string | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export interface JobFailure {
|
||||
message: string;
|
||||
phase: string | null;
|
||||
recoverable: boolean;
|
||||
timestamp: string;
|
||||
attempt: number;
|
||||
}
|
||||
|
||||
export interface JobRetryEvent {
|
||||
jobId: string;
|
||||
phase: string | null;
|
||||
attempt: number;
|
||||
delayMs: number;
|
||||
nextRunAt: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface JobProgressEvent {
|
||||
jobId: string;
|
||||
phase: string;
|
||||
phaseProgress: number;
|
||||
overallProgress: number;
|
||||
message: string | null;
|
||||
timestamp: string;
|
||||
details?: JsonValue;
|
||||
}
|
||||
|
||||
export interface JobRecord<TData extends JobData = JobData> {
|
||||
id: string;
|
||||
status: JobStatus;
|
||||
data: TData;
|
||||
currentPhase: string | null;
|
||||
phases: JobPhaseState[];
|
||||
phaseResults: Record<string, JsonValue | undefined>;
|
||||
progress: number;
|
||||
progressMessage: string | null;
|
||||
error: JobFailure | null;
|
||||
retryCount: number;
|
||||
maxAttempts: number;
|
||||
webhookUrl: string | null;
|
||||
webhookSent: boolean;
|
||||
createdAt: string;
|
||||
startedAt: string | null;
|
||||
completedAt: string | null;
|
||||
updatedAt: string;
|
||||
scheduledAt: string | null;
|
||||
cancelledAt: string | null;
|
||||
}
|
||||
|
||||
export interface EnqueueOptions {
|
||||
id?: string;
|
||||
scheduledAt?: string | Date;
|
||||
maxAttempts?: number;
|
||||
webhookUrl?: string;
|
||||
}
|
||||
|
||||
export interface RetryOptions {
|
||||
fromStart?: boolean;
|
||||
scheduledAt?: string | Date;
|
||||
}
|
||||
|
||||
export interface ListJobsOptions {
|
||||
statuses?: JobStatus[];
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export interface StreamOptions {
|
||||
jobId?: string;
|
||||
includeSnapshot?: boolean;
|
||||
keepAliveMs?: number;
|
||||
}
|
||||
|
||||
export interface RetryConfig<TData extends JobData = JobData> {
|
||||
maxAttempts?: number;
|
||||
strategy?: RetryBackoffStrategy;
|
||||
baseDelayMs?: number;
|
||||
maxDelayMs?: number;
|
||||
classifyError?: (
|
||||
error: unknown,
|
||||
job: JobRecord<TData>,
|
||||
) => Awaitable<ErrorDisposition>;
|
||||
}
|
||||
|
||||
export interface RetentionConfig<TData extends JobData = JobData> {
|
||||
staleAfterMs: number;
|
||||
deleteAfterMs: number;
|
||||
intervalMs?: number;
|
||||
onStale?: (job: JobRecord<TData>) => Awaitable<void>;
|
||||
onDelete?: (job: JobRecord<TData>) => Awaitable<void>;
|
||||
}
|
||||
|
||||
export type WebhookEventName =
|
||||
| 'job:completed'
|
||||
| 'job:failed'
|
||||
| 'job:retrying'
|
||||
| 'job:cancelled'
|
||||
| 'job:stale';
|
||||
|
||||
export interface WebhookConfig {
|
||||
url?: string;
|
||||
events?: WebhookEventName[];
|
||||
secret?: string;
|
||||
timeoutMs?: number;
|
||||
headers?: Record<string, string>;
|
||||
maxAttempts?: number;
|
||||
baseDelayMs?: number;
|
||||
maxDelayMs?: number;
|
||||
}
|
||||
|
||||
export interface QueueConfig<TData extends JobData = JobData> {
|
||||
dbPath: string;
|
||||
phases: readonly string[];
|
||||
concurrency?: number;
|
||||
retry?: RetryConfig<TData>;
|
||||
retention?: RetentionConfig<TData>;
|
||||
webhook?: WebhookConfig;
|
||||
shutdownTimeoutMs?: number;
|
||||
}
|
||||
|
||||
export interface PhaseContext<TData extends JobData = JobData> {
|
||||
readonly job: JobRecord<TData>;
|
||||
readonly phase: string;
|
||||
readonly signal: AbortSignal;
|
||||
progress: (percent: number, message?: string, details?: JsonValue) => Promise<JobRecord<TData>>;
|
||||
phaseResult: <TResult extends JsonValue = JsonValue>(phaseName: string) => TResult | undefined;
|
||||
phaseResults: () => Record<string, JsonValue | undefined>;
|
||||
isCancelled: () => boolean;
|
||||
throwIfCancelled: () => Promise<void>;
|
||||
}
|
||||
|
||||
export type PhaseHandler<TData extends JobData = JobData> = (
|
||||
job: JobRecord<TData>,
|
||||
context: PhaseContext<TData>,
|
||||
) => Awaitable<JsonValue | undefined>;
|
||||
|
||||
export interface WebhookDispatchResult {
|
||||
event: WebhookEventName;
|
||||
jobId: string;
|
||||
status: number;
|
||||
deliveredAt: string;
|
||||
}
|
||||
|
||||
export interface WebhookDispatchError {
|
||||
event: WebhookEventName;
|
||||
jobId: string;
|
||||
message: string;
|
||||
finalAttempt: number;
|
||||
}
|
||||
|
||||
export interface QueueStreamEvent<TData extends JobData = JobData> {
|
||||
type:
|
||||
| 'snapshot'
|
||||
| 'job:enqueued'
|
||||
| 'job:started'
|
||||
| 'job:progress'
|
||||
| 'job:phase:completed'
|
||||
| 'job:completed'
|
||||
| 'job:failed'
|
||||
| 'job:retrying'
|
||||
| 'job:cancelled'
|
||||
| 'job:stale'
|
||||
| 'job:deleted'
|
||||
| 'job:webhook:delivered'
|
||||
| 'job:webhook:failed'
|
||||
| 'ping';
|
||||
jobId?: string;
|
||||
job?: JobRecord<TData>;
|
||||
progress?: JobProgressEvent;
|
||||
phase?: JobPhaseState;
|
||||
failure?: JobFailure;
|
||||
retry?: JobRetryEvent;
|
||||
webhook?: WebhookDispatchResult | WebhookDispatchError;
|
||||
deletedJobId?: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface JobQueueEvents<TData extends JobData = JobData> {
|
||||
'job:enqueued': [job: JobRecord<TData>];
|
||||
'job:started': [job: JobRecord<TData>];
|
||||
'job:progress': [job: JobRecord<TData>, progress: JobProgressEvent];
|
||||
'job:phase:completed': [job: JobRecord<TData>, phase: JobPhaseState];
|
||||
'job:completed': [job: JobRecord<TData>];
|
||||
'job:failed': [job: JobRecord<TData>, failure: JobFailure];
|
||||
'job:retrying': [job: JobRecord<TData>, retry: JobRetryEvent];
|
||||
'job:cancelled': [job: JobRecord<TData>];
|
||||
'job:stale': [job: JobRecord<TData>];
|
||||
'job:deleted': [jobId: string];
|
||||
'job:webhook:delivered': [job: JobRecord<TData>, result: WebhookDispatchResult];
|
||||
'job:webhook:failed': [job: JobRecord<TData>, error: WebhookDispatchError];
|
||||
}
|
||||
Reference in New Issue
Block a user