Migrate from Bee-Queue to glide-mq
When to Apply
Use this skill when:
- Replacing bee-queue with glide-mq in an existing project
- Converting Bee-Queue's chained job API to glide-mq's options API
- Updating connection configuration from ioredis to valkey-glide
- Upgrading from bee-queue due to Node.js compatibility or maintenance issues
Step-by-step guide for converting Bee-Queue projects to glide-mq. Bee-Queue uses a chained job builder pattern - this migration requires rewriting job creation and separating producer/consumer concerns.
Why Migrate
- Unmaintained - last release 2021, accumulating Node.js compatibility issues
- No cluster support - cannot scale beyond a single Redis instance
- No TLS - requires manual ioredis workarounds for encrypted connections
- No native TypeScript - community
@types/bee-queue only, often outdated
- No priority queues - workaround is multiple queues
- No workflows - no parent-child jobs, no DAGs, no repeatable/cron jobs
- No rate limiting, batch processing, or broadcast
- glide-mq provides all Bee-Queue features plus 35%+ higher throughput
Breaking Changes Summary
| Feature |
Bee-Queue |
glide-mq |
| Queue + Worker |
Single Queue class |
Separate Queue (producer) and Worker (consumer) |
| Job creation |
queue.createJob(data).save() (chained) |
queue.add(name, data, opts) (single call) |
| Job name |
Not used - no name parameter |
Required first argument to queue.add() |
| Job options |
Chained: .timeout(ms).retries(n) |
Options object: { attempts, backoff, delay } |
| Retries |
.retries(n) |
{ attempts: n } (different name!) |
| Processing |
queue.process(concurrency, handler) |
new Worker(name, handler, { concurrency }) |
| Connection |
{ host, port } or redis URL |
{ addresses: [{ host, port }] } |
| Progress |
job.reportProgress(anyJSON) |
job.updateProgress(number | object) (number 0-100 or object) |
| Per-job events |
job.on('succeeded', ...) |
QueueEvents class (centralized) |
| Stall detection |
Manual checkStalledJobs() |
Automatic on Worker |
| Batch save |
queue.saveAll(jobs) |
queue.addBulk(jobs) |
| Producer-only |
{ isWorker: false } |
Producer class or just Queue |
Queue Settings Mapping
| Bee-Queue Setting |
Default |
glide-mq Equivalent |
Notes |
redis |
{} |
connection: { addresses: [...] } |
Array of { host, port } objects |
isWorker |
true |
Use Producer or Queue class |
Separate classes replace flag |
getEvents |
true |
Use QueueEvents class |
Separate class for event subscription |
sendEvents |
true |
events: true on Worker |
Controls lifecycle event emission |
storeJobs |
true |
Always true |
glide-mq always stores jobs |
ensureScripts |
true |
Automatic |
Server Functions loaded automatically |
activateDelayedJobs |
false |
Automatic |
Server-side delayed job activation |
removeOnSuccess |
false |
{ removeOnComplete: true } |
Per-job option on queue.add() |
removeOnFailure |
false |
{ removeOnFail: true } |
Per-job option on queue.add() |
stallInterval |
5000 |
lockDuration on Worker |
Lock-based stall detection |
nearTermWindow |
20min |
N/A |
Valkey-native delayed processing |
delayedDebounce |
1000 |
N/A |
Server-side scheduling |
prefix |
'bq' |
prefix on Queue |
Default: 'glide' |
quitCommandClient |
true |
Automatic |
Handled by graceful shutdown |
redisScanCount |
100 |
N/A |
Different key strategy |
Queue Method Mapping
| Bee-Queue Method |
glide-mq Equivalent |
Notes |
queue.createJob(data) |
queue.add(name, data, opts) |
Name is required; returns Job not builder |
queue.process(n, handler) |
new Worker(name, handler, { concurrency: n }) |
Separate class |
queue.checkStalledJobs(interval) |
Automatic on Worker |
No manual call needed |
queue.checkHealth() |
queue.getJobCounts() |
Returns { waiting, active, completed, failed, delayed } |
queue.close() |
gracefulShutdown([...]) |
Or individual .close() calls |
queue.ready() |
worker.waitUntilReady() |
On Worker, not Queue |
queue.isRunning() |
worker.isRunning() |
On Worker |
queue.getJob(id) |
queue.getJob(id) |
Same API |
queue.getJobs(type, page) |
queue.getJobs(type, start, end) |
Range-based pagination |
queue.removeJob(id) |
(await queue.getJob(id)).remove() |
Via Job instance |
queue.saveAll(jobs) |
queue.addBulk(jobs) |
Different input format |
queue.destroy() |
queue.obliterate() |
Removes all queue data |
Event Mapping
| Bee-Queue Event |
Source |
glide-mq Equivalent |
Source |
queue.on('ready') |
Queue |
worker.waitUntilReady() |
Worker |
queue.on('error', err) |
Queue |
worker.on('error', err) |
Worker |
queue.on('succeeded', job, result) |
Queue (local) |
worker.on('completed', job) |
Worker |
queue.on('retrying', job, err) |
Queue (local) |
worker.on('failed', job, err) |
Worker (with retries remaining) |
queue.on('failed', job, err) |
Queue (local) |
worker.on('failed', job, err) |
Worker |
queue.on('stalled', jobId) |
Queue |
worker.on('stalled', jobId) |
Worker |
queue.on('job succeeded', id, result) |
Queue (PubSub) |
events.on('completed', { jobId }) |
QueueEvents |
queue.on('job failed', id, err) |
Queue (PubSub) |
events.on('failed', { jobId }) |
QueueEvents |
queue.on('job retrying', id, err) |
Queue (PubSub) |
No direct equivalent |
Use events.on('failed') + retry check |
queue.on('job progress', id, data) |
Queue (PubSub) |
events.on('progress', { jobId, data }) |
QueueEvents |
job.on('succeeded', result) |
Job |
events.on('completed', { jobId }) |
QueueEvents (filter by jobId) |
job.on('failed', err) |
Job |
events.on('failed', { jobId }) |
QueueEvents (filter by jobId) |
job.on('progress', data) |
Job |
events.on('progress', { jobId }) |
QueueEvents (filter by jobId) |
Per-job events (job.on(...)) do not exist in glide-mq. Use QueueEvents and filter by jobId, or use queue.addAndWait() for request-reply patterns.
Step-by-Step Conversion
1. Connection
// BEFORE (Bee-Queue)
const Queue = require('bee-queue');
const queue = new Queue('tasks', {
redis: { host: 'localhost', port: 6379 }
});
// AFTER (glide-mq)
import { Queue, Worker } from 'glide-mq';
const connection = { addresses: [{ host: 'localhost', port: 6379 }] };
const queue = new Queue('tasks', { connection });
2. Job Creation (Biggest Change)
Bee-Queue uses chained builder with no job name. glide-mq uses a single call with a required name.
// BEFORE (Bee-Queue) - chained builder, no name
const job = await queue.createJob({ email: 'user@example.com' })
.retries(3)
.backoff('exponential', 1000)
.delayUntil(Date.now() + 60000)
.setId('unique-123')
.save();
// AFTER (glide-mq) - options object, name required
await queue.add('send-email',
{ email: 'user@example.com' },
{
attempts: 3, // NOT "retries" - different name!
backoff: { type: 'exponential', delay: 1000 },
delay: 60000,
jobId: 'unique-123',
}
);
3. Worker
// BEFORE (Bee-Queue)
queue.process(10, async (job) => {
return { processed: true };
});
queue.on('succeeded', (job, result) => console.log('Done:', result));
// AFTER (glide-mq) - separate Worker class
const worker = new Worker('tasks', async (job) => {
return { processed: true };
}, { connection, concurrency: 10 });
worker.on('completed', (job) => console.log('Done:', job.returnValue));
4. Batch Save
// BEFORE (Bee-Queue)
const jobs = items.map(item => queue.createJob(item));
await queue.saveAll(jobs);
// AFTER (glide-mq) - each entry needs a name
await queue.addBulk(items.map(item => ({
name: 'process',
data: item
})));
5. Producer-Only
// BEFORE (Bee-Queue) - disable worker mode
const queue = new Queue('tasks', {
isWorker: false, getEvents: false, sendEvents: false,
redis: { host: 'localhost', port: 6379 }
});
// AFTER (glide-mq) - Producer class
import { Producer } from 'glide-mq';
const producer = new Producer('tasks', { connection });
await producer.add('job-name', data);
await producer.close();
6. Progress Reporting
// BEFORE (Bee-Queue) - arbitrary JSON
queue.process(async (job) => {
job.reportProgress({ percent: 50, message: 'halfway' });
return result;
});
// AFTER (glide-mq) - number (0-100) or object
const worker = new Worker('tasks', async (job) => {
await job.updateProgress(50);
await job.updateProgress({ page: 3, total: 10 }); // objects also supported
await job.log('halfway done'); // structured info goes to job.log()
return result;
}, { connection });
7. Stall Detection
// BEFORE (Bee-Queue) - manual setup required
const queue = new Queue('tasks', { stallInterval: 5000 });
queue.checkStalledJobs(5000); // must call manually!
// AFTER (glide-mq) - automatic on Worker
const worker = new Worker('tasks', processor, {
connection,
lockDuration: 30000,
stalledInterval: 30000,
maxStalledCount: 2
});
// Stall detection runs automatically - no manual call
8. Health Check
// BEFORE (Bee-Queue)
const health = await queue.checkHealth();
// { waiting, active, succeeded, failed, delayed, newestJob }
// AFTER (glide-mq)
const counts = await queue.getJobCounts();
// { waiting, active, completed, failed, delayed }
9. Web UI (Arena to Dashboard)
// BEFORE (Bee-Queue) - Arena
const Arena = require('bull-arena');
app.use('/', Arena({ Bee: require('bee-queue'), queues: [{ name: 'tasks' }] }));
// AFTER (glide-mq) - Dashboard
import { createDashboard } from '@glidemq/dashboard';
app.use('/dashboard', createDashboard([queue]));
What You Gain
Features Bee-Queue does not have that are available after migration:
| Feature |
glide-mq API |
| Priority queues |
{ priority: 0 } (lower = higher, 0 is highest) |
| FlowProducer |
Parent-child job trees and DAG workflows |
| Broadcast |
Fan-out with subscriber groups |
| Batch processing |
Process multiple jobs per worker call |
| Deduplication |
Simple, throttle, and debounce modes |
| Schedulers |
Cron patterns and interval repeatable jobs |
| Rate limiting |
limiter: { max: 100, duration: 60000 } on Worker |
| LIFO mode |
Process newest jobs first with { lifo: true } |
| Dead letter queue |
deadLetterQueue: { name: 'dlq' } on Queue |
| Serverless pool |
Connection caching for Lambda/Edge |
| HTTP proxy |
Cross-language queue access via REST |
| OpenTelemetry |
Automatic span emission |
| Testing utilities |
TestQueue/TestWorker without Valkey |
| Cluster support |
Hash-tagged keys, AZ-affinity routing |
| TLS / IAM auth |
useTLS: true, IAM credentials for ElastiCache |
| Native TypeScript |
Full generic type support throughout |
| AI usage tracking |
job.reportUsage({ model, tokens, costs, ... }) |
| Token streaming |
job.stream() / queue.readStream() for real-time LLM output |
| Suspend/resume |
job.suspend() / queue.signal() for human-in-the-loop |
| Flow budget |
flow.add(tree, { budget: { maxTotalTokens } }) |
| Fallback chains |
opts.fallbacks: [{ model, provider }] |
| Dual-axis rate limiting |
tokenLimiter for RPM + TPM compliance |
| Vector search |
queue.createJobIndex() / queue.vectorSearch() |
Migration Checklist
- [ ] Install glide-mq, uninstall bee-queue and @types/bee-queue
- [ ] Create connection config (addresses array format)
- [ ] Convert queue.createJob().save() to queue.add(name, data, opts)
- [ ] Add job names to every queue.add() call (Bee-Queue had none)
- [ ] Convert .retries(n) to { attempts: n } (different name!)
- [ ] Convert .backoff(strategy, delay) to { backoff: { type, delay } }
- [ ] Convert .delayUntil(date) to { delay: ms }
- [ ] Convert .setId(id) to { jobId: id }
- [ ] Convert queue.process() to new Worker()
- [ ] Convert queue.saveAll() to queue.addBulk()
- [ ] Separate producer queues (isWorker:false to Producer class)
- [ ] Convert job.reportProgress(json) to job.updateProgress(number | object)
- [ ] Remove manual checkStalledJobs() calls (automatic on Worker)
- [ ] Convert checkHealth() to getJobCounts()
- [ ] Update event listeners (queue.on to worker.on or QueueEvents)
- [ ] Convert per-job events (job.on) to QueueEvents
- [ ] Keep the project's existing module system (CommonJS or ESM)
- [ ] Run full test suite
- [ ] Confirm queue counts: await queue.getJobCounts()
- [ ] Confirm no jobs stuck in active state
- [ ] Smoke-test QueueEvents or SSE listeners if the app exposes them
- [ ] Confirm workers, queues, and connections close cleanly
Troubleshooting
| Error |
Cause |
Fix |
queue.createJob is not a function |
API changed |
Use queue.add(name, data, opts) |
queue.process is not a function |
Separated producer/consumer |
Use new Worker(name, handler, opts) |
Cannot use require() |
Module system mismatch |
Keep the project's existing module system; glide-mq supports CommonJS and ESM |
job.reportProgress is not a function |
API renamed |
Use job.updateProgress(number) |
Cannot find module 'bee-queue' |
Leftover import |
grep -r "bee-queue" src/ to find remaining |
Missing job name |
Bee-Queue had no name |
Add a name as first arg to queue.add() |
retries option not recognized |
Different name |
Use attempts not retries |
| No stall detection |
Bee-Queue needed manual start |
glide-mq runs it automatically on Worker |
| Progress type changed |
Bee-Queue accepted any JSON |
Use job.updateProgress(number | object) - numbers (0-100) or objects supported |
| Per-job events not working |
No per-job events in glide-mq |
Use QueueEvents class and filter by jobId |
Quick Start Commands
npm uninstall bee-queue @types/bee-queue
npm install glide-mq
References
| Document |
Content |
| references/api-mapping.md |
Complete method-by-method API mapping |
| references/new-features.md |
Features available after migration |
1---2name: glide-mq-migrate-bee3description: Migrates Node.js applications from Bee-Queue to glide-mq. Covers the chained builder-to-options API conversion, Queue/Worker separation, and event mapping. Use when converting bee-queue projects to glide-mq, replacing bee-queue with glide-mq, or planning a bee-queue migration. Triggers on "bee-queue to glide-mq", "replace bee-queue with glide-mq", "migrate from bee-queue", "beequeue migration glide-mq".4license: Apache-2.05---67# Migrate from Bee-Queue to glide-mq89## When to Apply1011Use this skill when:12- Replacing bee-queue with glide-mq in an existing project13- Converting Bee-Queue's chained job API to glide-mq's options API14- Updating connection configuration from ioredis to valkey-glide15- Upgrading from bee-queue due to Node.js compatibility or maintenance issues1617Step-by-step guide for converting Bee-Queue projects to glide-mq. Bee-Queue uses a chained job builder pattern - this migration requires rewriting job creation and separating producer/consumer concerns.1819## Why Migrate2021- **Unmaintained** - last release 2021, accumulating Node.js compatibility issues22- **No cluster support** - cannot scale beyond a single Redis instance23- **No TLS** - requires manual ioredis workarounds for encrypted connections24- **No native TypeScript** - community `@types/bee-queue` only, often outdated25- **No priority queues** - workaround is multiple queues26- **No workflows** - no parent-child jobs, no DAGs, no repeatable/cron jobs27- **No rate limiting, batch processing, or broadcast**28- glide-mq provides all Bee-Queue features plus 35%+ higher throughput2930## Breaking Changes Summary3132| Feature | Bee-Queue | glide-mq |33|---------|-----------|----------|34| Queue + Worker | Single `Queue` class | Separate `Queue` (producer) and `Worker` (consumer) |35| Job creation | `queue.createJob(data).save()` (chained) | `queue.add(name, data, opts)` (single call) |36| Job name | Not used - no name parameter | **Required** first argument to `queue.add()` |37| Job options | Chained: `.timeout(ms).retries(n)` | Options object: `{ attempts, backoff, delay }` |38| Retries | `.retries(n)` | `{ attempts: n }` (different name!) |39| Processing | `queue.process(concurrency, handler)` | `new Worker(name, handler, { concurrency })` |40| Connection | `{ host, port }` or redis URL | `{ addresses: [{ host, port }] }` |41| Progress | `job.reportProgress(anyJSON)` | `job.updateProgress(number \| object)` (number 0-100 or object) |42| Per-job events | `job.on('succeeded', ...)` | `QueueEvents` class (centralized) |43| Stall detection | Manual `checkStalledJobs()` | Automatic on Worker |44| Batch save | `queue.saveAll(jobs)` | `queue.addBulk(jobs)` |45| Producer-only | `{ isWorker: false }` | `Producer` class or just `Queue` |4647## Queue Settings Mapping4849| Bee-Queue Setting | Default | glide-mq Equivalent | Notes |50|-------------------|---------|---------------------|-------|51| `redis` | `{}` | `connection: { addresses: [...] }` | Array of `{ host, port }` objects |52| `isWorker` | `true` | Use `Producer` or `Queue` class | Separate classes replace flag |53| `getEvents` | `true` | Use `QueueEvents` class | Separate class for event subscription |54| `sendEvents` | `true` | `events: true` on Worker | Controls lifecycle event emission |55| `storeJobs` | `true` | Always true | glide-mq always stores jobs |56| `ensureScripts` | `true` | Automatic | Server Functions loaded automatically |57| `activateDelayedJobs` | `false` | Automatic | Server-side delayed job activation |58| `removeOnSuccess` | `false` | `{ removeOnComplete: true }` | Per-job option on `queue.add()` |59| `removeOnFailure` | `false` | `{ removeOnFail: true }` | Per-job option on `queue.add()` |60| `stallInterval` | `5000` | `lockDuration` on Worker | Lock-based stall detection |61| `nearTermWindow` | `20min` | N/A | Valkey-native delayed processing |62| `delayedDebounce` | `1000` | N/A | Server-side scheduling |63| `prefix` | `'bq'` | `prefix` on Queue | Default: `'glide'` |64| `quitCommandClient` | `true` | Automatic | Handled by graceful shutdown |65| `redisScanCount` | `100` | N/A | Different key strategy |6667## Queue Method Mapping6869| Bee-Queue Method | glide-mq Equivalent | Notes |70|------------------|---------------------|-------|71| `queue.createJob(data)` | `queue.add(name, data, opts)` | Name is required; returns Job not builder |72| `queue.process(n, handler)` | `new Worker(name, handler, { concurrency: n })` | Separate class |73| `queue.checkStalledJobs(interval)` | Automatic on Worker | No manual call needed |74| `queue.checkHealth()` | `queue.getJobCounts()` | Returns `{ waiting, active, completed, failed, delayed }` |75| `queue.close()` | `gracefulShutdown([...])` | Or individual `.close()` calls |76| `queue.ready()` | `worker.waitUntilReady()` | On Worker, not Queue |77| `queue.isRunning()` | `worker.isRunning()` | On Worker |78| `queue.getJob(id)` | `queue.getJob(id)` | Same API |79| `queue.getJobs(type, page)` | `queue.getJobs(type, start, end)` | Range-based pagination |80| `queue.removeJob(id)` | `(await queue.getJob(id)).remove()` | Via Job instance |81| `queue.saveAll(jobs)` | `queue.addBulk(jobs)` | Different input format |82| `queue.destroy()` | `queue.obliterate()` | Removes all queue data |8384## Event Mapping8586| Bee-Queue Event | Source | glide-mq Equivalent | Source |87|-----------------|--------|---------------------|--------|88| `queue.on('ready')` | Queue | `worker.waitUntilReady()` | Worker |89| `queue.on('error', err)` | Queue | `worker.on('error', err)` | Worker |90| `queue.on('succeeded', job, result)` | Queue (local) | `worker.on('completed', job)` | Worker |91| `queue.on('retrying', job, err)` | Queue (local) | `worker.on('failed', job, err)` | Worker (with retries remaining) |92| `queue.on('failed', job, err)` | Queue (local) | `worker.on('failed', job, err)` | Worker |93| `queue.on('stalled', jobId)` | Queue | `worker.on('stalled', jobId)` | Worker |94| `queue.on('job succeeded', id, result)` | Queue (PubSub) | `events.on('completed', { jobId })` | QueueEvents |95| `queue.on('job failed', id, err)` | Queue (PubSub) | `events.on('failed', { jobId })` | QueueEvents |96| `queue.on('job retrying', id, err)` | Queue (PubSub) | No direct equivalent | Use `events.on('failed')` + retry check |97| `queue.on('job progress', id, data)` | Queue (PubSub) | `events.on('progress', { jobId, data })` | QueueEvents |98| `job.on('succeeded', result)` | Job | `events.on('completed', { jobId })` | QueueEvents (filter by jobId) |99| `job.on('failed', err)` | Job | `events.on('failed', { jobId })` | QueueEvents (filter by jobId) |100| `job.on('progress', data)` | Job | `events.on('progress', { jobId })` | QueueEvents (filter by jobId) |101102Per-job events (`job.on(...)`) do not exist in glide-mq. Use `QueueEvents` and filter by `jobId`, or use `queue.addAndWait()` for request-reply patterns.103104## Step-by-Step Conversion105106### 1. Connection107108```typescript109// BEFORE (Bee-Queue)110const Queue = require('bee-queue');111const queue = new Queue('tasks', {112 redis: { host: 'localhost', port: 6379 }113});114115// AFTER (glide-mq)116import { Queue, Worker } from 'glide-mq';117const connection = { addresses: [{ host: 'localhost', port: 6379 }] };118const queue = new Queue('tasks', { connection });119```120121### 2. Job Creation (Biggest Change)122123Bee-Queue uses chained builder with no job name. glide-mq uses a single call with a required name.124125```typescript126// BEFORE (Bee-Queue) - chained builder, no name127const job = await queue.createJob({ email: 'user@example.com' })128 .retries(3)129 .backoff('exponential', 1000)130 .delayUntil(Date.now() + 60000)131 .setId('unique-123')132 .save();133134// AFTER (glide-mq) - options object, name required135await queue.add('send-email',136 { email: 'user@example.com' },137 {138 attempts: 3, // NOT "retries" - different name!139 backoff: { type: 'exponential', delay: 1000 },140 delay: 60000,141 jobId: 'unique-123',142 }143);144```145146### 3. Worker147148```typescript149// BEFORE (Bee-Queue)150queue.process(10, async (job) => {151 return { processed: true };152});153queue.on('succeeded', (job, result) => console.log('Done:', result));154155// AFTER (glide-mq) - separate Worker class156const worker = new Worker('tasks', async (job) => {157 return { processed: true };158}, { connection, concurrency: 10 });159worker.on('completed', (job) => console.log('Done:', job.returnValue));160```161162### 4. Batch Save163164```typescript165// BEFORE (Bee-Queue)166const jobs = items.map(item => queue.createJob(item));167await queue.saveAll(jobs);168169// AFTER (glide-mq) - each entry needs a name170await queue.addBulk(items.map(item => ({171 name: 'process',172 data: item173})));174```175176### 5. Producer-Only177178```typescript179// BEFORE (Bee-Queue) - disable worker mode180const queue = new Queue('tasks', {181 isWorker: false, getEvents: false, sendEvents: false,182 redis: { host: 'localhost', port: 6379 }183});184185// AFTER (glide-mq) - Producer class186import { Producer } from 'glide-mq';187const producer = new Producer('tasks', { connection });188await producer.add('job-name', data);189await producer.close();190```191192### 6. Progress Reporting193194```typescript195// BEFORE (Bee-Queue) - arbitrary JSON196queue.process(async (job) => {197 job.reportProgress({ percent: 50, message: 'halfway' });198 return result;199});200201// AFTER (glide-mq) - number (0-100) or object202const worker = new Worker('tasks', async (job) => {203 await job.updateProgress(50);204 await job.updateProgress({ page: 3, total: 10 }); // objects also supported205 await job.log('halfway done'); // structured info goes to job.log()206 return result;207}, { connection });208```209210### 7. Stall Detection211212```typescript213// BEFORE (Bee-Queue) - manual setup required214const queue = new Queue('tasks', { stallInterval: 5000 });215queue.checkStalledJobs(5000); // must call manually!216217// AFTER (glide-mq) - automatic on Worker218const worker = new Worker('tasks', processor, {219 connection,220 lockDuration: 30000,221 stalledInterval: 30000,222 maxStalledCount: 2223});224// Stall detection runs automatically - no manual call225```226227### 8. Health Check228229```typescript230// BEFORE (Bee-Queue)231const health = await queue.checkHealth();232// { waiting, active, succeeded, failed, delayed, newestJob }233234// AFTER (glide-mq)235const counts = await queue.getJobCounts();236// { waiting, active, completed, failed, delayed }237```238239### 9. Web UI (Arena to Dashboard)240241```typescript242// BEFORE (Bee-Queue) - Arena243const Arena = require('bull-arena');244app.use('/', Arena({ Bee: require('bee-queue'), queues: [{ name: 'tasks' }] }));245246// AFTER (glide-mq) - Dashboard247import { createDashboard } from '@glidemq/dashboard';248app.use('/dashboard', createDashboard([queue]));249```250251## What You Gain252253Features Bee-Queue does not have that are available after migration:254255| Feature | glide-mq API |256|---------|-------------|257| Priority queues | `{ priority: 0 }` (lower = higher, 0 is highest) |258| FlowProducer | Parent-child job trees and DAG workflows |259| Broadcast | Fan-out with subscriber groups |260| Batch processing | Process multiple jobs per worker call |261| Deduplication | Simple, throttle, and debounce modes |262| Schedulers | Cron patterns and interval repeatable jobs |263| Rate limiting | `limiter: { max: 100, duration: 60000 }` on Worker |264| LIFO mode | Process newest jobs first with `{ lifo: true }` |265| Dead letter queue | `deadLetterQueue: { name: 'dlq' }` on Queue |266| Serverless pool | Connection caching for Lambda/Edge |267| HTTP proxy | Cross-language queue access via REST |268| OpenTelemetry | Automatic span emission |269| Testing utilities | `TestQueue`/`TestWorker` without Valkey |270| Cluster support | Hash-tagged keys, AZ-affinity routing |271| TLS / IAM auth | `useTLS: true`, IAM credentials for ElastiCache |272| Native TypeScript | Full generic type support throughout |273| **AI usage tracking** | `job.reportUsage({ model, tokens, costs, ... })` |274| **Token streaming** | `job.stream()` / `queue.readStream()` for real-time LLM output |275| **Suspend/resume** | `job.suspend()` / `queue.signal()` for human-in-the-loop |276| **Flow budget** | `flow.add(tree, { budget: { maxTotalTokens } })` |277| **Fallback chains** | `opts.fallbacks: [{ model, provider }]` |278| **Dual-axis rate limiting** | `tokenLimiter` for RPM + TPM compliance |279| **Vector search** | `queue.createJobIndex()` / `queue.vectorSearch()` |280281## Migration Checklist282283```284- [ ] Install glide-mq, uninstall bee-queue and @types/bee-queue285- [ ] Create connection config (addresses array format)286- [ ] Convert queue.createJob().save() to queue.add(name, data, opts)287- [ ] Add job names to every queue.add() call (Bee-Queue had none)288- [ ] Convert .retries(n) to { attempts: n } (different name!)289- [ ] Convert .backoff(strategy, delay) to { backoff: { type, delay } }290- [ ] Convert .delayUntil(date) to { delay: ms }291- [ ] Convert .setId(id) to { jobId: id }292- [ ] Convert queue.process() to new Worker()293- [ ] Convert queue.saveAll() to queue.addBulk()294- [ ] Separate producer queues (isWorker:false to Producer class)295- [ ] Convert job.reportProgress(json) to job.updateProgress(number | object)296- [ ] Remove manual checkStalledJobs() calls (automatic on Worker)297- [ ] Convert checkHealth() to getJobCounts()298- [ ] Update event listeners (queue.on to worker.on or QueueEvents)299- [ ] Convert per-job events (job.on) to QueueEvents300- [ ] Keep the project's existing module system (CommonJS or ESM)301- [ ] Run full test suite302- [ ] Confirm queue counts: await queue.getJobCounts()303- [ ] Confirm no jobs stuck in active state304- [ ] Smoke-test QueueEvents or SSE listeners if the app exposes them305- [ ] Confirm workers, queues, and connections close cleanly306```307308## Troubleshooting309310| Error | Cause | Fix |311|-------|-------|-----|312| `queue.createJob is not a function` | API changed | Use `queue.add(name, data, opts)` |313| `queue.process is not a function` | Separated producer/consumer | Use `new Worker(name, handler, opts)` |314| `Cannot use require()` | Module system mismatch | Keep the project's existing module system; glide-mq supports CommonJS and ESM |315| `job.reportProgress is not a function` | API renamed | Use `job.updateProgress(number)` |316| `Cannot find module 'bee-queue'` | Leftover import | `grep -r "bee-queue" src/` to find remaining |317| `Missing job name` | Bee-Queue had no name | Add a name as first arg to `queue.add()` |318| `retries option not recognized` | Different name | Use `attempts` not `retries` |319| No stall detection | Bee-Queue needed manual start | glide-mq runs it automatically on Worker |320| Progress type changed | Bee-Queue accepted any JSON | Use `job.updateProgress(number \| object)` - numbers (0-100) or objects supported |321| Per-job events not working | No per-job events in glide-mq | Use `QueueEvents` class and filter by `jobId` |322323## Quick Start Commands324325```bash326npm uninstall bee-queue @types/bee-queue327npm install glide-mq328```329330## References331332| Document | Content |333|----------|---------|334| [references/api-mapping.md](references/api-mapping.md) | Complete method-by-method API mapping |335| [references/new-features.md](references/new-features.md) | Features available after migration |