{"_id":"@alegrossi2001/bullshoot","name":"@alegrossi2001/bullshoot","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@alegrossi2001/bullshoot","version":"0.1.0","description":"Type-safe BullMQ + Redis setup in minutes. Define jobs and workers in your own code — never touch the library.","keywords":["bullmq","redis","queue","jobs","workers","typescript","zod","type-safe"],"author":{"name":"alegrossi2001"},"license":"MIT","homepage":"https://github.com/alegrossi2001/bullshoot#readme","repository":{"type":"git","url":"git+https://github.com/alegrossi2001/bullshoot.git"},"bugs":{"url":"https://github.com/alegrossi2001/bullshoot/issues"},"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./dashboard":{"types":"./dist/dashboard/index.d.ts","import":"./dist/dashboard/index.js","require":"./dist/dashboard/index.cjs"}},"publishConfig":{"access":"public"},"scripts":{"build":"tsup","dev":"tsup --watch","check-types":"tsc --noEmit","check-types:examples":"tsc --noEmit -p tsconfig.examples.json","prepublishOnly":"npm run build"},"dependencies":{"bullmq":"^5.72.1"},"peerDependencies":{"zod":"^3.23.0 || ^4.0.0"},"optionalDependencies":{"@bull-board/api":"^7.0.0","@bull-board/express":"^7.0.0"},"peerDependenciesMeta":{"zod":{"optional":false}},"devDependencies":{"@bull-board/api":"^7.0.0","@bull-board/express":"^7.0.0","@types/node":"^22.0.0","tsup":"^8.0.0","typescript":"^5.4.5","zod":"^3.25.76"},"engines":{"node":">=18"},"_id":"@alegrossi2001/bullshoot@0.1.0","gitHead":"b211fca0567ba2d6ae181c4594e4a0a7caec994e","_nodeVersion":"22.22.0","_npmVersion":"10.9.4","dist":{"integrity":"sha512-XxH5D0+/dkaZWvvBXqSQvtakzlJmHyHU+RG6Dj1qhA9JwkJFbm6BfFwA4oRLv13LEKSTcs8wOUY3JbxOYrec/w==","shasum":"b7e522696e3188b8e2af269d36da857a4bd28d4d","tarball":"https://registry.npmjs.org/@alegrossi2001/bullshoot/-/bullshoot-0.1.0.tgz","fileCount":17,"unpackedSize":111936,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIDKSRDRUC/4f0uWwoULRmWhJ1hnIn04+3W0bAI3yHZWJAiByTM6hAjFcLImqHGsP9j6YWiuvPjzdBfmrhrFnNke90g=="}]},"_npmUser":{"name":"alexgrossi","email":"alegrossi2001@gmail.com"},"directories":{},"maintainers":[{"name":"alexgrossi","email":"alegrossi2001@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/bullshoot_0.1.0_1784013664285_0.26444352006779637"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-14T07:21:04.059Z","0.1.0":"2026-07-14T07:21:04.444Z","modified":"2026-07-14T07:21:04.646Z"},"maintainers":[{"name":"alexgrossi","email":"alegrossi2001@gmail.com"}],"description":"Type-safe BullMQ + Redis setup in minutes. Define jobs and workers in your own code — never touch the library.","homepage":"https://github.com/alegrossi2001/bullshoot#readme","keywords":["bullmq","redis","queue","jobs","workers","typescript","zod","type-safe"],"repository":{"type":"git","url":"git+https://github.com/alegrossi2001/bullshoot.git"},"author":{"name":"alegrossi2001"},"bugs":{"url":"https://github.com/alegrossi2001/bullshoot/issues"},"license":"MIT","readme":"# bullshoot\n\n[![CI](https://github.com/alegrossi2001/bullshoot/actions/workflows/ci.yml/badge.svg)](https://github.com/alegrossi2001/bullshoot/actions/workflows/ci.yml)\n[![npm](https://img.shields.io/npm/v/@alegrossi2001/bullshoot.svg)](https://www.npmjs.com/package/@alegrossi2001/bullshoot)\n[![license](https://img.shields.io/npm/l/@alegrossi2001/bullshoot.svg)](./LICENSE)\n\n**Type-safe BullMQ infrastructure with runtime validation and almost zero boilerplate.**\n\nBullMQ is an excellent queue system, but most applications end up rebuilding the same infrastructure around it:\n\n* Queue registration\n* Worker wiring\n* Payload validation\n* Type-safe producers\n* Scheduling\n* Logging\n* Dashboard setup\n\n**bullshoot** packages those patterns into a single client while staying completely compatible with BullMQ. You define your jobs inside your own application, and bullshoot handles the repetitive plumbing.\n\n```bash\nnpm install @alegrossi2001/bullshoot bullmq zod\n```\n\n## Why bullshoot?\n\n* 🧩 **Jobs belong to your application** — define them in your own codebase. Never modify the library.\n* 🔒 **End-to-end type safety** — only valid job names and payloads compile.\n* ✅ **Runtime validation** — every payload is validated before your handler executes.\n* 🚀 **Minimal setup** — producers and workers share the same configuration.\n* 🪶 **Minimal dependencies** — no logging framework, environment loader or configuration magic.\n* 📊 **Optional dashboard** — Bull Board integration in one import.\n* 🔓 **Escape hatches included** — access the underlying BullMQ objects whenever needed.\n\n---\n\n# How it works\n\n```\n               enqueue()\n\n                   │\n\n          createQueueClient()\n\n                   │\n\n              Bullshoot\n\n                   │\n\n               BullMQ\n\n                   │\n\n                Redis\n\n                   │\n\n        startWorkers()\n\n                   │\n\n            Your handlers\n```\n\nYou define jobs once, create a single client, and deploy it wherever you need.\n\n* API servers only call `enqueue()`\n* Worker processes only call `startWorkers()`\n* Both share exactly the same configuration\n\n---\n\n# Quick Start\n\n## 1. Define your jobs\n\n```ts\nimport { z } from \"zod\";\nimport { defineJob } from \"@alegrossi2001/bullshoot\";\n\nexport const sendEmail = defineJob({\n  name: \"email.send\",\n\n  schema: z.object({\n    to: z.string().email(),\n    subject: z.string(),\n    body: z.string(),\n  }),\n\n  handler: async ({ data, logger }) => {\n    logger.info({ to: data.to }, \"Sending email\");\n\n    await mailer.send(data);\n  },\n\n  options: {\n    attempts: 5,\n    concurrency: 10,\n  },\n});\n\nexport const resizeImage = defineJob({\n  name: \"image.resize\",\n\n  schema: z.object({\n    url: z.string().url(),\n    width: z.number().int(),\n  }),\n\n  handler: async ({ data }) => {\n    await resize(data.url, data.width);\n  },\n});\n```\n\n---\n\n## 2. Create a queue client\n\n```ts\nimport { createQueueClient } from \"@alegrossi2001/bullshoot\";\nimport { sendEmail, resizeImage } from \"./jobs\";\n\nexport const queue = createQueueClient({\n  redis: {\n    host: \"127.0.0.1\",\n    port: 6379,\n  },\n\n  jobs: [\n    sendEmail,\n    resizeImage,\n  ],\n\n  defaults: {\n    removeOnComplete: {\n      count: 1000,\n    },\n  },\n});\n```\n\nThe client owns everything required to run your queues:\n\n* BullMQ queues\n* Workers\n* Queue events\n* Scheduling helpers\n* Type-safe producer API\n\n---\n\n## 3. Produce jobs\n\n```ts\nawait queue.enqueue(\"email.send\", {\n  to: \"user@example.com\",\n  subject: \"Welcome!\",\n  body: \"Thanks for joining.\",\n});\n```\n\nTypeScript catches mistakes before you deploy.\n\n```ts\n// Unknown job\nqueue.enqueue(\"email.snd\", ...);\n\n// Invalid payload\nqueue.enqueue(\"email.send\", {\n  to: 123,\n});\n```\n\n---\n\n## 4. Run workers\n\n```ts\nimport { queue } from \"./queue\";\n\nqueue.startWorkers();\n```\n\nOr start only selected jobs.\n\n```ts\nqueue.startWorkers({\n  only: [\n    \"image.resize\",\n  ],\n});\n```\n\nThe producer and worker use the exact same client.\n\nOnly the processes that call `startWorkers()` execute handlers.\n\n---\n\n# Redis configuration\n\nExplicit configuration is preferred.\n\n```ts\ncreateQueueClient({\n  redis: {\n    host,\n    port,\n    username,\n    password,\n    tls,\n  },\n\n  jobs,\n});\n```\n\nConnection URLs are also supported.\n\n```ts\ncreateQueueClient({\n  redis: {\n    url: \"rediss://:password@host:6379\",\n  },\n\n  jobs,\n});\n```\n\nTLS is automatically enabled for `rediss://`.\n\n---\n\n## Environment variables\n\nIf you prefer environment variables, opt in explicitly.\n\n```ts\nimport { connectionFromEnv } from \"@alegrossi2001/bullshoot\";\n\ncreateQueueClient({\n  redis: connectionFromEnv(),\n  jobs,\n});\n```\n\nSupported variables:\n\n* `REDIS_URL`\n* `REDIS_HOST`\n* `REDIS_PORT`\n* `REDIS_USERNAME`\n* `REDIS_PASSWORD`\n\nMissing required values throw an exception.\n\nThe library never exits your process.\n\n---\n\n# Scheduling\n\nDelayed jobs\n\n```ts\nawait queue.scheduleOnce(\n  \"email.send\",\n  data,\n  {\n    delay: 60_000,\n    jobId: \"welcome:42\",\n  }\n);\n```\n\nRepeatable jobs\n\n```ts\nawait queue.schedule(\n  \"image.resize\",\n  data,\n  {\n    repeat: {\n      cron: \"0 3 * * *\",\n    },\n  }\n);\n\nawait queue.schedule(\n  \"image.resize\",\n  data,\n  {\n    repeat: {\n      every: 30_000,\n    },\n  }\n);\n```\n\nRemove a schedule\n\n```ts\nawait queue.unschedule(\n  \"image.resize\",\n  {\n    cron: \"0 3 * * *\",\n  }\n);\n```\n\nBulk enqueue\n\n```ts\nawait queue.bulkEnqueue(\n  \"email.send\",\n  [\n    email1,\n    email2,\n    email3,\n  ]\n);\n```\n\n---\n\n# Configuration precedence\n\nSettings merge in this order.\n\n```\nClient defaults\n\n↓\n\nJob options\n\n↓\n\nenqueue() options\n```\n\nLater values always override earlier ones.\n\n| Option           | Available on           | Default          |\n| ---------------- | ---------------------- | ---------------- |\n| attempts         | Client / Job / Enqueue | 3                |\n| backoff          | Client / Job / Enqueue | Exponential (5s) |\n| concurrency      | Client / Job           | 1                |\n| lockDuration     | Client / Job           | 60000            |\n| removeOnComplete | Client / Job / Enqueue | Disabled         |\n| removeOnFail     | Client / Job / Enqueue | Disabled         |\n| priority         | Enqueue                | —                |\n| delay            | Enqueue                | —                |\n| jobId            | Enqueue                | —                |\n\n---\n\n# Validation\n\nEvery worker validates incoming payloads before your handler executes.\n\n```ts\nschema.parse(payload);\n```\n\nIf validation fails:\n\n* the handler is never called\n* the job fails normally\n* retries behave exactly as BullMQ expects\n* failures appear in Bull Board\n\nAny validation library exposing\n\n```ts\nparse(unknown): T\n```\n\nis supported.\n\nZod works out of the box.\n\n---\n\n# Logging\n\nUse any logger exposing\n\n* debug\n* info\n* warn\n* error\n\n```ts\ncreateQueueClient({\n  redis,\n  jobs,\n  logger: pino(),\n});\n```\n\nDisable logging entirely.\n\n```ts\ncreateQueueClient({\n  redis,\n  jobs,\n  logger: false,\n});\n```\n\n---\n\n# Dashboard\n\n```bash\nnpm install @bull-board/api @bull-board/express express\n```\n\n```ts\nimport express from \"express\";\n\nimport {\n  mountDashboard,\n} from \"@alegrossi2001/bullshoot/dashboard\";\n\nimport { queue } from \"./queue\";\n\nconst app = express();\n\nconst {\n  router,\n  basePath,\n} = await mountDashboard(queue);\n\napp.use(basePath, router);\n\napp.listen(3000);\n```\n\nBy default the dashboard is mounted at:\n\n```\n/admin/queues\n```\n\n---\n\n# Escape hatches\n\nBullshoot intentionally does **not** hide BullMQ.\n\nNeed something unsupported?\n\nUse the underlying objects directly.\n\n```ts\nqueue.getQueue(\"email.send\");\n```\n\nInside handlers:\n\n```ts\nctx.raw\n```\n\nShutdown gracefully:\n\n```ts\nawait queue.close();\n```\n\n---\n\n# Why not just BullMQ?\n\nBullMQ is already an excellent library.\n\nBullshoot is **not** a replacement.\n\nInstead, it removes the repetitive infrastructure that many applications rebuild:\n\n| BullMQ                                 | Bullshoot                   |\n| -------------------------------------- | --------------------------- |\n| Register queues manually               | Jobs register automatically |\n| Validate payloads manually             | Validation built in         |\n| Queue names are plain strings          | Fully typed queue names     |\n| Producers and workers wired separately | One shared client           |\n| Dashboard setup is manual              | Optional helper             |\n| Direct BullMQ APIs                     | Still available             |\n\nWhenever you need raw BullMQ functionality, you can access it directly.\n\n---\n\n# About\n\nBullshoot began as the queue infrastructure powering **QlickUp**, a commercial CRM platform handling production workloads including email delivery, automations, imports and background processing.\n\nAfter proving the architecture in production, the infrastructure was extracted into a standalone open-source package focused on three goals:\n\n* excellent TypeScript support\n* minimal setup\n* staying out of the way when advanced BullMQ features are needed\n\nRather than replacing BullMQ, bullshoot aims to make the common path dramatically simpler while keeping the full power of BullMQ available.\n\n---\n\n# License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-91f04efc5ac7a4426785b1fad02480c5"}