Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
avifenesh avatar

Glide Mq Migrate Bullmq

  • 4 installs
  • 93 repo stars
  • Updated August 4, 2026
  • avifenesh/glide-mq

glide-mq-migrate-bullmq is a Claude Code skill that migrates Node.js applications from BullMQ to glide-mq.

About

This skill guides migrating a Node.js application from BullMQ to glide-mq. Because the glide-mq API is intentionally similar, most changes are connection format and imports, so Queue.add, Worker, FlowProducer and QueueEvents keep nearly identical usage. It documents breaking changes such as the addresses-array connection format, TLS and password config, cluster mode, scheduler removal, and the move from opts.repeat to upsertJobScheduler. A developer uses it when replacing BullMQ in an existing project.

  • Step-by-step BullMQ to glide-mq conversion; API is intentionally similar, mostly connection changes
  • Breaking-changes table: connection format, TLS, password, cluster mode, scheduler removal
  • Maps repeatable jobs from opts.repeat to upsertJobScheduler and BullMQ Pro groups to open-source ordering

Glide Mq Migrate Bullmq by the numbers

  • 4 all-time installs (skills.sh)
  • Ranked #3,711 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
At a glance

glide-mq-migrate-bullmq capabilities & compatibility

Capabilities
refactoring · api development
Works with
redis
Use cases
refactoring · api development
Pricing
Free
From the docs

What glide-mq-migrate-bullmq says it does

The glide-mq API is intentionally similar to BullMQ. Most changes are connection format and imports.
SKILL.md
**Connection config** | `{ host, port }` | `{ addresses: [{ host, port }] }`
SKILL.md
npx skills add https://github.com/avifenesh/glide-mq --skill glide-mq-migrate-bullmq

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs4
repo stars93
Last updatedAugust 4, 2026
Repositoryavifenesh/glide-mq

What it does

Migrate a Node.js project from BullMQ to glide-mq, mostly changing connection format and imports.

Who is it for?

Replacing BullMQ with glide-mq in an existing Node.js project

Skip if: Greenfield queue setups with no existing BullMQ code

When should I use this skill?

When converting BullMQ queues and workers to glide-mq or comparing BullMQ vs glide-mq APIs

What you get

BullMQ Queue/Worker/FlowProducer code running on glide-mq with the connection format and breaking changes handled

By the numbers

  • single install swap: npm remove bullmq && npm install glide-mq
  • breaking-changes table with 15+ mapped differences

Files

SKILL.mdMarkdownGitHub ↗

Migrate from BullMQ to glide-mq

The glide-mq API is intentionally similar to BullMQ. Most changes are connection format and imports.

When to Apply

Use this skill when:

  • Replacing BullMQ with glide-mq in an existing project
  • Converting BullMQ Queue/Worker/FlowProducer code
  • Updating connection configuration from ioredis to valkey-glide format
  • Comparing API differences between BullMQ and glide-mq

Prerequisites

  • Node.js 20+
  • Valkey 7.0+ or Redis 7.0+ (both supported)
  • TypeScript 5+ recommended

Install

npm remove bullmq
npm install glide-mq
// Before
import { Queue, Worker, Job, QueueEvents, FlowProducer } from 'bullmq';

// After
import { Queue, Worker, Job, QueueEvents, FlowProducer } from 'glide-mq';

---

Breaking changes

FeatureBullMQglide-mq
Connection config{ host, port }{ addresses: [{ host, port }] }
TLStls: {}useTLS: true
Passwordpassword: 'secret'credentials: { password: 'secret' }
Cluster modeImplicit / natMapclusterMode: true
`defaultJobOptions`On QueueOptionsRemoved - wrap queue.add() with defaults
`queue.getJobs()`Accepts array of typesSingle type per call
`queue.getJobCounts()`Variadic type listAlways returns all states
`settings.backoffStrategy`Single functionbackoffStrategies named map on WorkerOptions
`worker.on('active')`Emits (job, prev)Emits (job, jobId)
`job.waitUntilFinished()`(queueEvents, ttl)(pollIntervalMs, timeoutMs) - no QueueEvents needed
Sandboxed processoruseWorkerThreads: truesandbox: { useWorkerThreads: true }
`QueueScheduler`Required in v1, optional in v2+Does not exist - promotion runs inside Worker
`opts.repeat`On queue.add()Removed - use queue.upsertJobScheduler()
FlowJob `data`OptionalRequired
`retries-exhausted` eventSeparate QueueEvents eventCheck attemptsMade >= opts.attempts in 'failed'
BullMQ Pro `group.id`group: { id } (Pro license)ordering: { key } (open source)
Group concurrencygroup.limit.max (Pro)ordering: { key, concurrency: N }
Group rate limitgroup.limit (Pro)ordering: { key, rateLimit: { max, duration } }

