AgentStack
SKILL verified Apache-2.0 Self-run

Glide Mq Migrate Bullmq

skill-agent-sh-glidemq-glide-mq-migrate-bullmq · by agent-sh

>-

No reviews yet
0 installs
0 views
view→install

Install

$ agentstack add skill-agent-sh-glidemq-glide-mq-migrate-bullmq

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Glide Mq Migrate Bullmq? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

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

| Feature | BullMQ | glide-mq | |---------|--------|----------| | Connection config | { host, port } | { addresses: [{ host, port }] } | | TLS | tls: {} | useTLS: true | | Password | password: 'secret' | credentials: { password: 'secret' } | | Cluster mode | Implicit / natMap | clusterMode: true | | defaultJobOptions | On QueueOptions | Removed - wrap queue.add() with defaults | | queue.getJobs() | Accepts array of types | Single type per call | | queue.getJobCounts() | Variadic type list | Always returns all states | | settings.backoffStrategy | Single function | backoffStrategies named map on WorkerOptions | | worker.on('active') | Emits (job, prev) | Emits (job, jobId) | | job.waitUntilFinished() | (queueEvents, ttl) | (pollIntervalMs, timeoutMs) - no QueueEvents needed | | Sandboxed processor | useWorkerThreads: true | sandbox: { 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 | Optional | Required | | retries-exhausted event | Separate QueueEvents event | Check attemptsMade >= opts.attempts in 'failed' | | BullMQ Pro group.id | group: { id } (Pro license) | ordering: { key } (open source) | | Group concurrency | group.limit.max (Pro) | ordering: { key, concurrency: N } | | Group rate limit | group.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](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](#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)

| Feature | API | Description | |---------|-----|-------------| | Per-key ordering | ordering: { key } | Sequential execution per key across all workers | | Group concurrency | ordering: { key, concurrency: N } | Max N parallel jobs per key | | Group rate limit | ordering: { key, rateLimit: { max, duration } } | Per-key rate limiting | | Token bucket | ordering: { key, tokenBucket } + opts.cost | Weighted rate limiting per key | | Global rate limit | queue.setGlobalRateLimit({ max, duration }) | Queue-wide cap across all workers | | Dead letter queue | deadLetterQueue: { name, maxRetries } | Native DLQ on QueueOptions | | Job revocation | queue.revoke(jobId) + job.abortSignal | Cancel in-flight jobs cooperatively | | Transparent compression | compression: 'gzip' on QueueOptions | 98% reduction on 15 KB payloads | | AZ-affinity routing | readFrom: 'AZAffinity' | Pin reads to local AZ replicas | | IAM auth | credentials: { type: 'iam', ... } | ElastiCache / MemoryDB native auth | | In-memory test mode | TestQueue, TestWorker from glide-mq/testing | No Valkey needed for tests | | Broadcast | BroadcastWorker | Pub/sub fan-out to all workers | | Batch processing | batch: { size, timeout } on WorkerOptions | Multiple jobs per processor call | | DAG workflows | FlowProducer.addDAG(), dag() helper | Jobs with multiple parents | | Workflow helpers | chain(), group(), chord() | Higher-level orchestration | | Step jobs | job.moveToDelayed(ts, nextStep?) | Multi-step state machines | | addAndWait | queue.addAndWait(name, data, { waitTimeout }) | Request-reply pattern | | Pluggable serializers | { serialize, deserialize } on options | MessagePack, Protobuf, etc. | | Job TTL | opts.ttl | Auto-expire jobs after N ms | | repeatAfterComplete | upsertJobScheduler('name', { repeatAfterComplete: 5000 }) | No-overlap scheduling (ms delay after completion) | | LIFO mode | lifo: true | Last-in-first-out processing | | Job search | queue.searchJobs(opts) | Full-text search over job data | | excludeData | queue.getJobs(type, start, end, { excludeData: true }) | Lightweight listings | | globalConcurrency | On WorkerOptions | Set queue-wide cap at worker startup | | AI usage tracking | job.reportUsage({ model, tokens, costs, ... }) | Per-job LLM usage metadata | | Token streaming | job.stream({ token }) / queue.readStream(jobId) | Real-time LLM output via per-job streams | | Suspend/resume | job.suspend() / queue.signal(jobId, name, data) | Human-in-the-loop approval | | Flow budget | flow.add(tree, { budget: { maxTotalTokens } }) | Cap tokens/cost across a flow | | Fallback chains | opts.fallbacks: [{ model, provider }] | Ordered model/provider failover | | Dual-axis rate limiting | tokenLimiter: { maxTokens, duration } | RPM + TPM for LLM API compliance | | Flow usage aggregation | queue.getFlowUsage(parentJobId) | Aggregate tokens/cost across a flow | | Vector search | queue.createJobIndex() / queue.vectorSearch() | KNN similarity search over job hashes |

See [references/new-features.md](references/new-features.md) for detailed documentation.


Current gaps

| Missing feature | Workaround | |-----------------|------------| | QueueEvents 'waiting', 'active', 'delayed', 'drained', 'deduplicated' events | Use worker-level events or poll getJobCounts() | | failParentOnFailure in FlowJob | Implement manually in the worker's failed handler |


Performance comparison

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

| Concurrency | glide-mq | BullMQ | Delta | |:-----------:|----------:|--------:|:-----:| | c=1 | 2,479 j/s | 2,535 j/s | -2% | | c=5 | 10,754 j/s | 9,866 j/s | +9% | | c=10 | 18,218 j/s | 13,541 j/s | +35% | | c=15 | 19,583 j/s | 14,162 j/s | +38% | | c=20 | 19,408 j/s | 16,085 j/s | +21% | | c=50 | 19,768 j/s | 19,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

| Error | Cause | Fix | |-------|-------|-----| | TypeError: connection.host is not defined | Using BullMQ { host, port } format | Change to { addresses: [{ host, port }] } | | Cannot read properties of undefined (reading 'backoffStrategy') | Using settings.backoffStrategy | Move to backoffStrategies map on WorkerOptions | | defaultJobOptions is not a valid option | glide-mq removed defaultJobOptions | Wrap queue.add() with a helper that spreads defaults | | getJobs expects a string, got array | Passing array of types to getJobs() | Call getJobs() once per type, combine results | | QueueScheduler is not exported | glide-mq has no QueueScheduler | Remove it - promotion runs inside the Worker | | opts.repeat is not supported | glide-mq uses upsertJobScheduler | Replace opts.repeat with queue.upsertJobScheduler() | | waitUntilFinished expects number | API changed from (qe, ttl) to (pollMs, ttl) | Pass (500, 30000) instead of (queueEvents, 30000) | | Job stuck in active forever | Worker crashed without completing | Stall detection auto-recovers stream jobs. For LIFO/priority, reset: DEL glide:{queueName}:list-active | | retries-exhausted listener never fires | Event renamed | Listen to 'failed' and check attemptsMade >= opts.attempts | | FlowProducer.add throws on missing data | data is required in glide-mq FlowJob | Always pass data field (use {} if empty) | | Duplicate custom jobId returns null | Expected behavior | queue.add() returns null for duplicate IDs (silent skip) |

Full Documentation

  • Migration Guide
  • [New Features Reference](references/new-features.md)
  • [Connection Mapping Reference](references/connection-mapping.md)

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.