@adonisjs/queue 0.1.1 → 0.2.1

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.
@@ -1 +1 @@
1
- {"commands":[{"commandName":"make:job","description":"Create a new job class","help":"","namespace":"make","aliases":[],"flags":[],"args":[{"name":"name","argumentName":"name","required":true,"description":"Name of the job","type":"string"}],"options":{"allowUnknownFlags":true},"filePath":"make_job.js"},{"commandName":"queue:scheduler:clear","description":"Remove all scheduled jobs","help":"","namespace":"queue","aliases":[],"flags":[],"args":[],"options":{"startApp":true},"filePath":"queue_scheduler_clear.js"},{"commandName":"queue:scheduler:list","description":"List all scheduled jobs","help":"","namespace":"queue","aliases":[],"flags":[{"name":"status","flagName":"status","required":false,"type":"string","description":"Filter by status (active, paused)","alias":"s"}],"args":[],"options":{"startApp":true},"filePath":"queue_scheduler_list.js"},{"commandName":"queue:scheduler:remove","description":"Remove a scheduled job by ID","help":"","namespace":"queue","aliases":[],"flags":[],"args":[{"name":"id","argumentName":"id","required":true,"description":"ID of the schedule to remove","type":"string"}],"options":{"startApp":true},"filePath":"queue_scheduler_remove.js"},{"commandName":"queue:work","description":"Start processing jobs from the queue","help":"","namespace":"queue","aliases":[],"flags":[{"name":"queue","flagName":"queue","required":false,"type":"string","description":"Comma-separated list of queues to process","alias":"q"}],"args":[],"options":{"startApp":true,"staysAlive":true},"filePath":"queue_work.js"}],"version":1}
1
+ {"commands":[{"commandName":"make:job","description":"Create a new job class","help":"","namespace":"make","aliases":[],"flags":[],"args":[{"name":"name","argumentName":"name","required":true,"description":"Name of the job","type":"string"}],"options":{"allowUnknownFlags":true},"filePath":"make_job.js"},{"commandName":"queue:scheduler:clear","description":"Remove all scheduled jobs","help":"","namespace":"queue","aliases":[],"flags":[],"args":[],"options":{"startApp":true},"filePath":"queue_scheduler_clear.js"},{"commandName":"queue:scheduler:list","description":"List all scheduled jobs","help":"","namespace":"queue","aliases":[],"flags":[{"name":"status","flagName":"status","required":false,"type":"string","description":"Filter by status (active, paused)","alias":"s"}],"args":[],"options":{"startApp":true},"filePath":"queue_scheduler_list.js"},{"commandName":"queue:scheduler:remove","description":"Remove a scheduled job by ID","help":"","namespace":"queue","aliases":[],"flags":[],"args":[{"name":"id","argumentName":"id","required":true,"description":"ID of the schedule to remove","type":"string"}],"options":{"startApp":true},"filePath":"queue_scheduler_remove.js"},{"commandName":"queue:work","description":"Start processing jobs from the queue","help":"","namespace":"queue","aliases":[],"flags":[{"name":"queue","flagName":"queue","required":false,"type":"string","description":"Comma-separated list of queues to process","alias":"q"},{"name":"concurrency","flagName":"concurrency","required":false,"type":"number","description":"Number of jobs to process concurrently","alias":"c"}],"args":[],"options":{"startApp":true,"staysAlive":true},"filePath":"queue_work.js"}],"version":1}
@@ -38,7 +38,7 @@ export default class QueueSchedulerList extends BaseCommand {
38
38
  const lastRun = schedule.lastRunAt?.toISOString() ?? 'Never';
39
39
  table.row([
40
40
  schedule.id,
41
- schedule.jobName,
41
+ schedule.name,
42
42
  scheduleExpr,
43
43
  schedule.status,
44
44
  String(schedule.runCount),
@@ -5,5 +5,6 @@ export default class QueueWork extends BaseCommand {
5
5
  static description: string;
6
6
  static options: CommandOptions;
7
7
  queue?: string;
8
+ concurrency?: number;
8
9
  run(): Promise<void>;
9
10
  }
@@ -13,6 +13,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
13
13
  return c > 3 && r && Object.defineProperty(target, key, r), r;
14
14
  };
15
15
  import { flags, BaseCommand } from '@adonisjs/core/ace';
16
+ import { resolveAdapters } from '../src/utils.js';
16
17
  export default class QueueWork extends BaseCommand {
17
18
  static commandName = 'queue:work';
18
19
  static description = 'Start processing jobs from the queue';
@@ -30,12 +31,20 @@ export default class QueueWork extends BaseCommand {
30
31
  */
31
32
  const router = await this.app.container.make('router');
32
33
  router.commit();
34
+ const resolvedAdapters = await resolveAdapters(config, this.app);
33
35
  const queues = this.queue ? this.queue.split(',').map((q) => q.trim()) : ['default'];
34
36
  this.logger.info(`Starting worker for queues: ${queues.join(', ')}`);
35
- const worker = new Worker(config);
37
+ const worker = new Worker({
38
+ ...config,
39
+ adapters: resolvedAdapters,
40
+ ...(this.concurrency && { concurrency: this.concurrency }),
41
+ });
36
42
  await worker.start(queues);
37
43
  }
38
44
  }
39
45
  __decorate([
40
46
  flags.string({ description: 'Comma-separated list of queues to process', alias: 'q' })
41
47
  ], QueueWork.prototype, "queue", void 0);
48
+ __decorate([
49
+ flags.number({ description: 'Number of jobs to process concurrently', alias: 'c' })
50
+ ], QueueWork.prototype, "concurrency", void 0);
@@ -7,6 +7,7 @@
7
7
  * file that was distributed with this source code.
8
8
  */
9
9
  import '../src/types/extended.js';
10
+ import { resolveAdapters } from '../src/utils.js';
10
11
  export default class QueueProvider {
11
12
  app;
12
13
  constructor(app) {
@@ -16,25 +17,14 @@ export default class QueueProvider {
16
17
  this.app.container.singleton('queue.manager', async () => {
17
18
  const { QueueManager } = await import('@boringnode/queue');
18
19
  const config = this.app.config.get('queue');
19
- /**
20
- * Resolve adapter factories from config providers
21
- */
22
- const resolvedAdapters = {};
23
- for (const [name, adapterConfig] of Object.entries(config.adapters)) {
24
- if (typeof adapterConfig === 'function') {
25
- resolvedAdapters[name] = adapterConfig;
26
- }
27
- else {
28
- resolvedAdapters[name] = await adapterConfig.resolver(this.app);
29
- }
30
- }
20
+ const resolvedAdapters = await resolveAdapters(config, this.app);
31
21
  /**
32
22
  * Inject jobFactory if not already defined.
33
23
  * This enables automatic dependency injection for job classes.
34
24
  */
35
25
  const jobFactory = config.jobFactory ??
36
- (async (JobClass, payload, context) => {
37
- return this.app.container.make(JobClass, [payload, context]);
26
+ (async (JobClass) => {
27
+ return this.app.container.make(JobClass);
38
28
  });
39
29
  const logger = await this.app.container.make('logger');
40
30
  await QueueManager.init({
@@ -1,5 +1,5 @@
1
- import type { QueueManagerConfig } from '@boringnode/queue/types';
1
+ import type { QueueConfig } from './types/main.js';
2
2
  /**
3
3
  * Define queue configuration with type-safety.
4
4
  */
5
- export declare function defineConfig(config: QueueManagerConfig): QueueManagerConfig;
5
+ export declare function defineConfig(config: QueueConfig): QueueConfig;
@@ -1 +1,11 @@
1
+ import type { ConfigProvider } from '@adonisjs/core/types';
2
+ import type { Adapter, QueueManagerConfig } from '@boringnode/queue/types';
1
3
  export * from '@boringnode/queue/types';
4
+ type AdapterFactory = () => Adapter;
5
+ /**
6
+ * AdonisJS-specific queue configuration that supports both
7
+ * direct adapter factories and config providers.
8
+ */
9
+ export interface QueueConfig extends Omit<QueueManagerConfig, 'adapters'> {
10
+ adapters: Record<string, AdapterFactory | ConfigProvider<AdapterFactory>>;
11
+ }
@@ -0,0 +1,14 @@
1
+ import type { ApplicationService } from '@adonisjs/core/types';
2
+ import type { QueueConfig } from './types/main.js';
3
+ type AdapterFactory = () => any;
4
+ /**
5
+ * Resolve adapter factories from config providers.
6
+ *
7
+ * Adapters in the config can be either:
8
+ * - Direct factory functions
9
+ * - ConfigProvider objects that need to be resolved
10
+ *
11
+ * This function normalizes them all to factory functions.
12
+ */
13
+ export declare function resolveAdapters(config: QueueConfig, app: ApplicationService): Promise<Record<string, AdapterFactory>>;
14
+ export {};
@@ -0,0 +1,29 @@
1
+ /*
2
+ * @adonisjs/queue
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ /**
10
+ * Resolve adapter factories from config providers.
11
+ *
12
+ * Adapters in the config can be either:
13
+ * - Direct factory functions
14
+ * - ConfigProvider objects that need to be resolved
15
+ *
16
+ * This function normalizes them all to factory functions.
17
+ */
18
+ export async function resolveAdapters(config, app) {
19
+ const resolvedAdapters = {};
20
+ for (const [name, adapterConfig] of Object.entries(config.adapters)) {
21
+ if (typeof adapterConfig === 'function') {
22
+ resolvedAdapters[name] = adapterConfig;
23
+ }
24
+ else {
25
+ resolvedAdapters[name] = await adapterConfig.resolver(app);
26
+ }
27
+ }
28
+ return resolvedAdapters;
29
+ }
@@ -6,14 +6,14 @@
6
6
  })
7
7
  }}}
8
8
  import { Job } from '@adonisjs/queue'
9
- import type { JobOption } from '@adonisjs/queue/types'
9
+ import type { JobOptions } from '@adonisjs/queue/types'
10
10
 
11
11
  interface {{ jobName }}Payload {
12
12
  // Define your payload type here
13
13
  }
14
14
 
15
15
  export default class {{ jobName }} extends Job<{{ jobName }}Payload> {
16
- static options: JobOption = {
16
+ static options: JobOptions = {
17
17
  queue: 'default',
18
18
  maxRetries: 3,
19
19
  }
@@ -13,7 +13,7 @@ export default class extends BaseSchema {
13
13
  this.schema.createTable('queue_jobs', (table) => {
14
14
  table.string('id', 255).notNullable()
15
15
  table.string('queue', 255).notNullable()
16
- table.enum('status', ['pending', 'active', 'delayed']).notNullable()
16
+ table.enu('status', ['pending', 'active', 'delayed']).notNullable()
17
17
  table.text('data').notNullable()
18
18
  table.bigint('score').unsigned().nullable()
19
19
  table.string('worker_id', 255).nullable()
@@ -30,7 +30,7 @@ export default class extends BaseSchema {
30
30
  this.schema.createTable('queue_schedules', (table) => {
31
31
  table.string('id', 255).primary()
32
32
  table.string('status', 50).notNullable().defaultTo('active')
33
- table.string('job_name', 255).notNullable()
33
+ table.string('name', 255).notNullable()
34
34
  table.text('payload').notNullable()
35
35
  table.string('cron_expression', 255).nullable()
36
36
  table.bigint('every_ms').unsigned().nullable()
@@ -41,7 +41,7 @@ export default class extends BaseSchema {
41
41
  table.integer('run_count').unsigned().notNullable().defaultTo(0)
42
42
  table.timestamp('next_run_at').nullable()
43
43
  table.timestamp('last_run_at').nullable()
44
- table.timestamp('created_at').notNullable()
44
+ table.timestamp('created_at').notNullable().defaultTo(this.#connection.fn.now())
45
45
  table.index(['status', 'next_run_at'])
46
46
  })
47
47
  }
@@ -0,0 +1,5 @@
1
+ import type { AppEnvironments } from '@adonisjs/core/types/app';
2
+ import { defineConfig } from '../index.js';
3
+ export declare function setupApp(env?: AppEnvironments, config?: {
4
+ queue?: ReturnType<typeof defineConfig>;
5
+ }): Promise<import("@adonisjs/core/types").ApplicationService>;
@@ -0,0 +1,51 @@
1
+ /*
2
+ * @adonisjs/queue
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
10
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
11
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
12
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
13
+ });
14
+ }
15
+ return path;
16
+ };
17
+ import { getActiveTest } from '@japa/runner';
18
+ import { IgnitorFactory } from '@adonisjs/core/factories';
19
+ import { defineConfig, drivers } from '../index.js';
20
+ const BASE_URL = new URL('./tmp/', import.meta.url);
21
+ export async function setupApp(env, config = {}) {
22
+ const ignitor = new IgnitorFactory()
23
+ .withCoreProviders()
24
+ .withCoreConfig()
25
+ .merge({
26
+ config: {
27
+ queue: config.queue ||
28
+ defineConfig({
29
+ default: 'sync',
30
+ adapters: {
31
+ sync: drivers.sync(),
32
+ },
33
+ }),
34
+ },
35
+ rcFileContents: {
36
+ providers: [() => import('../providers/queue_provider.js')],
37
+ },
38
+ })
39
+ .create(BASE_URL, {
40
+ importer: (filePath) => {
41
+ if (filePath.startsWith('./') || filePath.startsWith('../')) {
42
+ return import(__rewriteRelativeImportExtension(new URL(filePath, BASE_URL).href));
43
+ }
44
+ return import(__rewriteRelativeImportExtension(filePath));
45
+ },
46
+ });
47
+ const app = ignitor.createApp(env || 'web');
48
+ await app.init().then(() => app.boot());
49
+ getActiveTest()?.cleanup(() => app.terminate());
50
+ return app;
51
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,30 @@
1
+ /*
2
+ * @adonisjs/queue
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ import { test } from '@japa/runner';
10
+ import { setupApp } from './helpers.js';
11
+ test.group('Provider', () => {
12
+ test('should resolve queue manager from container', async ({ assert }) => {
13
+ const app = await setupApp();
14
+ const queueManager = await app.container.make('queue.manager');
15
+ assert.isDefined(queueManager);
16
+ assert.isFunction(queueManager.use);
17
+ assert.isFunction(queueManager.destroy);
18
+ });
19
+ test('should resolve adapters from config providers', async ({ assert }) => {
20
+ const app = await setupApp();
21
+ const queueManager = await app.container.make('queue.manager');
22
+ assert.isDefined(queueManager);
23
+ });
24
+ test('should shutdown queue manager when app terminates', async ({ assert }) => {
25
+ const app = await setupApp();
26
+ const queueManager = await app.container.make('queue.manager');
27
+ assert.isDefined(queueManager);
28
+ await app.terminate();
29
+ });
30
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,148 @@
1
+ /*
2
+ * @adonisjs/queue
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
10
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
11
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
12
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
13
+ });
14
+ }
15
+ return path;
16
+ };
17
+ import { test } from '@japa/runner';
18
+ import { IgnitorFactory } from '@adonisjs/core/factories';
19
+ import { defineConfig, drivers } from '../index.js';
20
+ import { resolveAdapters } from '../src/utils.js';
21
+ const BASE_URL = new URL('./tmp/', import.meta.url);
22
+ test.group('resolveAdapters', () => {
23
+ test('should resolve config providers to adapter factories', async ({ assert }) => {
24
+ const ignitor = new IgnitorFactory()
25
+ .withCoreProviders()
26
+ .withCoreConfig()
27
+ .merge({
28
+ config: {
29
+ queue: defineConfig({
30
+ default: 'sync',
31
+ adapters: {
32
+ sync: drivers.sync(),
33
+ },
34
+ }),
35
+ },
36
+ rcFileContents: {
37
+ providers: [() => import('../providers/queue_provider.js')],
38
+ },
39
+ })
40
+ .create(BASE_URL, {
41
+ importer: (filePath) => {
42
+ if (filePath.startsWith('./') || filePath.startsWith('../')) {
43
+ return import(__rewriteRelativeImportExtension(new URL(filePath, BASE_URL).href));
44
+ }
45
+ return import(__rewriteRelativeImportExtension(filePath));
46
+ },
47
+ });
48
+ const app = ignitor.createApp('console');
49
+ await app.init().then(() => app.boot());
50
+ const config = app.config.get('queue');
51
+ /**
52
+ * Before resolution, the adapter is a ConfigProvider (object with resolver method)
53
+ */
54
+ assert.isObject(config.adapters.sync);
55
+ assert.isFunction(config.adapters.sync.resolver);
56
+ /**
57
+ * After resolution, the adapter should be a factory function
58
+ */
59
+ const resolvedAdapters = await resolveAdapters(config, app);
60
+ assert.isFunction(resolvedAdapters.sync);
61
+ await app.terminate();
62
+ });
63
+ test('should pass through direct factory functions unchanged', async ({ assert }) => {
64
+ const directFactory = () => ({
65
+ pushOn: async () => { },
66
+ pushLaterOn: async () => { },
67
+ pop: async () => null,
68
+ acknowledge: async () => { },
69
+ fail: async () => { },
70
+ getFailedJobs: async () => [],
71
+ removeFailedJob: async () => { },
72
+ clearFailedJobs: async () => { },
73
+ destroy: async () => { },
74
+ });
75
+ const ignitor = new IgnitorFactory()
76
+ .withCoreProviders()
77
+ .withCoreConfig()
78
+ .merge({
79
+ config: {
80
+ queue: defineConfig({
81
+ default: 'custom',
82
+ adapters: {
83
+ custom: directFactory,
84
+ },
85
+ }),
86
+ },
87
+ rcFileContents: {
88
+ providers: [() => import('../providers/queue_provider.js')],
89
+ },
90
+ })
91
+ .create(BASE_URL, {
92
+ importer: (filePath) => {
93
+ if (filePath.startsWith('./') || filePath.startsWith('../')) {
94
+ return import(__rewriteRelativeImportExtension(new URL(filePath, BASE_URL).href));
95
+ }
96
+ return import(__rewriteRelativeImportExtension(filePath));
97
+ },
98
+ });
99
+ const app = ignitor.createApp('console');
100
+ await app.init().then(() => app.boot());
101
+ const config = app.config.get('queue');
102
+ const resolvedAdapters = await resolveAdapters(config, app);
103
+ assert.strictEqual(resolvedAdapters.custom, directFactory);
104
+ await app.terminate();
105
+ });
106
+ test('worker should accept resolved adapters without throwing', async ({ assert }) => {
107
+ const ignitor = new IgnitorFactory()
108
+ .withCoreProviders()
109
+ .withCoreConfig()
110
+ .merge({
111
+ config: {
112
+ queue: defineConfig({
113
+ default: 'sync',
114
+ adapters: {
115
+ sync: drivers.sync(),
116
+ },
117
+ }),
118
+ },
119
+ rcFileContents: {
120
+ providers: [() => import('../providers/queue_provider.js')],
121
+ },
122
+ })
123
+ .create(BASE_URL, {
124
+ importer: (filePath) => {
125
+ if (filePath.startsWith('./') || filePath.startsWith('../')) {
126
+ return import(__rewriteRelativeImportExtension(new URL(filePath, BASE_URL).href));
127
+ }
128
+ return import(__rewriteRelativeImportExtension(filePath));
129
+ },
130
+ });
131
+ const app = ignitor.createApp('console');
132
+ await app.init().then(() => app.boot());
133
+ const config = app.config.get('queue');
134
+ const resolvedAdapters = await resolveAdapters(config, app);
135
+ /**
136
+ * Creating a Worker with resolved adapters should not throw
137
+ * "Adapter must be a factory function" error
138
+ */
139
+ const { Worker } = await import('@boringnode/queue');
140
+ assert.doesNotThrow(() => {
141
+ new Worker({
142
+ ...config,
143
+ adapters: resolvedAdapters,
144
+ });
145
+ });
146
+ await app.terminate();
147
+ });
148
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@adonisjs/queue",
3
3
  "description": "Queue system for AdonisJS powered by @boringnode/queue",
4
- "version": "0.1.1",
4
+ "version": "0.2.1",
5
5
  "engines": {
6
6
  "node": ">=20.11.1"
7
7
  },
@@ -35,7 +35,7 @@
35
35
  "version": "npm run build"
36
36
  },
37
37
  "dependencies": {
38
- "@boringnode/queue": "^0.1.0",
38
+ "@boringnode/queue": "^0.2.0",
39
39
  "@poppinss/utils": "^6.10.1"
40
40
  },
41
41
  "devDependencies": {
@@ -61,10 +61,10 @@
61
61
  "typescript": "^5.8.2"
62
62
  },
63
63
  "peerDependencies": {
64
- "@adonisjs/assembler": "^7.0.0 || ^8.0.0",
65
- "@adonisjs/core": "^6.2.0 || ^7.0.0",
66
- "@adonisjs/lucid": "^20.0.0 || ^21.0.0 || ^22.0.0",
67
- "@adonisjs/redis": "^8.0.0 || ^9.0.0 || ^10.0.0"
64
+ "@adonisjs/assembler": "^7.0.0 || ^8.0.0 || ^8.0.0-next",
65
+ "@adonisjs/core": "^6.2.0 || ^7.0.0 || ^7.0.0-next",
66
+ "@adonisjs/lucid": "^20.0.0 || ^21.0.0 || ^22.0.0 || ^22.0.0-next",
67
+ "@adonisjs/redis": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^10.0.0-next"
68
68
  },
69
69
  "peerDependenciesMeta": {
70
70
  "@adonisjs/lucid": {