---

Step-by-step conversion

1. Connection config (the biggest change)

// BEFORE (BullMQ)
const connection = { host: 'localhost', port: 6379 };
// AFTER (glide-mq)
const connection = { addresses: [{ host: 'localhost', port: 6379 }] };

For TLS + password + cluster, see references/connection-mapping.md.

2. Queue.add - identical API

// BEFORE
const queue = new Queue('tasks', { connection });
await queue.add('send-email', { to: 'user@example.com' });
// AFTER - only the connection changes
const queue = new Queue('tasks', { connection });
await queue.add('send-email', { to: 'user@example.com' });

3. Worker - identical API, different connection

// BEFORE
const worker = new Worker('tasks', async (job) => {
  await sendEmail(job.data.to);
}, { connection: { host: 'localhost', port: 6379 }, concurrency: 10 });
// AFTER
const worker = new Worker('tasks', async (job) => {
  await sendEmail(job.data.to);
}, { connection: { addresses: [{ host: 'localhost', port: 6379 }] }, concurrency: 10 });

4. FlowProducer - identical API

// Both - same usage, only connection format differs
const flow = new FlowProducer({ connection });
await flow.add({
  name: 'parent',
  queueName: 'tasks',
  data: { step: 'final' },       // NOTE: data is required in glide-mq
  children: [
    { name: 'child-1', queueName: 'tasks', data: { step: '1' } },
    { name: 'child-2', queueName: 'tasks', data: { step: '2' } },
  ],
});

5. QueueEvents - identical API

// Both - same, only connection format differs
const qe = new QueueEvents('tasks', { connection });
qe.on('completed', ({ jobId }) => console.log(jobId, 'done'));
qe.on('failed', ({ jobId, failedReason }) => console.error(jobId, failedReason));

Note: some BullMQ events are not yet emitted. See Current gaps.

6. Graceful shutdown

// BullMQ
await worker.close();
await queue.close();
// glide-mq - identical
await worker.close();
await queue.close();

7. UnrecoverableError - identical

// Both
import { UnrecoverableError } from 'glide-mq'; // was 'bullmq'

throw new UnrecoverableError('permanent failure');

8. Scheduling (repeatable jobs)

// BEFORE - opts.repeat (deprecated in BullMQ v5)
await queue.add('report', data, {
  repeat: { pattern: '0 9 * * *', tz: 'America/New_York' },
});
// AFTER - upsertJobScheduler
await queue.upsertJobScheduler(
  'report',
  { pattern: '0 9 * * *', tz: 'America/New_York' },
  { name: 'report', data: { v: 1 } },
);

9. Custom backoff strategies

// BEFORE
const worker = new Worker('q', processor, {
  connection,
  settings: {
    backoffStrategy: (attemptsMade, type, delay, err) => {
      if (type === 'jitter') return delay + Math.random() * delay;
      return delay * attemptsMade;
    },
  },
});
// AFTER
const worker = new Worker('q', processor, {
  connection,
  backoffStrategies: {
    jitter: (attemptsMade, err) => 1000 + Math.random() * 1000,
    linear: (attemptsMade, err) => 1000 * attemptsMade,
  },
});

10. defaultJobOptions removal

