adonisjs-server-stats 1.12.3 → 1.13.0

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.
@@ -0,0 +1,629 @@
1
+ import { appImport } from '../../utils/app_import.js';
2
+ // ---------------------------------------------------------------------------
3
+ // Status mapping
4
+ // ---------------------------------------------------------------------------
5
+ /** Map a @boringnode JobStatus → dashboard status. */
6
+ function mapStatus(s) {
7
+ switch (s) {
8
+ case 'pending': return 'waiting';
9
+ case 'active': return 'active';
10
+ case 'delayed': return 'delayed';
11
+ case 'completed': return 'completed';
12
+ case 'failed': return 'failed';
13
+ default: return 'waiting';
14
+ }
15
+ }
16
+ /** Map a dashboard status → @boringnode store statuses for queries. */
17
+ function mapStatusToStore(s) {
18
+ switch (s) {
19
+ case 'waiting': return ['pending'];
20
+ case 'active': return ['active'];
21
+ case 'delayed': return ['delayed'];
22
+ case 'completed': return ['completed'];
23
+ case 'failed': return ['failed'];
24
+ case 'paused': return []; // no paused concept in @boringnode/queue
25
+ case 'all':
26
+ default:
27
+ return ['pending', 'active', 'delayed', 'completed', 'failed'];
28
+ }
29
+ }
30
+ // ---------------------------------------------------------------------------
31
+ // Named export: JobRecord → QueueJobSummary mapper (unit-testable without store)
32
+ // ---------------------------------------------------------------------------
33
+ /**
34
+ * Map a @boringnode `JobRecord` (plus optional DB `acquired_at` timestamp) to
35
+ * the `QueueJobSummary` shape used by the dashboard.
36
+ *
37
+ * Known gaps (no per-row data; documented inline):
38
+ * - `progress`: always 0 — @boringnode/queue does not persist per-row progress.
39
+ * - `returnValue`: always null — not stored in the queue table/hash.
40
+ * - `maxAttempts`: best-effort from `data.maxRetries`; falls back to `attempts`.
41
+ * - `stackTrace`: collapsed to `[record.error]`; no real stack trace in store.
42
+ *
43
+ * @param record The raw job record from the store.
44
+ * @param acquiredAtMs Unix-ms timestamp from DB `acquired_at` column, or null.
45
+ */
46
+ export function mapJobRecordToSummary(record, acquiredAtMs) {
47
+ const { data } = record;
48
+ const processedAt = acquiredAtMs;
49
+ const finishedAt = record.finishedAt ?? null;
50
+ const createdAt = data.createdAt ?? 0;
51
+ const duration = processedAt !== null && finishedAt !== null ? finishedAt - processedAt : null;
52
+ return {
53
+ id: data.id,
54
+ name: cleanJobName(data.name),
55
+ status: mapStatus(record.status),
56
+ data: (data.payload ?? null),
57
+ payload: (data.payload ?? null),
58
+ attempts: data.attempts ?? 0,
59
+ // maxRetries is not persisted per row in @boringnode/queue; best effort
60
+ maxAttempts: data.maxRetries ?? data.attempts ?? 1,
61
+ // progress not persisted by @boringnode/queue
62
+ progress: 0,
63
+ failedReason: record.error ?? null,
64
+ createdAt,
65
+ timestamp: createdAt,
66
+ processedAt,
67
+ finishedAt,
68
+ duration,
69
+ };
70
+ }
71
+ /**
72
+ * Extract a human-readable job name from the raw name stored by @boringnode/queue.
73
+ * Mirrors the heuristic in QueueInspector for BullMQ jobs.
74
+ */
75
+ function cleanJobName(raw) {
76
+ if (!raw || raw === '__default__')
77
+ return 'default';
78
+ // Strip file:// URLs down to the filename
79
+ if (raw.startsWith('file://') || raw.startsWith('/')) {
80
+ const filename = raw.split('/').pop() ?? raw;
81
+ const base = filename.replace(/\.(ts|js|mjs|cjs)$/, '');
82
+ return base
83
+ .split(/[-_]/)
84
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
85
+ .join('');
86
+ }
87
+ return raw;
88
+ }
89
+ // ---------------------------------------------------------------------------
90
+ // Helpers
91
+ // ---------------------------------------------------------------------------
92
+ /** Derive the list of queue names to aggregate from config. */
93
+ function resolveQueueNames(config) {
94
+ const names = new Set(['default']);
95
+ if (config.queues) {
96
+ for (const k of Object.keys(config.queues))
97
+ names.add(k);
98
+ }
99
+ return Array.from(names);
100
+ }
101
+ // ---------------------------------------------------------------------------
102
+ // DatabaseStoreReader — reads queue_jobs via Lucid knex
103
+ // ---------------------------------------------------------------------------
104
+ const TABLE = 'queue_jobs';
105
+ class DatabaseStoreReader {
106
+ #db;
107
+ #connectionName;
108
+ #queues;
109
+ constructor(db, connectionName, queues) {
110
+ this.#db = db;
111
+ this.#connectionName = connectionName;
112
+ this.#queues = queues;
113
+ }
114
+ #knex() {
115
+ return this.#db.connection(this.#connectionName).getWriteClient();
116
+ }
117
+ async getCounts() {
118
+ const defaults = { active: 0, waiting: 0, delayed: 0, completed: 0, failed: 0 };
119
+ try {
120
+ const rows = await this.#knex()(TABLE)
121
+ .whereIn('queue', this.#queues)
122
+ .select('status')
123
+ .count('* as count')
124
+ .groupBy('status');
125
+ const counts = { ...defaults };
126
+ for (const row of rows) {
127
+ const n = Number(row.count);
128
+ const ui = mapStatus(row.status);
129
+ if (ui === 'waiting')
130
+ counts.waiting += n;
131
+ else if (ui === 'active')
132
+ counts.active += n;
133
+ else if (ui === 'delayed')
134
+ counts.delayed += n;
135
+ else if (ui === 'completed')
136
+ counts.completed += n;
137
+ else if (ui === 'failed')
138
+ counts.failed += n;
139
+ }
140
+ return counts;
141
+ }
142
+ catch {
143
+ return defaults;
144
+ }
145
+ }
146
+ async listJobs(status, page = 1, perPage = 25) {
147
+ try {
148
+ const storeStatuses = mapStatusToStore(status);
149
+ if (storeStatuses.length === 0)
150
+ return { jobs: [], total: 0 };
151
+ const offset = (page - 1) * perPage;
152
+ const knex = this.#knex();
153
+ const [rows, totalRows] = await Promise.all([
154
+ knex(TABLE)
155
+ .whereIn('queue', this.#queues)
156
+ .whereIn('status', storeStatuses)
157
+ .orderBy('finished_at', 'desc')
158
+ .limit(perPage)
159
+ .offset(offset),
160
+ knex(TABLE)
161
+ .whereIn('queue', this.#queues)
162
+ .whereIn('status', storeStatuses)
163
+ .count('* as count'),
164
+ ]);
165
+ return {
166
+ jobs: rows.map((row) => this.#rowToSummary(row)),
167
+ total: Number(totalRows[0]?.count ?? 0),
168
+ };
169
+ }
170
+ catch {
171
+ return { jobs: [], total: 0 };
172
+ }
173
+ }
174
+ async getJob(id) {
175
+ try {
176
+ const row = await this.#knex()(TABLE)
177
+ .where('id', id)
178
+ .first();
179
+ if (!row)
180
+ return null;
181
+ const summary = this.#rowToSummary(row);
182
+ return {
183
+ ...summary,
184
+ stackTrace: row.error ? [String(row.error)] : [],
185
+ returnValue: null,
186
+ opts: {},
187
+ };
188
+ }
189
+ catch {
190
+ return null;
191
+ }
192
+ }
193
+ async retryJob(id) {
194
+ try {
195
+ const updated = await this.#knex()(TABLE)
196
+ .where('id', id)
197
+ .andWhere('status', 'failed')
198
+ .update({
199
+ status: 'pending',
200
+ worker_id: null,
201
+ acquired_at: null,
202
+ finished_at: null,
203
+ error: null,
204
+ score: Date.now(),
205
+ });
206
+ return updated > 0;
207
+ }
208
+ catch {
209
+ return false;
210
+ }
211
+ }
212
+ async getWorkerCount() {
213
+ try {
214
+ // COUNT(DISTINCT worker_id) is a reasonable proxy for active workers
215
+ const result = await this.#knex().raw(`SELECT COUNT(DISTINCT worker_id) as count FROM ${TABLE} WHERE status = ? AND worker_id IS NOT NULL`, ['active']);
216
+ // pg-style: result.rows; sqlite/mysql-style: result[0] array
217
+ const rows = result?.rows
218
+ ?? result?.[0]
219
+ ?? [];
220
+ return Number(rows[0]?.count ?? 0);
221
+ }
222
+ catch {
223
+ return 0;
224
+ }
225
+ }
226
+ #rowToSummary(row) {
227
+ let data;
228
+ try {
229
+ data = JSON.parse(String(row.data ?? '{}'));
230
+ }
231
+ catch {
232
+ data = { id: String(row.id ?? ''), name: 'unknown', attempts: 0 };
233
+ }
234
+ const record = {
235
+ status: row.status ?? 'pending',
236
+ data,
237
+ finishedAt: row.finished_at != null ? Number(row.finished_at) : undefined,
238
+ error: row.error != null ? String(row.error) : undefined,
239
+ };
240
+ return mapJobRecordToSummary(record, row.acquired_at != null ? Number(row.acquired_at) : null);
241
+ }
242
+ }
243
+ // ---------------------------------------------------------------------------
244
+ // RedisStoreReader — reads jobs::{queue}::* keys via @adonisjs/redis
245
+ // ---------------------------------------------------------------------------
246
+ class RedisStoreReader {
247
+ #redis;
248
+ #connectionName;
249
+ #queues;
250
+ constructor(redis, connectionName, queues) {
251
+ this.#redis = redis;
252
+ this.#connectionName = connectionName;
253
+ this.#queues = queues;
254
+ }
255
+ #conn() {
256
+ return this.#redis.connection(this.#connectionName);
257
+ }
258
+ #key(queue, suffix) {
259
+ return `jobs::${queue}::${suffix}`;
260
+ }
261
+ async getCounts() {
262
+ const defaults = { active: 0, waiting: 0, delayed: 0, completed: 0, failed: 0 };
263
+ try {
264
+ const conn = this.#conn();
265
+ let active = 0, waiting = 0, delayed = 0, completed = 0, failed = 0;
266
+ await Promise.all(this.#queues.map(async (q) => {
267
+ const [w, d, a, c, f] = await Promise.all([
268
+ conn.zcard(this.#key(q, 'pending')),
269
+ conn.zcard(this.#key(q, 'delayed')),
270
+ conn.hlen(this.#key(q, 'active')),
271
+ conn.hlen(this.#key(q, 'completed')),
272
+ conn.hlen(this.#key(q, 'failed')),
273
+ ]);
274
+ waiting += w;
275
+ delayed += d;
276
+ active += a;
277
+ completed += c;
278
+ failed += f;
279
+ }));
280
+ return { active, waiting, delayed, completed, failed };
281
+ }
282
+ catch {
283
+ return defaults;
284
+ }
285
+ }
286
+ async listJobs(status, page = 1, perPage = 25) {
287
+ try {
288
+ const storeStatuses = mapStatusToStore(status);
289
+ if (storeStatuses.length === 0)
290
+ return { jobs: [], total: 0 };
291
+ const conn = this.#conn();
292
+ const allJobs = [];
293
+ for (const q of this.#queues) {
294
+ for (const ss of storeStatuses) {
295
+ const jobs = await this.#listByStatus(conn, q, ss);
296
+ allJobs.push(...jobs);
297
+ }
298
+ }
299
+ // Sort newest-first then paginate in-memory (Redis has no ORDER BY)
300
+ allJobs.sort((a, b) => b.createdAt - a.createdAt);
301
+ const start = (page - 1) * perPage;
302
+ return { jobs: allJobs.slice(start, start + perPage), total: allJobs.length };
303
+ }
304
+ catch {
305
+ return { jobs: [], total: 0 };
306
+ }
307
+ }
308
+ async getJob(id) {
309
+ try {
310
+ const conn = this.#conn();
311
+ for (const q of this.#queues) {
312
+ // Try completed / failed hashes first — they contain full JobRecord JSON
313
+ for (const suffix of ['completed', 'failed']) {
314
+ const raw = await conn.hget(this.#key(q, suffix), id);
315
+ if (raw) {
316
+ try {
317
+ const record = JSON.parse(raw);
318
+ return {
319
+ ...mapJobRecordToSummary(record, null),
320
+ stackTrace: record.error ? [record.error] : [],
321
+ returnValue: null,
322
+ opts: {},
323
+ };
324
+ }
325
+ catch {
326
+ // malformed — try next
327
+ }
328
+ }
329
+ }
330
+ // Fall back to ::data hash (pending / delayed / active)
331
+ const raw = await conn.hget(this.#key(q, 'data'), id);
332
+ if (raw) {
333
+ try {
334
+ const data = JSON.parse(raw);
335
+ const bStatus = await this.#detectStatus(conn, q, id);
336
+ return {
337
+ ...mapJobRecordToSummary({ status: bStatus, data }, null),
338
+ stackTrace: [],
339
+ returnValue: null,
340
+ opts: {},
341
+ };
342
+ }
343
+ catch {
344
+ // malformed
345
+ }
346
+ }
347
+ }
348
+ return null;
349
+ }
350
+ catch {
351
+ return null;
352
+ }
353
+ }
354
+ async retryJob(id) {
355
+ try {
356
+ const conn = this.#conn();
357
+ for (const q of this.#queues) {
358
+ const raw = await conn.hget(this.#key(q, 'failed'), id);
359
+ if (!raw)
360
+ continue;
361
+ try {
362
+ const record = JSON.parse(raw);
363
+ // Restore data entry and move back to pending ZSET
364
+ await conn.hset(this.#key(q, 'data'), id, JSON.stringify(record.data));
365
+ await conn.zadd(this.#key(q, 'pending'), Date.now(), id);
366
+ await conn.hdel(this.#key(q, 'failed'), id);
367
+ // Remove from optional failed index ZSET if it exists
368
+ try {
369
+ await conn.zrem(this.#key(q, 'failed::index'), id);
370
+ }
371
+ catch { /* optional */ }
372
+ return true;
373
+ }
374
+ catch {
375
+ // continue to next queue
376
+ }
377
+ }
378
+ return false;
379
+ }
380
+ catch {
381
+ return false;
382
+ }
383
+ }
384
+ async getWorkerCount() {
385
+ try {
386
+ // Best-effort proxy: count of entries in ::active hashes
387
+ const conn = this.#conn();
388
+ let total = 0;
389
+ for (const q of this.#queues) {
390
+ total += await conn.hlen(this.#key(q, 'active'));
391
+ }
392
+ return total;
393
+ }
394
+ catch {
395
+ return 0;
396
+ }
397
+ }
398
+ /** Enumerate jobs in one queue+status bucket. */
399
+ async #listByStatus(conn, queue, status) {
400
+ if (status === 'pending' || status === 'delayed') {
401
+ // Stored as a ZSET; members are job IDs; full data is in ::data hash
402
+ const setKey = this.#key(queue, status === 'pending' ? 'pending' : 'delayed');
403
+ const ids = await conn.zrange(setKey, 0, -1);
404
+ const jobs = [];
405
+ for (const id of ids) {
406
+ const raw = await conn.hget(this.#key(queue, 'data'), id);
407
+ if (!raw)
408
+ continue;
409
+ try {
410
+ const data = JSON.parse(raw);
411
+ jobs.push(mapJobRecordToSummary({ status, data }, null));
412
+ }
413
+ catch { /* skip malformed */ }
414
+ }
415
+ return jobs;
416
+ }
417
+ if (status === 'active') {
418
+ // Stored as a HASH (field=jobId, value=workerId); data is in ::data hash
419
+ const ids = await this.#hkeys(conn, this.#key(queue, 'active'));
420
+ const jobs = [];
421
+ for (const id of ids) {
422
+ const raw = await conn.hget(this.#key(queue, 'data'), id);
423
+ if (!raw)
424
+ continue;
425
+ try {
426
+ const data = JSON.parse(raw);
427
+ jobs.push(mapJobRecordToSummary({ status: 'active', data }, null));
428
+ }
429
+ catch { /* skip */ }
430
+ }
431
+ return jobs;
432
+ }
433
+ // completed | failed — HASH; values are full JobRecord JSON
434
+ return this.#scanHashAsRecords(conn, this.#key(queue, status));
435
+ }
436
+ /** HSCAN a hash and parse each value as a JobRecord, returning summaries. */
437
+ async #scanHashAsRecords(conn, key) {
438
+ const jobs = [];
439
+ let cursor = '0';
440
+ do {
441
+ const [nextCursor, entries] = await conn.hscan(key, cursor, 'COUNT', '100');
442
+ cursor = nextCursor;
443
+ // entries: [field0, value0, field1, value1, …]
444
+ for (let i = 1; i < entries.length; i += 2) {
445
+ try {
446
+ const record = JSON.parse(entries[i]);
447
+ jobs.push(mapJobRecordToSummary(record, null));
448
+ }
449
+ catch { /* skip */ }
450
+ }
451
+ } while (cursor !== '0');
452
+ return jobs;
453
+ }
454
+ /**
455
+ * Return all field names of a Redis hash.
456
+ * Uses native HKEYS if available; falls back to HSCAN for compatibility.
457
+ */
458
+ async #hkeys(conn, key) {
459
+ if (typeof conn.hkeys === 'function') {
460
+ try {
461
+ return await conn.hkeys(key);
462
+ }
463
+ catch { /* fallback */ }
464
+ }
465
+ const keys = [];
466
+ let cursor = '0';
467
+ do {
468
+ const [nextCursor, entries] = await conn.hscan(key, cursor, 'COUNT', '100');
469
+ cursor = nextCursor;
470
+ for (let i = 0; i < entries.length; i += 2)
471
+ keys.push(entries[i]);
472
+ } while (cursor !== '0');
473
+ return keys;
474
+ }
475
+ /** Detect the current bucket of a job ID within a queue. */
476
+ async #detectStatus(conn, queue, id) {
477
+ const inActive = await conn.hget(this.#key(queue, 'active'), id);
478
+ if (inActive !== null)
479
+ return 'active';
480
+ const score = await conn.zscore(this.#key(queue, 'pending'), id);
481
+ if (score !== null)
482
+ return 'pending';
483
+ const dscore = await conn.zscore(this.#key(queue, 'delayed'), id);
484
+ if (dscore !== null)
485
+ return 'delayed';
486
+ return 'pending';
487
+ }
488
+ }
489
+ // ---------------------------------------------------------------------------
490
+ // Driver detection
491
+ // ---------------------------------------------------------------------------
492
+ function detectDriver(queueManager, config) {
493
+ try {
494
+ const name = queueManager.use()?.constructor?.name ?? '';
495
+ if (name === 'KnexAdapter')
496
+ return 'database';
497
+ if (name === 'RedisAdapter')
498
+ return 'redis';
499
+ }
500
+ catch {
501
+ // fall through
502
+ }
503
+ // Regex fallback on the default adapter key
504
+ const key = config.default ?? Object.keys(config.adapters ?? {})[0] ?? '';
505
+ if (/redis/i.test(key))
506
+ return 'redis';
507
+ if (/database|sql|knex/i.test(key))
508
+ return 'database';
509
+ return 'unknown';
510
+ }
511
+ /**
512
+ * Build a {@link QueueStoreReader} from already-resolved services.
513
+ *
514
+ * Both the inspector path (`app.container.make`) and the collector path
515
+ * (`appImport`) resolve their own services and pass them here.
516
+ * Returns a safe-defaults no-op reader if driver detection fails or the
517
+ * required service (db or redis) is absent.
518
+ */
519
+ export function buildQueueStoreReader(services) {
520
+ const { queueManager, config, db, redis, connectionName } = services;
521
+ const queues = resolveQueueNames(config);
522
+ const driver = detectDriver(queueManager, config);
523
+ if (driver === 'database') {
524
+ if (!db)
525
+ return noopReader();
526
+ const cfgConn = config.adapters?.[config.default ?? '']?.connectionName;
527
+ const connName = connectionName ?? cfgConn ?? db.primaryConnectionName;
528
+ return new DatabaseStoreReader(db, connName, queues);
529
+ }
530
+ if (driver === 'redis') {
531
+ if (!redis)
532
+ return noopReader();
533
+ const cfgConn = config.adapters?.[config.default ?? '']?.connectionName;
534
+ const connName = connectionName ?? cfgConn ?? 'main';
535
+ return new RedisStoreReader(redis, connName, queues);
536
+ }
537
+ return noopReader();
538
+ }
539
+ /**
540
+ * Resolve services from an AdonisJS Application instance and build a store reader.
541
+ *
542
+ * This is the canonical resolver: the queue config lives in the app's
543
+ * `config/queue.ts` (there is no package-level config export), so it can only
544
+ * be read from `app.config.get('queue')`. Both the inspector and collector
545
+ * paths funnel through here.
546
+ *
547
+ * Returns null if @adonisjs/queue is not registered (e.g. wrong environment).
548
+ */
549
+ export async function resolveFromApplication(app) {
550
+ try {
551
+ const queueManager = (await app.container.make('queue.manager'));
552
+ const config = (app.config.get('queue') ?? {});
553
+ // db and redis are optional; ignore failures
554
+ const [dbResult, redisResult] = await Promise.allSettled([
555
+ app.container.make('lucid.db'),
556
+ app.container.make('redis'),
557
+ ]);
558
+ return buildQueueStoreReader({
559
+ queueManager,
560
+ config,
561
+ db: dbResult.status === 'fulfilled' ? dbResult.value : undefined,
562
+ redis: redisResult.status === 'fulfilled' ? redisResult.value : undefined,
563
+ });
564
+ }
565
+ catch {
566
+ return null;
567
+ }
568
+ }
569
+ // ---------------------------------------------------------------------------
570
+ // Convenience resolver — inspector path (ApplicationService)
571
+ // ---------------------------------------------------------------------------
572
+ /**
573
+ * Resolve services for the inspector path.
574
+ *
575
+ * Accepts either a full Application-like object (with `config` + `container`)
576
+ * or a bare container (legacy). When given a bare container it resolves the
577
+ * application via the `app` binding to read the queue config.
578
+ *
579
+ * Returns null if @adonisjs/queue is not registered (e.g. wrong environment).
580
+ */
581
+ export async function resolveFromContainer(appOrContainer) {
582
+ // Full application instance (has its own config) — preferred path.
583
+ if ('config' in appOrContainer && 'container' in appOrContainer) {
584
+ return resolveFromApplication(appOrContainer);
585
+ }
586
+ // Bare container — recover the application from the `app` binding.
587
+ try {
588
+ const app = (await appOrContainer.make('app'));
589
+ return resolveFromApplication({ config: app.config, container: appOrContainer });
590
+ }
591
+ catch {
592
+ return null;
593
+ }
594
+ }
595
+ // ---------------------------------------------------------------------------
596
+ // Convenience resolver — collector path (appImport)
597
+ // ---------------------------------------------------------------------------
598
+ /**
599
+ * Resolve services via `appImport` and build a store reader.
600
+ *
601
+ * Designed for the collector path where no IoC container reference is held.
602
+ * Resolves the running Application via `@adonisjs/core/services/app` (the same
603
+ * service the official `@adonisjs/queue` package uses), then reads the queue
604
+ * config and services from it.
605
+ *
606
+ * Returns null if @adonisjs/queue is not installed in the host application.
607
+ */
608
+ export async function resolveFromAppImport() {
609
+ try {
610
+ const appMod = await appImport('@adonisjs/core/services/app');
611
+ return await resolveFromApplication(appMod.default);
612
+ }
613
+ catch {
614
+ return null;
615
+ }
616
+ }
617
+ // ---------------------------------------------------------------------------
618
+ // No-op reader — safe defaults when driver is unknown or services are absent
619
+ // ---------------------------------------------------------------------------
620
+ function noopReader() {
621
+ const zero = { active: 0, waiting: 0, delayed: 0, completed: 0, failed: 0 };
622
+ return {
623
+ async getCounts() { return { ...zero }; },
624
+ async listJobs() { return { jobs: [], total: 0 }; },
625
+ async getJob() { return null; },
626
+ async retryJob() { return false; },
627
+ async getWorkerCount() { return 0; },
628
+ };
629
+ }
@@ -1,6 +1,9 @@
1
1
  export { CacheInspector } from './cache_inspector.js';
2
2
  export type { CacheStats, CacheKeyEntry, CacheKeyListResult, CacheKeyDetail, } from './cache_inspector.js';
3
3
  export { QueueInspector } from './queue_inspector.js';
4
- export type { QueueOverview, QueueJobSummary, QueueJobDetail, QueueJobListResult, } from './queue_inspector.js';
4
+ export { AdonisQueueInspector } from './adonisjs_queue_inspector.js';
5
+ export type { QueueOverview, QueueJobSummary, QueueJobDetail, QueueJobListResult, JobStatus, ALL_STATUSES, QueueInspectorContract, } from './queue_inspector_contract.js';
5
6
  export { ConfigInspector } from './config_inspector.js';
6
7
  export type { SanitizedConfig, SanitizedEnvVars } from './config_inspector.js';
8
+ export { buildQueueStoreReader, mapJobRecordToSummary, resolveFromContainer, resolveFromAppImport } from './adonisjs_queue_store.js';
9
+ export type { QueueCounts, QueueStoreReader, QueueStoreReaderServices } from './adonisjs_queue_store.js';
@@ -1,3 +1,5 @@
1
1
  export { CacheInspector } from './cache_inspector.js';
2
2
  export { QueueInspector } from './queue_inspector.js';
3
+ export { AdonisQueueInspector } from './adonisjs_queue_inspector.js';
3
4
  export { ConfigInspector } from './config_inspector.js';
5
+ export { buildQueueStoreReader, mapJobRecordToSummary, resolveFromContainer, resolveFromAppImport } from './adonisjs_queue_store.js';