@workglow/job-queue 0.2.25 → 0.2.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.js +195 -32
- package/dist/browser.js.map +11 -11
- package/dist/bun.js +195 -32
- package/dist/bun.js.map +11 -11
- package/dist/job/JobQueueServer.d.ts.map +1 -1
- package/dist/job/JobQueueWorker.d.ts +7 -2
- package/dist/job/JobQueueWorker.d.ts.map +1 -1
- package/dist/limiter/CompositeLimiter.d.ts +17 -1
- package/dist/limiter/CompositeLimiter.d.ts.map +1 -1
- package/dist/limiter/ConcurrencyLimiter.d.ts +11 -1
- package/dist/limiter/ConcurrencyLimiter.d.ts.map +1 -1
- package/dist/limiter/DelayLimiter.d.ts +12 -1
- package/dist/limiter/DelayLimiter.d.ts.map +1 -1
- package/dist/limiter/EvenlySpacedRateLimiter.d.ts +18 -1
- package/dist/limiter/EvenlySpacedRateLimiter.d.ts.map +1 -1
- package/dist/limiter/ILimiter.d.ts +56 -0
- package/dist/limiter/ILimiter.d.ts.map +1 -1
- package/dist/limiter/NullLimiter.d.ts +6 -1
- package/dist/limiter/NullLimiter.d.ts.map +1 -1
- package/dist/limiter/RateLimiter.d.ts +42 -3
- package/dist/limiter/RateLimiter.d.ts.map +1 -1
- package/dist/node.js +195 -32
- package/dist/node.js.map +11 -11
- package/package.json +5 -5
package/dist/node.js.map
CHANGED
|
@@ -7,17 +7,17 @@
|
|
|
7
7
|
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/** Appended to job error messages; {@link JobQueueClient.buildErrorFromCode} maps this back onto `.stack`. */\nexport const JOB_ERROR_DIAGNOSTICS_MARKER = \"\\n\\n--- Error diagnostics ---\\n\";\n\nconst DEFAULT_MAX_DIAGNOSTICS_CHARS = 48_000;\n\n/**\n * Formats an error and its `.cause` chain (name, message, stack) for logs and persisted job errors.\n */\nexport function formatErrorChainForDiagnostics(\n err: unknown,\n maxChars: number = DEFAULT_MAX_DIAGNOSTICS_CHARS\n): string {\n const lines: string[] = [];\n let current: unknown = err;\n for (let depth = 0; depth < 8 && current != null; depth++) {\n if (current instanceof Error) {\n lines.push(`${current.name}: ${current.message}`);\n if (current.stack) {\n lines.push(current.stack);\n }\n const next = current.cause;\n if (next === undefined || next === null) {\n break;\n }\n lines.push(\"\");\n current = next;\n } else {\n lines.push(typeof current === \"string\" ? current : String(current));\n break;\n }\n }\n const text = lines.join(\"\\n\");\n if (text.length <= maxChars) {\n return text;\n }\n return `${text.slice(0, maxChars)}\\n…(truncated)`;\n}\n\n/**\n * Combines a short summary line with a full diagnostic dump (for storage on the failed job).\n */\nexport function withJobErrorDiagnostics(summaryLine: string, err: unknown): string {\n const diag = formatErrorChainForDiagnostics(err);\n if (diag.length === 0) {\n return summaryLine;\n }\n return `${summaryLine}${JOB_ERROR_DIAGNOSTICS_MARKER}${diag}`;\n}\n\n/**\n * When a persisted job error includes {@link JOB_ERROR_DIAGNOSTICS_MARKER}, set `.stack` so runtimes\n * (e.g. Vitest) print the worker-side trace instead of only the queue client frames.\n */\nexport function applyPersistedDiagnosticsToStack(\n jobError: JobErrorLike,\n fullMessage: string\n): void {\n if (!fullMessage.includes(JOB_ERROR_DIAGNOSTICS_MARKER)) {\n return;\n }\n const firstLine = fullMessage.split(\"\\n\")[0] ?? fullMessage;\n jobError.stack = `${jobError.name}: ${firstLine}\\n${fullMessage}`;\n}\n\ninterface JobErrorLike {\n readonly name: string;\n stack?: string;\n}\n",
|
|
8
8
|
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { IQueueStorage, JobStatus, JobStorageFormat, QueueChangePayload } from \"@workglow/storage\";\nimport { EventEmitter } from \"@workglow/util\";\nimport { Job } from \"./Job\";\nimport {\n AbortSignalJobError,\n JobDisabledError,\n JobError,\n JobNotFoundError,\n PermanentJobError,\n RetryableJobError,\n} from \"./JobError\";\nimport { applyPersistedDiagnosticsToStack } from \"./JobErrorDiagnostics\";\nimport {\n JobProgressListener,\n JobQueueEventListener,\n JobQueueEventListeners,\n JobQueueEventParameters,\n JobQueueEvents,\n} from \"./JobQueueEventListeners\";\nimport type { JobQueueServer } from \"./JobQueueServer\";\nimport { storageToClass } from \"./JobStorageConverters\";\n\n/**\n * Handle returned when submitting a job, providing methods to interact with the job\n */\nexport interface JobHandle<Output> {\n readonly id: unknown;\n waitFor(): Promise<Output>;\n abort(): Promise<void>;\n onProgress(callback: JobProgressListener): () => void;\n}\n\n/**\n * Options for creating a JobQueueClient\n */\nexport interface JobQueueClientOptions<Input, Output> {\n readonly storage: IQueueStorage<Input, Output>;\n readonly queueName: string;\n}\n\n/**\n * Client for submitting jobs and monitoring their progress.\n * Connect to a JobQueueServer for same-process optimization,\n * or use storage subscriptions for cross-process communication.\n */\nexport class JobQueueClient<Input, Output> {\n public readonly queueName: string;\n protected readonly storage: IQueueStorage<Input, Output>;\n protected readonly events = new EventEmitter<JobQueueEventListeners<Input, Output>>();\n protected server: JobQueueServer<Input, Output> | null = null;\n protected storageUnsubscribe: (() => void) | null = null;\n\n /**\n * Map of job IDs to their pending promise resolvers\n */\n protected readonly activeJobPromises: Map<\n unknown,\n Array<{\n resolve: (value: Output) => void;\n reject: (err: JobError) => void;\n }>\n > = new Map();\n\n /**\n * Map of job IDs to their progress listeners\n */\n protected readonly jobProgressListeners: Map<unknown, Set<JobProgressListener>> = new Map();\n\n /**\n * Last known progress state for each job\n */\n protected readonly lastKnownProgress: Map<\n unknown,\n {\n readonly progress: number;\n readonly message: string;\n readonly details: Record<string, unknown> | null;\n }\n > = new Map();\n\n constructor(options: JobQueueClientOptions<Input, Output>) {\n this.queueName = options.queueName;\n this.storage = options.storage;\n }\n\n /**\n * Attach to a local JobQueueServer for same-process event optimization.\n * When attached, events flow directly from server without storage polling.\n */\n public attach(server: JobQueueServer<Input, Output>): void {\n if (this.server) {\n this.detach();\n }\n this.server = server;\n server.addClient(this);\n\n // Unsubscribe from storage if we were using it\n if (this.storageUnsubscribe) {\n this.storageUnsubscribe();\n this.storageUnsubscribe = null;\n }\n }\n\n /**\n * Detach from the current server\n */\n public detach(): void {\n if (this.server) {\n this.server.removeClient(this);\n this.server = null;\n }\n }\n\n /**\n * Connect to storage for cross-process communication (when no local server).\n * Uses storage subscriptions to receive job updates.\n */\n public connect(): void {\n if (this.server) {\n return; // Already connected via server\n }\n\n if (this.storageUnsubscribe) {\n return; // Already subscribed\n }\n\n this.storageUnsubscribe = this.storage.subscribeToChanges(\n (change: QueueChangePayload<Input, Output>) => {\n this.handleStorageChange(change);\n }\n );\n }\n\n /**\n * Disconnect from storage subscriptions\n */\n public disconnect(): void {\n if (this.storageUnsubscribe) {\n this.storageUnsubscribe();\n this.storageUnsubscribe = null;\n }\n this.detach();\n }\n\n /**\n * Submit a job to the queue\n */\n public async submit(\n input: Input,\n options?: {\n readonly jobRunId?: string;\n readonly fingerprint?: string;\n readonly maxRetries?: number;\n readonly runAfter?: Date;\n readonly deadlineAt?: Date;\n }\n ): Promise<JobHandle<Output>> {\n const job: JobStorageFormat<Input, Output> = {\n queue: this.queueName,\n input,\n job_run_id: options?.jobRunId,\n fingerprint: options?.fingerprint,\n max_retries: options?.maxRetries ?? 10,\n run_after: options?.runAfter?.toISOString() ?? new Date().toISOString(),\n deadline_at: options?.deadlineAt?.toISOString() ?? null,\n completed_at: null,\n status: JobStatus.PENDING,\n };\n\n const id = await this.storage.add(job);\n\n // Same-process fast path: poke the worker directly so it doesn't have to\n // wait for the poll interval (crucial for Sqlite/Postgres, whose\n // subscribeToChanges throws).\n this.server?.handleJobAdded(id);\n\n return this.createJobHandle(id);\n }\n\n /**\n * Submit multiple jobs to the queue\n */\n public async submitBatch(\n inputs: readonly Input[],\n options?: {\n readonly jobRunId?: string;\n readonly maxRetries?: number;\n }\n ): Promise<readonly JobHandle<Output>[]> {\n const handles: JobHandle<Output>[] = [];\n for (const input of inputs) {\n const handle = await this.submit(input, options);\n handles.push(handle);\n }\n return handles;\n }\n\n /**\n * Get a job by ID\n */\n public async getJob(id: unknown): Promise<Job<Input, Output> | undefined> {\n if (!id) throw new JobNotFoundError(\"Cannot get undefined job\");\n const job = await this.storage.get(id);\n if (!job) return undefined;\n return this.storageToClass(job);\n }\n\n /**\n * Get jobs by run ID\n */\n public async getJobsByRunId(runId: string): Promise<readonly Job<Input, Output>[]> {\n if (!runId) throw new JobNotFoundError(\"Cannot get jobs by undefined runId\");\n const jobs = await this.storage.getByRunId(runId);\n return jobs.map((job) => this.storageToClass(job));\n }\n\n /**\n * Peek at jobs in the queue\n */\n public async peek(status?: JobStatus, num?: number): Promise<readonly Job<Input, Output>[]> {\n const jobs = await this.storage.peek(status, num);\n return jobs.map((job) => this.storageToClass(job));\n }\n\n /**\n * Get the size of the queue\n */\n public async size(status?: JobStatus): Promise<number> {\n return this.storage.size(status);\n }\n\n /**\n * Get the output for an input (if job completed)\n */\n public async outputForInput(input: Input): Promise<Output | null> {\n if (!input) throw new JobNotFoundError(\"Cannot get output for undefined input\");\n return this.storage.outputForInput(input);\n }\n\n /**\n * Wait for a job to complete.\n *\n * Registers the resolver BEFORE reading storage so that a `handleJobError`\n * / `handleJobComplete` event fired during the storage read isn't dropped\n * on the floor. The previous order (read first, register after) had a\n * TOCTOU window where a fast same-process abort could complete between the\n * read and the registration, leaving `waitFor` to register against an\n * already-finished job and hang forever.\n */\n public async waitFor(jobId: unknown): Promise<Output> {\n if (!jobId) throw new JobNotFoundError(\"Cannot wait for undefined job\");\n\n const { promise, resolve, reject } = Promise.withResolvers<Output>();\n promise.catch(() => {}); // Prevent unhandled rejection\n\n const promises = this.activeJobPromises.get(jobId) || [];\n promises.push({ resolve, reject });\n this.activeJobPromises.set(jobId, promises);\n\n // Now check storage — if the job is already terminal (raced us to it),\n // settle the promise ourselves and clean up the registration. The\n // handler paths (handleJobComplete/Error/Disabled) are idempotent on\n // already-settled promises.\n const job = await this.getJob(jobId);\n if (!job) {\n this.removePromise(jobId, resolve, reject);\n throw new JobNotFoundError(`Job ${jobId} not found`);\n }\n if (job.status === JobStatus.COMPLETED) {\n this.removePromise(jobId, resolve, reject);\n return job.output as Output;\n }\n if (job.status === JobStatus.DISABLED) {\n this.removePromise(jobId, resolve, reject);\n throw new JobDisabledError(`Job ${jobId} was disabled`);\n }\n if (job.status === JobStatus.FAILED) {\n this.removePromise(jobId, resolve, reject);\n throw this.buildErrorFromJob(job);\n }\n\n return promise;\n }\n\n private removePromise(\n jobId: unknown,\n resolve: (output: Output) => void,\n reject: (err: unknown) => void\n ): void {\n const list = this.activeJobPromises.get(jobId);\n if (!list) return;\n const idx = list.findIndex((p) => p.resolve === resolve && p.reject === reject);\n if (idx !== -1) list.splice(idx, 1);\n if (list.length === 0) this.activeJobPromises.delete(jobId);\n }\n\n /**\n * Abort a job.\n *\n * Same-process path: fires the in-memory abort controller on the attached\n * server — `handleAbort` will write FAILED directly, so we skip the\n * `storage.abort(…)` ABORTING write. Writing both would race (last-writer-\n * wins) and can leave the row stuck at ABORTING on async storages.\n *\n * Cross-process path (or job not currently running on any local worker):\n * write ABORTING to storage so the remote worker's poll picks it up.\n *\n * Crash window: if the process dies after the in-memory abort fires but\n * before `failJob` writes FAILED, the row stays PROCESSING. `fixupJobs()`\n * resets it to PENDING on next start and the job will re-run. Make handlers\n * idempotent (or use `uniquenessKey`) if that's not acceptable.\n */\n public async abort(jobId: unknown): Promise<void> {\n if (!jobId) throw new JobNotFoundError(\"Cannot abort undefined job\");\n const firedLocally = this.server?.abortJob(jobId) ?? false;\n if (!firedLocally) {\n try {\n await this.storage.abort(jobId);\n } finally {\n this.events.emit(\"job_aborting\", this.queueName, jobId);\n }\n return;\n }\n this.events.emit(\"job_aborting\", this.queueName, jobId);\n }\n\n /**\n * Abort all jobs in a job run\n */\n public async abortJobRun(jobRunId: string): Promise<void> {\n if (!jobRunId) throw new JobNotFoundError(\"Cannot abort job run with undefined jobRunId\");\n const jobs = await this.getJobsByRunId(jobRunId);\n await Promise.allSettled(\n jobs.map((job) => {\n if (job.status === JobStatus.PROCESSING || job.status === JobStatus.PENDING) {\n return this.abort(job.id);\n }\n })\n );\n }\n\n /**\n * Subscribe to progress updates for a specific job\n */\n public onJobProgress(jobId: unknown, listener: JobProgressListener): () => void {\n if (!this.jobProgressListeners.has(jobId)) {\n this.jobProgressListeners.set(jobId, new Set());\n }\n const listeners = this.jobProgressListeners.get(jobId)!;\n listeners.add(listener);\n\n return () => {\n const listeners = this.jobProgressListeners.get(jobId);\n if (listeners) {\n listeners.delete(listener);\n if (listeners.size === 0) {\n this.jobProgressListeners.delete(jobId);\n }\n }\n };\n }\n\n // ========================================================================\n // Event handling\n // ========================================================================\n\n public on<Event extends JobQueueEvents>(\n event: Event,\n listener: JobQueueEventListener<Event>\n ): void {\n this.events.on(event, listener);\n }\n\n public off<Event extends JobQueueEvents>(\n event: Event,\n listener: JobQueueEventListener<Event>\n ): void {\n this.events.off(event, listener);\n }\n\n public once<Event extends JobQueueEvents>(\n event: Event,\n listener: JobQueueEventListener<Event>\n ): void {\n this.events.once(event, listener);\n }\n\n public waitOn<Event extends JobQueueEvents>(\n event: Event\n ): Promise<JobQueueEventParameters<Event, Input, Output>> {\n return this.events.waitOn(event) as Promise<JobQueueEventParameters<Event, Input, Output>>;\n }\n\n /**\n * Subscribes to an event and returns a function to unsubscribe\n * @param event - The event name to subscribe to\n * @param listener - The listener function to add\n * @returns a function to unsubscribe from the event\n */\n public subscribe<Event extends JobQueueEvents>(\n event: Event,\n listener: JobQueueEventListener<Event>\n ): () => void {\n return this.events.subscribe(event, listener);\n }\n\n // ========================================================================\n // Internal methods called by JobQueueServer for same-process optimization\n // ========================================================================\n\n /**\n * Called by server when a job starts processing\n * @internal\n */\n public handleJobStart(jobId: unknown): void {\n this.lastKnownProgress.set(jobId, {\n progress: 0,\n message: \"\",\n details: null,\n });\n this.events.emit(\"job_start\", this.queueName, jobId);\n }\n\n /**\n * Called by server when a job completes\n * @internal\n */\n public handleJobComplete(jobId: unknown, output: Output): void {\n this.events.emit(\"job_complete\", this.queueName, jobId, output);\n\n const promises = this.activeJobPromises.get(jobId);\n if (promises) {\n promises.forEach(({ resolve }) => resolve(output));\n }\n this.cleanupJob(jobId);\n }\n\n /**\n * Called by server when a job fails\n * @internal\n */\n public handleJobError(jobId: unknown, error: string, errorCode?: string): void {\n this.events.emit(\"job_error\", this.queueName, jobId, error);\n\n const promises = this.activeJobPromises.get(jobId);\n if (promises) {\n const jobError = this.buildErrorFromCode(error, errorCode);\n promises.forEach(({ reject }) => reject(jobError));\n }\n this.cleanupJob(jobId);\n }\n\n /**\n * Called by server when a job is disabled\n * @internal\n */\n public handleJobDisabled(jobId: unknown): void {\n this.events.emit(\"job_disabled\", this.queueName, jobId);\n\n const promises = this.activeJobPromises.get(jobId);\n if (promises) {\n promises.forEach(({ reject }) => reject(new JobDisabledError(\"Job was disabled\")));\n }\n this.cleanupJob(jobId);\n }\n\n /**\n * Called by server when a job is retried\n * @internal\n */\n public handleJobRetry(jobId: unknown, runAfter: Date): void {\n this.events.emit(\"job_retry\", this.queueName, jobId, runAfter);\n }\n\n /**\n * Called by server when job progress updates\n * @internal\n */\n public handleJobProgress(\n jobId: unknown,\n progress: number,\n message: string,\n details: Record<string, unknown> | null\n ): void {\n this.lastKnownProgress.set(jobId, { progress, message, details });\n this.events.emit(\"job_progress\", this.queueName, jobId, progress, message, details);\n\n const listeners = this.jobProgressListeners.get(jobId);\n if (listeners) {\n for (const listener of listeners) {\n listener(progress, message, details);\n }\n }\n }\n\n // ========================================================================\n // Private helpers\n // ========================================================================\n\n private createJobHandle(id: unknown): JobHandle<Output> {\n return {\n id,\n waitFor: () => this.waitFor(id),\n abort: () => this.abort(id),\n onProgress: (callback: JobProgressListener) => this.onJobProgress(id, callback),\n };\n }\n\n private cleanupJob(jobId: unknown): void {\n this.activeJobPromises.delete(jobId);\n this.lastKnownProgress.delete(jobId);\n this.jobProgressListeners.delete(jobId);\n }\n\n private handleStorageChange(change: QueueChangePayload<Input, Output>): void {\n if (!change.new && !change.old) return;\n\n const jobId = change.new?.id ?? change.old?.id;\n if (!jobId) return;\n\n // Only process changes for our queue\n const queueName = change.new?.queue ?? change.old?.queue;\n if (queueName !== this.queueName) return;\n\n if (change.type === \"UPDATE\" && change.new) {\n const newStatus = change.new.status;\n const oldStatus = change.old?.status;\n\n if (newStatus === JobStatus.PROCESSING && oldStatus === JobStatus.PENDING) {\n this.handleJobStart(jobId);\n } else if (newStatus === JobStatus.COMPLETED) {\n this.handleJobComplete(jobId, change.new.output as Output);\n } else if (newStatus === JobStatus.FAILED) {\n this.handleJobError(\n jobId,\n change.new.error ?? \"Job failed\",\n change.new.error_code ?? undefined\n );\n } else if (newStatus === JobStatus.DISABLED) {\n this.handleJobDisabled(jobId);\n } else if (newStatus === JobStatus.PENDING && oldStatus === JobStatus.PROCESSING) {\n // Retry\n const runAfter = change.new.run_after ? new Date(change.new.run_after) : new Date();\n this.handleJobRetry(jobId, runAfter);\n }\n\n // Progress update\n if (\n change.new.progress !== change.old?.progress ||\n change.new.progress_message !== change.old?.progress_message\n ) {\n this.handleJobProgress(\n jobId,\n change.new.progress ?? 0,\n change.new.progress_message ?? \"\",\n change.new.progress_details ?? null\n );\n }\n }\n }\n\n protected storageToClass(details: JobStorageFormat<Input, Output>): Job<Input, Output> {\n return storageToClass(details, Job, { includeWorkerId: true });\n }\n\n protected buildErrorFromJob(job: Job<Input, Output>): JobError {\n return this.buildErrorFromCode(job.error || \"Job failed\", job.errorCode ?? undefined);\n }\n\n protected buildErrorFromCode(message: string, errorCode?: string): JobError {\n if (errorCode === \"PermanentJobError\") {\n const err = new PermanentJobError(message);\n applyPersistedDiagnosticsToStack(err, message);\n return err;\n }\n if (errorCode === \"RetryableJobError\") {\n const err = new RetryableJobError(message);\n applyPersistedDiagnosticsToStack(err, message);\n return err;\n }\n if (errorCode === \"AbortSignalJobError\") {\n const err = new AbortSignalJobError(message);\n applyPersistedDiagnosticsToStack(err, message);\n return err;\n }\n if (errorCode === \"JobDisabledError\") {\n const err = new JobDisabledError(message);\n applyPersistedDiagnosticsToStack(err, message);\n return err;\n }\n const err = new JobError(message);\n applyPersistedDiagnosticsToStack(err, message);\n return err;\n }\n}\n",
|
|
9
9
|
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { JobStatus, JobStorageFormat } from \"@workglow/storage\";\nimport { Job, JobClass } from \"./Job\";\n\n/**\n * Convert a date string to a Date object, or null if invalid\n */\nfunction toDate(date: string | null | undefined): Date | null {\n if (!date) return null;\n const d = new Date(date);\n return isNaN(d.getTime()) ? null : d;\n}\n\n/**\n * Convert a Date object to an ISO string, or null if invalid\n */\nfunction dateToISOString(date: Date | null | undefined): string | null {\n if (!date) return null;\n return isNaN(date.getTime()) ? null : date.toISOString();\n}\n\n/**\n * Convert storage format to Job class\n */\nexport function storageToClass<Input, Output>(\n details: JobStorageFormat<Input, Output>,\n jobClass: JobClass<Input, Output>,\n options?: {\n readonly includeWorkerId?: boolean;\n }\n): Job<Input, Output> {\n const includeWorkerId = options?.includeWorkerId ?? true;\n return new jobClass({\n id: details.id,\n jobRunId: details.job_run_id,\n queueName: details.queue,\n fingerprint: details.fingerprint,\n input: details.input as Input,\n output: details.output as Output,\n runAfter: toDate(details.run_after),\n createdAt: toDate(details.created_at)!,\n deadlineAt: toDate(details.deadline_at),\n lastRanAt: toDate(details.last_ran_at),\n completedAt: toDate(details.completed_at),\n progress: details.progress || 0,\n progressMessage: details.progress_message || \"\",\n progressDetails: details.progress_details ?? null,\n status: details.status as JobStatus,\n error: details.error ?? null,\n errorCode: details.error_code ?? null,\n runAttempts: details.run_attempts ?? 0,\n maxRetries: details.max_retries ?? 10,\n ...(includeWorkerId ? { workerId: details.worker_id ?? null } : {}),\n });\n}\n\n/**\n * Convert Job class to storage format\n */\nexport function classToStorage<Input, Output>(\n job: Job<Input, Output>,\n queueName: string\n): JobStorageFormat<Input, Output> {\n const now = new Date().toISOString();\n return {\n id: job.id,\n job_run_id: job.jobRunId,\n queue: job.queueName || queueName,\n fingerprint: job.fingerprint,\n input: job.input,\n status: job.status,\n output: job.output ?? null,\n error: job.error === null ? null : String(job.error),\n error_code: job.errorCode || null,\n run_attempts: job.runAttempts ?? 0,\n max_retries: job.maxRetries ?? 10,\n run_after: dateToISOString(job.runAfter) ?? now,\n created_at: dateToISOString(job.createdAt) ?? now,\n deadline_at: dateToISOString(job.deadlineAt),\n last_ran_at: dateToISOString(job.lastRanAt),\n completed_at: dateToISOString(job.completedAt),\n progress: job.progress ?? 0,\n progress_message: job.progressMessage ?? \"\",\n progress_details: job.progressDetails ?? null,\n worker_id: job.workerId ?? null,\n };\n}\n",
|
|
10
|
-
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { IQueueStorage, JobStatus, JobStorageFormat, QueueChangePayload } from \"@workglow/storage\";\nimport { EventEmitter, getLogger } from \"@workglow/util\";\nimport { ILimiter } from \"../limiter/ILimiter\";\nimport { NullLimiter } from \"../limiter/NullLimiter\";\nimport { Job, JobClass } from \"./Job\";\nimport { JobQueueClient } from \"./JobQueueClient\";\nimport { JobQueueWorker } from \"./JobQueueWorker\";\nimport { classToStorage, storageToClass } from \"./JobStorageConverters\";\n\n/**\n * Statistics tracked for the job queue\n */\nexport interface JobQueueStats {\n readonly totalJobs: number;\n readonly completedJobs: number;\n readonly failedJobs: number;\n readonly abortedJobs: number;\n readonly retriedJobs: number;\n readonly disabledJobs: number;\n readonly averageProcessingTime?: number;\n readonly lastUpdateTime: Date;\n}\n\n/**\n * Events emitted by JobQueueServer\n */\nexport type JobQueueServerEventListeners<Input, Output> = {\n server_start: (queueName: string) => void;\n server_stop: (queueName: string) => void;\n job_start: (queueName: string, jobId: unknown) => void;\n job_complete: (queueName: string, jobId: unknown, output: Output) => void;\n job_error: (queueName: string, jobId: unknown, error: string) => void;\n job_disabled: (queueName: string, jobId: unknown) => void;\n job_retry: (queueName: string, jobId: unknown, runAfter: Date) => void;\n job_progress: (\n queueName: string,\n jobId: unknown,\n progress: number,\n message: string,\n details: Record<string, unknown> | null\n ) => void;\n};\n\nexport type JobQueueServerEvents = keyof JobQueueServerEventListeners<unknown, unknown>;\n\n/**\n * Options for creating a JobQueueServer\n */\nexport interface JobQueueServerOptions<Input, Output> {\n readonly storage: IQueueStorage<Input, Output>;\n readonly queueName: string;\n readonly limiter?: ILimiter;\n readonly workerCount?: number;\n readonly pollIntervalMs?: number;\n readonly deleteAfterCompletionMs?: number;\n readonly deleteAfterFailureMs?: number;\n readonly deleteAfterDisabledMs?: number;\n readonly cleanupIntervalMs?: number;\n /**\n * Max time each worker's `stop()` waits for in-flight jobs before forcing aborts.\n * Defaults to 30s. Set to 0 to abort immediately.\n */\n readonly stopTimeoutMs?: number;\n}\n\n/**\n * Server that coordinates multiple workers and manages the job queue lifecycle.\n * Handles stuck job recovery, cleanup, and aggregates statistics.\n */\nexport class JobQueueServer<\n Input,\n Output,\n QueueJob extends Job<Input, Output> = Job<Input, Output>,\n> {\n public readonly queueName: string;\n protected readonly storage: IQueueStorage<Input, Output>;\n protected readonly jobClass: JobClass<Input, Output>;\n public readonly limiter: ILimiter;\n protected readonly workerCount: number;\n protected readonly pollIntervalMs: number;\n protected readonly deleteAfterCompletionMs?: number;\n protected readonly deleteAfterFailureMs?: number;\n protected readonly deleteAfterDisabledMs?: number;\n protected readonly cleanupIntervalMs: number;\n protected readonly stopTimeoutMs?: number;\n\n protected readonly events = new EventEmitter<JobQueueServerEventListeners<Input, Output>>();\n protected readonly workers: JobQueueWorker<Input, Output, QueueJob>[] = [];\n protected readonly clients: Set<JobQueueClient<Input, Output>> = new Set();\n\n protected running = false;\n protected cleanupTimer: ReturnType<typeof setTimeout> | null = null;\n protected storageUnsubscribe: (() => void) | null = null;\n\n protected stats: JobQueueStats = {\n totalJobs: 0,\n completedJobs: 0,\n failedJobs: 0,\n abortedJobs: 0,\n retriedJobs: 0,\n disabledJobs: 0,\n lastUpdateTime: new Date(),\n };\n\n constructor(jobClass: JobClass<Input, Output>, options: JobQueueServerOptions<Input, Output>) {\n this.queueName = options.queueName;\n this.storage = options.storage;\n this.jobClass = jobClass;\n this.limiter = options.limiter ?? new NullLimiter();\n this.workerCount = options.workerCount ?? 1;\n this.pollIntervalMs = options.pollIntervalMs ?? 100;\n this.deleteAfterCompletionMs = options.deleteAfterCompletionMs;\n this.deleteAfterFailureMs = options.deleteAfterFailureMs;\n this.deleteAfterDisabledMs = options.deleteAfterDisabledMs;\n this.cleanupIntervalMs = options.cleanupIntervalMs ?? 10000;\n this.stopTimeoutMs = options.stopTimeoutMs;\n\n this.initializeWorkers();\n }\n\n /**\n * Start the server and all workers\n */\n public async start(): Promise<this> {\n if (this.running) {\n return this;\n }\n\n this.running = true;\n this.events.emit(\"server_start\", this.queueName);\n\n // Fix stuck jobs from previous runs\n await this.fixupJobs();\n\n // Subscribe to storage changes to wake workers when new work arrives.\n // - Cross-process deployments rely on this for wake-up.\n // - Same-process attached clients are also primarily woken by the direct\n // `handleJobAdded` path on submit, but we keep the subscription as a\n // correctness backstop: some async backends (e.g. fake-indexeddb under\n // bun) have read-after-write visibility lag where a just-committed job\n // isn't yet returned by `storage.next()`. The subscription fires only\n // after the write is visible to readers, providing a guaranteed wake.\n // - Sqlite/Postgres throw here; the try/catch falls through and direct\n // notify is the sole wake path on those backends.\n try {\n this.storageUnsubscribe = this.storage.subscribeToChanges(\n (change: QueueChangePayload<Input, Output>) => {\n if (\n change.type === \"INSERT\" ||\n (change.type === \"UPDATE\" && change.new?.status === JobStatus.PENDING)\n ) {\n this.notifyWorkers();\n }\n }\n );\n } catch (err) {\n // Storage doesn't support change subscriptions — workers will poll.\n // Logged at debug because Sqlite/Postgres throw here by design; an\n // unexpected throw on a storage that *should* support subscriptions\n // (e.g. misconfigured Supabase realtime) is otherwise silent.\n getLogger().debug(\"subscribeToChanges unsupported on this storage\", {\n queueName: this.queueName,\n error: err,\n });\n }\n\n // Start all workers\n await Promise.all(this.workers.map((worker) => worker.start()));\n\n // Start cleanup loop only if at least one retention TTL is configured.\n if (this.hasRetentionTtls()) {\n this.startCleanupLoop();\n }\n\n return this;\n }\n\n /**\n * True if any delete-after-X TTL is set to a positive value.\n * When false, the cleanup loop would be a pure no-op and is skipped.\n */\n private hasRetentionTtls(): boolean {\n return (\n (this.deleteAfterCompletionMs !== undefined && this.deleteAfterCompletionMs > 0) ||\n (this.deleteAfterFailureMs !== undefined && this.deleteAfterFailureMs > 0) ||\n (this.deleteAfterDisabledMs !== undefined && this.deleteAfterDisabledMs > 0)\n );\n }\n\n /**\n * Stop the server and all workers\n */\n public async stop(): Promise<this> {\n if (!this.running) {\n return this;\n }\n\n this.running = false;\n\n // Unsubscribe from storage changes\n if (this.storageUnsubscribe) {\n this.storageUnsubscribe();\n this.storageUnsubscribe = null;\n }\n\n // Stop cleanup loop\n if (this.cleanupTimer) {\n clearTimeout(this.cleanupTimer);\n this.cleanupTimer = null;\n }\n\n // Stop all workers\n await Promise.all(this.workers.map((worker) => worker.stop()));\n\n this.events.emit(\"server_stop\", this.queueName);\n return this;\n }\n\n /**\n * Get the current queue statistics\n */\n public getStats(): JobQueueStats {\n return { ...this.stats };\n }\n\n /**\n * Get the storage instance (for client connection)\n */\n public getStorage(): IQueueStorage<Input, Output> {\n return this.storage;\n }\n\n /**\n * Scale the number of workers\n */\n public async scaleWorkers(count: number): Promise<void> {\n if (count < 1) {\n throw new Error(\"Worker count must be at least 1\");\n }\n\n const currentCount = this.workers.length;\n\n if (count > currentCount) {\n // Add more workers\n for (let i = currentCount; i < count; i++) {\n const worker = this.createWorker();\n this.workers.push(worker);\n if (this.running) {\n await worker.start();\n }\n }\n } else if (count < currentCount) {\n // Remove workers\n const toRemove = this.workers.splice(count);\n await Promise.all(toRemove.map((worker) => worker.stop()));\n }\n }\n\n /**\n * Check if the server is running\n */\n public isRunning(): boolean {\n return this.running;\n }\n\n /**\n * Get the number of workers\n */\n public getWorkerCount(): number {\n return this.workers.length;\n }\n\n // ========================================================================\n // Client management\n // ========================================================================\n\n /**\n * Add a client for same-process event forwarding\n * @internal\n */\n public addClient(client: JobQueueClient<Input, Output>): void {\n this.clients.add(client);\n }\n\n /**\n * Remove a client\n * @internal\n */\n public removeClient(client: JobQueueClient<Input, Output>): void {\n this.clients.delete(client);\n }\n\n /**\n * Wake all idle workers so they check for new jobs immediately.\n * @internal\n */\n public notifyWorkers(): void {\n for (const worker of this.workers) {\n worker.notify();\n }\n }\n\n /**\n * Called by an attached client immediately after a job is inserted into storage,\n * so the worker can pick it up without waiting for the poll interval.\n * @internal\n */\n public handleJobAdded(_jobId: unknown): void {\n this.notifyWorkers();\n }\n\n /**\n * Fire the abort controller for the given job on whichever worker is running it.\n * Returns true if any worker handled the abort locally.\n * @internal\n */\n public abortJob(jobId: unknown): boolean {\n for (const worker of this.workers) {\n if (worker.requestAbort(jobId)) {\n return true;\n }\n }\n return false;\n }\n\n // ========================================================================\n // Event handling\n // ========================================================================\n\n public on<Event extends JobQueueServerEvents>(\n event: Event,\n listener: JobQueueServerEventListeners<Input, Output>[Event]\n ): void {\n this.events.on(event, listener);\n }\n\n public off<Event extends JobQueueServerEvents>(\n event: Event,\n listener: JobQueueServerEventListeners<Input, Output>[Event]\n ): void {\n this.events.off(event, listener);\n }\n\n // ========================================================================\n // Protected methods\n // ========================================================================\n\n /**\n * Initialize workers\n */\n protected initializeWorkers(): void {\n for (let i = 0; i < this.workerCount; i++) {\n const worker = this.createWorker();\n this.workers.push(worker);\n }\n }\n\n /**\n * Create a new worker and wire up event forwarding\n */\n protected createWorker(): JobQueueWorker<Input, Output, QueueJob> {\n const worker = new JobQueueWorker<Input, Output, QueueJob>(this.jobClass, {\n storage: this.storage,\n queueName: this.queueName,\n limiter: this.limiter,\n pollIntervalMs: this.pollIntervalMs,\n stopTimeoutMs: this.stopTimeoutMs,\n });\n\n // Forward worker events to server and clients\n worker.on(\"job_start\", (jobId) => {\n this.stats = { ...this.stats, totalJobs: this.stats.totalJobs + 1 };\n this.events.emit(\"job_start\", this.queueName, jobId);\n this.forwardToClients(\"handleJobStart\", jobId);\n });\n\n worker.on(\"job_complete\", (jobId, output) => {\n this.stats = { ...this.stats, completedJobs: this.stats.completedJobs + 1 };\n this.updateAverageProcessingTime();\n this.events.emit(\"job_complete\", this.queueName, jobId, output);\n this.forwardToClients(\"handleJobComplete\", jobId, output);\n\n // Immediate deletion when configured\n if (this.deleteAfterCompletionMs === 0) {\n this.storage.delete(jobId).catch((err) => {\n console.error(\"Error deleting job after completion:\", err);\n });\n }\n\n // A concurrency slot freed up — wake idle workers\n this.notifyWorkers();\n });\n\n worker.on(\"job_error\", (jobId, error, errorCode) => {\n this.stats = { ...this.stats, failedJobs: this.stats.failedJobs + 1 };\n this.events.emit(\"job_error\", this.queueName, jobId, error);\n this.forwardToClients(\"handleJobError\", jobId, error, errorCode);\n\n // Immediate deletion when configured\n if (this.deleteAfterFailureMs === 0) {\n this.storage.delete(jobId).catch((err) => {\n console.error(\"Error deleting job after error:\", err);\n });\n }\n\n // A concurrency slot freed up — wake idle workers\n this.notifyWorkers();\n });\n\n worker.on(\"job_disabled\", (jobId) => {\n this.stats = { ...this.stats, disabledJobs: this.stats.disabledJobs + 1 };\n this.events.emit(\"job_disabled\", this.queueName, jobId);\n this.forwardToClients(\"handleJobDisabled\", jobId);\n\n // Immediate deletion when configured\n if (this.deleteAfterDisabledMs === 0) {\n this.storage.delete(jobId).catch((err) => {\n console.error(\"Error deleting job after disabling:\", err);\n });\n }\n\n // A concurrency slot freed up — wake idle workers\n this.notifyWorkers();\n });\n\n worker.on(\"job_retry\", (jobId, runAfter) => {\n this.stats = { ...this.stats, retriedJobs: this.stats.retriedJobs + 1 };\n this.events.emit(\"job_retry\", this.queueName, jobId, runAfter);\n this.forwardToClients(\"handleJobRetry\", jobId, runAfter);\n });\n\n worker.on(\"job_progress\", (jobId, progress, message, details) => {\n this.events.emit(\"job_progress\", this.queueName, jobId, progress, message, details);\n this.forwardToClients(\"handleJobProgress\", jobId, progress, message, details);\n });\n\n return worker;\n }\n\n /**\n * Forward events to all attached clients\n */\n protected forwardToClients(method: \"handleJobStart\", jobId: unknown): void;\n protected forwardToClients(method: \"handleJobComplete\", jobId: unknown, output: Output): void;\n protected forwardToClients(\n method: \"handleJobError\",\n jobId: unknown,\n error: string,\n errorCode?: string\n ): void;\n protected forwardToClients(method: \"handleJobDisabled\", jobId: unknown): void;\n protected forwardToClients(method: \"handleJobRetry\", jobId: unknown, runAfter: Date): void;\n protected forwardToClients(\n method: \"handleJobProgress\",\n jobId: unknown,\n progress: number,\n message: string,\n details: Record<string, unknown> | null\n ): void;\n protected forwardToClients(method: string, ...args: unknown[]): void {\n for (const client of this.clients) {\n const fn = (client as any)[method];\n if (typeof fn === \"function\") {\n fn.apply(client, args);\n }\n }\n }\n\n /**\n * Update average processing time from all workers\n */\n protected updateAverageProcessingTime(): void {\n const times: number[] = [];\n for (const worker of this.workers) {\n const avgTime = worker.getAverageProcessingTime();\n if (avgTime !== undefined) {\n times.push(avgTime);\n }\n }\n if (times.length > 0) {\n const avg = times.reduce((a, b) => a + b, 0) / times.length;\n this.stats = {\n ...this.stats,\n averageProcessingTime: avg,\n lastUpdateTime: new Date(),\n };\n }\n }\n\n /**\n * Start the cleanup loop\n */\n protected startCleanupLoop(): void {\n if (!this.running) return;\n\n this.cleanupJobs().finally(() => {\n if (this.running) {\n this.cleanupTimer = setTimeout(() => this.startCleanupLoop(), this.cleanupIntervalMs);\n }\n });\n }\n\n /**\n * Clean up completed/failed jobs based on TTL settings\n */\n protected async cleanupJobs(): Promise<void> {\n try {\n // The workers will handle the abort via their abort controllers\n // We just need to ensure the jobs get marked as failed\n\n // Delete completed jobs after TTL\n if (this.deleteAfterCompletionMs !== undefined && this.deleteAfterCompletionMs > 0) {\n await this.storage.deleteJobsByStatusAndAge(\n JobStatus.COMPLETED,\n this.deleteAfterCompletionMs\n );\n }\n\n // Delete failed jobs after TTL\n if (this.deleteAfterFailureMs !== undefined && this.deleteAfterFailureMs > 0) {\n await this.storage.deleteJobsByStatusAndAge(JobStatus.FAILED, this.deleteAfterFailureMs);\n }\n\n // Delete disabled jobs after TTL\n if (this.deleteAfterDisabledMs !== undefined && this.deleteAfterDisabledMs > 0) {\n await this.storage.deleteJobsByStatusAndAge(JobStatus.DISABLED, this.deleteAfterDisabledMs);\n }\n } catch (error) {\n console.error(\"Error in cleanup:\", error);\n }\n }\n\n /**\n * Fix stuck jobs from previous server runs.\n * Jobs in PROCESSING or ABORTING state that are not owned by any of the current\n * server's workers are considered orphaned and will be reset.\n */\n protected async fixupJobs(): Promise<void> {\n try {\n const stuckProcessingJobs = await this.storage.peek(JobStatus.PROCESSING);\n const stuckAbortingJobs = await this.storage.peek(JobStatus.ABORTING);\n const stuckJobs = [...stuckProcessingJobs, ...stuckAbortingJobs];\n\n // Get the worker IDs of all workers managed by this server\n const currentWorkerIds = new Set(this.getWorkerIds());\n\n for (const jobData of stuckJobs) {\n // Skip jobs that belong to workers in this server (they may still be processing)\n if (jobData.worker_id && currentWorkerIds.has(jobData.worker_id)) {\n continue;\n }\n\n const job = this.storageToClass(jobData);\n if (job.runAttempts >= job.maxRetries) {\n job.status = JobStatus.FAILED;\n job.error = \"Max retries reached\";\n job.errorCode = \"MAX_RETRIES_REACHED\";\n // Clear worker_id since job is now failed\n job.workerId = null;\n } else {\n job.status = JobStatus.PENDING;\n job.runAfter = job.lastRanAt || new Date();\n job.progress = 0;\n job.progressMessage = \"\";\n job.progressDetails = null;\n job.error = \"Server restarted\";\n // Clear worker_id so a new worker can claim this job\n job.workerId = null;\n }\n\n await this.storage.complete(this.classToStorage(job));\n }\n } catch (error) {\n console.error(\"Error in fixupJobs:\", error);\n }\n }\n\n /**\n * Convert storage format to Job class\n */\n protected storageToClass(details: JobStorageFormat<Input, Output>): Job<Input, Output> {\n return storageToClass(details, this.jobClass);\n }\n\n /**\n * Convert Job class to storage format\n */\n protected classToStorage(job: Job<Input, Output>): JobStorageFormat<Input, Output> {\n return classToStorage(job, this.queueName);\n }\n\n /**\n * Get the worker IDs of all workers managed by this server\n */\n public getWorkerIds(): string[] {\n return this.workers.map((worker) => worker.workerId);\n }\n}\n",
|
|
11
|
-
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { createServiceToken } from \"@workglow/util\";\nimport { ILimiter } from \"./ILimiter\";\n\nexport const NULL_JOB_LIMITER = createServiceToken<ILimiter>(\"jobqueue.limiter.null\");\n\n/**\n * Null limiter that does nothing.\n */\nexport class NullLimiter implements ILimiter {\n async canProceed(): Promise<boolean> {\n return true;\n }\n\n async recordJobStart(): Promise<void> {\n // Do nothing\n }\n\n async recordJobCompletion(): Promise<void> {\n // Do nothing\n }\n\n async getNextAvailableTime(): Promise<Date> {\n return new Date();\n }\n\n async setNextAvailableTime(date: Date): Promise<void> {\n // Do nothing\n }\n\n async clear(): Promise<void> {\n // Do nothing\n }\n}\n",
|
|
12
|
-
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { IQueueStorage, JobStatus, JobStorageFormat } from \"@workglow/storage\";\nimport {\n EventEmitter,\n getLogger,\n getTelemetryProvider,\n sleep,\n SpanStatusCode,\n uuid4,\n} from \"@workglow/util\";\nimport { ILimiter } from \"../limiter/ILimiter\";\nimport { NullLimiter } from \"../limiter/NullLimiter\";\nimport { Job, JobClass } from \"./Job\";\nimport {\n AbortSignalJobError,\n JobDisabledError,\n JobError,\n JobNotFoundError,\n PermanentJobError,\n RetryableJobError,\n} from \"./JobError\";\nimport { withJobErrorDiagnostics } from \"./JobErrorDiagnostics\";\nimport { classToStorage, storageToClass } from \"./JobStorageConverters\";\n\n/**\n * Events emitted by JobQueueWorker\n */\nexport type JobQueueWorkerEventListeners<Input, Output> = {\n job_start: (jobId: unknown) => void;\n job_complete: (jobId: unknown, output: Output) => void;\n job_error: (jobId: unknown, error: string, errorCode?: string) => void;\n job_disabled: (jobId: unknown) => void;\n job_retry: (jobId: unknown, runAfter: Date) => void;\n job_progress: (\n jobId: unknown,\n progress: number,\n message: string,\n details: Record<string, unknown> | null\n ) => void;\n worker_start: () => void;\n worker_stop: () => void;\n};\n\nexport type JobQueueWorkerEvents = keyof JobQueueWorkerEventListeners<unknown, unknown>;\n\n/**\n * Options for creating a JobQueueWorker\n */\nexport interface JobQueueWorkerOptions<Input, Output> {\n readonly storage: IQueueStorage<Input, Output>;\n readonly queueName: string;\n readonly limiter?: ILimiter;\n readonly pollIntervalMs?: number;\n /**\n * Optional worker ID. If not provided, a random UUID will be generated.\n * Use a persistent ID if you want the worker to reclaim its own jobs after restart.\n */\n readonly workerId?: string | null;\n /**\n * Max time `stop()` waits for in-flight jobs to finish before forcing aborts.\n * Defaults to 30s. Set to 0 to abort immediately.\n */\n readonly stopTimeoutMs?: number;\n}\n\n/**\n * Worker that processes jobs from the queue.\n * Reports progress and completion back to storage.\n */\nexport class JobQueueWorker<\n Input,\n Output,\n QueueJob extends Job<Input, Output> = Job<Input, Output>,\n> {\n public readonly queueName: string;\n public readonly workerId: string;\n protected readonly storage: IQueueStorage<Input, Output>;\n protected readonly jobClass: JobClass<Input, Output>;\n protected readonly limiter: ILimiter;\n protected readonly pollIntervalMs: number;\n protected readonly stopTimeoutMs: number;\n protected readonly events = new EventEmitter<JobQueueWorkerEventListeners<Input, Output>>();\n\n protected running = false;\n\n /**\n * Tracks in-flight job executions for drain-on-stop.\n * Each entry's promise resolves (never rejects) when the job settles\n * (complete / fail / retry / abort).\n */\n private readonly inFlight: Map<unknown, Promise<void>> = new Map();\n\n /**\n * Resolve function for the idle wait promise.\n * When set, the worker is idle and waiting for either a notification or poll timeout.\n */\n private wakeResolve: (() => void) | null = null;\n private wakeTimer: ReturnType<typeof setTimeout> | null = null;\n\n /**\n * Set when {@link notify} fires while the worker is not yet idle. The next\n * {@link waitForWakeOrTimeout} call consumes it and returns immediately\n * instead of sleeping. Without this flag, a notify that arrives during\n * `processJobs`'s pre-idle awaits (e.g. while `storage.next` and\n * `storage.peek` are in flight) would be dropped on the floor and the\n * worker would sleep for the full poll interval despite there being work\n * to do — observed under load on IndexedDb.\n */\n private wakePending = false;\n\n /**\n * Promise for the running `processJobs` loop. Captured in {@link start} so\n * {@link stop} can await actual loop exit instead of returning while the\n * loop is still suspended mid-iteration. Without this, a loop parked in\n * `await this.next()` could resume after stop returned and claim a job\n * that was submitted after stop — starting `processSingleJob` on a worker\n * that's no longer running.\n */\n private loopPromise: Promise<void> | null = null;\n\n /**\n * Abort controllers for active jobs\n */\n protected readonly activeJobAbortControllers: Map<unknown, AbortController> = new Map();\n\n /**\n * Processing times for statistics\n */\n protected readonly processingTimes: Map<unknown, number> = new Map();\n\n constructor(jobClass: JobClass<Input, Output>, options: JobQueueWorkerOptions<Input, Output>) {\n this.queueName = options.queueName;\n this.workerId = options.workerId ?? uuid4();\n this.storage = options.storage;\n this.jobClass = jobClass;\n this.limiter = options.limiter ?? new NullLimiter();\n this.pollIntervalMs = options.pollIntervalMs ?? 100;\n this.stopTimeoutMs = options.stopTimeoutMs ?? 30_000;\n }\n\n /**\n * Start the worker processing loop\n */\n public async start(): Promise<this> {\n if (this.running) {\n return this;\n }\n this.running = true;\n this.events.emit(\"worker_start\");\n this.loopPromise = this.processJobs();\n return this;\n }\n\n /**\n * If this worker is currently processing the given job, fire its abort controller\n * immediately and return true. Returns false if the job isn't active on this worker.\n * @internal\n */\n public requestAbort(jobId: unknown): boolean {\n const controller = this.activeJobAbortControllers.get(jobId);\n if (controller && !controller.signal.aborted) {\n controller.abort();\n return true;\n }\n return false;\n }\n\n /**\n * Wake the worker from idle sleep so it checks for jobs immediately.\n * If the worker is not yet idle, latches a pending-wake flag that the next\n * {@link waitForWakeOrTimeout} call consumes — so wake notifications that\n * race with worker startup or mid-iteration awaits are not lost.\n */\n public notify(): void {\n this.wakePending = true;\n if (this.wakeResolve) {\n if (this.wakeTimer) {\n clearTimeout(this.wakeTimer);\n this.wakeTimer = null;\n }\n const resolve = this.wakeResolve;\n this.wakeResolve = null;\n resolve();\n }\n }\n\n /**\n * Stop the worker, draining in-flight jobs up to {@link stopTimeoutMs}\n * before aborting anything still running.\n */\n public async stop(): Promise<this> {\n if (!this.running) {\n return this;\n }\n this.running = false;\n\n // Wake from idle sleep so the processing loop can exit.\n this.notify();\n\n // Wait for the processJobs loop to actually exit. Without this, a loop\n // suspended mid-iteration (e.g. inside `await this.next()`) could resume\n // after stop returned and claim/start a freshly-submitted job — leaving\n // a \"stopped\" worker running PROCESSING jobs.\n const loopPromise = this.loopPromise;\n this.loopPromise = null;\n if (loopPromise) {\n await loopPromise;\n }\n\n // Phase 1 — graceful drain.\n if (this.stopTimeoutMs > 0 && this.inFlight.size > 0) {\n const drain = Promise.allSettled([...this.inFlight.values()]);\n await Promise.race([drain, sleep(this.stopTimeoutMs)]);\n }\n\n // Phase 2 — anything still running gets aborted.\n if (this.inFlight.size > 0) {\n for (const controller of this.activeJobAbortControllers.values()) {\n if (!controller.signal.aborted) {\n controller.abort();\n }\n }\n const abortDrain = Promise.allSettled([...this.inFlight.values()]);\n await Promise.race([abortDrain, sleep(1000)]);\n }\n\n this.events.emit(\"worker_stop\");\n return this;\n }\n\n /**\n * Process a single job manually (useful for testing or manual control)\n */\n public async processNext(): Promise<boolean> {\n const canProceed = await this.limiter.canProceed();\n if (!canProceed) {\n return false;\n }\n\n const job = await this.next();\n if (!job) {\n return false;\n }\n\n await this.processSingleJob(job);\n return true;\n }\n\n /**\n * Check if the worker is currently running\n */\n public isRunning(): boolean {\n return this.running;\n }\n\n /**\n * Get the number of active jobs being processed\n */\n public getActiveJobCount(): number {\n return this.activeJobAbortControllers.size;\n }\n\n /**\n * Get average processing time\n */\n public getAverageProcessingTime(): number | undefined {\n const times = Array.from(this.processingTimes.values());\n if (times.length === 0) return undefined;\n return times.reduce((a, b) => a + b, 0) / times.length;\n }\n\n // ========================================================================\n // Event handling\n // ========================================================================\n\n public on<Event extends JobQueueWorkerEvents>(\n event: Event,\n listener: JobQueueWorkerEventListeners<Input, Output>[Event]\n ): void {\n this.events.on(event, listener);\n }\n\n public off<Event extends JobQueueWorkerEvents>(\n event: Event,\n listener: JobQueueWorkerEventListeners<Input, Output>[Event]\n ): void {\n this.events.off(event, listener);\n }\n\n // ========================================================================\n // Protected methods\n // ========================================================================\n\n /**\n * Get the next job from the queue\n */\n protected async next(): Promise<QueueJob | undefined> {\n const job = await this.storage.next(this.workerId);\n if (!job) return undefined;\n return this.storageToClass(job) as QueueJob;\n }\n\n /**\n * Main job processing loop.\n *\n * Runs as a tight `while` loop (no recursive `setTimeout`) so that\n * back-to-back jobs are picked up with minimal inter-job latency.\n *\n * When no jobs are available the worker sleeps until either:\n * - {@link notify} is called (e.g. because the server saw a new job inserted\n * or a running job completed and freed a concurrency slot), or\n * - the poll-interval timeout expires (fallback for storages without push\n * events).\n */\n protected async processJobs(): Promise<void> {\n while (this.running) {\n try {\n // Check for aborting jobs\n await this.checkForAbortingJobs();\n\n const canProceed = await this.limiter.canProceed();\n if (canProceed) {\n const job = await this.next();\n if (job) {\n if (!this.running) {\n // We were stopped during one of the awaits above and `next()`\n // claimed this job after the fact. Release it back to PENDING\n // so it isn't stuck PROCESSING on a worker that's no longer\n // running — `fixupJobs()` skips current-server worker IDs, so\n // nothing else would clean it up.\n await this.releaseClaimedJob(job);\n return;\n }\n // Don't await - process in background to allow concurrent jobs.\n // The loop will re-check canProceed on the next iteration; if the\n // limiter is at capacity it will wait for a notify (fired by the\n // server when a job completes and frees a slot).\n this.processSingleJob(job);\n continue;\n }\n }\n\n // Either no jobs available or limiter is at capacity — wait for\n // something to change before re-checking.\n if (canProceed) {\n // Queue is empty — sleep until notified of new work or until\n // the next deferred job becomes ready.\n const delay = await this.getIdleDelay();\n await this.waitForWakeOrTimeout(delay);\n } else {\n // At capacity — wait until notified (a job completes and frees a\n // slot) or the poll interval expires as a fallback.\n await this.waitForWakeOrTimeout(this.pollIntervalMs);\n }\n } catch {\n // Don't let transient errors kill the loop\n await sleep(this.pollIntervalMs);\n }\n }\n }\n\n /**\n * Determine how long to sleep when idle.\n *\n * Peeks at the earliest PENDING job: if it has a future `run_after`,\n * returns the time until it becomes ready (clamped to `pollIntervalMs`);\n * otherwise returns `pollIntervalMs`.\n */\n private async getIdleDelay(): Promise<number> {\n try {\n const pending = await this.storage.peek(JobStatus.PENDING, 1);\n if (pending.length > 0 && pending[0].run_after) {\n const delay = new Date(pending[0].run_after).getTime() - Date.now();\n if (delay > 0) {\n return Math.min(delay, this.pollIntervalMs);\n }\n }\n } catch {\n // If peek fails, fall back to default\n }\n return this.pollIntervalMs;\n }\n\n /**\n * Wait for either a {@link notify} call or the given timeout,\n * whichever comes first. Consumes any pending wake latched while the worker\n * was not yet idle (see {@link wakePending}) — returns immediately in that\n * case rather than sleeping.\n */\n private waitForWakeOrTimeout(timeoutMs: number): Promise<void> {\n if (this.wakePending) {\n this.wakePending = false;\n return Promise.resolve();\n }\n return new Promise<void>((resolve) => {\n this.wakeTimer = setTimeout(() => {\n this.wakeTimer = null;\n this.wakeResolve = null;\n this.wakePending = false;\n resolve();\n }, timeoutMs);\n\n this.wakeResolve = () => {\n this.wakePending = false;\n resolve();\n };\n });\n }\n\n /**\n * Check for jobs that have been marked for abort and trigger their abort controllers.\n *\n * Only relevant for jobs running on THIS worker (we have an abort controller\n * registered for them). When no jobs are active, the peek result is irrelevant\n * — skip the storage round-trip entirely. Important for battery life on\n * same-process deployments (browser/mobile) where workers spend most time idle.\n */\n protected async checkForAbortingJobs(): Promise<void> {\n if (this.activeJobAbortControllers.size === 0) {\n return;\n }\n const abortingJobs = await this.storage.peek(JobStatus.ABORTING);\n for (const jobData of abortingJobs) {\n const controller = this.activeJobAbortControllers.get(jobData.id);\n if (controller && !controller.signal.aborted) {\n controller.abort();\n }\n }\n }\n\n /**\n * Process a single job\n */\n protected async processSingleJob(job: Job<Input, Output>): Promise<void> {\n if (!job || !job.id) {\n throw new JobNotFoundError(\"Invalid job provided for processing\");\n }\n\n const { promise: inFlightPromise, resolve: resolveInFlight } = Promise.withResolvers<void>();\n this.inFlight.set(job.id, inFlightPromise);\n\n const startTime = Date.now();\n\n // Start telemetry span for job processing\n const telemetry = getTelemetryProvider();\n const span = telemetry.isEnabled\n ? telemetry.startSpan(\"workglow.job.process\", {\n attributes: {\n \"workglow.job.id\": String(job.id),\n \"workglow.job.queue\": this.queueName,\n \"workglow.job.worker_id\": this.workerId,\n \"workglow.job.run_attempt\": job.runAttempts,\n \"workglow.job.max_retries\": job.maxRetries,\n },\n })\n : undefined;\n\n try {\n await this.validateJobState(job);\n await this.limiter.recordJobStart();\n\n const abortController = this.createAbortController(job.id);\n this.events.emit(\"job_start\", job.id);\n\n const output = await this.executeJob(job, abortController.signal);\n await this.completeJob(job, output);\n\n const elapsed = Date.now() - startTime;\n this.processingTimes.set(job.id, elapsed);\n\n if (span) {\n span.setAttributes({ \"workglow.job.duration_ms\": elapsed });\n span.setStatus(SpanStatusCode.OK);\n }\n } catch (err: unknown) {\n const error = this.normalizeError(err);\n let spanErrorMessage = error.message;\n if (error instanceof RetryableJobError) {\n const currentJob = await this.getJob(job.id);\n if (!currentJob) {\n throw new JobNotFoundError(`Job ${job.id} not found`);\n }\n\n if (currentJob.runAttempts >= currentJob.maxRetries) {\n spanErrorMessage = \"Max retries reached\";\n await this.failJob(currentJob, new PermanentJobError(spanErrorMessage));\n span?.setStatus(SpanStatusCode.ERROR, spanErrorMessage);\n } else {\n await this.rescheduleJob(currentJob, error.retryDate);\n span?.addEvent(\"workglow.job.retry\", {\n \"workglow.job.run_attempt\": currentJob.runAttempts,\n });\n span?.setStatus(SpanStatusCode.UNSET);\n }\n } else {\n await this.failJob(job, error);\n span?.setStatus(SpanStatusCode.ERROR, error.message);\n }\n span?.setAttributes({ \"workglow.job.error\": spanErrorMessage });\n } finally {\n span?.end();\n try {\n await this.limiter.recordJobCompletion();\n } finally {\n this.inFlight.delete(job.id);\n resolveInFlight();\n }\n }\n }\n\n /**\n * Execute a job with the provided abort signal\n */\n protected async executeJob(job: Job<Input, Output>, signal: AbortSignal): Promise<Output> {\n if (!job) throw new JobNotFoundError(\"Cannot execute null or undefined job\");\n return await job.execute(job.input, {\n signal,\n updateProgress: this.updateProgress.bind(this, job.id),\n });\n }\n\n /**\n * Update progress for a job.\n *\n * Mid-job progress is delivered in-memory via the `job_progress` event;\n * storage is only touched at terminal transitions (complete / fail / retry).\n * Cross-process observers therefore see state transitions but not fine-grained\n * progress — subscribe to an attached `JobQueueClient` for that.\n */\n protected async updateProgress(\n jobId: unknown,\n progress: number,\n message: string = \"\",\n details: Record<string, unknown> | null = null\n ): Promise<void> {\n progress = Math.max(0, Math.min(100, progress));\n this.events.emit(\"job_progress\", jobId, progress, message, details);\n }\n\n /**\n * Mark a job as completed\n */\n protected async completeJob(job: Job<Input, Output>, output?: Output): Promise<void> {\n try {\n job.status = JobStatus.COMPLETED;\n job.progress = 100;\n job.progressMessage = \"\";\n job.progressDetails = null;\n job.completedAt = new Date();\n job.output = output ?? null;\n job.error = null;\n job.errorCode = null;\n\n await this.storage.complete(this.classToStorage(job));\n this.events.emit(\"job_complete\", job.id, output as Output);\n } catch (err) {\n getLogger().error(\"completeJob errored:\", { error: err });\n } finally {\n this.cleanupJob(job.id);\n }\n }\n\n /**\n * Mark a job as failed\n */\n protected async failJob(job: Job<Input, Output>, error: JobError): Promise<void> {\n try {\n job.status = JobStatus.FAILED;\n job.progress = 100;\n job.completedAt = new Date();\n job.progressMessage = \"\";\n job.progressDetails = null;\n job.error = error.message;\n job.errorCode = error?.constructor?.name ?? null;\n\n await this.storage.complete(this.classToStorage(job));\n this.events.emit(\"job_error\", job.id, error.message, error.constructor.name);\n } catch (err) {\n getLogger().error(\"failJob errored:\", { error: err });\n } finally {\n this.cleanupJob(job.id);\n }\n }\n\n /**\n * Mark a job as disabled\n */\n protected async disableJob(job: Job<Input, Output>): Promise<void> {\n try {\n job.status = JobStatus.DISABLED;\n job.progress = 100;\n job.completedAt = new Date();\n job.progressMessage = \"\";\n job.progressDetails = null;\n\n await this.storage.complete(this.classToStorage(job));\n this.events.emit(\"job_disabled\", job.id);\n } catch (err) {\n getLogger().error(\"disableJob errored:\", { error: err });\n } finally {\n this.cleanupJob(job.id);\n }\n }\n\n /**\n * Release a job that {@link next} just claimed but that we won't process\n * because the worker was stopped mid-claim. Resets the row to PENDING so\n * the next started worker can pick it up. `fixupJobs()` would otherwise\n * skip it (it ignores rows owned by current-server worker IDs).\n *\n * Uses `storage.release()` rather than `storage.complete()` so the retry\n * budget isn't burned: the worker never actually attempted execution.\n */\n protected async releaseClaimedJob(job: Job<Input, Output>): Promise<void> {\n try {\n await this.storage.release(job.id);\n } catch (err) {\n getLogger().error(\"releaseClaimedJob errored:\", { error: err });\n }\n }\n\n /**\n * Reschedule a job for retry\n */\n protected async rescheduleJob(job: Job<Input, Output>, retryDate?: Date): Promise<void> {\n try {\n job.status = JobStatus.PENDING;\n const nextAvailableTime = await this.limiter.getNextAvailableTime();\n job.runAfter = retryDate instanceof Date ? retryDate : nextAvailableTime;\n job.progress = 0;\n job.progressMessage = \"\";\n job.progressDetails = null;\n // Increment runAttempts to keep in-memory object in sync with storage\n // The storage layer will read from DB and increment, so this keeps them aligned\n job.runAttempts = (job.runAttempts ?? 0) + 1;\n\n await this.storage.complete(this.classToStorage(job));\n this.events.emit(\"job_retry\", job.id, job.runAfter);\n } catch (err) {\n getLogger().error(\"rescheduleJob errored:\", { error: err });\n }\n }\n\n /**\n * Create an abort controller for a job.\n *\n * The job MUST already be registered in {@link inFlight} — this enforces\n * the invariant that `activeJobAbortControllers ⊆ inFlight`, which\n * {@link handleAbort} relies on to decide whether `processSingleJob` is\n * still on the hook for the terminal write. Calling this from any path\n * other than `processSingleJob` (which registers `inFlight` first) is a\n * programming error.\n */\n protected createAbortController(jobId: unknown): AbortController {\n if (!jobId) throw new JobNotFoundError(\"Cannot create abort controller for undefined job\");\n\n if (!this.inFlight.has(jobId)) {\n throw new Error(\n `createAbortController invariant violated: jobId ${String(jobId)} is not in inFlight. ` +\n `Abort controllers must only be created from within processSingleJob.`\n );\n }\n\n if (this.activeJobAbortControllers.has(jobId)) {\n return this.activeJobAbortControllers.get(jobId)!;\n }\n\n const abortController = new AbortController();\n abortController.signal.addEventListener(\"abort\", () => this.handleAbort(jobId));\n this.activeJobAbortControllers.set(jobId, abortController);\n return abortController;\n }\n\n /**\n * Handle job abort.\n *\n * Two callers fire the controller and reach this listener:\n * 1. `requestAbort` — same-process abort while the job is in flight here.\n * 2. `checkForAbortingJobs` — cross-process abort observed via storage poll.\n *\n * In both cases, if processSingleJob is still running this job locally,\n * the abort signal will propagate into the user task and processSingleJob's\n * own catch path will write the terminal state. We must not race it: doing\n * so duplicates the `job_error` emit and, worse, can clobber a successful\n * `completeJob` that won the race (the COMPLETED→FAILED overwrite bug).\n *\n * If the job is no longer in flight here, it has already settled — recheck\n * storage and only write FAILED for non-terminal states (i.e. an ABORTING\n * row left over from a cross-process abort that this worker never picked up).\n */\n protected async handleAbort(jobId: unknown): Promise<void> {\n if (this.inFlight.has(jobId)) {\n return;\n }\n const job = await this.getJob(jobId);\n if (!job) {\n getLogger().error(\"handleAbort: job not found\", { jobId });\n return;\n }\n if (\n job.status === JobStatus.COMPLETED ||\n job.status === JobStatus.FAILED ||\n job.status === JobStatus.DISABLED\n ) {\n return;\n }\n await this.failJob(job, new AbortSignalJobError(\"Job Aborted\"));\n }\n\n /**\n * Get a job by ID\n */\n protected async getJob(id: unknown): Promise<Job<Input, Output> | undefined> {\n const job = await this.storage.get(id);\n if (!job) return undefined;\n return this.storageToClass(job);\n }\n\n /**\n * Validate job state before processing\n */\n protected async validateJobState(job: Job<Input, Output>): Promise<void> {\n if (job.status === JobStatus.COMPLETED) {\n throw new PermanentJobError(`Job ${job.id} is already completed`);\n }\n if (job.status === JobStatus.FAILED) {\n throw new PermanentJobError(`Job ${job.id} has failed`);\n }\n if (\n job.status === JobStatus.ABORTING ||\n this.activeJobAbortControllers.get(job.id)?.signal.aborted\n ) {\n throw new AbortSignalJobError(`Job ${job.id} is being aborted`);\n }\n if (job.deadlineAt && job.deadlineAt < new Date()) {\n throw new PermanentJobError(`Job ${job.id} has exceeded its deadline`);\n }\n if (job.status === JobStatus.DISABLED) {\n throw new JobDisabledError(`Job ${job.id} has been disabled`);\n }\n }\n\n /**\n * Normalize errors into JobError instances\n */\n protected normalizeError(err: unknown): JobError {\n if (err instanceof JobError) {\n return err;\n }\n if (err instanceof Error) {\n return new PermanentJobError(withJobErrorDiagnostics(err.message, err));\n }\n return new PermanentJobError(String(err));\n }\n\n /**\n * Clean up job state after completion/failure\n */\n protected cleanupJob(jobId: unknown): void {\n this.activeJobAbortControllers.delete(jobId);\n }\n\n /**\n * Convert storage format to Job class\n */\n protected storageToClass(details: JobStorageFormat<Input, Output>): Job<Input, Output> {\n return storageToClass(details, this.jobClass);\n }\n\n /**\n * Convert Job class to storage format\n */\n protected classToStorage(job: Job<Input, Output>): JobStorageFormat<Input, Output> {\n return classToStorage(job, this.queueName);\n }\n}\n",
|
|
13
|
-
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { ILimiter } from \"./ILimiter\";\n\nexport class CompositeLimiter implements ILimiter {\n private limiters: ILimiter[] = [];\n\n constructor(limiters: ILimiter[] = []) {\n this.limiters = limiters;\n }\n\n addLimiter(limiter: ILimiter): void {\n this.limiters.push(limiter);\n }\n\n async canProceed(): Promise<boolean> {\n for (const limiter of this.limiters) {\n if (!(await limiter.canProceed())) {\n return false; // If any limiter says \"no\", proceed no further\n }\n }\n return true; // All limiters agree\n }\n\n async recordJobStart(): Promise<void> {\n await Promise.all(this.limiters.map((limiter) => limiter.recordJobStart()));\n }\n\n async recordJobCompletion(): Promise<void> {\n await Promise.all(this.limiters.map((limiter) => limiter.recordJobCompletion()));\n }\n\n async getNextAvailableTime(): Promise<Date> {\n let maxDate = new Date(); // Assume now as the default\n for (const limiter of this.limiters) {\n const limiterNextTime = await limiter.getNextAvailableTime();\n if (limiterNextTime > maxDate) {\n maxDate = limiterNextTime; // Find the latest time among limiters\n }\n }\n return maxDate;\n }\n\n async setNextAvailableTime(date: Date): Promise<void> {\n for (const limiter of this.limiters) {\n await limiter.setNextAvailableTime(date);\n }\n }\n\n async clear(): Promise<void> {\n await Promise.all(this.limiters.map((limiter) => limiter.clear()));\n }\n}\n",
|
|
14
|
-
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { createServiceToken } from \"@workglow/util\";\nimport { ILimiter } from \"./ILimiter\";\n\nexport const CONCURRENT_JOB_LIMITER = createServiceToken<ILimiter>(\"jobqueue.limiter.concurrent\");\n\n/**\n * Concurrency limiter that limits the number of concurrent jobs.\n */\nexport class ConcurrencyLimiter implements ILimiter {\n private currentRunningJobs: number = 0;\n private readonly maxConcurrentJobs: number;\n private nextAllowedStartTime: Date = new Date();\n\n constructor(maxConcurrentJobs: number) {\n this.maxConcurrentJobs = maxConcurrentJobs;\n }\n\n async canProceed(): Promise<boolean> {\n return (\n this.currentRunningJobs < this.maxConcurrentJobs &&\n Date.now() >= this.nextAllowedStartTime.getTime()\n );\n }\n\n async recordJobStart(): Promise<void> {\n this.currentRunningJobs++;\n }\n\n async recordJobCompletion(): Promise<void> {\n this.currentRunningJobs = Math.max(0, this.currentRunningJobs - 1);\n }\n\n async getNextAvailableTime(): Promise<Date> {\n return this.currentRunningJobs > this.maxConcurrentJobs ? new Date() : new Date(Date.now() - 1);\n }\n\n async setNextAvailableTime(date: Date): Promise<void> {\n if (date > this.nextAllowedStartTime) {\n this.nextAllowedStartTime = date;\n }\n }\n\n async clear(): Promise<void> {\n this.currentRunningJobs = 0;\n this.nextAllowedStartTime = new Date();\n }\n}\n",
|
|
15
|
-
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { ILimiter } from \"./ILimiter\";\n\nexport class DelayLimiter implements ILimiter {\n private nextAvailableTime: Date = new Date();\n constructor(private delayInMilliseconds: number = 50) {}\n\n async canProceed(): Promise<boolean> {\n return Date.now() >= this.nextAvailableTime.getTime();\n }\n\n async recordJobStart(): Promise<void> {\n this.nextAvailableTime = new Date(Date.now() + this.delayInMilliseconds);\n }\n\n async recordJobCompletion(): Promise<void> {\n // No action needed.\n }\n\n async getNextAvailableTime(): Promise<Date> {\n return this.nextAvailableTime;\n }\n\n async setNextAvailableTime(date: Date): Promise<void> {\n if (date > this.nextAvailableTime) {\n this.nextAvailableTime = date;\n }\n }\n async clear(): Promise<void> {\n this.nextAvailableTime = new Date();\n }\n}\n",
|
|
16
|
-
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { createServiceToken } from \"@workglow/util\";\nimport { ILimiter, RateLimiterOptions } from \"./ILimiter\";\n\nexport const EVENLY_SPACED_JOB_RATE_LIMITER = createServiceToken<ILimiter>(\n \"jobqueue.limiter.rate.evenlyspaced\"\n);\n\n/**\n * Rate limiter that spreads requests evenly across a time window.\n * Instead of allowing all requests up to the limit and then waiting,\n * this limiter spaces out the requests evenly across the window.\n */\nexport class EvenlySpacedRateLimiter implements ILimiter {\n private readonly maxExecutions: number;\n private readonly windowSizeMs: number;\n private readonly idealInterval: number;\n private nextAvailableTime: number = Date.now();\n private lastStartTime: number = 0;\n private durations: number[] = [];\n\n constructor({ maxExecutions, windowSizeInSeconds }: RateLimiterOptions) {\n if (maxExecutions <= 0) {\n throw new Error(\"maxExecutions must be > 0\");\n }\n if (windowSizeInSeconds <= 0) {\n throw new Error(\"windowSizeInSeconds must be > 0\");\n }\n this.maxExecutions = maxExecutions;\n this.windowSizeMs = windowSizeInSeconds * 1_000;\n // If you want exactly maxExecutions in windowSize, start one every this many ms:\n this.idealInterval = this.windowSizeMs / this.maxExecutions;\n }\n\n /** Can we start a new job right now? */\n async canProceed(): Promise<boolean> {\n const now = Date.now();\n return now >= this.nextAvailableTime;\n }\n\n /** Record that a job is starting now. */\n async recordJobStart(): Promise<void> {\n const now = Date.now();\n this.lastStartTime = now;\n\n // If no timing data yet, assume zero run-time (ideal interval)\n if (this.durations.length === 0) {\n this.nextAvailableTime = now + this.idealInterval;\n } else {\n // Compute average run duration\n const sum = this.durations.reduce((a, b) => a + b, 0);\n const avgDuration = sum / this.durations.length;\n // Schedule next start: ideal spacing minus average duration\n const waitMs = Math.max(0, this.idealInterval - avgDuration);\n this.nextAvailableTime = now + waitMs;\n }\n }\n\n /**\n * Call this when a job finishes.\n * We measure its duration, update our running-average,\n * and then compute how long to wait before the next job start.\n */\n async recordJobCompletion(): Promise<void> {\n const now = Date.now();\n const duration = now - this.lastStartTime;\n this.durations.push(duration);\n if (this.durations.length > this.maxExecutions) {\n this.durations.shift();\n }\n }\n\n async getNextAvailableTime(): Promise<Date> {\n return new Date(this.nextAvailableTime);\n }\n\n async setNextAvailableTime(date: Date): Promise<void> {\n const t = date.getTime();\n if (t > this.nextAvailableTime) {\n this.nextAvailableTime = t;\n }\n }\n\n async clear(): Promise<void> {\n this.durations = [];\n this.nextAvailableTime = Date.now();\n this.lastStartTime = 0;\n }\n}\n",
|
|
17
|
-
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { createServiceToken } from \"@workglow/util\";\n\nexport const JOB_LIMITER = createServiceToken<ILimiter>(\"jobqueue.limiter\");\n\n/**\n * Interface for a job limiter.\n */\nexport interface ILimiter {\n canProceed(): Promise<boolean>;\n recordJobStart(): Promise<void>;\n recordJobCompletion(): Promise<void>;\n getNextAvailableTime(): Promise<Date>;\n setNextAvailableTime(date: Date): Promise<void>;\n clear(): Promise<void>;\n}\n\nexport interface RateLimiterOptions {\n readonly maxExecutions: number;\n readonly windowSizeInSeconds: number;\n}\n\nexport interface RateLimiterWithBackoffOptions extends RateLimiterOptions {\n readonly initialBackoffDelay?: number;\n readonly backoffMultiplier?: number;\n readonly maxBackoffDelay?: number;\n}\n",
|
|
18
|
-
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { IRateLimiterStorage } from \"@workglow/storage\";\nimport { ILimiter, RateLimiterWithBackoffOptions } from \"./ILimiter\";\n\n/**\n * Base rate limiter implementation that uses a storage backend.\n * Manages request counts and delays to control job execution.\n */\nexport class RateLimiter implements ILimiter {\n protected readonly windowSizeInMilliseconds: number;\n protected currentBackoffDelay: number;\n protected readonly maxExecutions: number;\n protected readonly initialBackoffDelay: number;\n protected readonly backoffMultiplier: number;\n protected readonly maxBackoffDelay: number;\n\n constructor(\n protected readonly storage: IRateLimiterStorage,\n protected readonly queueName: string,\n {\n maxExecutions,\n windowSizeInSeconds,\n initialBackoffDelay = 1_000,\n backoffMultiplier = 2,\n maxBackoffDelay = 600_000, // 10 minutes\n }: RateLimiterWithBackoffOptions\n ) {\n if (maxExecutions <= 0) {\n throw new Error(\"maxExecutions must be greater than 0\");\n }\n if (windowSizeInSeconds <= 0) {\n throw new Error(\"windowSizeInSeconds must be greater than 0\");\n }\n if (initialBackoffDelay <= 0) {\n throw new Error(\"initialBackoffDelay must be greater than 0\");\n }\n if (backoffMultiplier <= 1) {\n throw new Error(\"backoffMultiplier must be greater than 1\");\n }\n if (maxBackoffDelay <= initialBackoffDelay) {\n throw new Error(\"maxBackoffDelay must be greater than initialBackoffDelay\");\n }\n\n this.windowSizeInMilliseconds = windowSizeInSeconds * 1000;\n this.maxExecutions = maxExecutions;\n this.initialBackoffDelay = initialBackoffDelay;\n this.backoffMultiplier = backoffMultiplier;\n this.maxBackoffDelay = maxBackoffDelay;\n this.currentBackoffDelay = initialBackoffDelay;\n }\n\n protected addJitter(base: number): number {\n // full jitter in [base, 2*base)\n return base + Math.random() * base;\n }\n\n protected increaseBackoff(): void {\n this.currentBackoffDelay = Math.min(\n this.currentBackoffDelay * this.backoffMultiplier,\n this.maxBackoffDelay\n );\n }\n\n /**\n * Checks if a job can proceed based on rate limiting rules.\n * @returns True if the job can proceed, false otherwise\n */\n async canProceed(): Promise<boolean> {\n // First check if the window allows more executions\n const windowStartTime = new Date(Date.now() - this.windowSizeInMilliseconds).toISOString();\n const attemptCount = await this.storage.getExecutionCount(this.queueName, windowStartTime);\n const canProceedNow = attemptCount < this.maxExecutions;\n\n // If the window allows more executions, clear any backoff and proceed\n if (canProceedNow) {\n // Clear any existing nextAvailableTime backoff since the window allows more executions\n const nextAvailableTime = await this.storage.getNextAvailableTime(this.queueName);\n if (nextAvailableTime && new Date(nextAvailableTime).getTime() > Date.now()) {\n // Clear the backoff by setting it to the past\n const pastTime = new Date(Date.now() - 1000);\n await this.storage.setNextAvailableTime(this.queueName, pastTime.toISOString());\n }\n this.currentBackoffDelay = this.initialBackoffDelay;\n return true;\n }\n\n // Window is full, check if there's a backoff delay\n const nextAvailableTime = await this.storage.getNextAvailableTime(this.queueName);\n if (nextAvailableTime && new Date(nextAvailableTime).getTime() > Date.now()) {\n this.increaseBackoff();\n return false;\n }\n\n // Window is full but no backoff delay, so we can't proceed\n this.increaseBackoff();\n return false;\n }\n\n /**\n * Records a new job attempt.\n */\n async recordJobStart(): Promise<void> {\n await this.storage.recordExecution(this.queueName);\n\n const windowStartTime = new Date(Date.now() - this.windowSizeInMilliseconds).toISOString();\n const attemptCount = await this.storage.getExecutionCount(this.queueName, windowStartTime);\n\n if (attemptCount >= this.maxExecutions) {\n const backoffExpires = new Date(Date.now() + this.addJitter(this.currentBackoffDelay));\n await this.setNextAvailableTime(backoffExpires);\n } else {\n // Window allows more executions, clear any existing nextAvailableTime by setting it to the past\n const nextAvailableTime = await this.storage.getNextAvailableTime(this.queueName);\n if (nextAvailableTime && new Date(nextAvailableTime).getTime() > Date.now()) {\n // Clear the backoff since the window now allows more executions\n // Set to a time in the past to effectively clear it\n const pastTime = new Date(Date.now() - 1000);\n await this.storage.setNextAvailableTime(this.queueName, pastTime.toISOString());\n }\n }\n }\n\n async recordJobCompletion(): Promise<void> {\n // Implementation can be no-op as completion doesn't affect rate limiting\n }\n\n /**\n * Retrieves the next available time for the specific queue.\n * @returns The next available time\n */\n async getNextAvailableTime(): Promise<Date> {\n // Get the time when the rate limit will allow the next job execution\n const oldestExecution = await this.storage.getOldestExecutionAtOffset(\n this.queueName,\n this.maxExecutions - 1\n );\n\n let rateLimitedTime = new Date();\n if (oldestExecution) {\n rateLimitedTime = new Date(oldestExecution);\n rateLimitedTime.setSeconds(\n rateLimitedTime.getSeconds() + this.windowSizeInMilliseconds / 1000\n );\n }\n\n // Get the next available time set externally, if any\n const nextAvailableStr = await this.storage.getNextAvailableTime(this.queueName);\n let nextAvailableTime = new Date();\n if (nextAvailableStr) {\n nextAvailableTime = new Date(nextAvailableStr);\n }\n\n return nextAvailableTime > rateLimitedTime ? nextAvailableTime : rateLimitedTime;\n }\n\n /**\n * Sets the next available time for the specific queue.\n * @param date - The new next available time\n */\n async setNextAvailableTime(date: Date): Promise<void> {\n await this.storage.setNextAvailableTime(this.queueName, date.toISOString());\n }\n\n /**\n * Clears all rate limit entries for this queue.\n */\n async clear(): Promise<void> {\n await this.storage.clear(this.queueName);\n this.currentBackoffDelay = this.initialBackoffDelay;\n }\n}\n"
|
|
10
|
+
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { IQueueStorage, JobStatus, JobStorageFormat, QueueChangePayload } from \"@workglow/storage\";\nimport { EventEmitter, getLogger } from \"@workglow/util\";\nimport { ILimiter } from \"../limiter/ILimiter\";\nimport { NullLimiter } from \"../limiter/NullLimiter\";\nimport { Job, JobClass } from \"./Job\";\nimport { JobQueueClient } from \"./JobQueueClient\";\nimport { JobQueueWorker } from \"./JobQueueWorker\";\nimport { classToStorage, storageToClass } from \"./JobStorageConverters\";\n\n/**\n * Statistics tracked for the job queue\n */\nexport interface JobQueueStats {\n readonly totalJobs: number;\n readonly completedJobs: number;\n readonly failedJobs: number;\n readonly abortedJobs: number;\n readonly retriedJobs: number;\n readonly disabledJobs: number;\n readonly averageProcessingTime?: number;\n readonly lastUpdateTime: Date;\n}\n\n/**\n * Events emitted by JobQueueServer\n */\nexport type JobQueueServerEventListeners<Input, Output> = {\n server_start: (queueName: string) => void;\n server_stop: (queueName: string) => void;\n job_start: (queueName: string, jobId: unknown) => void;\n job_complete: (queueName: string, jobId: unknown, output: Output) => void;\n job_error: (queueName: string, jobId: unknown, error: string) => void;\n job_disabled: (queueName: string, jobId: unknown) => void;\n job_retry: (queueName: string, jobId: unknown, runAfter: Date) => void;\n job_progress: (\n queueName: string,\n jobId: unknown,\n progress: number,\n message: string,\n details: Record<string, unknown> | null\n ) => void;\n};\n\nexport type JobQueueServerEvents = keyof JobQueueServerEventListeners<unknown, unknown>;\n\n/**\n * Options for creating a JobQueueServer\n */\nexport interface JobQueueServerOptions<Input, Output> {\n readonly storage: IQueueStorage<Input, Output>;\n readonly queueName: string;\n readonly limiter?: ILimiter;\n readonly workerCount?: number;\n readonly pollIntervalMs?: number;\n readonly deleteAfterCompletionMs?: number;\n readonly deleteAfterFailureMs?: number;\n readonly deleteAfterDisabledMs?: number;\n readonly cleanupIntervalMs?: number;\n /**\n * Max time each worker's `stop()` waits for in-flight jobs before forcing aborts.\n * Defaults to 30s. Set to 0 to abort immediately.\n */\n readonly stopTimeoutMs?: number;\n}\n\n/**\n * Server that coordinates multiple workers and manages the job queue lifecycle.\n * Handles stuck job recovery, cleanup, and aggregates statistics.\n */\nexport class JobQueueServer<\n Input,\n Output,\n QueueJob extends Job<Input, Output> = Job<Input, Output>,\n> {\n public readonly queueName: string;\n protected readonly storage: IQueueStorage<Input, Output>;\n protected readonly jobClass: JobClass<Input, Output>;\n public readonly limiter: ILimiter;\n protected readonly workerCount: number;\n protected readonly pollIntervalMs: number;\n protected readonly deleteAfterCompletionMs?: number;\n protected readonly deleteAfterFailureMs?: number;\n protected readonly deleteAfterDisabledMs?: number;\n protected readonly cleanupIntervalMs: number;\n protected readonly stopTimeoutMs?: number;\n\n protected readonly events = new EventEmitter<JobQueueServerEventListeners<Input, Output>>();\n protected readonly workers: JobQueueWorker<Input, Output, QueueJob>[] = [];\n protected readonly clients: Set<JobQueueClient<Input, Output>> = new Set();\n\n protected running = false;\n protected cleanupTimer: ReturnType<typeof setTimeout> | null = null;\n protected storageUnsubscribe: (() => void) | null = null;\n\n protected stats: JobQueueStats = {\n totalJobs: 0,\n completedJobs: 0,\n failedJobs: 0,\n abortedJobs: 0,\n retriedJobs: 0,\n disabledJobs: 0,\n lastUpdateTime: new Date(),\n };\n\n constructor(jobClass: JobClass<Input, Output>, options: JobQueueServerOptions<Input, Output>) {\n this.queueName = options.queueName;\n this.storage = options.storage;\n this.jobClass = jobClass;\n this.limiter = options.limiter ?? new NullLimiter();\n this.workerCount = options.workerCount ?? 1;\n this.pollIntervalMs = options.pollIntervalMs ?? 100;\n this.deleteAfterCompletionMs = options.deleteAfterCompletionMs;\n this.deleteAfterFailureMs = options.deleteAfterFailureMs;\n this.deleteAfterDisabledMs = options.deleteAfterDisabledMs;\n this.cleanupIntervalMs = options.cleanupIntervalMs ?? 10000;\n this.stopTimeoutMs = options.stopTimeoutMs;\n\n this.initializeWorkers();\n }\n\n /**\n * Start the server and all workers\n */\n public async start(): Promise<this> {\n if (this.running) {\n return this;\n }\n\n this.running = true;\n this.events.emit(\"server_start\", this.queueName);\n\n // Warn when a process-scoped limiter is paired with a cluster-scoped\n // queue storage. The user almost certainly meant their configured limit\n // to be enforced cluster-wide; with a process-scoped limiter they get\n // N× the limit (one bucket per process).\n if (\n this.limiter.scope === \"process\" &&\n this.storage.scope === \"cluster\" &&\n !(this.limiter instanceof NullLimiter)\n ) {\n getLogger().warn(\n \"Process-scoped limiter on cluster-scoped queue storage — limit is enforced per-process, not cluster-wide. Use a cluster-scoped rate limiter storage (Postgres/Supabase) for global enforcement.\",\n {\n queueName: this.queueName,\n limiterScope: this.limiter.scope,\n storage: this.storage.constructor.name,\n }\n );\n }\n\n // Fix stuck jobs from previous runs\n await this.fixupJobs();\n\n // Subscribe to storage changes to wake workers when new work arrives.\n // - Cross-process deployments rely on this for wake-up.\n // - Same-process attached clients are also primarily woken by the direct\n // `handleJobAdded` path on submit, but we keep the subscription as a\n // correctness backstop: some async backends (e.g. fake-indexeddb under\n // bun) have read-after-write visibility lag where a just-committed job\n // isn't yet returned by `storage.next()`. The subscription fires only\n // after the write is visible to readers, providing a guaranteed wake.\n // - Sqlite/Postgres throw here; the try/catch falls through and direct\n // notify is the sole wake path on those backends.\n try {\n this.storageUnsubscribe = this.storage.subscribeToChanges(\n (change: QueueChangePayload<Input, Output>) => {\n if (\n change.type === \"INSERT\" ||\n change.type === \"RESYNC\" ||\n (change.type === \"UPDATE\" && change.new?.status === JobStatus.PENDING)\n ) {\n this.notifyWorkers();\n }\n }\n );\n } catch (err) {\n // Storage doesn't support change subscriptions — workers will poll.\n // Logged at debug because Sqlite/Postgres throw here by design; an\n // unexpected throw on a storage that *should* support subscriptions\n // (e.g. misconfigured Supabase realtime) is otherwise silent.\n getLogger().debug(\"subscribeToChanges unsupported on this storage\", {\n queueName: this.queueName,\n error: err,\n });\n }\n\n // Start all workers\n await Promise.all(this.workers.map((worker) => worker.start()));\n\n // Start cleanup loop only if at least one retention TTL is configured.\n if (this.hasRetentionTtls()) {\n this.startCleanupLoop();\n }\n\n return this;\n }\n\n /**\n * True if any delete-after-X TTL is set to a positive value.\n * When false, the cleanup loop would be a pure no-op and is skipped.\n */\n private hasRetentionTtls(): boolean {\n return (\n (this.deleteAfterCompletionMs !== undefined && this.deleteAfterCompletionMs > 0) ||\n (this.deleteAfterFailureMs !== undefined && this.deleteAfterFailureMs > 0) ||\n (this.deleteAfterDisabledMs !== undefined && this.deleteAfterDisabledMs > 0)\n );\n }\n\n /**\n * Stop the server and all workers\n */\n public async stop(): Promise<this> {\n if (!this.running) {\n return this;\n }\n\n this.running = false;\n\n // Unsubscribe from storage changes\n if (this.storageUnsubscribe) {\n this.storageUnsubscribe();\n this.storageUnsubscribe = null;\n }\n\n // Stop cleanup loop\n if (this.cleanupTimer) {\n clearTimeout(this.cleanupTimer);\n this.cleanupTimer = null;\n }\n\n // Stop all workers\n await Promise.all(this.workers.map((worker) => worker.stop()));\n\n this.events.emit(\"server_stop\", this.queueName);\n return this;\n }\n\n /**\n * Get the current queue statistics\n */\n public getStats(): JobQueueStats {\n return { ...this.stats };\n }\n\n /**\n * Get the storage instance (for client connection)\n */\n public getStorage(): IQueueStorage<Input, Output> {\n return this.storage;\n }\n\n /**\n * Scale the number of workers\n */\n public async scaleWorkers(count: number): Promise<void> {\n if (count < 1) {\n throw new Error(\"Worker count must be at least 1\");\n }\n\n const currentCount = this.workers.length;\n\n if (count > currentCount) {\n // Add more workers\n for (let i = currentCount; i < count; i++) {\n const worker = this.createWorker();\n this.workers.push(worker);\n if (this.running) {\n await worker.start();\n }\n }\n } else if (count < currentCount) {\n // Remove workers\n const toRemove = this.workers.splice(count);\n await Promise.all(toRemove.map((worker) => worker.stop()));\n }\n }\n\n /**\n * Check if the server is running\n */\n public isRunning(): boolean {\n return this.running;\n }\n\n /**\n * Get the number of workers\n */\n public getWorkerCount(): number {\n return this.workers.length;\n }\n\n // ========================================================================\n // Client management\n // ========================================================================\n\n /**\n * Add a client for same-process event forwarding\n * @internal\n */\n public addClient(client: JobQueueClient<Input, Output>): void {\n this.clients.add(client);\n }\n\n /**\n * Remove a client\n * @internal\n */\n public removeClient(client: JobQueueClient<Input, Output>): void {\n this.clients.delete(client);\n }\n\n /**\n * Wake all idle workers so they check for new jobs immediately.\n * @internal\n */\n public notifyWorkers(): void {\n for (const worker of this.workers) {\n worker.notify();\n }\n }\n\n /**\n * Called by an attached client immediately after a job is inserted into storage,\n * so the worker can pick it up without waiting for the poll interval.\n * @internal\n */\n public handleJobAdded(_jobId: unknown): void {\n this.notifyWorkers();\n }\n\n /**\n * Fire the abort controller for the given job on whichever worker is running it.\n * Returns true if any worker handled the abort locally.\n * @internal\n */\n public abortJob(jobId: unknown): boolean {\n for (const worker of this.workers) {\n if (worker.requestAbort(jobId)) {\n return true;\n }\n }\n return false;\n }\n\n // ========================================================================\n // Event handling\n // ========================================================================\n\n public on<Event extends JobQueueServerEvents>(\n event: Event,\n listener: JobQueueServerEventListeners<Input, Output>[Event]\n ): void {\n this.events.on(event, listener);\n }\n\n public off<Event extends JobQueueServerEvents>(\n event: Event,\n listener: JobQueueServerEventListeners<Input, Output>[Event]\n ): void {\n this.events.off(event, listener);\n }\n\n // ========================================================================\n // Protected methods\n // ========================================================================\n\n /**\n * Initialize workers\n */\n protected initializeWorkers(): void {\n for (let i = 0; i < this.workerCount; i++) {\n const worker = this.createWorker();\n this.workers.push(worker);\n }\n }\n\n /**\n * Create a new worker and wire up event forwarding\n */\n protected createWorker(): JobQueueWorker<Input, Output, QueueJob> {\n const worker = new JobQueueWorker<Input, Output, QueueJob>(this.jobClass, {\n storage: this.storage,\n queueName: this.queueName,\n limiter: this.limiter,\n pollIntervalMs: this.pollIntervalMs,\n stopTimeoutMs: this.stopTimeoutMs,\n });\n\n // Forward worker events to server and clients\n worker.on(\"job_start\", (jobId) => {\n this.stats = { ...this.stats, totalJobs: this.stats.totalJobs + 1 };\n this.events.emit(\"job_start\", this.queueName, jobId);\n this.forwardToClients(\"handleJobStart\", jobId);\n });\n\n worker.on(\"job_complete\", (jobId, output) => {\n this.stats = { ...this.stats, completedJobs: this.stats.completedJobs + 1 };\n this.updateAverageProcessingTime();\n this.events.emit(\"job_complete\", this.queueName, jobId, output);\n this.forwardToClients(\"handleJobComplete\", jobId, output);\n\n // Immediate deletion when configured\n if (this.deleteAfterCompletionMs === 0) {\n this.storage.delete(jobId).catch((err) => {\n console.error(\"Error deleting job after completion:\", err);\n });\n }\n\n // A concurrency slot freed up — wake idle workers\n this.notifyWorkers();\n });\n\n worker.on(\"job_error\", (jobId, error, errorCode) => {\n this.stats = { ...this.stats, failedJobs: this.stats.failedJobs + 1 };\n this.events.emit(\"job_error\", this.queueName, jobId, error);\n this.forwardToClients(\"handleJobError\", jobId, error, errorCode);\n\n // Immediate deletion when configured\n if (this.deleteAfterFailureMs === 0) {\n this.storage.delete(jobId).catch((err) => {\n console.error(\"Error deleting job after error:\", err);\n });\n }\n\n // A concurrency slot freed up — wake idle workers\n this.notifyWorkers();\n });\n\n worker.on(\"job_disabled\", (jobId) => {\n this.stats = { ...this.stats, disabledJobs: this.stats.disabledJobs + 1 };\n this.events.emit(\"job_disabled\", this.queueName, jobId);\n this.forwardToClients(\"handleJobDisabled\", jobId);\n\n // Immediate deletion when configured\n if (this.deleteAfterDisabledMs === 0) {\n this.storage.delete(jobId).catch((err) => {\n console.error(\"Error deleting job after disabling:\", err);\n });\n }\n\n // A concurrency slot freed up — wake idle workers\n this.notifyWorkers();\n });\n\n worker.on(\"job_retry\", (jobId, runAfter) => {\n this.stats = { ...this.stats, retriedJobs: this.stats.retriedJobs + 1 };\n this.events.emit(\"job_retry\", this.queueName, jobId, runAfter);\n this.forwardToClients(\"handleJobRetry\", jobId, runAfter);\n });\n\n worker.on(\"job_progress\", (jobId, progress, message, details) => {\n this.events.emit(\"job_progress\", this.queueName, jobId, progress, message, details);\n this.forwardToClients(\"handleJobProgress\", jobId, progress, message, details);\n });\n\n return worker;\n }\n\n /**\n * Forward events to all attached clients\n */\n protected forwardToClients(method: \"handleJobStart\", jobId: unknown): void;\n protected forwardToClients(method: \"handleJobComplete\", jobId: unknown, output: Output): void;\n protected forwardToClients(\n method: \"handleJobError\",\n jobId: unknown,\n error: string,\n errorCode?: string\n ): void;\n protected forwardToClients(method: \"handleJobDisabled\", jobId: unknown): void;\n protected forwardToClients(method: \"handleJobRetry\", jobId: unknown, runAfter: Date): void;\n protected forwardToClients(\n method: \"handleJobProgress\",\n jobId: unknown,\n progress: number,\n message: string,\n details: Record<string, unknown> | null\n ): void;\n protected forwardToClients(method: string, ...args: unknown[]): void {\n for (const client of this.clients) {\n const fn = (client as any)[method];\n if (typeof fn === \"function\") {\n fn.apply(client, args);\n }\n }\n }\n\n /**\n * Update average processing time from all workers\n */\n protected updateAverageProcessingTime(): void {\n const times: number[] = [];\n for (const worker of this.workers) {\n const avgTime = worker.getAverageProcessingTime();\n if (avgTime !== undefined) {\n times.push(avgTime);\n }\n }\n if (times.length > 0) {\n const avg = times.reduce((a, b) => a + b, 0) / times.length;\n this.stats = {\n ...this.stats,\n averageProcessingTime: avg,\n lastUpdateTime: new Date(),\n };\n }\n }\n\n /**\n * Start the cleanup loop\n */\n protected startCleanupLoop(): void {\n if (!this.running) return;\n\n this.cleanupJobs().finally(() => {\n if (this.running) {\n this.cleanupTimer = setTimeout(() => this.startCleanupLoop(), this.cleanupIntervalMs);\n }\n });\n }\n\n /**\n * Clean up completed/failed jobs based on TTL settings\n */\n protected async cleanupJobs(): Promise<void> {\n try {\n // The workers will handle the abort via their abort controllers\n // We just need to ensure the jobs get marked as failed\n\n // Delete completed jobs after TTL\n if (this.deleteAfterCompletionMs !== undefined && this.deleteAfterCompletionMs > 0) {\n await this.storage.deleteJobsByStatusAndAge(\n JobStatus.COMPLETED,\n this.deleteAfterCompletionMs\n );\n }\n\n // Delete failed jobs after TTL\n if (this.deleteAfterFailureMs !== undefined && this.deleteAfterFailureMs > 0) {\n await this.storage.deleteJobsByStatusAndAge(JobStatus.FAILED, this.deleteAfterFailureMs);\n }\n\n // Delete disabled jobs after TTL\n if (this.deleteAfterDisabledMs !== undefined && this.deleteAfterDisabledMs > 0) {\n await this.storage.deleteJobsByStatusAndAge(JobStatus.DISABLED, this.deleteAfterDisabledMs);\n }\n } catch (error) {\n console.error(\"Error in cleanup:\", error);\n }\n }\n\n /**\n * Fix stuck jobs from previous server runs.\n * Jobs in PROCESSING or ABORTING state that are not owned by any of the current\n * server's workers are considered orphaned and will be reset.\n */\n protected async fixupJobs(): Promise<void> {\n try {\n const stuckProcessingJobs = await this.storage.peek(JobStatus.PROCESSING);\n const stuckAbortingJobs = await this.storage.peek(JobStatus.ABORTING);\n const stuckJobs = [...stuckProcessingJobs, ...stuckAbortingJobs];\n\n // Get the worker IDs of all workers managed by this server\n const currentWorkerIds = new Set(this.getWorkerIds());\n\n for (const jobData of stuckJobs) {\n // Skip jobs that belong to workers in this server (they may still be processing)\n if (jobData.worker_id && currentWorkerIds.has(jobData.worker_id)) {\n continue;\n }\n\n const job = this.storageToClass(jobData);\n if (job.runAttempts >= job.maxRetries) {\n job.status = JobStatus.FAILED;\n job.error = \"Max retries reached\";\n job.errorCode = \"MAX_RETRIES_REACHED\";\n // Clear worker_id since job is now failed\n job.workerId = null;\n } else {\n job.status = JobStatus.PENDING;\n job.runAfter = job.lastRanAt || new Date();\n job.progress = 0;\n job.progressMessage = \"\";\n job.progressDetails = null;\n job.error = \"Server restarted\";\n // Clear worker_id so a new worker can claim this job\n job.workerId = null;\n }\n\n await this.storage.complete(this.classToStorage(job));\n }\n } catch (error) {\n console.error(\"Error in fixupJobs:\", error);\n }\n }\n\n /**\n * Convert storage format to Job class\n */\n protected storageToClass(details: JobStorageFormat<Input, Output>): Job<Input, Output> {\n return storageToClass(details, this.jobClass);\n }\n\n /**\n * Convert Job class to storage format\n */\n protected classToStorage(job: Job<Input, Output>): JobStorageFormat<Input, Output> {\n return classToStorage(job, this.queueName);\n }\n\n /**\n * Get the worker IDs of all workers managed by this server\n */\n public getWorkerIds(): string[] {\n return this.workers.map((worker) => worker.workerId);\n }\n}\n",
|
|
11
|
+
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { createServiceToken } from \"@workglow/util\";\nimport { ILimiter, LimiterScope } from \"./ILimiter\";\n\nexport const NULL_JOB_LIMITER = createServiceToken<ILimiter>(\"jobqueue.limiter.null\");\n\n/**\n * Null limiter that does nothing.\n */\nexport class NullLimiter implements ILimiter {\n public readonly scope: LimiterScope = \"process\";\n\n /** Sentinel token — non-null so callers' truthy checks see it as a success. */\n private static readonly SENTINEL = Symbol(\"NullLimiter.acquired\");\n\n async tryAcquire(): Promise<unknown | null> {\n return NullLimiter.SENTINEL;\n }\n\n async release(_token: unknown): Promise<void> {\n // Do nothing\n }\n\n async canProceed(): Promise<boolean> {\n return true;\n }\n\n async recordJobStart(): Promise<void> {\n // Do nothing\n }\n\n async recordJobCompletion(): Promise<void> {\n // Do nothing\n }\n\n async getNextAvailableTime(): Promise<Date> {\n return new Date();\n }\n\n async setNextAvailableTime(date: Date): Promise<void> {\n // Do nothing\n }\n\n async clear(): Promise<void> {\n // Do nothing\n }\n}\n",
|
|
12
|
+
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { IQueueStorage, JobStatus, JobStorageFormat } from \"@workglow/storage\";\nimport {\n EventEmitter,\n getLogger,\n getTelemetryProvider,\n sleep,\n SpanStatusCode,\n uuid4,\n} from \"@workglow/util\";\nimport { ILimiter } from \"../limiter/ILimiter\";\nimport { NullLimiter } from \"../limiter/NullLimiter\";\nimport { Job, JobClass } from \"./Job\";\nimport {\n AbortSignalJobError,\n JobDisabledError,\n JobError,\n JobNotFoundError,\n PermanentJobError,\n RetryableJobError,\n} from \"./JobError\";\nimport { withJobErrorDiagnostics } from \"./JobErrorDiagnostics\";\nimport { classToStorage, storageToClass } from \"./JobStorageConverters\";\n\n/**\n * Upper bound on {@link JobQueueWorker.getLimiterWakeDelay}. Prevents a\n * misconfigured or stuck limiter (e.g. one whose `getNextAvailableTime`\n * returns hours in the future) from making the worker unresponsive — it\n * wakes at least this often regardless of what the limiter says.\n */\nconst MAX_LIMITER_WAKE_MS = 30_000;\n\n/**\n * Events emitted by JobQueueWorker\n */\nexport type JobQueueWorkerEventListeners<Input, Output> = {\n job_start: (jobId: unknown) => void;\n job_complete: (jobId: unknown, output: Output) => void;\n job_error: (jobId: unknown, error: string, errorCode?: string) => void;\n job_disabled: (jobId: unknown) => void;\n job_retry: (jobId: unknown, runAfter: Date) => void;\n job_progress: (\n jobId: unknown,\n progress: number,\n message: string,\n details: Record<string, unknown> | null\n ) => void;\n worker_start: () => void;\n worker_stop: () => void;\n};\n\nexport type JobQueueWorkerEvents = keyof JobQueueWorkerEventListeners<unknown, unknown>;\n\n/**\n * Options for creating a JobQueueWorker\n */\nexport interface JobQueueWorkerOptions<Input, Output> {\n readonly storage: IQueueStorage<Input, Output>;\n readonly queueName: string;\n readonly limiter?: ILimiter;\n readonly pollIntervalMs?: number;\n /**\n * Optional worker ID. If not provided, a random UUID will be generated.\n * Use a persistent ID if you want the worker to reclaim its own jobs after restart.\n */\n readonly workerId?: string | null;\n /**\n * Max time `stop()` waits for in-flight jobs to finish before forcing aborts.\n * Defaults to 30s. Set to 0 to abort immediately.\n */\n readonly stopTimeoutMs?: number;\n}\n\n/**\n * Worker that processes jobs from the queue.\n * Reports progress and completion back to storage.\n */\nexport class JobQueueWorker<\n Input,\n Output,\n QueueJob extends Job<Input, Output> = Job<Input, Output>,\n> {\n public readonly queueName: string;\n public readonly workerId: string;\n protected readonly storage: IQueueStorage<Input, Output>;\n protected readonly jobClass: JobClass<Input, Output>;\n protected readonly limiter: ILimiter;\n protected readonly pollIntervalMs: number;\n protected readonly stopTimeoutMs: number;\n protected readonly events = new EventEmitter<JobQueueWorkerEventListeners<Input, Output>>();\n\n protected running = false;\n\n /**\n * Tracks in-flight job executions for drain-on-stop.\n * Each entry's promise resolves (never rejects) when the job settles\n * (complete / fail / retry / abort).\n */\n private readonly inFlight: Map<unknown, Promise<void>> = new Map();\n\n /**\n * Resolve function for the idle wait promise.\n * When set, the worker is idle and waiting for either a notification or poll timeout.\n */\n private wakeResolve: (() => void) | null = null;\n private wakeTimer: ReturnType<typeof setTimeout> | null = null;\n\n /**\n * Set when {@link notify} fires while the worker is not yet idle. The next\n * {@link waitForWakeOrTimeout} call consumes it and returns immediately\n * instead of sleeping. Without this flag, a notify that arrives during\n * `processJobs`'s pre-idle awaits (e.g. while `storage.next` and\n * `storage.peek` are in flight) would be dropped on the floor and the\n * worker would sleep for the full poll interval despite there being work\n * to do — observed under load on IndexedDb.\n */\n private wakePending = false;\n\n /**\n * Promise for the running `processJobs` loop. Captured in {@link start} so\n * {@link stop} can await actual loop exit instead of returning while the\n * loop is still suspended mid-iteration. Without this, a loop parked in\n * `await this.next()` could resume after stop returned and claim a job\n * that was submitted after stop — starting `processSingleJob` on a worker\n * that's no longer running.\n */\n private loopPromise: Promise<void> | null = null;\n\n /**\n * Abort controllers for active jobs\n */\n protected readonly activeJobAbortControllers: Map<unknown, AbortController> = new Map();\n\n /**\n * Processing times for statistics\n */\n protected readonly processingTimes: Map<unknown, number> = new Map();\n\n constructor(jobClass: JobClass<Input, Output>, options: JobQueueWorkerOptions<Input, Output>) {\n this.queueName = options.queueName;\n this.workerId = options.workerId ?? uuid4();\n this.storage = options.storage;\n this.jobClass = jobClass;\n this.limiter = options.limiter ?? new NullLimiter();\n this.pollIntervalMs = options.pollIntervalMs ?? 100;\n this.stopTimeoutMs = options.stopTimeoutMs ?? 30_000;\n }\n\n /**\n * Start the worker processing loop\n */\n public async start(): Promise<this> {\n if (this.running) {\n return this;\n }\n this.running = true;\n this.events.emit(\"worker_start\");\n this.loopPromise = this.processJobs();\n return this;\n }\n\n /**\n * If this worker is currently processing the given job, fire its abort controller\n * immediately and return true. Returns false if the job isn't active on this worker.\n * @internal\n */\n public requestAbort(jobId: unknown): boolean {\n const controller = this.activeJobAbortControllers.get(jobId);\n if (controller && !controller.signal.aborted) {\n controller.abort();\n return true;\n }\n return false;\n }\n\n /**\n * Wake the worker from idle sleep so it checks for jobs immediately.\n * If the worker is not yet idle, latches a pending-wake flag that the next\n * {@link waitForWakeOrTimeout} call consumes — so wake notifications that\n * race with worker startup or mid-iteration awaits are not lost.\n */\n public notify(): void {\n this.wakePending = true;\n if (this.wakeResolve) {\n if (this.wakeTimer) {\n clearTimeout(this.wakeTimer);\n this.wakeTimer = null;\n }\n const resolve = this.wakeResolve;\n this.wakeResolve = null;\n resolve();\n }\n }\n\n /**\n * Stop the worker, draining in-flight jobs up to {@link stopTimeoutMs}\n * before aborting anything still running.\n */\n public async stop(): Promise<this> {\n if (!this.running) {\n return this;\n }\n this.running = false;\n\n // Wake from idle sleep so the processing loop can exit.\n this.notify();\n\n // Wait for the processJobs loop to actually exit. Without this, a loop\n // suspended mid-iteration (e.g. inside `await this.next()`) could resume\n // after stop returned and claim/start a freshly-submitted job — leaving\n // a \"stopped\" worker running PROCESSING jobs.\n const loopPromise = this.loopPromise;\n this.loopPromise = null;\n if (loopPromise) {\n await loopPromise;\n }\n\n // Phase 1 — graceful drain.\n if (this.stopTimeoutMs > 0 && this.inFlight.size > 0) {\n const drain = Promise.allSettled([...this.inFlight.values()]);\n await Promise.race([drain, sleep(this.stopTimeoutMs)]);\n }\n\n // Phase 2 — anything still running gets aborted.\n if (this.inFlight.size > 0) {\n for (const controller of this.activeJobAbortControllers.values()) {\n if (!controller.signal.aborted) {\n controller.abort();\n }\n }\n const abortDrain = Promise.allSettled([...this.inFlight.values()]);\n await Promise.race([abortDrain, sleep(1000)]);\n }\n\n this.events.emit(\"worker_stop\");\n return this;\n }\n\n /**\n * Process a single job manually (useful for testing or manual control).\n *\n * Uses the atomic claim->acquire->release pattern: claim a job, then atomically\n * reserve a limiter slot. If the limiter rejects (e.g. raced another worker\n * to the last slot), the claimed job is released back to PENDING.\n */\n public async processNext(): Promise<boolean> {\n const job = await this.next();\n if (!job) {\n return false;\n }\n const limiterToken = await this.limiter.tryAcquire();\n if (limiterToken === null || limiterToken === undefined) {\n await this.releaseClaimedJob(job);\n return false;\n }\n await this.processSingleJob(job, limiterToken);\n return true;\n }\n\n /**\n * Check if the worker is currently running\n */\n public isRunning(): boolean {\n return this.running;\n }\n\n /**\n * Get the number of active jobs being processed\n */\n public getActiveJobCount(): number {\n return this.activeJobAbortControllers.size;\n }\n\n /**\n * Get average processing time\n */\n public getAverageProcessingTime(): number | undefined {\n const times = Array.from(this.processingTimes.values());\n if (times.length === 0) return undefined;\n return times.reduce((a, b) => a + b, 0) / times.length;\n }\n\n // ========================================================================\n // Event handling\n // ========================================================================\n\n public on<Event extends JobQueueWorkerEvents>(\n event: Event,\n listener: JobQueueWorkerEventListeners<Input, Output>[Event]\n ): void {\n this.events.on(event, listener);\n }\n\n public off<Event extends JobQueueWorkerEvents>(\n event: Event,\n listener: JobQueueWorkerEventListeners<Input, Output>[Event]\n ): void {\n this.events.off(event, listener);\n }\n\n // ========================================================================\n // Protected methods\n // ========================================================================\n\n /**\n * Get the next job from the queue\n */\n protected async next(): Promise<QueueJob | undefined> {\n const job = await this.storage.next(this.workerId);\n if (!job) return undefined;\n return this.storageToClass(job) as QueueJob;\n }\n\n /**\n * Main job processing loop.\n *\n * Runs as a tight `while` loop (no recursive `setTimeout`) so that\n * back-to-back jobs are picked up with minimal inter-job latency.\n *\n * When no jobs are available the worker sleeps until either:\n * - {@link notify} is called (e.g. because the server saw a new job inserted\n * or a running job completed and freed a concurrency slot), or\n * - the poll-interval timeout expires (fallback for storages without push\n * events).\n */\n protected async processJobs(): Promise<void> {\n while (this.running) {\n try {\n // Check for aborting jobs\n await this.checkForAbortingJobs();\n\n // Claim first, then atomically reserve a limiter slot. Doing the claim\n // before the acquire avoids a race window: with the previous \"check\n // canProceed -> claim -> recordJobStart\" sequence, two workers could\n // both observe count < max, both claim, and both then increment —\n // overshooting the configured limit by exactly the worker concurrency.\n // The atomic tryAcquire guarantees only one of N concurrent acquirers\n // succeeds when there's one slot left.\n const job = await this.next();\n if (!job) {\n // Queue is empty — sleep until notified of new work or until the\n // next deferred job becomes ready.\n const delay = await this.getIdleDelay();\n await this.waitForWakeOrTimeout(delay);\n continue;\n }\n\n if (!this.running) {\n // Stopped during the await. Release the job back to PENDING.\n await this.releaseClaimedJob(job);\n return;\n }\n\n const limiterToken = await this.limiter.tryAcquire();\n if (limiterToken === null || limiterToken === undefined) {\n // Lost the race for the last slot, or hit the rate-limit window.\n // Give the job back and wait for capacity.\n await this.releaseClaimedJob(job);\n await this.waitForWakeOrTimeout(await this.getLimiterWakeDelay());\n continue;\n }\n\n if (!this.running) {\n // Stop fired while tryAcquire was in flight. Undo both the limiter\n // reservation (release THIS token, not \"the most recent\") and the\n // job claim so we don't start processing on a worker that's about\n // to exit.\n try {\n await this.limiter.release(limiterToken);\n } catch {\n // best-effort\n }\n await this.releaseClaimedJob(job);\n return;\n }\n\n // Don't await - process in background to allow concurrent jobs.\n // The loop will claim+acquire on the next iteration.\n this.processSingleJob(job, limiterToken);\n } catch {\n // Don't let transient errors kill the loop\n await sleep(this.pollIntervalMs);\n }\n }\n }\n\n /**\n * How long to sleep when the limiter rejected an acquire. Reads the limiter's\n * own next-available time so we wake exactly when capacity opens, instead of\n * polling every {@link pollIntervalMs}. Clamped to {@link pollIntervalMs}\n * (lower bound) and {@link MAX_LIMITER_WAKE_MS} (upper bound) so a\n * misconfigured/stuck limiter still wakes occasionally.\n */\n private async getLimiterWakeDelay(): Promise<number> {\n try {\n const next = await this.limiter.getNextAvailableTime();\n const delay = next.getTime() - Date.now();\n if (delay <= 0) return this.pollIntervalMs;\n return Math.min(Math.max(delay, this.pollIntervalMs), MAX_LIMITER_WAKE_MS);\n } catch {\n return this.pollIntervalMs;\n }\n }\n\n /**\n * Determine how long to sleep when idle.\n *\n * Peeks at the earliest PENDING job: if it has a future `run_after`,\n * returns the time until it becomes ready (clamped to `pollIntervalMs`);\n * otherwise returns `pollIntervalMs`.\n */\n private async getIdleDelay(): Promise<number> {\n try {\n const pending = await this.storage.peek(JobStatus.PENDING, 1);\n if (pending.length > 0 && pending[0].run_after) {\n const delay = new Date(pending[0].run_after).getTime() - Date.now();\n if (delay > 0) {\n return Math.min(delay, this.pollIntervalMs);\n }\n }\n } catch {\n // If peek fails, fall back to default\n }\n return this.pollIntervalMs;\n }\n\n /**\n * Wait for either a {@link notify} call or the given timeout,\n * whichever comes first. Consumes any pending wake latched while the worker\n * was not yet idle (see {@link wakePending}) — returns immediately in that\n * case rather than sleeping.\n */\n private waitForWakeOrTimeout(timeoutMs: number): Promise<void> {\n if (this.wakePending) {\n this.wakePending = false;\n return Promise.resolve();\n }\n return new Promise<void>((resolve) => {\n this.wakeTimer = setTimeout(() => {\n this.wakeTimer = null;\n this.wakeResolve = null;\n this.wakePending = false;\n resolve();\n }, timeoutMs);\n\n this.wakeResolve = () => {\n this.wakePending = false;\n resolve();\n };\n });\n }\n\n /**\n * Check for jobs that have been marked for abort and trigger their abort controllers.\n *\n * Only relevant for jobs running on THIS worker (we have an abort controller\n * registered for them). When no jobs are active, the peek result is irrelevant\n * — skip the storage round-trip entirely. Important for battery life on\n * same-process deployments (browser/mobile) where workers spend most time idle.\n */\n protected async checkForAbortingJobs(): Promise<void> {\n if (this.activeJobAbortControllers.size === 0) {\n return;\n }\n const abortingJobs = await this.storage.peek(JobStatus.ABORTING);\n for (const jobData of abortingJobs) {\n const controller = this.activeJobAbortControllers.get(jobData.id);\n if (controller && !controller.signal.aborted) {\n controller.abort();\n }\n }\n }\n\n /**\n * Process a single job\n */\n protected async processSingleJob(\n job: Job<Input, Output>,\n limiterToken: unknown\n ): Promise<void> {\n if (!job || !job.id) {\n throw new JobNotFoundError(\"Invalid job provided for processing\");\n }\n\n const { promise: inFlightPromise, resolve: resolveInFlight } = Promise.withResolvers<void>();\n this.inFlight.set(job.id, inFlightPromise);\n\n const startTime = Date.now();\n\n // Start telemetry span for job processing\n const telemetry = getTelemetryProvider();\n const span = telemetry.isEnabled\n ? telemetry.startSpan(\"workglow.job.process\", {\n attributes: {\n \"workglow.job.id\": String(job.id),\n \"workglow.job.queue\": this.queueName,\n \"workglow.job.worker_id\": this.workerId,\n \"workglow.job.run_attempt\": job.runAttempts,\n \"workglow.job.max_retries\": job.maxRetries,\n },\n })\n : undefined;\n\n // Set when validateJobState fails and we release() the limiter slot\n // ourselves — the finally block then skips recordJobCompletion to avoid\n // double-decrementing limiters where release() and recordJobCompletion()\n // both decrement (e.g. ConcurrencyLimiter).\n let slotReleased = false;\n try {\n // The limiter slot was already atomically reserved by tryAcquire() in\n // the main loop (or processNext), so we no longer call recordJobStart\n // here — doing so would double-count.\n try {\n await this.validateJobState(job);\n } catch (validationErr) {\n // Validation failed before we ran any actual work — release THIS\n // limiter slot (by token, not by recency) so it doesn't count toward\n // the rate limit and we don't accidentally release another worker's\n // slot.\n try {\n await this.limiter.release(limiterToken);\n slotReleased = true;\n } catch {\n // best-effort\n }\n throw validationErr;\n }\n\n const abortController = this.createAbortController(job.id);\n this.events.emit(\"job_start\", job.id);\n\n const output = await this.executeJob(job, abortController.signal);\n await this.completeJob(job, output);\n\n const elapsed = Date.now() - startTime;\n this.processingTimes.set(job.id, elapsed);\n\n if (span) {\n span.setAttributes({ \"workglow.job.duration_ms\": elapsed });\n span.setStatus(SpanStatusCode.OK);\n }\n } catch (err: unknown) {\n const error = this.normalizeError(err);\n let spanErrorMessage = error.message;\n if (error instanceof RetryableJobError) {\n const currentJob = await this.getJob(job.id);\n if (!currentJob) {\n throw new JobNotFoundError(`Job ${job.id} not found`);\n }\n\n if (currentJob.runAttempts >= currentJob.maxRetries) {\n spanErrorMessage = \"Max retries reached\";\n await this.failJob(currentJob, new PermanentJobError(spanErrorMessage));\n span?.setStatus(SpanStatusCode.ERROR, spanErrorMessage);\n } else {\n await this.rescheduleJob(currentJob, error.retryDate);\n span?.addEvent(\"workglow.job.retry\", {\n \"workglow.job.run_attempt\": currentJob.runAttempts,\n });\n span?.setStatus(SpanStatusCode.UNSET);\n }\n } else {\n await this.failJob(job, error);\n span?.setStatus(SpanStatusCode.ERROR, error.message);\n }\n span?.setAttributes({ \"workglow.job.error\": spanErrorMessage });\n } finally {\n span?.end();\n try {\n if (!slotReleased) {\n await this.limiter.recordJobCompletion();\n }\n } finally {\n this.inFlight.delete(job.id);\n resolveInFlight();\n }\n }\n }\n\n /**\n * Execute a job with the provided abort signal\n */\n protected async executeJob(job: Job<Input, Output>, signal: AbortSignal): Promise<Output> {\n if (!job) throw new JobNotFoundError(\"Cannot execute null or undefined job\");\n return await job.execute(job.input, {\n signal,\n updateProgress: this.updateProgress.bind(this, job.id),\n });\n }\n\n /**\n * Update progress for a job.\n *\n * Mid-job progress is delivered in-memory via the `job_progress` event;\n * storage is only touched at terminal transitions (complete / fail / retry).\n * Cross-process observers therefore see state transitions but not fine-grained\n * progress — subscribe to an attached `JobQueueClient` for that.\n */\n protected async updateProgress(\n jobId: unknown,\n progress: number,\n message: string = \"\",\n details: Record<string, unknown> | null = null\n ): Promise<void> {\n progress = Math.max(0, Math.min(100, progress));\n this.events.emit(\"job_progress\", jobId, progress, message, details);\n }\n\n /**\n * Mark a job as completed\n */\n protected async completeJob(job: Job<Input, Output>, output?: Output): Promise<void> {\n try {\n job.status = JobStatus.COMPLETED;\n job.progress = 100;\n job.progressMessage = \"\";\n job.progressDetails = null;\n job.completedAt = new Date();\n job.output = output ?? null;\n job.error = null;\n job.errorCode = null;\n\n await this.storage.complete(this.classToStorage(job));\n this.events.emit(\"job_complete\", job.id, output as Output);\n } catch (err) {\n getLogger().error(\"completeJob errored:\", { error: err });\n } finally {\n this.cleanupJob(job.id);\n }\n }\n\n /**\n * Mark a job as failed\n */\n protected async failJob(job: Job<Input, Output>, error: JobError): Promise<void> {\n try {\n job.status = JobStatus.FAILED;\n job.progress = 100;\n job.completedAt = new Date();\n job.progressMessage = \"\";\n job.progressDetails = null;\n job.error = error.message;\n job.errorCode = error?.constructor?.name ?? null;\n\n await this.storage.complete(this.classToStorage(job));\n this.events.emit(\"job_error\", job.id, error.message, error.constructor.name);\n } catch (err) {\n getLogger().error(\"failJob errored:\", { error: err });\n } finally {\n this.cleanupJob(job.id);\n }\n }\n\n /**\n * Mark a job as disabled\n */\n protected async disableJob(job: Job<Input, Output>): Promise<void> {\n try {\n job.status = JobStatus.DISABLED;\n job.progress = 100;\n job.completedAt = new Date();\n job.progressMessage = \"\";\n job.progressDetails = null;\n\n await this.storage.complete(this.classToStorage(job));\n this.events.emit(\"job_disabled\", job.id);\n } catch (err) {\n getLogger().error(\"disableJob errored:\", { error: err });\n } finally {\n this.cleanupJob(job.id);\n }\n }\n\n /**\n * Release a job that {@link next} just claimed but that we won't process\n * because the worker was stopped mid-claim. Resets the row to PENDING so\n * the next started worker can pick it up. `fixupJobs()` would otherwise\n * skip it (it ignores rows owned by current-server worker IDs).\n *\n * Uses `storage.release()` rather than `storage.complete()` so the retry\n * budget isn't burned: the worker never actually attempted execution.\n */\n protected async releaseClaimedJob(job: Job<Input, Output>): Promise<void> {\n try {\n await this.storage.release(job.id);\n } catch (err) {\n getLogger().error(\"releaseClaimedJob errored:\", { error: err });\n }\n }\n\n /**\n * Reschedule a job for retry\n */\n protected async rescheduleJob(job: Job<Input, Output>, retryDate?: Date): Promise<void> {\n try {\n job.status = JobStatus.PENDING;\n const nextAvailableTime = await this.limiter.getNextAvailableTime();\n job.runAfter = retryDate instanceof Date ? retryDate : nextAvailableTime;\n job.progress = 0;\n job.progressMessage = \"\";\n job.progressDetails = null;\n // Increment runAttempts to keep in-memory object in sync with storage\n // The storage layer will read from DB and increment, so this keeps them aligned\n job.runAttempts = (job.runAttempts ?? 0) + 1;\n\n await this.storage.complete(this.classToStorage(job));\n this.events.emit(\"job_retry\", job.id, job.runAfter);\n } catch (err) {\n getLogger().error(\"rescheduleJob errored:\", { error: err });\n }\n }\n\n /**\n * Create an abort controller for a job.\n *\n * The job MUST already be registered in {@link inFlight} — this enforces\n * the invariant that `activeJobAbortControllers ⊆ inFlight`, which\n * {@link handleAbort} relies on to decide whether `processSingleJob` is\n * still on the hook for the terminal write. Calling this from any path\n * other than `processSingleJob` (which registers `inFlight` first) is a\n * programming error.\n */\n protected createAbortController(jobId: unknown): AbortController {\n if (!jobId) throw new JobNotFoundError(\"Cannot create abort controller for undefined job\");\n\n if (!this.inFlight.has(jobId)) {\n throw new Error(\n `createAbortController invariant violated: jobId ${String(jobId)} is not in inFlight. ` +\n `Abort controllers must only be created from within processSingleJob.`\n );\n }\n\n if (this.activeJobAbortControllers.has(jobId)) {\n return this.activeJobAbortControllers.get(jobId)!;\n }\n\n const abortController = new AbortController();\n abortController.signal.addEventListener(\"abort\", () => this.handleAbort(jobId));\n this.activeJobAbortControllers.set(jobId, abortController);\n return abortController;\n }\n\n /**\n * Handle job abort.\n *\n * Two callers fire the controller and reach this listener:\n * 1. `requestAbort` — same-process abort while the job is in flight here.\n * 2. `checkForAbortingJobs` — cross-process abort observed via storage poll.\n *\n * In both cases, if processSingleJob is still running this job locally,\n * the abort signal will propagate into the user task and processSingleJob's\n * own catch path will write the terminal state. We must not race it: doing\n * so duplicates the `job_error` emit and, worse, can clobber a successful\n * `completeJob` that won the race (the COMPLETED→FAILED overwrite bug).\n *\n * If the job is no longer in flight here, it has already settled — recheck\n * storage and only write FAILED for non-terminal states (i.e. an ABORTING\n * row left over from a cross-process abort that this worker never picked up).\n */\n protected async handleAbort(jobId: unknown): Promise<void> {\n if (this.inFlight.has(jobId)) {\n return;\n }\n const job = await this.getJob(jobId);\n if (!job) {\n getLogger().error(\"handleAbort: job not found\", { jobId });\n return;\n }\n if (\n job.status === JobStatus.COMPLETED ||\n job.status === JobStatus.FAILED ||\n job.status === JobStatus.DISABLED\n ) {\n return;\n }\n await this.failJob(job, new AbortSignalJobError(\"Job Aborted\"));\n }\n\n /**\n * Get a job by ID\n */\n protected async getJob(id: unknown): Promise<Job<Input, Output> | undefined> {\n const job = await this.storage.get(id);\n if (!job) return undefined;\n return this.storageToClass(job);\n }\n\n /**\n * Validate job state before processing\n */\n protected async validateJobState(job: Job<Input, Output>): Promise<void> {\n if (job.status === JobStatus.COMPLETED) {\n throw new PermanentJobError(`Job ${job.id} is already completed`);\n }\n if (job.status === JobStatus.FAILED) {\n throw new PermanentJobError(`Job ${job.id} has failed`);\n }\n if (\n job.status === JobStatus.ABORTING ||\n this.activeJobAbortControllers.get(job.id)?.signal.aborted\n ) {\n throw new AbortSignalJobError(`Job ${job.id} is being aborted`);\n }\n if (job.deadlineAt && job.deadlineAt < new Date()) {\n throw new PermanentJobError(`Job ${job.id} has exceeded its deadline`);\n }\n if (job.status === JobStatus.DISABLED) {\n throw new JobDisabledError(`Job ${job.id} has been disabled`);\n }\n }\n\n /**\n * Normalize errors into JobError instances\n */\n protected normalizeError(err: unknown): JobError {\n if (err instanceof JobError) {\n return err;\n }\n if (err instanceof Error) {\n return new PermanentJobError(withJobErrorDiagnostics(err.message, err));\n }\n return new PermanentJobError(String(err));\n }\n\n /**\n * Clean up job state after completion/failure\n */\n protected cleanupJob(jobId: unknown): void {\n this.activeJobAbortControllers.delete(jobId);\n }\n\n /**\n * Convert storage format to Job class\n */\n protected storageToClass(details: JobStorageFormat<Input, Output>): Job<Input, Output> {\n return storageToClass(details, this.jobClass);\n }\n\n /**\n * Convert Job class to storage format\n */\n protected classToStorage(job: Job<Input, Output>): JobStorageFormat<Input, Output> {\n return classToStorage(job, this.queueName);\n }\n}\n",
|
|
13
|
+
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { ILimiter, LimiterScope } from \"./ILimiter\";\n\nexport class CompositeLimiter implements ILimiter {\n private limiters: ILimiter[] = [];\n\n constructor(limiters: ILimiter[] = []) {\n this.limiters = limiters;\n }\n\n /**\n * `\"cluster\"` only when EVERY child is cluster-scoped. A single process-scoped\n * child means the composite as a whole can't enforce a cluster-wide limit.\n */\n public get scope(): LimiterScope {\n return this.limiters.every((l) => l.scope === \"cluster\") && this.limiters.length > 0\n ? \"cluster\"\n : \"process\";\n }\n\n addLimiter(limiter: ILimiter): void {\n this.limiters.push(limiter);\n }\n\n async canProceed(): Promise<boolean> {\n for (const limiter of this.limiters) {\n if (!(await limiter.canProceed())) {\n return false; // If any limiter says \"no\", proceed no further\n }\n }\n return true; // All limiters agree\n }\n\n /**\n * Atomic against the composite: acquires children sequentially and rolls\n * back any successfully-acquired prefix if a later child rejects, so the\n * \"all-or-nothing\" semantics hold under concurrency.\n *\n * The returned token is an array of per-child tokens (parallel to\n * `this.limiters`) so {@link release} can free each child's specific slot\n * — never \"the most recent\", which would race other acquirers.\n */\n async tryAcquire(): Promise<unknown | null> {\n const tokens: unknown[] = [];\n for (const limiter of this.limiters) {\n const t = await limiter.tryAcquire();\n if (t === null || t === undefined) {\n // Roll back the partial acquisition. Iterate in reverse so children\n // are released in opposite order of acquisition.\n for (let i = tokens.length - 1; i >= 0; i--) {\n try {\n await this.limiters[i].release(tokens[i]);\n } catch {\n // best-effort\n }\n }\n return null;\n }\n tokens.push(t);\n }\n return tokens;\n }\n\n async release(token: unknown): Promise<void> {\n if (!Array.isArray(token)) return;\n await Promise.all(\n this.limiters.map((l, i) => l.release(token[i]).catch(() => {}))\n );\n }\n\n async recordJobStart(): Promise<void> {\n await Promise.all(this.limiters.map((limiter) => limiter.recordJobStart()));\n }\n\n async recordJobCompletion(): Promise<void> {\n await Promise.all(this.limiters.map((limiter) => limiter.recordJobCompletion()));\n }\n\n async getNextAvailableTime(): Promise<Date> {\n let maxDate = new Date(); // Assume now as the default\n for (const limiter of this.limiters) {\n const limiterNextTime = await limiter.getNextAvailableTime();\n if (limiterNextTime > maxDate) {\n maxDate = limiterNextTime; // Find the latest time among limiters\n }\n }\n return maxDate;\n }\n\n async setNextAvailableTime(date: Date): Promise<void> {\n for (const limiter of this.limiters) {\n await limiter.setNextAvailableTime(date);\n }\n }\n\n async clear(): Promise<void> {\n await Promise.all(this.limiters.map((limiter) => limiter.clear()));\n }\n}\n",
|
|
14
|
+
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { createServiceToken } from \"@workglow/util\";\nimport { ILimiter, LimiterScope } from \"./ILimiter\";\n\nexport const CONCURRENT_JOB_LIMITER = createServiceToken<ILimiter>(\"jobqueue.limiter.concurrent\");\n\n/**\n * Concurrency limiter that limits the number of concurrent jobs.\n */\nexport class ConcurrencyLimiter implements ILimiter {\n /** In-memory counter — not shared across processes. */\n public readonly scope: LimiterScope = \"process\";\n private currentRunningJobs: number = 0;\n private readonly maxConcurrentJobs: number;\n private nextAllowedStartTime: Date = new Date();\n\n constructor(maxConcurrentJobs: number) {\n this.maxConcurrentJobs = maxConcurrentJobs;\n }\n\n async canProceed(): Promise<boolean> {\n return (\n this.currentRunningJobs < this.maxConcurrentJobs &&\n Date.now() >= this.nextAllowedStartTime.getTime()\n );\n }\n\n /** Sentinel token; ConcurrencyLimiter has no per-row identity, just a counter. */\n private static readonly SENTINEL = Symbol(\"ConcurrencyLimiter.acquired\");\n\n /**\n * Atomic in JS's single-threaded sense: the read-then-increment runs without\n * an `await` between them, so no other task can interleave.\n */\n async tryAcquire(): Promise<unknown | null> {\n if (\n this.currentRunningJobs >= this.maxConcurrentJobs ||\n Date.now() < this.nextAllowedStartTime.getTime()\n ) {\n return null;\n }\n this.currentRunningJobs++;\n return ConcurrencyLimiter.SENTINEL;\n }\n\n async release(token: unknown): Promise<void> {\n if (token !== ConcurrencyLimiter.SENTINEL) return;\n this.currentRunningJobs = Math.max(0, this.currentRunningJobs - 1);\n }\n\n async recordJobStart(): Promise<void> {\n this.currentRunningJobs++;\n }\n\n async recordJobCompletion(): Promise<void> {\n this.currentRunningJobs = Math.max(0, this.currentRunningJobs - 1);\n }\n\n async getNextAvailableTime(): Promise<Date> {\n return this.currentRunningJobs > this.maxConcurrentJobs ? new Date() : new Date(Date.now() - 1);\n }\n\n async setNextAvailableTime(date: Date): Promise<void> {\n if (date > this.nextAllowedStartTime) {\n this.nextAllowedStartTime = date;\n }\n }\n\n async clear(): Promise<void> {\n this.currentRunningJobs = 0;\n this.nextAllowedStartTime = new Date();\n }\n}\n",
|
|
15
|
+
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { ILimiter, LimiterScope } from \"./ILimiter\";\n\nexport class DelayLimiter implements ILimiter {\n /** In-memory state — not shared across processes. */\n public readonly scope: LimiterScope = \"process\";\n private nextAvailableTime: Date = new Date();\n /** Time of the most recent successful tryAcquire, used to undo on release. */\n private lastAcquireBaseline: number = 0;\n constructor(private delayInMilliseconds: number = 50) {}\n\n async canProceed(): Promise<boolean> {\n return Date.now() >= this.nextAvailableTime.getTime();\n }\n\n /**\n * Token records the previous nextAvailableTime so release can roll back to\n * exactly the state before this acquire — even if other acquires (or\n * setNextAvailableTime calls) ran in between.\n */\n async tryAcquire(): Promise<unknown | null> {\n const now = Date.now();\n if (now < this.nextAvailableTime.getTime()) {\n return null;\n }\n const previous = this.nextAvailableTime.getTime();\n this.lastAcquireBaseline = previous;\n this.nextAvailableTime = new Date(now + this.delayInMilliseconds);\n return previous;\n }\n\n async release(token: unknown): Promise<void> {\n if (typeof token !== \"number\") return;\n // Only roll back if no later acquire/setNextAvailableTime moved the\n // window forward — otherwise we'd undo someone else's reservation.\n if (this.nextAvailableTime.getTime() === this.lastAcquireBaseline + this.delayInMilliseconds) {\n this.nextAvailableTime = new Date(token);\n }\n }\n\n async recordJobStart(): Promise<void> {\n this.nextAvailableTime = new Date(Date.now() + this.delayInMilliseconds);\n }\n\n async recordJobCompletion(): Promise<void> {\n // No action needed.\n }\n\n async getNextAvailableTime(): Promise<Date> {\n return this.nextAvailableTime;\n }\n\n async setNextAvailableTime(date: Date): Promise<void> {\n if (date > this.nextAvailableTime) {\n this.nextAvailableTime = date;\n }\n }\n async clear(): Promise<void> {\n this.nextAvailableTime = new Date();\n }\n}\n",
|
|
16
|
+
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { createServiceToken } from \"@workglow/util\";\nimport { ILimiter, LimiterScope, RateLimiterOptions } from \"./ILimiter\";\n\nexport const EVENLY_SPACED_JOB_RATE_LIMITER = createServiceToken<ILimiter>(\n \"jobqueue.limiter.rate.evenlyspaced\"\n);\n\n/**\n * Rate limiter that spreads requests evenly across a time window.\n * Instead of allowing all requests up to the limit and then waiting,\n * this limiter spaces out the requests evenly across the window.\n */\nexport class EvenlySpacedRateLimiter implements ILimiter {\n /** In-memory per-instance state — not shared across processes. */\n public readonly scope: LimiterScope = \"process\";\n private readonly maxExecutions: number;\n private readonly windowSizeMs: number;\n private readonly idealInterval: number;\n private nextAvailableTime: number = Date.now();\n private lastStartTime: number = 0;\n private durations: number[] = [];\n /** Promise chain used to serialize concurrent {@link tryAcquire} callers. */\n private acquireChain: Promise<unknown> = Promise.resolve();\n\n constructor({ maxExecutions, windowSizeInSeconds }: RateLimiterOptions) {\n if (maxExecutions <= 0) {\n throw new Error(\"maxExecutions must be > 0\");\n }\n if (windowSizeInSeconds <= 0) {\n throw new Error(\"windowSizeInSeconds must be > 0\");\n }\n this.maxExecutions = maxExecutions;\n this.windowSizeMs = windowSizeInSeconds * 1_000;\n // If you want exactly maxExecutions in windowSize, start one every this many ms:\n this.idealInterval = this.windowSizeMs / this.maxExecutions;\n }\n\n /** Can we start a new job right now? */\n async canProceed(): Promise<boolean> {\n const now = Date.now();\n return now >= this.nextAvailableTime;\n }\n\n /**\n * Atomic acquire: serialized by an internal promise chain so two concurrent\n * acquirers cannot both observe `now >= nextAvailableTime` and both proceed.\n * Returns a token capturing the prior `nextAvailableTime` so {@link release}\n * can roll back to the exact state before this acquire.\n */\n async tryAcquire(): Promise<unknown | null> {\n const previous = this.acquireChain;\n let release!: (v: unknown) => void;\n const next = new Promise((r) => {\n release = r;\n });\n this.acquireChain = next;\n try {\n await previous;\n const now = Date.now();\n if (now < this.nextAvailableTime) {\n return null;\n }\n const priorNextAvailable = this.nextAvailableTime;\n // Reserve the slot by advancing nextAvailableTime now (recordJobStart-style)\n // so a follow-up tryAcquire from another caller in the same tick blocks.\n this.lastStartTime = now;\n if (this.durations.length === 0) {\n this.nextAvailableTime = now + this.idealInterval;\n } else {\n const sum = this.durations.reduce((a, b) => a + b, 0);\n const avgDuration = sum / this.durations.length;\n const waitMs = Math.max(0, this.idealInterval - avgDuration);\n this.nextAvailableTime = now + waitMs;\n }\n return { priorNextAvailable, advancedTo: this.nextAvailableTime };\n } finally {\n release(undefined);\n }\n }\n\n /**\n * Roll back the slot identified by `token`. Only undoes the advance if no\n * later acquire moved the window forward — otherwise we'd undo someone\n * else's reservation.\n */\n async release(token: unknown): Promise<void> {\n if (\n !token ||\n typeof token !== \"object\" ||\n typeof (token as { advancedTo?: unknown }).advancedTo !== \"number\"\n ) {\n return;\n }\n const t = token as { priorNextAvailable: number; advancedTo: number };\n if (this.nextAvailableTime === t.advancedTo) {\n this.nextAvailableTime = t.priorNextAvailable;\n }\n }\n\n /** Record that a job is starting now. */\n async recordJobStart(): Promise<void> {\n const now = Date.now();\n this.lastStartTime = now;\n\n // If no timing data yet, assume zero run-time (ideal interval)\n if (this.durations.length === 0) {\n this.nextAvailableTime = now + this.idealInterval;\n } else {\n // Compute average run duration\n const sum = this.durations.reduce((a, b) => a + b, 0);\n const avgDuration = sum / this.durations.length;\n // Schedule next start: ideal spacing minus average duration\n const waitMs = Math.max(0, this.idealInterval - avgDuration);\n this.nextAvailableTime = now + waitMs;\n }\n }\n\n /**\n * Call this when a job finishes.\n * We measure its duration, update our running-average,\n * and then compute how long to wait before the next job start.\n */\n async recordJobCompletion(): Promise<void> {\n const now = Date.now();\n const duration = now - this.lastStartTime;\n this.durations.push(duration);\n if (this.durations.length > this.maxExecutions) {\n this.durations.shift();\n }\n }\n\n async getNextAvailableTime(): Promise<Date> {\n return new Date(this.nextAvailableTime);\n }\n\n async setNextAvailableTime(date: Date): Promise<void> {\n const t = date.getTime();\n if (t > this.nextAvailableTime) {\n this.nextAvailableTime = t;\n }\n }\n\n async clear(): Promise<void> {\n this.durations = [];\n this.nextAvailableTime = Date.now();\n this.lastStartTime = 0;\n }\n}\n",
|
|
17
|
+
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { createServiceToken } from \"@workglow/util\";\n\nexport const JOB_LIMITER = createServiceToken<ILimiter>(\"jobqueue.limiter\");\n\n/**\n * Whether a limiter's state is shared across processes.\n *\n * - `\"process\"` — state lives in this process only. Multiple workers in the\n * same process share it, but separate processes do not. The configured limit\n * is multiplied by the number of processes.\n * - `\"cluster\"` — state lives in shared storage (Postgres, Supabase, etc.)\n * visible to every process in the cluster. The configured limit is enforced\n * globally.\n */\nexport type LimiterScope = \"process\" | \"cluster\";\n\n/**\n * Interface for a job limiter.\n *\n * The atomic primitive is {@link tryAcquire}: it both checks whether a job may\n * proceed and reserves the slot in a single uninterruptible step. Callers\n * MUST use {@link tryAcquire}/{@link release} (not the legacy\n * {@link canProceed}/{@link recordJobStart} pair) when correctness matters\n * under concurrency.\n */\nexport interface ILimiter {\n /**\n * Whether this limiter's state is shared across processes. See\n * {@link LimiterScope}. In-memory limiters MUST report `\"process\"` so users\n * don't mistake them for cluster-safe.\n */\n readonly scope: LimiterScope;\n\n /**\n * Atomic check-and-record. Returns an opaque, non-null token on success\n * (the caller may proceed AND the reservation has been recorded). Returns\n * `null` without side effects if the limiter is at capacity.\n *\n * The returned token MUST be passed to {@link release} to free the slot —\n * otherwise rolling back the reservation under contention would race to\n * delete some other worker's slot. Tokens are implementation-defined and\n * callers must treat them as opaque.\n *\n * Implementations must be safe under concurrent callers — two parallel\n * `tryAcquire()` calls with one slot remaining must result in exactly one\n * non-null token and one `null`.\n */\n tryAcquire(): Promise<unknown | null>;\n\n /**\n * Release a slot previously reserved by {@link tryAcquire}. The token must\n * be the value that {@link tryAcquire} returned for the slot being freed.\n * Used when the caller cannot actually use the slot it acquired (e.g.\n * claimed a job that vanished, executor failed before running, worker\n * shut down).\n */\n release(token: unknown): Promise<void>;\n\n /**\n * Legacy non-binding \"would tryAcquire succeed?\" probe. SUBJECT TO RACES —\n * do not use this followed by {@link recordJobStart} in production code; use\n * {@link tryAcquire} instead. Retained for observability and tests.\n */\n canProceed(): Promise<boolean>;\n\n /**\n * Legacy \"force-record an execution\" hook. SUBJECT TO RACES when paired with\n * {@link canProceed} — use {@link tryAcquire} instead. Retained for tests\n * and external bookkeeping.\n */\n recordJobStart(): Promise<void>;\n\n recordJobCompletion(): Promise<void>;\n getNextAvailableTime(): Promise<Date>;\n setNextAvailableTime(date: Date): Promise<void>;\n clear(): Promise<void>;\n}\n\nexport interface RateLimiterOptions {\n readonly maxExecutions: number;\n readonly windowSizeInSeconds: number;\n}\n\nexport interface RateLimiterWithBackoffOptions extends RateLimiterOptions {\n readonly initialBackoffDelay?: number;\n readonly backoffMultiplier?: number;\n readonly maxBackoffDelay?: number;\n}\n",
|
|
18
|
+
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { IRateLimiterStorage } from \"@workglow/storage\";\nimport { ILimiter, LimiterScope, RateLimiterWithBackoffOptions } from \"./ILimiter\";\n\n/**\n * Base rate limiter implementation that uses a storage backend.\n * Manages request counts and delays to control job execution.\n */\nexport class RateLimiter implements ILimiter {\n protected readonly windowSizeInMilliseconds: number;\n protected currentBackoffDelay: number;\n protected readonly maxExecutions: number;\n protected readonly initialBackoffDelay: number;\n protected readonly backoffMultiplier: number;\n protected readonly maxBackoffDelay: number;\n /**\n * Per-instance backoff hint set by the most recent failed {@link tryAcquire}.\n * Exposed via {@link getNextAvailableTime} so this process's worker sleeps\n * until the hint expires, but NOT written to storage so a parallel worker's\n * {@link release} can't clobber it. Cross-process workers each maintain\n * their own hint.\n */\n protected localBackoffUntilMs: number = 0;\n\n constructor(\n protected readonly storage: IRateLimiterStorage,\n protected readonly queueName: string,\n {\n maxExecutions,\n windowSizeInSeconds,\n initialBackoffDelay = 1_000,\n backoffMultiplier = 2,\n maxBackoffDelay = 600_000, // 10 minutes\n }: RateLimiterWithBackoffOptions\n ) {\n if (maxExecutions <= 0) {\n throw new Error(\"maxExecutions must be greater than 0\");\n }\n if (windowSizeInSeconds <= 0) {\n throw new Error(\"windowSizeInSeconds must be greater than 0\");\n }\n if (initialBackoffDelay <= 0) {\n throw new Error(\"initialBackoffDelay must be greater than 0\");\n }\n if (backoffMultiplier <= 1) {\n throw new Error(\"backoffMultiplier must be greater than 1\");\n }\n if (maxBackoffDelay <= initialBackoffDelay) {\n throw new Error(\"maxBackoffDelay must be greater than initialBackoffDelay\");\n }\n\n this.windowSizeInMilliseconds = windowSizeInSeconds * 1000;\n this.maxExecutions = maxExecutions;\n this.initialBackoffDelay = initialBackoffDelay;\n this.backoffMultiplier = backoffMultiplier;\n this.maxBackoffDelay = maxBackoffDelay;\n this.currentBackoffDelay = initialBackoffDelay;\n }\n\n /**\n * Inherits its scope from the underlying storage. Storage-backed RateLimiter\n * is `\"cluster\"` for Postgres/Supabase, `\"process\"` for InMemory/IndexedDb/Sqlite.\n */\n public get scope(): LimiterScope {\n return this.storage.scope;\n }\n\n /**\n * Atomic check-and-record. Delegates to {@link IRateLimiterStorage.tryReserveExecution}\n * which performs the count, next-available, and insert under a single\n * critical section per backend (advisory xact lock for Postgres, BEGIN\n * IMMEDIATE for SQLite, per-key promise mutex for in-memory).\n *\n * Returns the storage-assigned row id as an opaque token on success, or\n * `null` on failure. The caller MUST pass the token to {@link release} to\n * roll back — releasing without a token would race to delete the wrong\n * worker's slot under contention.\n */\n async tryAcquire(): Promise<unknown | null> {\n const token = await this.storage.tryReserveExecution(\n this.queueName,\n this.maxExecutions,\n this.windowSizeInMilliseconds\n );\n if (token !== null && token !== undefined) {\n this.currentBackoffDelay = this.initialBackoffDelay;\n this.localBackoffUntilMs = 0;\n return token;\n }\n\n // Capacity reached — apply jittered exponential backoff. We hold this\n // hint on the instance only; writing to the shared storage sentinel\n // would race with parallel acquire/release calls (a peer's release()\n // could clobber our hint, and clobbering the storage sentinel is\n // dangerous because tryReserveExecution treats it as a hard gate).\n this.localBackoffUntilMs = Date.now() + this.addJitter(this.currentBackoffDelay);\n this.increaseBackoff();\n return null;\n }\n\n /**\n * Release the slot identified by `token` (a value returned from\n * {@link tryAcquire}). Deletes the specific row by id — NOT the most recent\n * — so two concurrent acquirers don't race to release each other's slots.\n * Does NOT touch the shared storage `nextAvailableAt` sentinel; that field\n * is reserved for explicit external pauses (e.g. cluster-wide rate-limit\n * cool-down set via {@link setNextAvailableTime}), and clearing it here\n * would clobber a parallel worker's failed-acquire backoff or a deliberate\n * cluster-wide pause. Local per-instance backoff is cleared via\n * {@link localBackoffUntilMs}.\n */\n async release(token: unknown): Promise<void> {\n if (token === null || token === undefined) return;\n await this.storage.releaseExecution(this.queueName, token);\n this.currentBackoffDelay = this.initialBackoffDelay;\n this.localBackoffUntilMs = 0;\n }\n\n protected addJitter(base: number): number {\n // full jitter in [base, 2*base)\n return base + Math.random() * base;\n }\n\n protected increaseBackoff(): void {\n this.currentBackoffDelay = Math.min(\n this.currentBackoffDelay * this.backoffMultiplier,\n this.maxBackoffDelay\n );\n }\n\n /**\n * Checks if a job can proceed based on rate limiting rules.\n * @returns True if the job can proceed, false otherwise\n */\n async canProceed(): Promise<boolean> {\n // First check if the window allows more executions\n const windowStartTime = new Date(Date.now() - this.windowSizeInMilliseconds).toISOString();\n const attemptCount = await this.storage.getExecutionCount(this.queueName, windowStartTime);\n const canProceedNow = attemptCount < this.maxExecutions;\n\n // If the window allows more executions, clear any backoff and proceed\n if (canProceedNow) {\n // Clear any existing nextAvailableTime backoff since the window allows more executions\n const nextAvailableTime = await this.storage.getNextAvailableTime(this.queueName);\n if (nextAvailableTime && new Date(nextAvailableTime).getTime() > Date.now()) {\n // Clear the backoff by setting it to the past\n const pastTime = new Date(Date.now() - 1000);\n await this.storage.setNextAvailableTime(this.queueName, pastTime.toISOString());\n }\n this.currentBackoffDelay = this.initialBackoffDelay;\n return true;\n }\n\n // Window is full, check if there's a backoff delay\n const nextAvailableTime = await this.storage.getNextAvailableTime(this.queueName);\n if (nextAvailableTime && new Date(nextAvailableTime).getTime() > Date.now()) {\n this.increaseBackoff();\n return false;\n }\n\n // Window is full but no backoff delay, so we can't proceed\n this.increaseBackoff();\n return false;\n }\n\n /**\n * Records a new job attempt.\n */\n async recordJobStart(): Promise<void> {\n await this.storage.recordExecution(this.queueName);\n\n const windowStartTime = new Date(Date.now() - this.windowSizeInMilliseconds).toISOString();\n const attemptCount = await this.storage.getExecutionCount(this.queueName, windowStartTime);\n\n if (attemptCount >= this.maxExecutions) {\n const backoffExpires = new Date(Date.now() + this.addJitter(this.currentBackoffDelay));\n await this.setNextAvailableTime(backoffExpires);\n } else {\n // Window allows more executions, clear any existing nextAvailableTime by setting it to the past\n const nextAvailableTime = await this.storage.getNextAvailableTime(this.queueName);\n if (nextAvailableTime && new Date(nextAvailableTime).getTime() > Date.now()) {\n // Clear the backoff since the window now allows more executions\n // Set to a time in the past to effectively clear it\n const pastTime = new Date(Date.now() - 1000);\n await this.storage.setNextAvailableTime(this.queueName, pastTime.toISOString());\n }\n }\n }\n\n async recordJobCompletion(): Promise<void> {\n // Implementation can be no-op as completion doesn't affect rate limiting\n }\n\n /**\n * Retrieves the next available time for the specific queue. Returns the\n * latest of: the rate-limit wall (oldest execution + window), any externally\n * set storage sentinel (explicit pause), and this instance's local backoff\n * hint from the most recent failed {@link tryAcquire}.\n */\n async getNextAvailableTime(): Promise<Date> {\n // Get the time when the rate limit will allow the next job execution\n const oldestExecution = await this.storage.getOldestExecutionAtOffset(\n this.queueName,\n this.maxExecutions - 1\n );\n\n let latestMs = Date.now();\n if (oldestExecution) {\n latestMs = Math.max(\n latestMs,\n new Date(oldestExecution).getTime() + this.windowSizeInMilliseconds\n );\n }\n\n // External pause set via setNextAvailableTime (cluster-visible).\n const nextAvailableStr = await this.storage.getNextAvailableTime(this.queueName);\n if (nextAvailableStr) {\n latestMs = Math.max(latestMs, new Date(nextAvailableStr).getTime());\n }\n\n // Local backoff hint from the most recent failed tryAcquire on this\n // instance — keeps this process's worker from re-acquiring before the\n // hint expires without polluting cluster state.\n if (this.localBackoffUntilMs > latestMs) {\n latestMs = this.localBackoffUntilMs;\n }\n\n return new Date(latestMs);\n }\n\n /**\n * Sets the next available time for the specific queue.\n * @param date - The new next available time\n */\n async setNextAvailableTime(date: Date): Promise<void> {\n await this.storage.setNextAvailableTime(this.queueName, date.toISOString());\n }\n\n /**\n * Clears all rate limit entries for this queue.\n */\n async clear(): Promise<void> {\n await this.storage.clear(this.queueName);\n this.currentBackoffDelay = this.initialBackoffDelay;\n this.localBackoffUntilMs = 0;\n }\n}\n"
|
|
19
19
|
],
|
|
20
|
-
"mappings": "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",
|
|
21
|
-
"debugId": "
|
|
20
|
+
"mappings": "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",
|
|
21
|
+
"debugId": "E843281190707FDF64756E2164756E21",
|
|
22
22
|
"names": []
|
|
23
23
|
}
|