// BEFORE
const queue = new Queue('tasks', {
  connection,
  defaultJobOptions: { attempts: 3, backoff: { type: 'exponential', delay: 1000 } },
});
// AFTER - wrap add() with your defaults
const DEFAULTS = { attempts: 3, backoff: { type: 'exponential', delay: 1000 } } as const;
const add = (name: string, data: unknown, opts?: JobOptions) =>
  queue.add(name, data, { ...DEFAULTS, ...opts });

11. getJobs with multiple types

// BEFORE
const jobs = await queue.getJobs(['waiting', 'active'], 0, 99);
// AFTER
const [waiting, active] = await Promise.all([
  queue.getJobs('waiting', 0, 99),
  queue.getJobs('active', 0, 99),
]);
const jobs = [...waiting, ...active];

12. job.waitUntilFinished

// BEFORE
const qe = new QueueEvents('tasks', { connection });
const result = await job.waitUntilFinished(qe, 30000);
// AFTER - no QueueEvents needed
const result = await job.waitUntilFinished(500, 30000);
// args: pollIntervalMs (default 500), timeoutMs (default 30000)

13. BullMQ Pro groups to ordering keys

// BEFORE (BullMQ Pro)
await queue.add('job', data, {
  group: { id: 'tenant-123', limit: { max: 2, duration: 0 } },
});
// AFTER (glide-mq, open source)
await queue.add('job', data, {
  ordering: { key: 'tenant-123', concurrency: 2 },
});

---

What's new in glide-mq (not in BullMQ)

FeatureAPIDescription
Per-key orderingordering: { key }Sequential execution per key across all workers
Group concurrencyordering: { key, concurrency: N }Max N parallel jobs per key
Group rate limitordering: { key, rateLimit: { max, duration } }Per-key rate limiting
Token bucketordering: { key, tokenBucket } + opts.costWeighted rate limiting per key
Global rate limitqueue.setGlobalRateLimit({ max, duration })Queue-wide cap across all workers
Dead letter queuedeadLetterQueue: { name, maxRetries }Native DLQ on QueueOptions
Job revocationqueue.revoke(jobId) + job.abortSignalCancel in-flight jobs cooperatively
Transparent compressioncompression: 'gzip' on QueueOptions98% reduction on 15 KB payloads
AZ-affinity routingreadFrom: 'AZAffinity'Pin reads to local AZ replicas
IAM authcredentials: { type: 'iam', ... }ElastiCache / MemoryDB native auth
In-memory test modeTestQueue, TestWorker from glide-mq/testingNo Valkey needed for tests
BroadcastBroadcastWorkerPub/sub fan-out to all workers
Batch processingbatch: { size, timeout } on WorkerOptionsMultiple jobs per processor call
DAG workflowsFlowProducer.addDAG(), dag() helperJobs with multiple parents
Workflow helperschain(), group(), chord()Higher-level orchestration
Step jobsjob.moveToDelayed(ts, nextStep?)Multi-step state machines
addAndWaitqueue.addAndWait(name, data, { waitTimeout })Request-reply pattern
Pluggable serializers{ serialize, deserialize } on optionsMessagePack, Protobuf, etc.
Job TTLopts.ttlAuto-expire jobs after N ms
repeatAfterCompleteupsertJobScheduler('name', { repeatAfterComplete: 5000 })No-overlap scheduling (ms delay after completion)
LIFO modelifo: trueLast-in-first-out processing
Job searchqueue.searchJobs(opts)Full-text search over job data
excludeDataqueue.getJobs(type, start, end, { excludeData: true })Lightweight listings
globalConcurrencyOn WorkerOptionsSet queue-wide cap at worker startup
AI usage trackingjob.reportUsage({ model, tokens, costs, ... })Per-job LLM usage metadata
Token streamingjob.stream({ token }) / queue.readStream(jobId)Real-time LLM output via per-job streams
Suspend/resumejob.suspend() / queue.signal(jobId, name, data)Human-in-the-loop approval
Flow budgetflow.add(tree, { budget: { maxTotalTokens } })Cap tokens/cost across a flow
Fallback chainsopts.fallbacks: [{ model, provider }]Ordered model/provider failover
Dual-axis rate limitingtokenLimiter: { maxTokens, duration }RPM + TPM for LLM API compliance
Flow usage aggregationqueue.getFlowUsage(parentJobId)Aggregate tokens/cost across a flow
Vector searchqueue.createJobIndex() / queue.vectorSearch()KNN similarity search over job hashes

