@boringnode/queue 0.0.1-alpha.0 → 0.0.1-alpha.2
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/README.md +53 -13
- package/build/index.js +20 -8
- package/build/index.js.map +1 -1
- package/build/src/drivers/knex_adapter.d.ts +43 -0
- package/build/src/drivers/knex_adapter.js +176 -0
- package/build/src/drivers/knex_adapter.js.map +1 -0
- package/build/src/drivers/redis_adapter.d.ts +11 -1
- package/build/src/drivers/redis_adapter.js +8 -4
- package/build/src/drivers/redis_adapter.js.map +1 -1
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ npm install @boringnode/queue
|
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
- **Multiple Queue Adapters**: Support for Redis (
|
|
13
|
+
- **Multiple Queue Adapters**: Support for Redis, Knex (PostgreSQL, MySQL, SQLite), and Sync
|
|
14
14
|
- **Type-Safe Jobs**: Define jobs as TypeScript classes with typed payloads
|
|
15
15
|
- **Delayed Jobs**: Schedule jobs to run after a specific delay
|
|
16
16
|
- **Multiple Queues**: Organize jobs into different queues for better organization
|
|
@@ -173,6 +173,38 @@ import { sync } from '@boringnode/queue/drivers/sync_adapter'
|
|
|
173
173
|
const adapter = sync()
|
|
174
174
|
```
|
|
175
175
|
|
|
176
|
+
### Knex Adapter
|
|
177
|
+
|
|
178
|
+
For SQL databases (PostgreSQL, MySQL, SQLite) using Knex:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { knex } from '@boringnode/queue/drivers/knex_adapter'
|
|
182
|
+
|
|
183
|
+
// With configuration (adapter manages connection lifecycle)
|
|
184
|
+
const adapter = knex({
|
|
185
|
+
client: 'pg',
|
|
186
|
+
connection: {
|
|
187
|
+
host: 'localhost',
|
|
188
|
+
port: 5432,
|
|
189
|
+
user: 'postgres',
|
|
190
|
+
password: 'postgres',
|
|
191
|
+
database: 'myapp',
|
|
192
|
+
},
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
// Or with an existing Knex instance (you manage connection lifecycle)
|
|
196
|
+
import Knex from 'knex'
|
|
197
|
+
|
|
198
|
+
const connection = Knex({ client: 'pg', connection: '...' })
|
|
199
|
+
const adapter = knex(connection)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The adapter automatically creates the `queue_jobs` table on first use. You can customize the table name:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
const adapter = knex(config, 'custom_jobs_table')
|
|
206
|
+
```
|
|
207
|
+
|
|
176
208
|
## Worker Configuration
|
|
177
209
|
|
|
178
210
|
Workers process jobs from one or more queues:
|
|
@@ -303,24 +335,32 @@ Jobs must:
|
|
|
303
335
|
|
|
304
336
|
## Benchmarks
|
|
305
337
|
|
|
306
|
-
Performance comparison with BullMQ
|
|
338
|
+
Performance comparison with BullMQ using realistic jobs (5ms simulated work per job):
|
|
307
339
|
|
|
308
340
|
| Jobs | Concurrency | @boringnode/queue | BullMQ | Diff |
|
|
309
341
|
|------|-------------|-------------------|--------|--------------|
|
|
310
|
-
| 100 | 1 |
|
|
311
|
-
| 100 | 5 |
|
|
312
|
-
| 100 | 10 |
|
|
313
|
-
|
|
|
314
|
-
|
|
|
315
|
-
|
|
|
316
|
-
|
|
|
317
|
-
|
|
|
318
|
-
|
|
|
319
|
-
|
|
320
|
-
|
|
342
|
+
| 100 | 1 | 562ms | 596ms | 5.7% faster |
|
|
343
|
+
| 100 | 5 | 116ms | 117ms | ~same |
|
|
344
|
+
| 100 | 10 | 62ms | 62ms | ~same |
|
|
345
|
+
| 500 | 1 | 2728ms | 2798ms | 2.5% faster |
|
|
346
|
+
| 500 | 5 | 565ms | 565ms | ~same |
|
|
347
|
+
| 500 | 10 | 287ms | 288ms | ~same |
|
|
348
|
+
| 1000 | 1 | 5450ms | 5547ms | 1.7% faster |
|
|
349
|
+
| 1000 | 5 | 1096ms | 1116ms | 1.8% faster |
|
|
350
|
+
| 1000 | 10 | 565ms | 579ms | 2.4% faster |
|
|
351
|
+
| 100K | 5 | 110.5s | 112.3s | 1.5% faster |
|
|
352
|
+
| 100K | 10 | 56.2s | 57.5s | 2.1% faster |
|
|
353
|
+
| 100K | 20 | 29.1s | 29.6s | 1.7% faster |
|
|
321
354
|
|
|
322
355
|
Run benchmarks yourself:
|
|
323
356
|
|
|
324
357
|
```bash
|
|
358
|
+
# Realistic benchmark (5ms job duration)
|
|
359
|
+
npm run benchmark -- --realistic
|
|
360
|
+
|
|
361
|
+
# Pure dequeue overhead (no-op jobs)
|
|
325
362
|
npm run benchmark
|
|
363
|
+
|
|
364
|
+
# Custom job duration
|
|
365
|
+
npm run benchmark -- --duration=10
|
|
326
366
|
```
|
package/build/index.js
CHANGED
|
@@ -130,9 +130,12 @@ var Worker = class {
|
|
|
130
130
|
}
|
|
131
131
|
async *#fillPool(queues) {
|
|
132
132
|
const concurrency = this.#config.worker?.concurrency || 1;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
const slotsAvailable = concurrency - this.#pool.size;
|
|
134
|
+
if (slotsAvailable <= 0) return;
|
|
135
|
+
const popPromises = Array.from({ length: slotsAvailable }, () => this.#acquireNextJob(queues));
|
|
136
|
+
const results = await Promise.all(popPromises);
|
|
137
|
+
for (const result of results) {
|
|
138
|
+
if (!result) continue;
|
|
136
139
|
const { job, queue } = result;
|
|
137
140
|
const promise = this.#execute(job, queue);
|
|
138
141
|
this.#pool.add(job, queue, promise);
|
|
@@ -248,11 +251,13 @@ import { Locator as Locator2 } from "#src/locator";
|
|
|
248
251
|
var QueueManagerSingleton = class {
|
|
249
252
|
#defaultAdapter;
|
|
250
253
|
#adapters = {};
|
|
254
|
+
#adapterInstances = /* @__PURE__ */ new Map();
|
|
251
255
|
#globalRetryConfig;
|
|
252
256
|
#queueConfigs = /* @__PURE__ */ new Map();
|
|
253
257
|
async init(config) {
|
|
254
258
|
debug2("initializing queue manager with config: %O", config);
|
|
255
259
|
this.#validateConfig(config);
|
|
260
|
+
this.#adapterInstances.clear();
|
|
256
261
|
this.#defaultAdapter = config.default;
|
|
257
262
|
this.#adapters = config.adapters;
|
|
258
263
|
this.#globalRetryConfig = config.retry;
|
|
@@ -268,13 +273,19 @@ var QueueManagerSingleton = class {
|
|
|
268
273
|
if (!adapter) {
|
|
269
274
|
adapter = this.#defaultAdapter;
|
|
270
275
|
}
|
|
271
|
-
const
|
|
272
|
-
if (
|
|
276
|
+
const cached = this.#adapterInstances.get(adapter);
|
|
277
|
+
if (cached) {
|
|
278
|
+
return cached;
|
|
279
|
+
}
|
|
280
|
+
const adapterFactory = this.#adapters[adapter];
|
|
281
|
+
if (!adapterFactory) {
|
|
273
282
|
throw new errors2.E_CONFIGURATION_ERROR([`Adapter "${adapter}" is not registered`]);
|
|
274
283
|
}
|
|
275
284
|
debug2('using adapter "%s"', adapter);
|
|
276
285
|
try {
|
|
277
|
-
|
|
286
|
+
const instance = adapterFactory();
|
|
287
|
+
this.#adapterInstances.set(adapter, instance);
|
|
288
|
+
return instance;
|
|
278
289
|
} catch (error) {
|
|
279
290
|
throw new Error();
|
|
280
291
|
}
|
|
@@ -311,10 +322,11 @@ var QueueManagerSingleton = class {
|
|
|
311
322
|
}
|
|
312
323
|
}
|
|
313
324
|
async destroy() {
|
|
314
|
-
for (const
|
|
315
|
-
|
|
325
|
+
for (const [name, adapter] of this.#adapterInstances) {
|
|
326
|
+
debug2('destroying adapter "%s"', name);
|
|
316
327
|
await adapter.destroy();
|
|
317
328
|
}
|
|
329
|
+
this.#adapterInstances.clear();
|
|
318
330
|
}
|
|
319
331
|
};
|
|
320
332
|
var QueueManager2 = new QueueManagerSingleton();
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/job.ts","../src/worker.ts","../src/queue_manager.ts","../src/strategies/backoff_strategy.ts"],"sourcesContent":["import { JobDispatcher } from '#src/job_dispatcher'\nimport type { JobOptions } from '#types/main'\n\nexport abstract class Job<Payload = any> {\n readonly #payload: Payload\n\n static options: JobOptions = {}\n\n get payload(): Payload {\n return this.#payload\n }\n\n constructor(payload: Payload) {\n this.#payload = payload\n }\n\n static dispatch<T extends Job>(\n this: new (payload: any) => T,\n payload: T extends Job<infer P> ? P : never\n ): JobDispatcher<T extends Job<infer P> ? P : never> {\n const dispatcher = new JobDispatcher<T extends Job<infer P> ? P : never>(\n (this as any).jobName,\n payload\n )\n\n if ((this as any).options.queue) {\n dispatcher.toQueue((this as any).options.queue)\n }\n\n if ((this as any).options.adapter) {\n dispatcher.with((this as any).options.adapter)\n }\n\n if ((this as any).options.priority !== undefined) {\n dispatcher.priority((this as any).options.priority)\n }\n\n return dispatcher\n }\n\n abstract execute(signal?: AbortSignal): Promise<void>\n\n failed?(error: Error): Promise<void>\n}\n","import { randomUUID } from 'node:crypto'\nimport { setTimeout } from 'node:timers/promises'\nimport debug from '#src/debug'\nimport { parse } from '#src/utils'\nimport * as errors from '#src/exceptions'\nimport { QueueManager } from '#src/queue_manager'\nimport { JobPool } from '#src/job_pool'\nimport type { Adapter, AcquiredJob } from '#contracts/adapter'\nimport type { QueueManagerConfig, WorkerCycle } from '#types/main'\nimport { Locator } from '#src/locator'\nimport type { JobOptions } from '#types/main'\nimport type { Job } from '#src/job'\n\nexport class Worker {\n readonly #id: string\n readonly #config: QueueManagerConfig\n #adapter!: Adapter\n #running = false\n #initialized = false\n #generator?: AsyncGenerator<WorkerCycle, void, unknown>\n #pool?: JobPool\n\n get id() {\n return this.#id\n }\n\n constructor(config: QueueManagerConfig) {\n this.#config = config\n this.#id = randomUUID()\n\n debug('created worker with id %s and config %O', this.#id, config)\n }\n\n async init() {\n if (this.#initialized) {\n return\n }\n\n debug('initializing worker %s', this.#id)\n\n await QueueManager.init(this.#config)\n\n this.#adapter = QueueManager.use()\n this.#adapter.setWorkerId(this.#id)\n\n this.#initialized = true\n\n debug('worker %s initialized', this.#id)\n }\n\n async start(queues: string[] = ['default']): Promise<void> {\n await this.init()\n\n if (this.#running) {\n debug('worker %s is already running', this.#id)\n return\n }\n\n this.#running = true\n\n debug('starting worker %s on queues: %O', this.#id, queues)\n\n await this.#setupGracefulShutdown()\n\n for await (const cycle of this.process(queues)) {\n if (['started', 'completed'].includes(cycle.type)) {\n continue\n }\n\n if (['idle', 'error'].includes(cycle.type)) {\n // @ts-expect-error - we know suggestedDelay exists for these types\n const delay = parse(cycle.suggestedDelay)\n\n if (cycle.type === 'error') {\n debug('worker %s encountered an error: %O', this.#id, cycle.error)\n } else {\n debug('worker %s is idle, waiting for %dms', this.#id, delay)\n }\n\n await setTimeout(delay)\n }\n }\n }\n\n async stop() {\n debug('stopping worker %s', this.#id)\n\n this.#running = false\n\n if (this.#pool) {\n debug('worker %s: waiting for %d running jobs to complete', this.#id, this.#pool.size)\n await this.#pool.drain()\n }\n\n if (this.#adapter) {\n await this.#adapter.destroy()\n }\n }\n\n async processCycle(queues: string[]): Promise<WorkerCycle | null> {\n await this.init()\n\n this.#running = true\n\n if (!this.#generator) {\n this.#generator = this.process(queues)\n }\n\n const result = await this.#generator.next()\n\n if (result.done) {\n this.#generator = undefined\n return null\n }\n\n return result.value\n }\n\n async *process(queues: string[]): AsyncGenerator<WorkerCycle, void, unknown> {\n const pollingInterval = parse(this.#config.worker?.pollingInterval || '2s')\n this.#pool = new JobPool()\n\n while (this.#running) {\n try {\n yield* this.#fillPool(queues)\n\n if (this.#pool.isEmpty()) {\n yield { type: 'idle', suggestedDelay: pollingInterval }\n continue\n }\n\n const completed = await this.#pool.waitForNextCompletion()\n yield { type: 'completed', queue: completed.queue, job: completed.job }\n } catch (error) {\n yield { type: 'error', error: error as Error, suggestedDelay: parse('5s') }\n }\n }\n }\n\n async *#fillPool(queues: string[]): AsyncGenerator<WorkerCycle, void, unknown> {\n const concurrency = this.#config.worker?.concurrency || 1\n\n while (this.#pool!.hasCapacity(concurrency)) {\n const result = await this.#acquireNextJob(queues)\n if (!result) break\n\n const { job, queue } = result\n const promise = this.#execute(job, queue)\n this.#pool!.add(job, queue, promise)\n\n yield { type: 'started', queue, job }\n }\n }\n\n async #execute(job: AcquiredJob, queue: string): Promise<void> {\n const startTime = performance.now()\n\n debug('worker %s: executing job %s (%s)', this.#id, job.id, job.name)\n\n const { instance, options, timeout } = await this.#initJob(job, queue)\n\n try {\n await this.#executeWithTimeout(instance, timeout)\n await this.#adapter.completeJob(job.id, queue)\n\n const duration = (performance.now() - startTime).toFixed(2)\n debug('worker %s: successfully executed job %s in %dms', this.#id, job.id, duration)\n } catch (e) {\n const isTimeout = e instanceof errors.E_JOB_TIMEOUT\n\n if (isTimeout && options.failOnTimeout) {\n debug('worker %s: job %s timed out and failOnTimeout is set', this.#id, job.id)\n await this.#adapter.failJob(job.id, queue, e as Error)\n await instance.failed?.(e as Error)\n return\n }\n\n const mergedConfig = QueueManager.getMergedRetryConfig(queue, options.retry)\n\n if (typeof mergedConfig.maxRetries === 'undefined' || mergedConfig.maxRetries <= 0) {\n debug('worker %s: job %s has no retries configured, marking as failed', this.#id, job.id)\n await this.#adapter.failJob(job.id, queue, e as Error)\n await instance.failed?.(e as Error)\n return\n }\n\n if (job.attempts >= mergedConfig.maxRetries!) {\n debug(\n 'worker %s: job %s has exceeded max retries (%d), marking as failed',\n this.#id,\n job.id,\n mergedConfig.maxRetries\n )\n await this.#adapter.failJob(job.id, queue, e as Error)\n const exception = new errors.E_JOB_MAX_ATTEMPTS_REACHED([job.name])\n await instance.failed?.(exception)\n\n return\n }\n\n if (mergedConfig.backoff) {\n const strategy = mergedConfig.backoff()\n const nextRetryAt = strategy.getNextRetryAt(job.attempts + 1)\n\n debug('worker %s: job %s will retry at %s', this.#id, job.id, nextRetryAt.toISOString())\n\n await this.#adapter.retryJob(job.id, queue, nextRetryAt)\n return\n }\n\n await this.#adapter.retryJob(job.id, queue)\n }\n }\n\n async #initJob(\n job: AcquiredJob,\n queue: string\n ): Promise<{ instance: Job; options: JobOptions; timeout: number | undefined }> {\n try {\n const JobClass = Locator.getOrThrow(job.name)\n const instance = new JobClass(job.payload)\n const options = JobClass.options || {}\n const timeout = this.#getJobTimeout(options)\n\n return { instance, options, timeout }\n } catch (error) {\n debug('worker %s: failed to initialize job %s (%s)', this.#id, job.id, job.name)\n await this.#adapter.failJob(job.id, queue, error as Error)\n throw error\n }\n }\n\n #getJobTimeout(options: JobOptions): number | undefined {\n if (options.timeout !== undefined) {\n return parse(options.timeout)\n }\n\n if (this.#config.worker?.timeout !== undefined) {\n return parse(this.#config.worker.timeout)\n }\n\n return undefined\n }\n\n async #executeWithTimeout(instance: Job, timeout?: number): Promise<void> {\n if (!timeout) {\n return instance.execute()\n }\n\n const signal = AbortSignal.timeout(timeout)\n\n const abortPromise = new Promise<never>((_, reject) => {\n signal.addEventListener('abort', () => {\n reject(new errors.E_JOB_TIMEOUT([instance.constructor.name, timeout]))\n })\n })\n\n await Promise.race([instance.execute(signal), abortPromise])\n }\n\n async #acquireNextJob(queues: string[]): Promise<{ job: AcquiredJob; queue: string } | null> {\n for (const queue of queues) {\n const job = await this.#adapter.popFrom(queue)\n\n if (!job) {\n continue\n }\n\n debug('worker %s: acquired job %s', this.#id, job.id)\n return { job, queue }\n }\n\n return null\n }\n\n async #setupGracefulShutdown() {\n const shutdown = async () => {\n debug('received shutdown signal, stopping worker...')\n await this.stop()\n process.exit(0)\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n }\n}\n","import * as errors from '#src/exceptions'\nimport debug from '#src/debug'\nimport { Locator } from '#src/locator'\nimport type { Adapter } from '#contracts/adapter'\nimport type { AdapterFactory, QueueConfig, QueueManagerConfig, RetryConfig } from '#types/main'\n\nclass QueueManagerSingleton {\n #defaultAdapter!: string\n #adapters: Record<string, AdapterFactory> = {}\n #globalRetryConfig?: RetryConfig\n #queueConfigs: Map<string, QueueConfig> = new Map()\n\n async init(config: QueueManagerConfig) {\n debug('initializing queue manager with config: %O', config)\n\n this.#validateConfig(config)\n\n this.#defaultAdapter = config.default\n this.#adapters = config.adapters\n this.#globalRetryConfig = config.retry\n\n if (config.queues) {\n for (const [queue, queueConfig] of Object.entries(config.queues)) {\n this.#queueConfigs.set(queue, queueConfig as QueueConfig)\n }\n }\n\n await Locator.registerFromGlob(config.locations)\n\n return this\n }\n\n use(adapter?: string): Adapter {\n if (!adapter) {\n adapter = this.#defaultAdapter\n }\n\n const adapterInstance = this.#adapters[adapter]\n\n if (!adapterInstance) {\n throw new errors.E_CONFIGURATION_ERROR([`Adapter \"${adapter}\" is not registered`])\n }\n\n debug('using adapter \"%s\"', adapter)\n\n try {\n return adapterInstance()\n } catch (error) {\n // TODO: Improve error handling\n throw new Error()\n // throw new errors.E_ADAPTER_ERROR(`Failed to initialize adapter \"${adapter}\"`, error as Error)\n }\n }\n\n /**\n * Priority: job > queue > global\n */\n getMergedRetryConfig(queue: string, jobRetryConfig?: RetryConfig): RetryConfig {\n const queueConfig = this.#queueConfigs.get(queue)\n const queueRetryConfig = queueConfig?.retry || {}\n\n let maxRetries =\n jobRetryConfig?.maxRetries ||\n queueRetryConfig.maxRetries ||\n this.#globalRetryConfig?.maxRetries ||\n 0\n\n let backoff =\n jobRetryConfig?.backoff || queueRetryConfig.backoff || this.#globalRetryConfig?.backoff\n\n return { maxRetries, backoff }\n }\n\n #validateConfig(config: QueueManagerConfig): void {\n if (!config.adapters || Object.keys(config.adapters).length === 0) {\n throw new errors.E_CONFIGURATION_ERROR(['At least one adapter must be configured'])\n }\n\n if (!config.default) {\n throw new errors.E_CONFIGURATION_ERROR(['Default adapter must be specified'])\n }\n\n if (!config.locations || config.locations.length === 0) {\n throw new errors.E_CONFIGURATION_ERROR(['Job locations must be specified'])\n }\n\n if (!config.adapters[config.default]) {\n throw new errors.E_CONFIGURATION_ERROR([\n `Default adapter \"${config.default}\" not found in adapters configuration`,\n ])\n }\n\n for (const [name, factory] of Object.entries(config.adapters)) {\n if (typeof factory !== 'function') {\n throw new errors.E_CONFIGURATION_ERROR([`Adapter \"${name}\" must be a factory function`])\n }\n }\n }\n\n async destroy() {\n for (const adapterName in this.#adapters) {\n const adapter = this.#adapters[adapterName]()\n await adapter.destroy()\n }\n }\n}\n\nexport const QueueManager = new QueueManagerSingleton()\n","import type { BackoffConfig, Duration } from '#types/main'\nimport * as errors from '#src/exceptions'\nimport { parse } from '#src/utils'\nimport { RuntimeException } from '@poppinss/utils'\nimport { assertUnreachable } from '@poppinss/utils/assert'\n\nexport class BackoffStrategy {\n readonly #config: BackoffConfig\n\n constructor(config: BackoffConfig) {\n this.#config = config\n this.#validateConfig()\n }\n\n calculateDelay(attempt: number): number {\n if (attempt < 1) {\n throw new RuntimeException('Attempt number must be >= 1')\n }\n\n const baseDelayMs = parse(this.#config.baseDelay)\n const maxDelayMs = this.#config.maxDelay ? parse(this.#config.maxDelay) : Infinity\n const multiplier = this.#config.multiplier ?? 2\n\n let delay: number\n\n switch (this.#config.strategy) {\n case 'exponential':\n delay = baseDelayMs * Math.pow(multiplier, attempt - 1)\n break\n case 'linear':\n delay = baseDelayMs * attempt\n break\n case 'fixed':\n delay = baseDelayMs\n break\n default:\n assertUnreachable(this.#config.strategy)\n }\n\n // Apply max delay limit\n delay = Math.min(delay, maxDelayMs)\n\n if (this.#config.jitter) {\n delay = this.#applyJitter(delay)\n }\n\n return Math.floor(delay)\n }\n\n getNextRetryAt(attempt: number): Date {\n const delay = this.calculateDelay(attempt)\n return new Date(Date.now() + delay)\n }\n\n getConfig(): Readonly<BackoffConfig> {\n return Object.freeze({ ...this.#config })\n }\n\n #validateConfig() {\n const baseDelayMs = parse(this.#config.baseDelay)\n\n if (baseDelayMs <= 0) {\n throw new errors.E_INVALID_BASE_DELAY([\n 'Base delay must be a positive integer greater than zero',\n ])\n }\n\n if (this.#config.maxDelay) {\n const maxDelayMs = parse(this.#config.maxDelay)\n\n if (maxDelayMs <= 0) {\n throw new errors.E_INVALID_MAX_DELAY([\n 'Max delay must be a positive integer greater than zero',\n ])\n }\n\n if (maxDelayMs <= baseDelayMs) {\n throw new errors.E_INVALID_MAX_DELAY(['Max delay should be greater than base delay'])\n }\n }\n\n if (this.#config.multiplier !== undefined) {\n if (this.#config.multiplier <= 0) {\n throw new errors.E_INVALID_MULTIPLIER([\n 'Multiplier must be a positive number greater than zero',\n ])\n }\n\n if (this.#config.strategy === 'exponential' && this.#config.multiplier < 1) {\n throw new errors.E_INVALID_MULTIPLIER(['Exponential strategy multiplier should be >= 1'])\n }\n }\n }\n\n #applyJitter(delay: number): number {\n const jitterRange = delay * 0.25\n const jitter = (Math.random() - 0.5) * 2 * jitterRange\n\n return Math.max(0, delay + jitter)\n }\n}\n\nexport function exponentialBackoff(config?: Partial<Omit<BackoffConfig, 'strategy'>>) {\n return () =>\n new BackoffStrategy({\n strategy: 'exponential',\n baseDelay: '1s',\n maxDelay: '5m',\n multiplier: 2,\n jitter: true,\n ...config,\n })\n}\n\nexport function linearBackoff(config?: Partial<Omit<BackoffConfig, 'strategy'>>) {\n return () =>\n new BackoffStrategy({\n strategy: 'linear',\n baseDelay: '5s',\n maxDelay: '2m',\n ...config,\n })\n}\n\nexport function fixedBackoff(delay: Duration = '10s') {\n return () =>\n new BackoffStrategy({\n strategy: 'fixed',\n baseDelay: delay,\n })\n}\n\nexport function customBackoff(config: BackoffConfig) {\n return () => new BackoffStrategy(config)\n}\n"],"mappings":"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","names":["errors","debug","Locator","QueueManager","errors","parse"]}
|
|
1
|
+
{"version":3,"sources":["../src/job.ts","../src/worker.ts","../src/queue_manager.ts","../src/strategies/backoff_strategy.ts"],"sourcesContent":["import { JobDispatcher } from '#src/job_dispatcher'\nimport type { JobOptions } from '#types/main'\n\nexport abstract class Job<Payload = any> {\n readonly #payload: Payload\n\n static options: JobOptions = {}\n\n get payload(): Payload {\n return this.#payload\n }\n\n constructor(payload: Payload) {\n this.#payload = payload\n }\n\n static dispatch<T extends Job>(\n this: new (payload: any) => T,\n payload: T extends Job<infer P> ? P : never\n ): JobDispatcher<T extends Job<infer P> ? P : never> {\n const dispatcher = new JobDispatcher<T extends Job<infer P> ? P : never>(\n (this as any).jobName,\n payload\n )\n\n if ((this as any).options.queue) {\n dispatcher.toQueue((this as any).options.queue)\n }\n\n if ((this as any).options.adapter) {\n dispatcher.with((this as any).options.adapter)\n }\n\n if ((this as any).options.priority !== undefined) {\n dispatcher.priority((this as any).options.priority)\n }\n\n return dispatcher\n }\n\n abstract execute(signal?: AbortSignal): Promise<void>\n\n failed?(error: Error): Promise<void>\n}\n","import { randomUUID } from 'node:crypto'\nimport { setTimeout } from 'node:timers/promises'\nimport debug from '#src/debug'\nimport { parse } from '#src/utils'\nimport * as errors from '#src/exceptions'\nimport { QueueManager } from '#src/queue_manager'\nimport { JobPool } from '#src/job_pool'\nimport type { Adapter, AcquiredJob } from '#contracts/adapter'\nimport type { QueueManagerConfig, WorkerCycle } from '#types/main'\nimport { Locator } from '#src/locator'\nimport type { JobOptions } from '#types/main'\nimport type { Job } from '#src/job'\n\nexport class Worker {\n readonly #id: string\n readonly #config: QueueManagerConfig\n #adapter!: Adapter\n #running = false\n #initialized = false\n #generator?: AsyncGenerator<WorkerCycle, void, unknown>\n #pool?: JobPool\n\n get id() {\n return this.#id\n }\n\n constructor(config: QueueManagerConfig) {\n this.#config = config\n this.#id = randomUUID()\n\n debug('created worker with id %s and config %O', this.#id, config)\n }\n\n async init() {\n if (this.#initialized) {\n return\n }\n\n debug('initializing worker %s', this.#id)\n\n await QueueManager.init(this.#config)\n\n this.#adapter = QueueManager.use()\n this.#adapter.setWorkerId(this.#id)\n\n this.#initialized = true\n\n debug('worker %s initialized', this.#id)\n }\n\n async start(queues: string[] = ['default']): Promise<void> {\n await this.init()\n\n if (this.#running) {\n debug('worker %s is already running', this.#id)\n return\n }\n\n this.#running = true\n\n debug('starting worker %s on queues: %O', this.#id, queues)\n\n await this.#setupGracefulShutdown()\n\n for await (const cycle of this.process(queues)) {\n if (['started', 'completed'].includes(cycle.type)) {\n continue\n }\n\n if (['idle', 'error'].includes(cycle.type)) {\n // @ts-expect-error - we know suggestedDelay exists for these types\n const delay = parse(cycle.suggestedDelay)\n\n if (cycle.type === 'error') {\n debug('worker %s encountered an error: %O', this.#id, cycle.error)\n } else {\n debug('worker %s is idle, waiting for %dms', this.#id, delay)\n }\n\n await setTimeout(delay)\n }\n }\n }\n\n async stop() {\n debug('stopping worker %s', this.#id)\n\n this.#running = false\n\n if (this.#pool) {\n debug('worker %s: waiting for %d running jobs to complete', this.#id, this.#pool.size)\n await this.#pool.drain()\n }\n\n if (this.#adapter) {\n await this.#adapter.destroy()\n }\n }\n\n async processCycle(queues: string[]): Promise<WorkerCycle | null> {\n await this.init()\n\n this.#running = true\n\n if (!this.#generator) {\n this.#generator = this.process(queues)\n }\n\n const result = await this.#generator.next()\n\n if (result.done) {\n this.#generator = undefined\n return null\n }\n\n return result.value\n }\n\n async *process(queues: string[]): AsyncGenerator<WorkerCycle, void, unknown> {\n const pollingInterval = parse(this.#config.worker?.pollingInterval || '2s')\n this.#pool = new JobPool()\n\n while (this.#running) {\n try {\n yield* this.#fillPool(queues)\n\n if (this.#pool.isEmpty()) {\n yield { type: 'idle', suggestedDelay: pollingInterval }\n continue\n }\n\n const completed = await this.#pool.waitForNextCompletion()\n yield { type: 'completed', queue: completed.queue, job: completed.job }\n } catch (error) {\n yield { type: 'error', error: error as Error, suggestedDelay: parse('5s') }\n }\n }\n }\n\n async *#fillPool(queues: string[]): AsyncGenerator<WorkerCycle, void, unknown> {\n const concurrency = this.#config.worker?.concurrency || 1\n const slotsAvailable = concurrency - this.#pool!.size\n\n if (slotsAvailable <= 0) return\n\n const popPromises = Array.from({ length: slotsAvailable }, () => this.#acquireNextJob(queues))\n\n const results = await Promise.all(popPromises)\n\n for (const result of results) {\n if (!result) continue\n\n const { job, queue } = result\n const promise = this.#execute(job, queue)\n this.#pool!.add(job, queue, promise)\n\n yield { type: 'started', queue, job }\n }\n }\n\n async #execute(job: AcquiredJob, queue: string): Promise<void> {\n const startTime = performance.now()\n\n debug('worker %s: executing job %s (%s)', this.#id, job.id, job.name)\n\n const { instance, options, timeout } = await this.#initJob(job, queue)\n\n try {\n await this.#executeWithTimeout(instance, timeout)\n await this.#adapter.completeJob(job.id, queue)\n\n const duration = (performance.now() - startTime).toFixed(2)\n debug('worker %s: successfully executed job %s in %dms', this.#id, job.id, duration)\n } catch (e) {\n const isTimeout = e instanceof errors.E_JOB_TIMEOUT\n\n if (isTimeout && options.failOnTimeout) {\n debug('worker %s: job %s timed out and failOnTimeout is set', this.#id, job.id)\n await this.#adapter.failJob(job.id, queue, e as Error)\n await instance.failed?.(e as Error)\n return\n }\n\n const mergedConfig = QueueManager.getMergedRetryConfig(queue, options.retry)\n\n if (typeof mergedConfig.maxRetries === 'undefined' || mergedConfig.maxRetries <= 0) {\n debug('worker %s: job %s has no retries configured, marking as failed', this.#id, job.id)\n await this.#adapter.failJob(job.id, queue, e as Error)\n await instance.failed?.(e as Error)\n return\n }\n\n if (job.attempts >= mergedConfig.maxRetries!) {\n debug(\n 'worker %s: job %s has exceeded max retries (%d), marking as failed',\n this.#id,\n job.id,\n mergedConfig.maxRetries\n )\n await this.#adapter.failJob(job.id, queue, e as Error)\n const exception = new errors.E_JOB_MAX_ATTEMPTS_REACHED([job.name])\n await instance.failed?.(exception)\n\n return\n }\n\n if (mergedConfig.backoff) {\n const strategy = mergedConfig.backoff()\n const nextRetryAt = strategy.getNextRetryAt(job.attempts + 1)\n\n debug('worker %s: job %s will retry at %s', this.#id, job.id, nextRetryAt.toISOString())\n\n await this.#adapter.retryJob(job.id, queue, nextRetryAt)\n return\n }\n\n await this.#adapter.retryJob(job.id, queue)\n }\n }\n\n async #initJob(\n job: AcquiredJob,\n queue: string\n ): Promise<{ instance: Job; options: JobOptions; timeout: number | undefined }> {\n try {\n const JobClass = Locator.getOrThrow(job.name)\n const instance = new JobClass(job.payload)\n const options = JobClass.options || {}\n const timeout = this.#getJobTimeout(options)\n\n return { instance, options, timeout }\n } catch (error) {\n debug('worker %s: failed to initialize job %s (%s)', this.#id, job.id, job.name)\n await this.#adapter.failJob(job.id, queue, error as Error)\n throw error\n }\n }\n\n #getJobTimeout(options: JobOptions): number | undefined {\n if (options.timeout !== undefined) {\n return parse(options.timeout)\n }\n\n if (this.#config.worker?.timeout !== undefined) {\n return parse(this.#config.worker.timeout)\n }\n\n return undefined\n }\n\n async #executeWithTimeout(instance: Job, timeout?: number): Promise<void> {\n if (!timeout) {\n return instance.execute()\n }\n\n const signal = AbortSignal.timeout(timeout)\n\n const abortPromise = new Promise<never>((_, reject) => {\n signal.addEventListener('abort', () => {\n reject(new errors.E_JOB_TIMEOUT([instance.constructor.name, timeout]))\n })\n })\n\n await Promise.race([instance.execute(signal), abortPromise])\n }\n\n async #acquireNextJob(queues: string[]): Promise<{ job: AcquiredJob; queue: string } | null> {\n for (const queue of queues) {\n const job = await this.#adapter.popFrom(queue)\n\n if (!job) {\n continue\n }\n\n debug('worker %s: acquired job %s', this.#id, job.id)\n return { job, queue }\n }\n\n return null\n }\n\n async #setupGracefulShutdown() {\n const shutdown = async () => {\n debug('received shutdown signal, stopping worker...')\n await this.stop()\n process.exit(0)\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n }\n}\n","import * as errors from '#src/exceptions'\nimport debug from '#src/debug'\nimport { Locator } from '#src/locator'\nimport type { Adapter } from '#contracts/adapter'\nimport type { AdapterFactory, QueueConfig, QueueManagerConfig, RetryConfig } from '#types/main'\n\nclass QueueManagerSingleton {\n #defaultAdapter!: string\n #adapters: Record<string, AdapterFactory> = {}\n #adapterInstances: Map<string, Adapter> = new Map()\n #globalRetryConfig?: RetryConfig\n #queueConfigs: Map<string, QueueConfig> = new Map()\n\n async init(config: QueueManagerConfig) {\n debug('initializing queue manager with config: %O', config)\n\n this.#validateConfig(config)\n\n this.#adapterInstances.clear()\n\n this.#defaultAdapter = config.default\n this.#adapters = config.adapters\n this.#globalRetryConfig = config.retry\n\n if (config.queues) {\n for (const [queue, queueConfig] of Object.entries(config.queues)) {\n this.#queueConfigs.set(queue, queueConfig as QueueConfig)\n }\n }\n\n await Locator.registerFromGlob(config.locations)\n\n return this\n }\n\n use(adapter?: string): Adapter {\n if (!adapter) {\n adapter = this.#defaultAdapter\n }\n\n // Return cached instance if exists\n const cached = this.#adapterInstances.get(adapter)\n if (cached) {\n return cached\n }\n\n const adapterFactory = this.#adapters[adapter]\n\n if (!adapterFactory) {\n throw new errors.E_CONFIGURATION_ERROR([`Adapter \"${adapter}\" is not registered`])\n }\n\n debug('using adapter \"%s\"', adapter)\n\n try {\n const instance = adapterFactory()\n this.#adapterInstances.set(adapter, instance)\n return instance\n } catch (error) {\n // TODO: Improve error handling\n throw new Error()\n // throw new errors.E_ADAPTER_ERROR(`Failed to initialize adapter \"${adapter}\"`, error as Error)\n }\n }\n\n /**\n * Priority: job > queue > global\n */\n getMergedRetryConfig(queue: string, jobRetryConfig?: RetryConfig): RetryConfig {\n const queueConfig = this.#queueConfigs.get(queue)\n const queueRetryConfig = queueConfig?.retry || {}\n\n let maxRetries =\n jobRetryConfig?.maxRetries ||\n queueRetryConfig.maxRetries ||\n this.#globalRetryConfig?.maxRetries ||\n 0\n\n let backoff =\n jobRetryConfig?.backoff || queueRetryConfig.backoff || this.#globalRetryConfig?.backoff\n\n return { maxRetries, backoff }\n }\n\n #validateConfig(config: QueueManagerConfig): void {\n if (!config.adapters || Object.keys(config.adapters).length === 0) {\n throw new errors.E_CONFIGURATION_ERROR(['At least one adapter must be configured'])\n }\n\n if (!config.default) {\n throw new errors.E_CONFIGURATION_ERROR(['Default adapter must be specified'])\n }\n\n if (!config.locations || config.locations.length === 0) {\n throw new errors.E_CONFIGURATION_ERROR(['Job locations must be specified'])\n }\n\n if (!config.adapters[config.default]) {\n throw new errors.E_CONFIGURATION_ERROR([\n `Default adapter \"${config.default}\" not found in adapters configuration`,\n ])\n }\n\n for (const [name, factory] of Object.entries(config.adapters)) {\n if (typeof factory !== 'function') {\n throw new errors.E_CONFIGURATION_ERROR([`Adapter \"${name}\" must be a factory function`])\n }\n }\n }\n\n async destroy() {\n for (const [name, adapter] of this.#adapterInstances) {\n debug('destroying adapter \"%s\"', name)\n await adapter.destroy()\n }\n this.#adapterInstances.clear()\n }\n}\n\nexport const QueueManager = new QueueManagerSingleton()\n","import type { BackoffConfig, Duration } from '#types/main'\nimport * as errors from '#src/exceptions'\nimport { parse } from '#src/utils'\nimport { RuntimeException } from '@poppinss/utils'\nimport { assertUnreachable } from '@poppinss/utils/assert'\n\nexport class BackoffStrategy {\n readonly #config: BackoffConfig\n\n constructor(config: BackoffConfig) {\n this.#config = config\n this.#validateConfig()\n }\n\n calculateDelay(attempt: number): number {\n if (attempt < 1) {\n throw new RuntimeException('Attempt number must be >= 1')\n }\n\n const baseDelayMs = parse(this.#config.baseDelay)\n const maxDelayMs = this.#config.maxDelay ? parse(this.#config.maxDelay) : Infinity\n const multiplier = this.#config.multiplier ?? 2\n\n let delay: number\n\n switch (this.#config.strategy) {\n case 'exponential':\n delay = baseDelayMs * Math.pow(multiplier, attempt - 1)\n break\n case 'linear':\n delay = baseDelayMs * attempt\n break\n case 'fixed':\n delay = baseDelayMs\n break\n default:\n assertUnreachable(this.#config.strategy)\n }\n\n // Apply max delay limit\n delay = Math.min(delay, maxDelayMs)\n\n if (this.#config.jitter) {\n delay = this.#applyJitter(delay)\n }\n\n return Math.floor(delay)\n }\n\n getNextRetryAt(attempt: number): Date {\n const delay = this.calculateDelay(attempt)\n return new Date(Date.now() + delay)\n }\n\n getConfig(): Readonly<BackoffConfig> {\n return Object.freeze({ ...this.#config })\n }\n\n #validateConfig() {\n const baseDelayMs = parse(this.#config.baseDelay)\n\n if (baseDelayMs <= 0) {\n throw new errors.E_INVALID_BASE_DELAY([\n 'Base delay must be a positive integer greater than zero',\n ])\n }\n\n if (this.#config.maxDelay) {\n const maxDelayMs = parse(this.#config.maxDelay)\n\n if (maxDelayMs <= 0) {\n throw new errors.E_INVALID_MAX_DELAY([\n 'Max delay must be a positive integer greater than zero',\n ])\n }\n\n if (maxDelayMs <= baseDelayMs) {\n throw new errors.E_INVALID_MAX_DELAY(['Max delay should be greater than base delay'])\n }\n }\n\n if (this.#config.multiplier !== undefined) {\n if (this.#config.multiplier <= 0) {\n throw new errors.E_INVALID_MULTIPLIER([\n 'Multiplier must be a positive number greater than zero',\n ])\n }\n\n if (this.#config.strategy === 'exponential' && this.#config.multiplier < 1) {\n throw new errors.E_INVALID_MULTIPLIER(['Exponential strategy multiplier should be >= 1'])\n }\n }\n }\n\n #applyJitter(delay: number): number {\n const jitterRange = delay * 0.25\n const jitter = (Math.random() - 0.5) * 2 * jitterRange\n\n return Math.max(0, delay + jitter)\n }\n}\n\nexport function exponentialBackoff(config?: Partial<Omit<BackoffConfig, 'strategy'>>) {\n return () =>\n new BackoffStrategy({\n strategy: 'exponential',\n baseDelay: '1s',\n maxDelay: '5m',\n multiplier: 2,\n jitter: true,\n ...config,\n })\n}\n\nexport function linearBackoff(config?: Partial<Omit<BackoffConfig, 'strategy'>>) {\n return () =>\n new BackoffStrategy({\n strategy: 'linear',\n baseDelay: '5s',\n maxDelay: '2m',\n ...config,\n })\n}\n\nexport function fixedBackoff(delay: Duration = '10s') {\n return () =>\n new BackoffStrategy({\n strategy: 'fixed',\n baseDelay: delay,\n })\n}\n\nexport function customBackoff(config: BackoffConfig) {\n return () => new BackoffStrategy(config)\n}\n"],"mappings":"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","names":["errors","debug","Locator","QueueManager","errors","parse"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Knex } from 'knex';
|
|
2
|
+
import { A as Adapter, a as AcquiredJob, b as JobData } from '../../job-Bd_c2lFK.js';
|
|
3
|
+
|
|
4
|
+
interface KnexAdapterOptions {
|
|
5
|
+
connection: Knex;
|
|
6
|
+
tableName?: string;
|
|
7
|
+
ownsConnection?: boolean;
|
|
8
|
+
}
|
|
9
|
+
type KnexConfig = Knex | Knex.Config;
|
|
10
|
+
/**
|
|
11
|
+
* Create a new Knex adapter factory.
|
|
12
|
+
* Accepts either a Knex instance or a Knex configuration object.
|
|
13
|
+
*
|
|
14
|
+
* When passing a config object, the adapter will create and manage
|
|
15
|
+
* the connection lifecycle (closing it on destroy).
|
|
16
|
+
*
|
|
17
|
+
* When passing a Knex instance, the caller is responsible for
|
|
18
|
+
* managing the connection lifecycle.
|
|
19
|
+
*/
|
|
20
|
+
declare function knex(config: KnexConfig, tableName?: string): () => KnexAdapter;
|
|
21
|
+
/**
|
|
22
|
+
* Knex adapter for the queue system.
|
|
23
|
+
* Stores jobs in a SQL database using Knex.
|
|
24
|
+
*/
|
|
25
|
+
declare class KnexAdapter implements Adapter {
|
|
26
|
+
#private;
|
|
27
|
+
constructor(config: KnexAdapterOptions);
|
|
28
|
+
setWorkerId(workerId: string): void;
|
|
29
|
+
destroy(): Promise<void>;
|
|
30
|
+
pop(): Promise<AcquiredJob | null>;
|
|
31
|
+
popFrom(queue: string): Promise<AcquiredJob | null>;
|
|
32
|
+
completeJob(jobId: string, queue: string): Promise<void>;
|
|
33
|
+
failJob(jobId: string, queue: string, _error?: Error): Promise<void>;
|
|
34
|
+
retryJob(jobId: string, queue: string, retryAt?: Date): Promise<void>;
|
|
35
|
+
push(jobData: JobData): Promise<void>;
|
|
36
|
+
pushOn(queue: string, jobData: JobData): Promise<void>;
|
|
37
|
+
pushLater(jobData: JobData, delay: number): Promise<void>;
|
|
38
|
+
pushLaterOn(queue: string, jobData: JobData, delay: number): Promise<void>;
|
|
39
|
+
size(): Promise<number>;
|
|
40
|
+
sizeOf(queue: string): Promise<number>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export { KnexAdapter, type KnexAdapterOptions, knex };
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// src/drivers/knex_adapter.ts
|
|
2
|
+
import KnexPkg from "knex";
|
|
3
|
+
function knex(config, tableName) {
|
|
4
|
+
return () => {
|
|
5
|
+
const isKnexInstance = typeof config === "function";
|
|
6
|
+
const connection = isKnexInstance ? config : KnexPkg(config);
|
|
7
|
+
return new KnexAdapter({ connection, tableName, ownsConnection: !isKnexInstance });
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
var KnexAdapter = class {
|
|
11
|
+
#connection;
|
|
12
|
+
#tableName;
|
|
13
|
+
#ownsConnection;
|
|
14
|
+
#workerId = "";
|
|
15
|
+
#initialized = false;
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.#connection = config.connection;
|
|
18
|
+
this.#tableName = config.tableName ?? "queue_jobs";
|
|
19
|
+
this.#ownsConnection = config.ownsConnection ?? false;
|
|
20
|
+
}
|
|
21
|
+
setWorkerId(workerId) {
|
|
22
|
+
this.#workerId = workerId;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Ensure the jobs table exists.
|
|
26
|
+
* Creates it if not exists, handles race conditions.
|
|
27
|
+
*/
|
|
28
|
+
async #ensureTableExists() {
|
|
29
|
+
if (this.#initialized) return;
|
|
30
|
+
try {
|
|
31
|
+
await this.#connection.schema.createTable(this.#tableName, (table) => {
|
|
32
|
+
table.string("id", 255).notNullable();
|
|
33
|
+
table.string("queue", 255).notNullable();
|
|
34
|
+
table.enu("status", ["pending", "active", "delayed"]).notNullable();
|
|
35
|
+
table.text("data").notNullable();
|
|
36
|
+
table.bigint("score").unsigned().nullable();
|
|
37
|
+
table.string("worker_id", 255).nullable();
|
|
38
|
+
table.bigint("acquired_at").unsigned().nullable();
|
|
39
|
+
table.bigint("execute_at").unsigned().nullable();
|
|
40
|
+
table.primary(["id", "queue"]);
|
|
41
|
+
table.index(["queue", "status", "score"]);
|
|
42
|
+
table.index(["queue", "status", "execute_at"]);
|
|
43
|
+
});
|
|
44
|
+
} catch {
|
|
45
|
+
const hasTable = await this.#connection.schema.hasTable(this.#tableName);
|
|
46
|
+
if (!hasTable) {
|
|
47
|
+
throw new Error(`Failed to create table "${this.#tableName}"`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
this.#initialized = true;
|
|
51
|
+
}
|
|
52
|
+
async destroy() {
|
|
53
|
+
if (this.#ownsConnection) {
|
|
54
|
+
await this.#connection.destroy();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async pop() {
|
|
58
|
+
return this.popFrom("default");
|
|
59
|
+
}
|
|
60
|
+
async popFrom(queue) {
|
|
61
|
+
await this.#ensureTableExists();
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
await this.#processDelayedJobs(queue, now);
|
|
64
|
+
return this.#connection.transaction(async (trx) => {
|
|
65
|
+
const job = await trx(this.#tableName).where("queue", queue).where("status", "pending").orderBy("score", "asc").first();
|
|
66
|
+
if (!job) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
await trx(this.#tableName).where("id", job.id).where("queue", queue).update({
|
|
70
|
+
status: "active",
|
|
71
|
+
worker_id: this.#workerId,
|
|
72
|
+
acquired_at: now
|
|
73
|
+
});
|
|
74
|
+
const jobData = JSON.parse(job.data);
|
|
75
|
+
return {
|
|
76
|
+
...jobData,
|
|
77
|
+
acquiredAt: now
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async #processDelayedJobs(queue, now) {
|
|
82
|
+
const delayedJobs = await this.#connection(this.#tableName).where("queue", queue).where("status", "delayed").where("execute_at", "<=", now).select("id", "data");
|
|
83
|
+
if (delayedJobs.length === 0) return;
|
|
84
|
+
for (const job of delayedJobs) {
|
|
85
|
+
const jobData = JSON.parse(job.data);
|
|
86
|
+
const priority = jobData.priority ?? 5;
|
|
87
|
+
const score = priority * 1e13 + now;
|
|
88
|
+
await this.#connection(this.#tableName).where("id", job.id).where("queue", queue).update({
|
|
89
|
+
status: "pending",
|
|
90
|
+
score,
|
|
91
|
+
execute_at: null
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async completeJob(jobId, queue) {
|
|
96
|
+
await this.#ensureTableExists();
|
|
97
|
+
await this.#connection(this.#tableName).where("id", jobId).where("queue", queue).delete();
|
|
98
|
+
}
|
|
99
|
+
async failJob(jobId, queue, _error) {
|
|
100
|
+
await this.#ensureTableExists();
|
|
101
|
+
await this.#connection(this.#tableName).where("id", jobId).where("queue", queue).delete();
|
|
102
|
+
}
|
|
103
|
+
async retryJob(jobId, queue, retryAt) {
|
|
104
|
+
await this.#ensureTableExists();
|
|
105
|
+
const now = Date.now();
|
|
106
|
+
const activeJob = await this.#connection(this.#tableName).where("id", jobId).where("queue", queue).where("status", "active").first();
|
|
107
|
+
if (!activeJob) return;
|
|
108
|
+
const jobData = JSON.parse(activeJob.data);
|
|
109
|
+
jobData.attempts = (jobData.attempts || 0) + 1;
|
|
110
|
+
const updatedData = JSON.stringify(jobData);
|
|
111
|
+
if (retryAt && retryAt.getTime() > now) {
|
|
112
|
+
await this.#connection(this.#tableName).where("id", jobId).where("queue", queue).update({
|
|
113
|
+
status: "delayed",
|
|
114
|
+
data: updatedData,
|
|
115
|
+
worker_id: null,
|
|
116
|
+
acquired_at: null,
|
|
117
|
+
score: null,
|
|
118
|
+
execute_at: retryAt.getTime()
|
|
119
|
+
});
|
|
120
|
+
} else {
|
|
121
|
+
const priority = jobData.priority ?? 5;
|
|
122
|
+
const score = priority * 1e13 + now;
|
|
123
|
+
await this.#connection(this.#tableName).where("id", jobId).where("queue", queue).update({
|
|
124
|
+
status: "pending",
|
|
125
|
+
data: updatedData,
|
|
126
|
+
worker_id: null,
|
|
127
|
+
acquired_at: null,
|
|
128
|
+
score,
|
|
129
|
+
execute_at: null
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async push(jobData) {
|
|
134
|
+
return this.pushOn("default", jobData);
|
|
135
|
+
}
|
|
136
|
+
async pushOn(queue, jobData) {
|
|
137
|
+
await this.#ensureTableExists();
|
|
138
|
+
const priority = jobData.priority ?? 5;
|
|
139
|
+
const timestamp = Date.now();
|
|
140
|
+
const score = priority * 1e13 + timestamp;
|
|
141
|
+
await this.#connection(this.#tableName).insert({
|
|
142
|
+
id: jobData.id,
|
|
143
|
+
queue,
|
|
144
|
+
status: "pending",
|
|
145
|
+
data: JSON.stringify(jobData),
|
|
146
|
+
score
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
async pushLater(jobData, delay) {
|
|
150
|
+
return this.pushLaterOn("default", jobData, delay);
|
|
151
|
+
}
|
|
152
|
+
async pushLaterOn(queue, jobData, delay) {
|
|
153
|
+
await this.#ensureTableExists();
|
|
154
|
+
const executeAt = Date.now() + delay;
|
|
155
|
+
await this.#connection(this.#tableName).insert({
|
|
156
|
+
id: jobData.id,
|
|
157
|
+
queue,
|
|
158
|
+
status: "delayed",
|
|
159
|
+
data: JSON.stringify(jobData),
|
|
160
|
+
execute_at: executeAt
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
async size() {
|
|
164
|
+
return this.sizeOf("default");
|
|
165
|
+
}
|
|
166
|
+
async sizeOf(queue) {
|
|
167
|
+
await this.#ensureTableExists();
|
|
168
|
+
const result = await this.#connection(this.#tableName).where("queue", queue).where("status", "pending").count("* as count").first();
|
|
169
|
+
return Number(result?.count ?? 0);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
export {
|
|
173
|
+
KnexAdapter,
|
|
174
|
+
knex
|
|
175
|
+
};
|
|
176
|
+
//# sourceMappingURL=knex_adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/drivers/knex_adapter.ts"],"sourcesContent":["import KnexPkg from 'knex'\nimport type { Knex } from 'knex'\nimport type { Adapter, AcquiredJob } from '#contracts/adapter'\nimport type { JobData } from '#types/main'\n\nexport interface KnexAdapterOptions {\n connection: Knex\n tableName?: string\n ownsConnection?: boolean\n}\n\ntype KnexConfig = Knex | Knex.Config\n\n/**\n * Create a new Knex adapter factory.\n * Accepts either a Knex instance or a Knex configuration object.\n *\n * When passing a config object, the adapter will create and manage\n * the connection lifecycle (closing it on destroy).\n *\n * When passing a Knex instance, the caller is responsible for\n * managing the connection lifecycle.\n */\nexport function knex(config: KnexConfig, tableName?: string) {\n return () => {\n const isKnexInstance = typeof config === 'function'\n const connection = isKnexInstance ? config : KnexPkg(config)\n return new KnexAdapter({ connection, tableName, ownsConnection: !isKnexInstance })\n }\n}\n\n/**\n * Knex adapter for the queue system.\n * Stores jobs in a SQL database using Knex.\n */\nexport class KnexAdapter implements Adapter {\n readonly #connection: Knex\n readonly #tableName: string\n readonly #ownsConnection: boolean\n #workerId: string = ''\n #initialized: boolean = false\n\n constructor(config: KnexAdapterOptions) {\n this.#connection = config.connection\n this.#tableName = config.tableName ?? 'queue_jobs'\n this.#ownsConnection = config.ownsConnection ?? false\n }\n\n setWorkerId(workerId: string): void {\n this.#workerId = workerId\n }\n\n /**\n * Ensure the jobs table exists.\n * Creates it if not exists, handles race conditions.\n */\n async #ensureTableExists(): Promise<void> {\n if (this.#initialized) return\n\n try {\n await this.#connection.schema.createTable(this.#tableName, (table) => {\n table.string('id', 255).notNullable()\n table.string('queue', 255).notNullable()\n table.enu('status', ['pending', 'active', 'delayed']).notNullable()\n table.text('data').notNullable()\n table.bigint('score').unsigned().nullable()\n table.string('worker_id', 255).nullable()\n table.bigint('acquired_at').unsigned().nullable()\n table.bigint('execute_at').unsigned().nullable()\n table.primary(['id', 'queue'])\n table.index(['queue', 'status', 'score'])\n table.index(['queue', 'status', 'execute_at'])\n })\n } catch {\n /**\n * If table creation fails, verify the table actually exists.\n * This handles race conditions where multiple instances try to create\n * the table simultaneously.\n */\n const hasTable = await this.#connection.schema.hasTable(this.#tableName)\n if (!hasTable) {\n throw new Error(`Failed to create table \"${this.#tableName}\"`)\n }\n }\n\n this.#initialized = true\n }\n\n async destroy(): Promise<void> {\n if (this.#ownsConnection) {\n await this.#connection.destroy()\n }\n }\n\n async pop(): Promise<AcquiredJob | null> {\n return this.popFrom('default')\n }\n\n async popFrom(queue: string): Promise<AcquiredJob | null> {\n await this.#ensureTableExists()\n\n const now = Date.now()\n\n // First, move ready delayed jobs to pending\n await this.#processDelayedJobs(queue, now)\n\n // Use a transaction to atomically pop a job\n return this.#connection.transaction(async (trx) => {\n // Select the highest priority job (lowest score)\n const job = await trx(this.#tableName)\n .where('queue', queue)\n .where('status', 'pending')\n .orderBy('score', 'asc')\n .first()\n\n if (!job) {\n return null\n }\n\n // Update job to active status\n await trx(this.#tableName)\n .where('id', job.id)\n .where('queue', queue)\n .update({\n status: 'active',\n worker_id: this.#workerId,\n acquired_at: now,\n })\n\n const jobData: JobData = JSON.parse(job.data)\n\n return {\n ...jobData,\n acquiredAt: now,\n }\n })\n }\n\n async #processDelayedJobs(queue: string, now: number): Promise<void> {\n // Get all ready delayed jobs\n const delayedJobs = await this.#connection(this.#tableName)\n .where('queue', queue)\n .where('status', 'delayed')\n .where('execute_at', '<=', now)\n .select('id', 'data')\n\n if (delayedJobs.length === 0) return\n\n // Move them to pending\n for (const job of delayedJobs) {\n const jobData: JobData = JSON.parse(job.data)\n const priority = jobData.priority ?? 5\n const score = priority * 1e13 + now\n\n await this.#connection(this.#tableName)\n .where('id', job.id)\n .where('queue', queue)\n .update({\n status: 'pending',\n score,\n execute_at: null,\n })\n }\n }\n\n async completeJob(jobId: string, queue: string): Promise<void> {\n await this.#ensureTableExists()\n\n await this.#connection(this.#tableName)\n .where('id', jobId)\n .where('queue', queue)\n .delete()\n }\n\n async failJob(jobId: string, queue: string, _error?: Error): Promise<void> {\n await this.#ensureTableExists()\n\n await this.#connection(this.#tableName)\n .where('id', jobId)\n .where('queue', queue)\n .delete()\n }\n\n async retryJob(jobId: string, queue: string, retryAt?: Date): Promise<void> {\n await this.#ensureTableExists()\n\n const now = Date.now()\n\n // Get the active job\n const activeJob = await this.#connection(this.#tableName)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .first()\n\n if (!activeJob) return\n\n const jobData: JobData = JSON.parse(activeJob.data)\n jobData.attempts = (jobData.attempts || 0) + 1\n\n const updatedData = JSON.stringify(jobData)\n\n if (retryAt && retryAt.getTime() > now) {\n // Move to delayed\n await this.#connection(this.#tableName)\n .where('id', jobId)\n .where('queue', queue)\n .update({\n status: 'delayed',\n data: updatedData,\n worker_id: null,\n acquired_at: null,\n score: null,\n execute_at: retryAt.getTime(),\n })\n } else {\n // Move back to pending\n const priority = jobData.priority ?? 5\n const score = priority * 1e13 + now\n\n await this.#connection(this.#tableName)\n .where('id', jobId)\n .where('queue', queue)\n .update({\n status: 'pending',\n data: updatedData,\n worker_id: null,\n acquired_at: null,\n score,\n execute_at: null,\n })\n }\n }\n\n async push(jobData: JobData): Promise<void> {\n return this.pushOn('default', jobData)\n }\n\n async pushOn(queue: string, jobData: JobData): Promise<void> {\n await this.#ensureTableExists()\n\n const priority = jobData.priority ?? 5\n const timestamp = Date.now()\n const score = priority * 1e13 + timestamp\n\n await this.#connection(this.#tableName).insert({\n id: jobData.id,\n queue,\n status: 'pending',\n data: JSON.stringify(jobData),\n score,\n })\n }\n\n async pushLater(jobData: JobData, delay: number): Promise<void> {\n return this.pushLaterOn('default', jobData, delay)\n }\n\n async pushLaterOn(queue: string, jobData: JobData, delay: number): Promise<void> {\n await this.#ensureTableExists()\n\n const executeAt = Date.now() + delay\n\n await this.#connection(this.#tableName).insert({\n id: jobData.id,\n queue,\n status: 'delayed',\n data: JSON.stringify(jobData),\n execute_at: executeAt,\n })\n }\n\n async size(): Promise<number> {\n return this.sizeOf('default')\n }\n\n async sizeOf(queue: string): Promise<number> {\n await this.#ensureTableExists()\n\n const result = await this.#connection(this.#tableName)\n .where('queue', queue)\n .where('status', 'pending')\n .count('* as count')\n .first()\n\n return Number(result?.count ?? 0)\n }\n}\n"],"mappings":";AAAA,OAAO,aAAa;AAuBb,SAAS,KAAK,QAAoB,WAAoB;AAC3D,SAAO,MAAM;AACX,UAAM,iBAAiB,OAAO,WAAW;AACzC,UAAM,aAAa,iBAAiB,SAAS,QAAQ,MAAM;AAC3D,WAAO,IAAI,YAAY,EAAE,YAAY,WAAW,gBAAgB,CAAC,eAAe,CAAC;AAAA,EACnF;AACF;AAMO,IAAM,cAAN,MAAqC;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACT,YAAoB;AAAA,EACpB,eAAwB;AAAA,EAExB,YAAY,QAA4B;AACtC,SAAK,cAAc,OAAO;AAC1B,SAAK,aAAa,OAAO,aAAa;AACtC,SAAK,kBAAkB,OAAO,kBAAkB;AAAA,EAClD;AAAA,EAEA,YAAY,UAAwB;AAClC,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,qBAAoC;AACxC,QAAI,KAAK,aAAc;AAEvB,QAAI;AACF,YAAM,KAAK,YAAY,OAAO,YAAY,KAAK,YAAY,CAAC,UAAU;AACpE,cAAM,OAAO,MAAM,GAAG,EAAE,YAAY;AACpC,cAAM,OAAO,SAAS,GAAG,EAAE,YAAY;AACvC,cAAM,IAAI,UAAU,CAAC,WAAW,UAAU,SAAS,CAAC,EAAE,YAAY;AAClE,cAAM,KAAK,MAAM,EAAE,YAAY;AAC/B,cAAM,OAAO,OAAO,EAAE,SAAS,EAAE,SAAS;AAC1C,cAAM,OAAO,aAAa,GAAG,EAAE,SAAS;AACxC,cAAM,OAAO,aAAa,EAAE,SAAS,EAAE,SAAS;AAChD,cAAM,OAAO,YAAY,EAAE,SAAS,EAAE,SAAS;AAC/C,cAAM,QAAQ,CAAC,MAAM,OAAO,CAAC;AAC7B,cAAM,MAAM,CAAC,SAAS,UAAU,OAAO,CAAC;AACxC,cAAM,MAAM,CAAC,SAAS,UAAU,YAAY,CAAC;AAAA,MAC/C,CAAC;AAAA,IACH,QAAQ;AAMN,YAAM,WAAW,MAAM,KAAK,YAAY,OAAO,SAAS,KAAK,UAAU;AACvE,UAAI,CAAC,UAAU;AACb,cAAM,IAAI,MAAM,2BAA2B,KAAK,UAAU,GAAG;AAAA,MAC/D;AAAA,IACF;AAEA,SAAK,eAAe;AAAA,EACtB;AAAA,EAEA,MAAM,UAAyB;AAC7B,QAAI,KAAK,iBAAiB;AACxB,YAAM,KAAK,YAAY,QAAQ;AAAA,IACjC;AAAA,EACF;AAAA,EAEA,MAAM,MAAmC;AACvC,WAAO,KAAK,QAAQ,SAAS;AAAA,EAC/B;AAAA,EAEA,MAAM,QAAQ,OAA4C;AACxD,UAAM,KAAK,mBAAmB;AAE9B,UAAM,MAAM,KAAK,IAAI;AAGrB,UAAM,KAAK,oBAAoB,OAAO,GAAG;AAGzC,WAAO,KAAK,YAAY,YAAY,OAAO,QAAQ;AAEjD,YAAM,MAAM,MAAM,IAAI,KAAK,UAAU,EAClC,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,SAAS,EACzB,QAAQ,SAAS,KAAK,EACtB,MAAM;AAET,UAAI,CAAC,KAAK;AACR,eAAO;AAAA,MACT;AAGA,YAAM,IAAI,KAAK,UAAU,EACtB,MAAM,MAAM,IAAI,EAAE,EAClB,MAAM,SAAS,KAAK,EACpB,OAAO;AAAA,QACN,QAAQ;AAAA,QACR,WAAW,KAAK;AAAA,QAChB,aAAa;AAAA,MACf,CAAC;AAEH,YAAM,UAAmB,KAAK,MAAM,IAAI,IAAI;AAE5C,aAAO;AAAA,QACL,GAAG;AAAA,QACH,YAAY;AAAA,MACd;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,oBAAoB,OAAe,KAA4B;AAEnE,UAAM,cAAc,MAAM,KAAK,YAAY,KAAK,UAAU,EACvD,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,SAAS,EACzB,MAAM,cAAc,MAAM,GAAG,EAC7B,OAAO,MAAM,MAAM;AAEtB,QAAI,YAAY,WAAW,EAAG;AAG9B,eAAW,OAAO,aAAa;AAC7B,YAAM,UAAmB,KAAK,MAAM,IAAI,IAAI;AAC5C,YAAM,WAAW,QAAQ,YAAY;AACrC,YAAM,QAAQ,WAAW,OAAO;AAEhC,YAAM,KAAK,YAAY,KAAK,UAAU,EACnC,MAAM,MAAM,IAAI,EAAE,EAClB,MAAM,SAAS,KAAK,EACpB,OAAO;AAAA,QACN,QAAQ;AAAA,QACR;AAAA,QACA,YAAY;AAAA,MACd,CAAC;AAAA,IACL;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,OAAe,OAA8B;AAC7D,UAAM,KAAK,mBAAmB;AAE9B,UAAM,KAAK,YAAY,KAAK,UAAU,EACnC,MAAM,MAAM,KAAK,EACjB,MAAM,SAAS,KAAK,EACpB,OAAO;AAAA,EACZ;AAAA,EAEA,MAAM,QAAQ,OAAe,OAAe,QAA+B;AACzE,UAAM,KAAK,mBAAmB;AAE9B,UAAM,KAAK,YAAY,KAAK,UAAU,EACnC,MAAM,MAAM,KAAK,EACjB,MAAM,SAAS,KAAK,EACpB,OAAO;AAAA,EACZ;AAAA,EAEA,MAAM,SAAS,OAAe,OAAe,SAA+B;AAC1E,UAAM,KAAK,mBAAmB;AAE9B,UAAM,MAAM,KAAK,IAAI;AAGrB,UAAM,YAAY,MAAM,KAAK,YAAY,KAAK,UAAU,EACrD,MAAM,MAAM,KAAK,EACjB,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,QAAQ,EACxB,MAAM;AAET,QAAI,CAAC,UAAW;AAEhB,UAAM,UAAmB,KAAK,MAAM,UAAU,IAAI;AAClD,YAAQ,YAAY,QAAQ,YAAY,KAAK;AAE7C,UAAM,cAAc,KAAK,UAAU,OAAO;AAE1C,QAAI,WAAW,QAAQ,QAAQ,IAAI,KAAK;AAEtC,YAAM,KAAK,YAAY,KAAK,UAAU,EACnC,MAAM,MAAM,KAAK,EACjB,MAAM,SAAS,KAAK,EACpB,OAAO;AAAA,QACN,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,WAAW;AAAA,QACX,aAAa;AAAA,QACb,OAAO;AAAA,QACP,YAAY,QAAQ,QAAQ;AAAA,MAC9B,CAAC;AAAA,IACL,OAAO;AAEL,YAAM,WAAW,QAAQ,YAAY;AACrC,YAAM,QAAQ,WAAW,OAAO;AAEhC,YAAM,KAAK,YAAY,KAAK,UAAU,EACnC,MAAM,MAAM,KAAK,EACjB,MAAM,SAAS,KAAK,EACpB,OAAO;AAAA,QACN,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,WAAW;AAAA,QACX,aAAa;AAAA,QACb;AAAA,QACA,YAAY;AAAA,MACd,CAAC;AAAA,IACL;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,SAAiC;AAC1C,WAAO,KAAK,OAAO,WAAW,OAAO;AAAA,EACvC;AAAA,EAEA,MAAM,OAAO,OAAe,SAAiC;AAC3D,UAAM,KAAK,mBAAmB;AAE9B,UAAM,WAAW,QAAQ,YAAY;AACrC,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,QAAQ,WAAW,OAAO;AAEhC,UAAM,KAAK,YAAY,KAAK,UAAU,EAAE,OAAO;AAAA,MAC7C,IAAI,QAAQ;AAAA,MACZ;AAAA,MACA,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,OAAO;AAAA,MAC5B;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAU,SAAkB,OAA8B;AAC9D,WAAO,KAAK,YAAY,WAAW,SAAS,KAAK;AAAA,EACnD;AAAA,EAEA,MAAM,YAAY,OAAe,SAAkB,OAA8B;AAC/E,UAAM,KAAK,mBAAmB;AAE9B,UAAM,YAAY,KAAK,IAAI,IAAI;AAE/B,UAAM,KAAK,YAAY,KAAK,UAAU,EAAE,OAAO;AAAA,MAC7C,IAAI,QAAQ;AAAA,MACZ;AAAA,MACA,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,OAAO;AAAA,MAC5B,YAAY;AAAA,IACd,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,OAAwB;AAC5B,WAAO,KAAK,OAAO,SAAS;AAAA,EAC9B;AAAA,EAEA,MAAM,OAAO,OAAgC;AAC3C,UAAM,KAAK,mBAAmB;AAE9B,UAAM,SAAS,MAAM,KAAK,YAAY,KAAK,UAAU,EAClD,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,SAAS,EACzB,MAAM,YAAY,EAClB,MAAM;AAET,WAAO,OAAO,QAAQ,SAAS,CAAC;AAAA,EAClC;AACF;","names":[]}
|
|
@@ -2,10 +2,20 @@ import { Redis, RedisOptions } from 'ioredis';
|
|
|
2
2
|
import { A as Adapter, a as AcquiredJob, b as JobData } from '../../job-Bd_c2lFK.js';
|
|
3
3
|
|
|
4
4
|
type RedisConfig = Redis | RedisOptions;
|
|
5
|
+
/**
|
|
6
|
+
* Create a new Redis adapter factory.
|
|
7
|
+
* Accepts either a Redis instance or Redis options.
|
|
8
|
+
*
|
|
9
|
+
* When passing options, the adapter will create and manage
|
|
10
|
+
* the connection lifecycle (closing it on destroy).
|
|
11
|
+
*
|
|
12
|
+
* When passing a Redis instance, the caller is responsible for
|
|
13
|
+
* managing the connection lifecycle.
|
|
14
|
+
*/
|
|
5
15
|
declare function redis(config?: RedisConfig): () => RedisAdapter;
|
|
6
16
|
declare class RedisAdapter implements Adapter {
|
|
7
17
|
#private;
|
|
8
|
-
constructor(connection: Redis);
|
|
18
|
+
constructor(connection: Redis, ownsConnection?: boolean);
|
|
9
19
|
setWorkerId(workerId: string): void;
|
|
10
20
|
destroy(): Promise<void>;
|
|
11
21
|
pop(): Promise<AcquiredJob | null>;
|
|
@@ -96,7 +96,7 @@ var RETRY_JOB_SCRIPT = `
|
|
|
96
96
|
function redis(config) {
|
|
97
97
|
return () => {
|
|
98
98
|
if (config instanceof Redis) {
|
|
99
|
-
return new RedisAdapter(config);
|
|
99
|
+
return new RedisAdapter(config, false);
|
|
100
100
|
}
|
|
101
101
|
const options = {
|
|
102
102
|
host: "localhost",
|
|
@@ -106,20 +106,24 @@ function redis(config) {
|
|
|
106
106
|
...config
|
|
107
107
|
};
|
|
108
108
|
const connection = new Redis(options);
|
|
109
|
-
return new RedisAdapter(connection);
|
|
109
|
+
return new RedisAdapter(connection, true);
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
112
|
var RedisAdapter = class {
|
|
113
113
|
#connection;
|
|
114
|
+
#ownsConnection;
|
|
114
115
|
#workerId = "";
|
|
115
|
-
constructor(connection) {
|
|
116
|
+
constructor(connection, ownsConnection = false) {
|
|
116
117
|
this.#connection = connection;
|
|
118
|
+
this.#ownsConnection = ownsConnection;
|
|
117
119
|
}
|
|
118
120
|
setWorkerId(workerId) {
|
|
119
121
|
this.#workerId = workerId;
|
|
120
122
|
}
|
|
121
123
|
async destroy() {
|
|
122
|
-
|
|
124
|
+
if (this.#ownsConnection) {
|
|
125
|
+
await this.#connection.quit();
|
|
126
|
+
}
|
|
123
127
|
}
|
|
124
128
|
pop() {
|
|
125
129
|
return this.popFrom("default");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/drivers/redis_adapter.ts"],"sourcesContent":["import { Redis, type RedisOptions } from 'ioredis'\nimport type { Adapter, AcquiredJob } from '#contracts/adapter'\nimport type { JobData } from '#types/main'\n\nconst redisKey = 'jobs'\ntype RedisConfig = Redis | RedisOptions\n\n/**\n * Lua script for atomic job acquisition.\n * 1. Check and process delayed jobs\n * 2. Pop from pending queue\n * 3. Add to active hash with worker info\n * 4. Return job data\n */\nconst ACQUIRE_JOB_SCRIPT = `\n local pending_key = KEYS[1]\n local active_key = KEYS[2]\n local delayed_key = KEYS[3]\n local worker_id = ARGV[1]\n local now = ARGV[2]\n\n -- First, process delayed jobs\n local ready_jobs = redis.call('ZRANGEBYSCORE', delayed_key, 0, now)\n if #ready_jobs > 0 then\n for i = 1, #ready_jobs do\n local job_data = ready_jobs[i]\n local job = cjson.decode(job_data)\n local priority = job.priority or 5\n local timestamp = tonumber(now)\n local score = priority * 10000000000000 + timestamp\n redis.call('ZADD', pending_key, score, job_data)\n redis.call('ZREM', delayed_key, job_data)\n end\n end\n\n -- Pop highest priority job (lowest score)\n local result = redis.call('ZPOPMIN', pending_key)\n if not result or #result == 0 then\n return nil\n end\n\n local job_data = result[1]\n local job = cjson.decode(job_data)\n\n -- Store in active hash: jobId -> {workerId, acquiredAt, data}\n local active_data = cjson.encode({\n workerId = worker_id,\n acquiredAt = tonumber(now),\n data = job\n })\n redis.call('HSET', active_key, job.id, active_data)\n\n -- Return job with acquiredAt\n job.acquiredAt = tonumber(now)\n return cjson.encode(job)\n`\n\n/**\n * Lua script for completing a job.\n * Removes the job from active hash.\n */\nconst COMPLETE_JOB_SCRIPT = `\n local active_key = KEYS[1]\n local job_id = ARGV[1]\n\n redis.call('HDEL', active_key, job_id)\n return 1\n`\n\n/**\n * Lua script for failing a job permanently.\n * Removes from active hash.\n */\nconst FAIL_JOB_SCRIPT = `\n local active_key = KEYS[1]\n local job_id = ARGV[1]\n\n redis.call('HDEL', active_key, job_id)\n return 1\n`\n\n/**\n * Lua script for retrying a job.\n * 1. Get job from active hash\n * 2. Remove from active hash\n * 3. Increment attempts\n * 4. Add back to pending (or delayed if retryAt is set)\n */\nconst RETRY_JOB_SCRIPT = `\n local active_key = KEYS[1]\n local pending_key = KEYS[2]\n local delayed_key = KEYS[3]\n local job_id = ARGV[1]\n local retry_at = tonumber(ARGV[2])\n local now = tonumber(ARGV[3])\n\n -- Get job from active hash\n local active_data = redis.call('HGET', active_key, job_id)\n if not active_data then\n return 0\n end\n\n local active = cjson.decode(active_data)\n local job = active.data\n\n -- Remove from active\n redis.call('HDEL', active_key, job_id)\n\n -- Increment attempts\n job.attempts = (job.attempts or 0) + 1\n\n local job_data = cjson.encode(job)\n\n -- Add back to pending or delayed\n if retry_at and retry_at > now then\n redis.call('ZADD', delayed_key, retry_at, job_data)\n else\n local priority = job.priority or 5\n local score = priority * 10000000000000 + now\n redis.call('ZADD', pending_key, score, job_data)\n end\n\n return 1\n`\n\nexport function redis(config?: RedisConfig) {\n return () => {\n if (config instanceof Redis) {\n return new RedisAdapter(config)\n }\n\n const options: RedisOptions = {\n host: 'localhost',\n port: 6379,\n keyPrefix: 'boringnode::queue::',\n db: 0,\n ...config,\n }\n\n const connection = new Redis(options)\n return new RedisAdapter(connection)\n }\n}\n\nexport class RedisAdapter implements Adapter {\n readonly #connection: Redis\n #workerId: string = ''\n\n constructor(connection: Redis) {\n this.#connection = connection\n }\n\n setWorkerId(workerId: string): void {\n this.#workerId = workerId\n }\n\n async destroy(): Promise<void> {\n await this.#connection.quit()\n }\n\n pop(): Promise<AcquiredJob | null> {\n return this.popFrom('default')\n }\n\n async popFrom(queue: string): Promise<AcquiredJob | null> {\n const now = Date.now()\n const pendingKey = `${redisKey}::${queue}`\n const activeKey = `${redisKey}::${queue}::active`\n const delayedKey = `${redisKey}::delayed::${queue}`\n\n const result = await this.#connection.eval(\n ACQUIRE_JOB_SCRIPT,\n 3,\n pendingKey,\n activeKey,\n delayedKey,\n this.#workerId,\n now.toString()\n )\n\n if (!result) {\n return null\n }\n\n return JSON.parse(result as string)\n }\n\n async popAndWait(queue: string, timeout: number): Promise<AcquiredJob | null> {\n // First try immediate pop\n const immediate = await this.popFrom(queue)\n if (immediate) {\n return immediate\n }\n\n // Wait for new job using BZPOPMIN on pending queue\n const pendingKey = `${redisKey}::${queue}`\n const activeKey = `${redisKey}::${queue}::active`\n const now = Date.now()\n\n // BZPOPMIN returns [key, member, score] or null\n const result = await this.#connection.bzpopmin(pendingKey, timeout / 1000)\n\n if (!result) {\n return null\n }\n\n const [, jobData] = result\n const job = JSON.parse(jobData)\n\n // Store in active hash\n const activeData = JSON.stringify({\n workerId: this.#workerId,\n acquiredAt: now,\n data: job,\n })\n await this.#connection.hset(activeKey, job.id, activeData)\n\n return {\n ...job,\n acquiredAt: now,\n }\n }\n\n async completeJob(jobId: string, queue: string): Promise<void> {\n const activeKey = `${redisKey}::${queue}::active`\n\n await this.#connection.eval(COMPLETE_JOB_SCRIPT, 1, activeKey, jobId)\n }\n\n async failJob(jobId: string, queue: string, _error?: Error): Promise<void> {\n const activeKey = `${redisKey}::${queue}::active`\n\n await this.#connection.eval(FAIL_JOB_SCRIPT, 1, activeKey, jobId)\n }\n\n async retryJob(jobId: string, queue: string, retryAt?: Date): Promise<void> {\n const now = Date.now()\n const activeKey = `${redisKey}::${queue}::active`\n const pendingKey = `${redisKey}::${queue}`\n const delayedKey = `${redisKey}::delayed::${queue}`\n\n await this.#connection.eval(\n RETRY_JOB_SCRIPT,\n 3,\n activeKey,\n pendingKey,\n delayedKey,\n jobId,\n retryAt ? retryAt.getTime().toString() : '0',\n now.toString()\n )\n }\n\n push(jobData: JobData): Promise<void> {\n return this.pushOn('default', jobData)\n }\n\n pushLater(jobData: JobData, delay: number): Promise<void> {\n return this.pushLaterOn('default', jobData, delay)\n }\n\n async pushLaterOn(queue: string, jobData: JobData, delay: number): Promise<void> {\n const executeAt = Date.now() + delay\n const delayedKey = `${redisKey}::delayed::${queue}`\n\n await this.#connection.zadd(delayedKey, executeAt, JSON.stringify(jobData))\n }\n\n async pushOn(queue: string, jobData: JobData): Promise<void> {\n const priority = jobData.priority ?? 5\n\n // Use priority as primary score, add timestamp for FIFO order within same priority\n // Date.now() precision is sufficient but perfect FIFO within the same millisecond is not guaranteed\n const timestamp = Date.now()\n const score = priority * 1e13 + timestamp\n\n await this.#connection.zadd(`${redisKey}::${queue}`, score, JSON.stringify(jobData))\n }\n\n size(): Promise<number> {\n return this.sizeOf('default')\n }\n\n sizeOf(queue: string): Promise<number> {\n return this.#connection.zcard(`${redisKey}::${queue}`)\n }\n}\n"],"mappings":";AAAA,SAAS,aAAgC;AAIzC,IAAM,WAAW;AAUjB,IAAM,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+C3B,IAAM,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAY5B,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAexB,IAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqClB,SAAS,MAAM,QAAsB;AAC1C,SAAO,MAAM;AACX,QAAI,kBAAkB,OAAO;AAC3B,aAAO,IAAI,aAAa,MAAM;AAAA,IAChC;AAEA,UAAM,UAAwB;AAAA,MAC5B,MAAM;AAAA,MACN,MAAM;AAAA,MACN,WAAW;AAAA,MACX,IAAI;AAAA,MACJ,GAAG;AAAA,IACL;AAEA,UAAM,aAAa,IAAI,MAAM,OAAO;AACpC,WAAO,IAAI,aAAa,UAAU;AAAA,EACpC;AACF;AAEO,IAAM,eAAN,MAAsC;AAAA,EAClC;AAAA,EACT,YAAoB;AAAA,EAEpB,YAAY,YAAmB;AAC7B,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,YAAY,UAAwB;AAClC,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,MAAM,UAAyB;AAC7B,UAAM,KAAK,YAAY,KAAK;AAAA,EAC9B;AAAA,EAEA,MAAmC;AACjC,WAAO,KAAK,QAAQ,SAAS;AAAA,EAC/B;AAAA,EAEA,MAAM,QAAQ,OAA4C;AACxD,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,aAAa,GAAG,QAAQ,KAAK,KAAK;AACxC,UAAM,YAAY,GAAG,QAAQ,KAAK,KAAK;AACvC,UAAM,aAAa,GAAG,QAAQ,cAAc,KAAK;AAEjD,UAAM,SAAS,MAAM,KAAK,YAAY;AAAA,MACpC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,KAAK;AAAA,MACL,IAAI,SAAS;AAAA,IACf;AAEA,QAAI,CAAC,QAAQ;AACX,aAAO;AAAA,IACT;AAEA,WAAO,KAAK,MAAM,MAAgB;AAAA,EACpC;AAAA,EAEA,MAAM,WAAW,OAAe,SAA8C;AAE5E,UAAM,YAAY,MAAM,KAAK,QAAQ,KAAK;AAC1C,QAAI,WAAW;AACb,aAAO;AAAA,IACT;AAGA,UAAM,aAAa,GAAG,QAAQ,KAAK,KAAK;AACxC,UAAM,YAAY,GAAG,QAAQ,KAAK,KAAK;AACvC,UAAM,MAAM,KAAK,IAAI;AAGrB,UAAM,SAAS,MAAM,KAAK,YAAY,SAAS,YAAY,UAAU,GAAI;AAEzE,QAAI,CAAC,QAAQ;AACX,aAAO;AAAA,IACT;AAEA,UAAM,CAAC,EAAE,OAAO,IAAI;AACpB,UAAM,MAAM,KAAK,MAAM,OAAO;AAG9B,UAAM,aAAa,KAAK,UAAU;AAAA,MAChC,UAAU,KAAK;AAAA,MACf,YAAY;AAAA,MACZ,MAAM;AAAA,IACR,CAAC;AACD,UAAM,KAAK,YAAY,KAAK,WAAW,IAAI,IAAI,UAAU;AAEzD,WAAO;AAAA,MACL,GAAG;AAAA,MACH,YAAY;AAAA,IACd;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,OAAe,OAA8B;AAC7D,UAAM,YAAY,GAAG,QAAQ,KAAK,KAAK;AAEvC,UAAM,KAAK,YAAY,KAAK,qBAAqB,GAAG,WAAW,KAAK;AAAA,EACtE;AAAA,EAEA,MAAM,QAAQ,OAAe,OAAe,QAA+B;AACzE,UAAM,YAAY,GAAG,QAAQ,KAAK,KAAK;AAEvC,UAAM,KAAK,YAAY,KAAK,iBAAiB,GAAG,WAAW,KAAK;AAAA,EAClE;AAAA,EAEA,MAAM,SAAS,OAAe,OAAe,SAA+B;AAC1E,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,YAAY,GAAG,QAAQ,KAAK,KAAK;AACvC,UAAM,aAAa,GAAG,QAAQ,KAAK,KAAK;AACxC,UAAM,aAAa,GAAG,QAAQ,cAAc,KAAK;AAEjD,UAAM,KAAK,YAAY;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,QAAQ,QAAQ,EAAE,SAAS,IAAI;AAAA,MACzC,IAAI,SAAS;AAAA,IACf;AAAA,EACF;AAAA,EAEA,KAAK,SAAiC;AACpC,WAAO,KAAK,OAAO,WAAW,OAAO;AAAA,EACvC;AAAA,EAEA,UAAU,SAAkB,OAA8B;AACxD,WAAO,KAAK,YAAY,WAAW,SAAS,KAAK;AAAA,EACnD;AAAA,EAEA,MAAM,YAAY,OAAe,SAAkB,OAA8B;AAC/E,UAAM,YAAY,KAAK,IAAI,IAAI;AAC/B,UAAM,aAAa,GAAG,QAAQ,cAAc,KAAK;AAEjD,UAAM,KAAK,YAAY,KAAK,YAAY,WAAW,KAAK,UAAU,OAAO,CAAC;AAAA,EAC5E;AAAA,EAEA,MAAM,OAAO,OAAe,SAAiC;AAC3D,UAAM,WAAW,QAAQ,YAAY;AAIrC,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,QAAQ,WAAW,OAAO;AAEhC,UAAM,KAAK,YAAY,KAAK,GAAG,QAAQ,KAAK,KAAK,IAAI,OAAO,KAAK,UAAU,OAAO,CAAC;AAAA,EACrF;AAAA,EAEA,OAAwB;AACtB,WAAO,KAAK,OAAO,SAAS;AAAA,EAC9B;AAAA,EAEA,OAAO,OAAgC;AACrC,WAAO,KAAK,YAAY,MAAM,GAAG,QAAQ,KAAK,KAAK,EAAE;AAAA,EACvD;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/drivers/redis_adapter.ts"],"sourcesContent":["import { Redis, type RedisOptions } from 'ioredis'\nimport type { Adapter, AcquiredJob } from '#contracts/adapter'\nimport type { JobData } from '#types/main'\n\nconst redisKey = 'jobs'\ntype RedisConfig = Redis | RedisOptions\n\n/**\n * Lua script for atomic job acquisition.\n * 1. Check and process delayed jobs\n * 2. Pop from pending queue\n * 3. Add to active hash with worker info\n * 4. Return job data\n */\nconst ACQUIRE_JOB_SCRIPT = `\n local pending_key = KEYS[1]\n local active_key = KEYS[2]\n local delayed_key = KEYS[3]\n local worker_id = ARGV[1]\n local now = ARGV[2]\n\n -- First, process delayed jobs\n local ready_jobs = redis.call('ZRANGEBYSCORE', delayed_key, 0, now)\n if #ready_jobs > 0 then\n for i = 1, #ready_jobs do\n local job_data = ready_jobs[i]\n local job = cjson.decode(job_data)\n local priority = job.priority or 5\n local timestamp = tonumber(now)\n local score = priority * 10000000000000 + timestamp\n redis.call('ZADD', pending_key, score, job_data)\n redis.call('ZREM', delayed_key, job_data)\n end\n end\n\n -- Pop highest priority job (lowest score)\n local result = redis.call('ZPOPMIN', pending_key)\n if not result or #result == 0 then\n return nil\n end\n\n local job_data = result[1]\n local job = cjson.decode(job_data)\n\n -- Store in active hash: jobId -> {workerId, acquiredAt, data}\n local active_data = cjson.encode({\n workerId = worker_id,\n acquiredAt = tonumber(now),\n data = job\n })\n redis.call('HSET', active_key, job.id, active_data)\n\n -- Return job with acquiredAt\n job.acquiredAt = tonumber(now)\n return cjson.encode(job)\n`\n\n/**\n * Lua script for completing a job.\n * Removes the job from active hash.\n */\nconst COMPLETE_JOB_SCRIPT = `\n local active_key = KEYS[1]\n local job_id = ARGV[1]\n\n redis.call('HDEL', active_key, job_id)\n return 1\n`\n\n/**\n * Lua script for failing a job permanently.\n * Removes from active hash.\n */\nconst FAIL_JOB_SCRIPT = `\n local active_key = KEYS[1]\n local job_id = ARGV[1]\n\n redis.call('HDEL', active_key, job_id)\n return 1\n`\n\n/**\n * Lua script for retrying a job.\n * 1. Get job from active hash\n * 2. Remove from active hash\n * 3. Increment attempts\n * 4. Add back to pending (or delayed if retryAt is set)\n */\nconst RETRY_JOB_SCRIPT = `\n local active_key = KEYS[1]\n local pending_key = KEYS[2]\n local delayed_key = KEYS[3]\n local job_id = ARGV[1]\n local retry_at = tonumber(ARGV[2])\n local now = tonumber(ARGV[3])\n\n -- Get job from active hash\n local active_data = redis.call('HGET', active_key, job_id)\n if not active_data then\n return 0\n end\n\n local active = cjson.decode(active_data)\n local job = active.data\n\n -- Remove from active\n redis.call('HDEL', active_key, job_id)\n\n -- Increment attempts\n job.attempts = (job.attempts or 0) + 1\n\n local job_data = cjson.encode(job)\n\n -- Add back to pending or delayed\n if retry_at and retry_at > now then\n redis.call('ZADD', delayed_key, retry_at, job_data)\n else\n local priority = job.priority or 5\n local score = priority * 10000000000000 + now\n redis.call('ZADD', pending_key, score, job_data)\n end\n\n return 1\n`\n\n/**\n * Create a new Redis adapter factory.\n * Accepts either a Redis instance or Redis options.\n *\n * When passing options, the adapter will create and manage\n * the connection lifecycle (closing it on destroy).\n *\n * When passing a Redis instance, the caller is responsible for\n * managing the connection lifecycle.\n */\nexport function redis(config?: RedisConfig) {\n return () => {\n if (config instanceof Redis) {\n return new RedisAdapter(config, false)\n }\n\n const options: RedisOptions = {\n host: 'localhost',\n port: 6379,\n keyPrefix: 'boringnode::queue::',\n db: 0,\n ...config,\n }\n\n const connection = new Redis(options)\n return new RedisAdapter(connection, true)\n }\n}\n\nexport class RedisAdapter implements Adapter {\n readonly #connection: Redis\n readonly #ownsConnection: boolean\n #workerId: string = ''\n\n constructor(connection: Redis, ownsConnection: boolean = false) {\n this.#connection = connection\n this.#ownsConnection = ownsConnection\n }\n\n setWorkerId(workerId: string): void {\n this.#workerId = workerId\n }\n\n async destroy(): Promise<void> {\n if (this.#ownsConnection) {\n await this.#connection.quit()\n }\n }\n\n pop(): Promise<AcquiredJob | null> {\n return this.popFrom('default')\n }\n\n async popFrom(queue: string): Promise<AcquiredJob | null> {\n const now = Date.now()\n const pendingKey = `${redisKey}::${queue}`\n const activeKey = `${redisKey}::${queue}::active`\n const delayedKey = `${redisKey}::delayed::${queue}`\n\n const result = await this.#connection.eval(\n ACQUIRE_JOB_SCRIPT,\n 3,\n pendingKey,\n activeKey,\n delayedKey,\n this.#workerId,\n now.toString()\n )\n\n if (!result) {\n return null\n }\n\n return JSON.parse(result as string)\n }\n\n async popAndWait(queue: string, timeout: number): Promise<AcquiredJob | null> {\n // First try immediate pop\n const immediate = await this.popFrom(queue)\n if (immediate) {\n return immediate\n }\n\n // Wait for new job using BZPOPMIN on pending queue\n const pendingKey = `${redisKey}::${queue}`\n const activeKey = `${redisKey}::${queue}::active`\n const now = Date.now()\n\n // BZPOPMIN returns [key, member, score] or null\n const result = await this.#connection.bzpopmin(pendingKey, timeout / 1000)\n\n if (!result) {\n return null\n }\n\n const [, jobData] = result\n const job = JSON.parse(jobData)\n\n // Store in active hash\n const activeData = JSON.stringify({\n workerId: this.#workerId,\n acquiredAt: now,\n data: job,\n })\n await this.#connection.hset(activeKey, job.id, activeData)\n\n return {\n ...job,\n acquiredAt: now,\n }\n }\n\n async completeJob(jobId: string, queue: string): Promise<void> {\n const activeKey = `${redisKey}::${queue}::active`\n\n await this.#connection.eval(COMPLETE_JOB_SCRIPT, 1, activeKey, jobId)\n }\n\n async failJob(jobId: string, queue: string, _error?: Error): Promise<void> {\n const activeKey = `${redisKey}::${queue}::active`\n\n await this.#connection.eval(FAIL_JOB_SCRIPT, 1, activeKey, jobId)\n }\n\n async retryJob(jobId: string, queue: string, retryAt?: Date): Promise<void> {\n const now = Date.now()\n const activeKey = `${redisKey}::${queue}::active`\n const pendingKey = `${redisKey}::${queue}`\n const delayedKey = `${redisKey}::delayed::${queue}`\n\n await this.#connection.eval(\n RETRY_JOB_SCRIPT,\n 3,\n activeKey,\n pendingKey,\n delayedKey,\n jobId,\n retryAt ? retryAt.getTime().toString() : '0',\n now.toString()\n )\n }\n\n push(jobData: JobData): Promise<void> {\n return this.pushOn('default', jobData)\n }\n\n pushLater(jobData: JobData, delay: number): Promise<void> {\n return this.pushLaterOn('default', jobData, delay)\n }\n\n async pushLaterOn(queue: string, jobData: JobData, delay: number): Promise<void> {\n const executeAt = Date.now() + delay\n const delayedKey = `${redisKey}::delayed::${queue}`\n\n await this.#connection.zadd(delayedKey, executeAt, JSON.stringify(jobData))\n }\n\n async pushOn(queue: string, jobData: JobData): Promise<void> {\n const priority = jobData.priority ?? 5\n\n // Use priority as primary score, add timestamp for FIFO order within same priority\n // Date.now() precision is sufficient but perfect FIFO within the same millisecond is not guaranteed\n const timestamp = Date.now()\n const score = priority * 1e13 + timestamp\n\n await this.#connection.zadd(`${redisKey}::${queue}`, score, JSON.stringify(jobData))\n }\n\n size(): Promise<number> {\n return this.sizeOf('default')\n }\n\n sizeOf(queue: string): Promise<number> {\n return this.#connection.zcard(`${redisKey}::${queue}`)\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boringnode/queue",
|
|
3
3
|
"description": "A simple and efficient queue system for Node.js applications",
|
|
4
|
-
"version": "0.0.1-alpha.
|
|
4
|
+
"version": "0.0.1-alpha.2",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
@@ -46,12 +46,17 @@
|
|
|
46
46
|
"@japa/expect-type": "^2.0.3",
|
|
47
47
|
"@japa/file-system": "^2.3.2",
|
|
48
48
|
"@japa/runner": "^4.4.0",
|
|
49
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
49
50
|
"@types/node": "^24.3.1",
|
|
51
|
+
"@types/pg": "^8",
|
|
52
|
+
"better-sqlite3": "^12.5.0",
|
|
50
53
|
"bullmq": "^5.65.1",
|
|
51
54
|
"c8": "^10.1.3",
|
|
52
55
|
"del-cli": "^7.0.0",
|
|
53
56
|
"eslint": "^9.35.0",
|
|
54
57
|
"ioredis": "^5.7.0",
|
|
58
|
+
"knex": "^3.1.0",
|
|
59
|
+
"pg": "^8.16.3",
|
|
55
60
|
"prettier": "^3.6.2",
|
|
56
61
|
"release-it": "^19.0.4",
|
|
57
62
|
"testcontainers": "^11.5.1",
|