See references/new-features.md for detailed documentation.

---

Current gaps

Missing featureWorkaround
QueueEvents 'waiting', 'active', 'delayed', 'drained', 'deduplicated' eventsUse worker-level events or poll getJobCounts()
failParentOnFailure in FlowJobImplement manually in the worker's failed handler

---

Performance comparison

AWS ElastiCache Valkey 8.2 (r7g.large), TLS enabled, same-region EC2 client.

Concurrencyglide-mqBullMQDelta
c=12,479 j/s2,535 j/s-2%
c=510,754 j/s9,866 j/s+9%
c=1018,218 j/s13,541 j/s+35%
c=1519,583 j/s14,162 j/s+38%
c=2019,408 j/s16,085 j/s+21%
c=5019,768 j/s19,159 j/s+3%

Most production deployments run c=5 to c=20, where glide-mq's 1-RTT architecture pays off the most.

---

Migration checklist

- [ ] Replace `bullmq` with `glide-mq` in package.json
- [ ] Update all imports from 'bullmq' to 'glide-mq'
- [ ] Convert connection configs: { host, port } -> { addresses: [{ host, port }] }
- [ ] Convert TLS: tls: {} -> useTLS: true
- [ ] Convert password: password -> credentials: { password }
- [ ] Replace opts.repeat with queue.upsertJobScheduler()
- [ ] Replace settings.backoffStrategy with backoffStrategies map
- [ ] Remove QueueScheduler instantiation (not needed)
- [ ] Remove defaultJobOptions from QueueOptions; apply per job or via wrapper
- [ ] Replace queue.getJobs([...types]) with per-type calls
- [ ] Update worker.on('active') handlers: (job, jobId) not (job, prev)
- [ ] Replace job.waitUntilFinished(queueEvents, ttl) with (pollMs, timeoutMs)
- [ ] Check QueueEvents listeners for removed events (waiting, active, delayed, drained)
- [ ] Replace group.id (BullMQ Pro) with ordering.key
- [ ] Run test suite: npm test
- [ ] 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

ErrorCauseFix
TypeError: connection.host is not definedUsing BullMQ { host, port } formatChange to { addresses: [{ host, port }] }
Cannot read properties of undefined (reading 'backoffStrategy')Using settings.backoffStrategyMove to backoffStrategies map on WorkerOptions
defaultJobOptions is not a valid optionglide-mq removed defaultJobOptionsWrap queue.add() with a helper that spreads defaults
getJobs expects a string, got arrayPassing array of types to getJobs()Call getJobs() once per type, combine results
QueueScheduler is not exportedglide-mq has no QueueSchedulerRemove it - promotion runs inside the Worker
opts.repeat is not supportedglide-mq uses upsertJobSchedulerReplace opts.repeat with queue.upsertJobScheduler()
waitUntilFinished expects numberAPI changed from (qe, ttl) to (pollMs, ttl)Pass (500, 30000) instead of (queueEvents, 30000)
Job stuck in active foreverWorker crashed without completingStall detection auto-recovers stream jobs. For LIFO/priority, reset: DEL glide:{queueName}:list-active
retries-exhausted listener never firesEvent renamedListen to 'failed' and check attemptsMade >= opts.attempts
FlowProducer.add throws on missing datadata is required in glide-mq FlowJobAlways pass data field (use {} if empty)
Duplicate custom jobId returns nullExpected behaviorqueue.add() returns null for duplicate IDs (silent skip)

Full Documentation

Related skills

FAQ

How different is glide-mq from BullMQ?

The API is intentionally similar; Queue.add, Worker, FlowProducer and QueueEvents are nearly identical, and most changes are the connection format and imports.

What replaces BullMQ's repeat option?

opts.repeat is removed; repeatable jobs use queue.upsertJobScheduler instead, and QueueScheduler no longer exists because promotion runs inside the Worker.

Backend & APIsbackendintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.