diff --git a/README.md b/README.md index 4dcd6c5b..0f4f13de 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ With this library you get a beautiful UI for visualizing what's happening with e | [@bull-board/koa](https://www.npmjs.com/package/@bull-board/koa) | ![npm (scoped)](https://img.shields.io/npm/v/@bull-board/koa) | | [@bull-board/hapi](https://www.npmjs.com/package/@bull-board/hapi) | ![npm (scoped)](https://img.shields.io/npm/v/@bull-board/hapi) | | [@bull-board/nestjs](https://www.npmjs.com/package/@bull-board/nestjs) | ![npm (scoped)](https://img.shields.io/npm/v/@bull-board/nestjs) | +| [@bull-board/hono](https://www.npmjs.com/package/@bull-board/hono) | ![npm (scoped)](https://img.shields.io/npm/v/@bull-board/hono) | ## Notes @@ -51,6 +52,10 @@ yarn add @bull-board/fastify yarn add @bull-board/hapi # or yarn add @bull-board/koa +# or +yarn add @bull-board/nestjs +# or +yarn add @bull-board/hono ``` ### NestJS specific setup @@ -105,6 +110,7 @@ For more advanced usages check the `examples` folder, currently it contains: 5. [With Koa.js server](https://github.com/felixmosh/bull-board/tree/master/examples/with-koa) 6. [With Nest.js server using the built-in module](https://github.com/felixmosh/bull-board/tree/master/examples/with-nestjs-module) (Thanx to @dennissnijder) 7. [With Nest.js server using the express adapter](https://github.com/felixmosh/bull-board/tree/master/examples/with-nestjs) (Thanx to @lodi-g) +8. [With Hono server](https://github.com/felixmosh/bull-board/tree/master/examples/with-hono) (Thanks to @nihalgonsalves) ### Board options 1. `uiConfig.boardTitle` (default: `Bull Dashboard`) diff --git a/examples/with-hono/README.md b/examples/with-hono/README.md new file mode 100644 index 00000000..16315c91 --- /dev/null +++ b/examples/with-hono/README.md @@ -0,0 +1,3 @@ +# Hono example + +This example shows how to use [Hono](https://hono.dev) as a server for bull-board. diff --git a/examples/with-hono/index.js b/examples/with-hono/index.js new file mode 100644 index 00000000..d49bef7e --- /dev/null +++ b/examples/with-hono/index.js @@ -0,0 +1,79 @@ +const { createBullBoard } = require('@bull-board/api'); +const { BullMQAdapter } = require('@bull-board/api/bullMQAdapter'); +const { HonoAdapter } = require('@bull-board/hono'); +const { Queue: QueueMQ, Worker } = require('bullmq'); +const { Hono } = require('hono'); +const { showRoutes } = require('hono/dev'); +const { serve } = require('@hono/node-server'); +const { serveStatic } = require('@hono/node-server/serve-static'); + +const sleep = (t) => new Promise((resolve) => setTimeout(resolve, t * 1000)); + +const redisOptions = { + port: 6379, + host: 'localhost', + password: '', + tls: false, +}; + +const createQueueMQ = (name) => new QueueMQ(name, { connection: redisOptions }); + +async function setupBullMQProcessor(queueName) { + new Worker( + queueName, + async (job) => { + for (let i = 0; i <= 100; i++) { + await sleep(Math.random()); + await job.updateProgress(i); + await job.log(`Processing job at interval ${i}`); + + if (Math.random() * 200 < 1) throw new Error(`Random error ${i}`); + } + + return { jobId: `This is the return value of job (${job.id})` }; + }, + { connection: redisOptions } + ); +} + +const run = async () => { + const exampleBullMq = createQueueMQ('BullMQ'); + + await setupBullMQProcessor(exampleBullMq.name); + + const app = new Hono(); + + const serverAdapter = new HonoAdapter(serveStatic); + + createBullBoard({ + queues: [new BullMQAdapter(exampleBullMq)], + serverAdapter, + }); + + const basePath = '/ui' + serverAdapter.setBasePath(basePath); + app.route(basePath, serverAdapter.registerPlugin()); + + app.get('/add', async (c) => { + await exampleBullMq.add('Add', { title: c.req.query('title') }); + + return c.json({ ok: true }) + }); + + showRoutes(app); + + serve({ fetch: app.fetch, port: 3000 }, ({ address, port }) => { + /* eslint-disable no-console */ + console.log(`Running on ${address}:${port}...`); + console.log(`For the UI of instance1, open http://localhost:${port}/ui`); + console.log('Make sure Redis is running on port 6379 by default'); + console.log('To populate the queue, run:'); + console.log(` curl http://localhost:${port}/add?title=Example`); + /* eslint-enable */ + }) +}; + +run().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/examples/with-hono/package.json b/examples/with-hono/package.json new file mode 100644 index 00000000..eefe2e85 --- /dev/null +++ b/examples/with-hono/package.json @@ -0,0 +1,18 @@ +{ + "name": "bull-board-with-hono", + "version": "1.0.0", + "description": "Example of how to use Hono server with bull-board", + "main": "index.js", + "scripts": { + "start": "node index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "felixmosh", + "license": "ISC", + "dependencies": { + "@bull-board/hono": "../../packages/hono", + "@hono/node-server": "^1.4.0", + "bullmq": "^4.6.0", + "hono": "^3.12.0" + } +} diff --git a/examples/with-hono/yarn.lock b/examples/with-hono/yarn.lock new file mode 100644 index 00000000..6fe54cf8 --- /dev/null +++ b/examples/with-hono/yarn.lock @@ -0,0 +1,381 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@bull-board/api@5.10.3": + version "5.10.3" + resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-5.10.3.tgz#a4340734619bc7fa82cee195efaf078b9a879b69" + integrity sha512-QdHeAoKnJl+fXWt5o8EhxMOIcVCD89A6bdN9sn2jCyF8pNT2+2vCzhkf2Eb0vmh7JwMivc+pNWEjJdoJ/V65Yw== + dependencies: + redis-info "^3.0.8" + +"@bull-board/hono@../../packages/hono": + version "5.10.3" + dependencies: + "@bull-board/api" "5.10.3" + "@bull-board/ui" "5.10.3" + ejs "^3.1.7" + hono "^3.12.0" + +"@bull-board/ui@5.10.3": + version "5.10.3" + resolved "https://registry.yarnpkg.com/@bull-board/ui/-/ui-5.10.3.tgz#559e8bf49b65d5ca3a353f9a028b52e1231f5991" + integrity sha512-UELmoXQUCP+oL81aoXAZsU0AkWwMEg0xl0hzC0YYWEXZ7Pkd+gYgOxhPkKTDlIZqtamJ7K7HgtZ0SIiHjcR/XQ== + dependencies: + "@bull-board/api" "5.10.3" + +"@hono/node-server@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@hono/node-server/-/node-server-1.4.0.tgz#e11459f2aedc9e87ce8adba57f2e0ada55fc377a" + integrity sha512-bhDkhldW7w9VgjrX0gG1vJ2YyvTxFWd5WG9nHjSR4UauhVECQZC3qy7mVVuQ054I5NWhKttHfKzYfoPzmUzAjw== + +"@ioredis/commands@^1.1.1": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" + integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== + +"@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz#44d752c1a2dc113f15f781b7cc4f53a307e3fa38" + integrity sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ== + +"@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.2.tgz#f954f34355712212a8e06c465bc06c40852c6bb3" + integrity sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw== + +"@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.2.tgz#45c63037f045c2b15c44f80f0393fa24f9655367" + integrity sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg== + +"@msgpackr-extract/msgpackr-extract-linux-arm@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.2.tgz#35707efeafe6d22b3f373caf9e8775e8920d1399" + integrity sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA== + +"@msgpackr-extract/msgpackr-extract-linux-x64@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.2.tgz#091b1218b66c341f532611477ef89e83f25fae4f" + integrity sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA== + +"@msgpackr-extract/msgpackr-extract-win32-x64@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.2.tgz#0f164b726869f71da3c594171df5ebc1c4b0a407" + integrity sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ== + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +async@^3.2.3: + version "3.2.4" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" + integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +bullmq@^4.6.0: + version "4.17.0" + resolved "https://registry.yarnpkg.com/bullmq/-/bullmq-4.17.0.tgz#f8f61bea80fb72ab690c1e926de0464f3222f1ea" + integrity sha512-URnHgB01rlCP8RTpmW3kFnvv3vdd2aI1OcBMYQwnqODxGiJUlz9MibDVXE83mq7ee1eS1IvD9lMQqGszX6E5Pw== + dependencies: + cron-parser "^4.6.0" + glob "^8.0.3" + ioredis "^5.3.2" + lodash "^4.17.21" + msgpackr "^1.6.2" + node-abort-controller "^3.1.1" + semver "^7.5.4" + tslib "^2.0.0" + uuid "^9.0.0" + +chalk@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +cluster-key-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d" + integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +cron-parser@^4.6.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-4.9.0.tgz#0340694af3e46a0894978c6f52a6dbb5c0f11ad5" + integrity sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q== + dependencies: + luxon "^3.2.1" + +debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +denque@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" + integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== + +ejs@^3.1.7: + version "3.1.8" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b" + integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ== + dependencies: + jake "^10.8.5" + +filelist@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +glob@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" + integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hono@^3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/hono/-/hono-3.12.0.tgz#889214938af36ac265a99864f4a0104c4942f3fa" + integrity sha512-UPEtZuLY7Wo7g0mqKWSOjLFdT8t7wJ60IYEcxKl3AQNU4u+R2QqU2fJMPmSu24C+/ag20Z8mOTQOErZzK4DMvA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ioredis@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.3.2.tgz#9139f596f62fc9c72d873353ac5395bcf05709f7" + integrity sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA== + dependencies: + "@ioredis/commands" "^1.1.1" + cluster-key-slot "^1.1.0" + debug "^4.3.4" + denque "^2.1.0" + lodash.defaults "^4.2.0" + lodash.isarguments "^3.1.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + +jake@^10.8.5: + version "10.8.5" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" + integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.1" + minimatch "^3.0.4" + +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== + +lodash.isarguments@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== + +lodash@^4.17.11, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +luxon@^3.2.1: + version "3.4.4" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.4.4.tgz#cf20dc27dc532ba41a169c43fdcc0063601577af" + integrity sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA== + +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" + integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== + dependencies: + brace-expansion "^2.0.1" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +msgpackr-extract@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-3.0.2.tgz#e05ec1bb4453ddf020551bcd5daaf0092a2c279d" + integrity sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A== + dependencies: + node-gyp-build-optional-packages "5.0.7" + optionalDependencies: + "@msgpackr-extract/msgpackr-extract-darwin-arm64" "3.0.2" + "@msgpackr-extract/msgpackr-extract-darwin-x64" "3.0.2" + "@msgpackr-extract/msgpackr-extract-linux-arm" "3.0.2" + "@msgpackr-extract/msgpackr-extract-linux-arm64" "3.0.2" + "@msgpackr-extract/msgpackr-extract-linux-x64" "3.0.2" + "@msgpackr-extract/msgpackr-extract-win32-x64" "3.0.2" + +msgpackr@^1.6.2: + version "1.10.1" + resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.10.1.tgz#51953bb4ce4f3494f0c4af3f484f01cfbb306555" + integrity sha512-r5VRLv9qouXuLiIBrLpl2d5ZvPt8svdQTl5/vMvE4nzDMyEX4sgW5yWhuBBj5UmgwOTWj8CIdSXn5sAfsHAWIQ== + optionalDependencies: + msgpackr-extract "^3.0.2" + +node-abort-controller@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" + integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== + +node-gyp-build-optional-packages@5.0.7: + version "5.0.7" + resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz#5d2632bbde0ab2f6e22f1bbac2199b07244ae0b3" + integrity sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== + +redis-info@^3.0.8: + version "3.1.0" + resolved "https://registry.yarnpkg.com/redis-info/-/redis-info-3.1.0.tgz#5e349c8720e82d27ac84c73136dce0931e10469a" + integrity sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg== + dependencies: + lodash "^4.17.11" + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== + dependencies: + redis-errors "^1.0.0" + +semver@^7.5.4: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + +standard-as-callback@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" + integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +tslib@^2.0.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== diff --git a/packages/hono/README.md b/packages/hono/README.md new file mode 100644 index 00000000..2ae0cb6e --- /dev/null +++ b/packages/hono/README.md @@ -0,0 +1,24 @@ +# @bull-board @bull-board/hono + +[Hono](https://hono.dev) server adapter for `bull-board`. + +

+ + npm version + + + npm downloads + + + licence + +

+ +![Overview](https://raw.githubusercontent.com/felixmosh/bull-board/master/screenshots/overview.png) +![UI](https://raw.githubusercontent.com/felixmosh/bull-board/master/screenshots/dashboard.png) + +# Usage examples + +1. [Simple Hono setup](https://github.com/felixmosh/bull-board/tree/master/examples/with-hono) + +For more info visit the main [README](https://github.com/felixmosh/bull-board#readme) diff --git a/packages/hono/package.json b/packages/hono/package.json new file mode 100644 index 00000000..ea2aafd0 --- /dev/null +++ b/packages/hono/package.json @@ -0,0 +1,43 @@ +{ + "name": "@bull-board/hono", + "version": "5.10.3", + "description": "A Hono server adapter for Bull-Board dashboard.", + "keywords": [ + "bull", + "bullmq", + "redis", + "hono", + "adapter", + "queue", + "monitoring", + "dashboard" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/felixmosh/bull-board.git", + "directory": "packages/hono" + }, + "license": "MIT", + "author": "felixmosh", + "main": "dist/index.js", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "clean": "rm -rf dist" + }, + "dependencies": { + "@bull-board/api": "5.10.3", + "@bull-board/ui": "5.10.3", + "ejs": "^3.1.7", + "hono": "^3.12.0" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20231218.0", + "@hono/node-server": "^1.4.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/hono/src/HonoAdapter.ts b/packages/hono/src/HonoAdapter.ts new file mode 100644 index 00000000..b5b16efb --- /dev/null +++ b/packages/hono/src/HonoAdapter.ts @@ -0,0 +1,185 @@ +import path from 'node:path'; + +import type { + AppControllerRoute, + AppViewRoute, + BullBoardQueues, + ControllerHandlerReturnType, + IServerAdapter, + UIConfig, +} from '@bull-board/api/dist/typings/app'; +import type { serveStatic as nodeServeStatic } from '@hono/node-server/serve-static'; +import ejs from 'ejs'; +import { Hono } from 'hono'; +import type { Context } from 'hono'; +import type { serveStatic as bunServeStatic } from 'hono/bun'; +import type { serveStatic as cloudflarePagesServeStatic } from 'hono/cloudflare-pages'; +import type { serveStatic as cloudflareWorkersServeStatic } from 'hono/cloudflare-workers'; +import type { serveStatic as denoServeStatic } from 'hono/deno'; + +export class HonoAdapter implements IServerAdapter { + protected bullBoardQueues: BullBoardQueues | undefined; + + protected errorHandler: ((error: Error) => ControllerHandlerReturnType) | undefined; + + protected uiConfig?: UIConfig; + + protected staticRoute?: string; + + protected staticPath?: string; + + protected entryRoute?: AppViewRoute; + + protected viewPath?: string; + + protected apiRoutes: Hono; + + protected basePath = '/'; + + constructor( + protected serveStatic: + | typeof bunServeStatic + | typeof nodeServeStatic + | typeof cloudflarePagesServeStatic + | typeof cloudflareWorkersServeStatic + | typeof denoServeStatic + ) { + this.apiRoutes = new Hono(); + } + + public setBasePath(path: string): this { + this.basePath = path; + return this; + } + + setStaticPath(staticRoute: string, staticPath: string): this { + this.staticRoute = staticRoute; + this.staticPath = staticPath; + return this; + } + + setViewsPath(viewPath: string): this { + this.viewPath = viewPath; + return this; + } + + setErrorHandler(handler: (error: Error) => ControllerHandlerReturnType): this { + this.errorHandler = handler; + return this; + } + + setApiRoutes(routes: readonly AppControllerRoute[]): this { + const { errorHandler, bullBoardQueues } = this; + + if (!errorHandler || !bullBoardQueues) { + throw new Error(''); + } + + routes.forEach(({ method: methodOrMethods, route, handler }) => { + const methods = Array.isArray(methodOrMethods) ? methodOrMethods : [methodOrMethods]; + + methods.forEach((m) => { + this.registerRoute(route, m, handler); + }); + }); + + return this; + } + + private registerRoute( + routeOrRoutes: string | string[], + method: 'get' | 'post' | 'put', + handler: AppControllerRoute['handler'] + ) { + const { bullBoardQueues } = this; + + if (!bullBoardQueues) { + throw new Error(`Please call 'setQueues' before using 'registerPlugin'`); + } + + const routes = Array.isArray(routeOrRoutes) ? routeOrRoutes : [routeOrRoutes]; + + routes.forEach((route) => { + this.apiRoutes[method](route, async (c: Context) => { + try { + const response = await handler({ + queues: bullBoardQueues, + params: c.req.param(), + query: c.req.query(), + }); + return c.json(response.body, response.status || 200); + } catch (e) { + if (!this.errorHandler || !(e instanceof Error)) { + throw e; + } + + const response = this.errorHandler(e); + + if (typeof response.body === 'string') { + return c.text(response.body, response.status); + } + + return c.json(response.body, response.status); + } + }); + }); + } + + setEntryRoute(routeDef: AppViewRoute): this { + this.entryRoute = routeDef; + return this; + } + + setQueues(bullBoardQueues: BullBoardQueues): this { + this.bullBoardQueues = bullBoardQueues; + return this; + } + + setUIConfig(config: UIConfig): this { + this.uiConfig = config; + return this; + } + + registerPlugin() { + const { staticRoute, staticPath, entryRoute, viewPath, uiConfig } = this; + + if (!staticRoute || !staticPath) { + throw new Error(`Please call 'setStaticPath' before using 'registerPlugin'`); + } else if (!entryRoute) { + throw new Error(`Please call 'setEntryRoute' before using 'registerPlugin'`); + } else if (!viewPath) { + throw new Error(`Please call 'setViewsPath' before using 'registerPlugin'`); + } else if (!uiConfig) { + throw new Error(`Please call 'setUIConfig' before using 'registerPlugin'`); + } + + const app = new Hono(); + + app.get( + `${staticRoute}/*`, + this.serveStatic({ + root: path.relative(process.cwd(), staticPath), + rewriteRequestPath: (p: string) => p.replace(path.join(this.basePath, staticRoute), ''), + }) + ); + + app.route('/', this.apiRoutes); + + const routeOrRoutes = entryRoute.route; + const routes = Array.isArray(routeOrRoutes) ? routeOrRoutes : [routeOrRoutes]; + + routes.forEach((route) => { + app[entryRoute.method](route, async (c: Context) => { + const { name: fileName, params } = entryRoute.handler({ + basePath: this.basePath, + uiConfig, + }); + + const template = await ejs.renderFile(`${this.viewPath}/${fileName}`, params); + return c.html(template); + }); + }); + + return app; + } +} diff --git a/packages/hono/src/index.ts b/packages/hono/src/index.ts new file mode 100644 index 00000000..60a17d1a --- /dev/null +++ b/packages/hono/src/index.ts @@ -0,0 +1 @@ +export { HonoAdapter } from './HonoAdapter'; diff --git a/packages/hono/tsconfig.json b/packages/hono/tsconfig.json new file mode 100644 index 00000000..59f5bfff --- /dev/null +++ b/packages/hono/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "outDir": "dist", + "esModuleInterop": true, + "lib": ["es2019", "DOM"], + "module": "CommonJS", + "moduleResolution": "node", + "noImplicitAny": true, + "sourceMap": true, + "strict": true, + "target": "es2019", + "noUnusedParameters": true, + "noUnusedLocals": true, + "resolveJsonModule": true, + "declaration": true + }, + "include": ["./src", "./typings/*.d.ts"] +} diff --git a/yarn.lock b/yarn.lock index 11fa94ff..26ced76c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1175,6 +1175,11 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@cloudflare/workers-types@^4.20231218.0": + version "4.20231218.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-4.20231218.0.tgz#3deac906c68aa5c8d8cdefe828146215eed6804c" + integrity sha512-Vs1FKjfUjXYGbCsXzkl+ITp0Iyb6QiW6+vTERTNThC+v96T0IvPVAioH4tT20rXwoxAfxh380mAaxYtTrJUNVg== + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -1936,6 +1941,11 @@ "@hapi/bourne" "^3.0.0" "@hapi/hoek" "^11.0.2" +"@hono/node-server@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@hono/node-server/-/node-server-1.4.0.tgz#e11459f2aedc9e87ce8adba57f2e0ada55fc377a" + integrity sha512-bhDkhldW7w9VgjrX0gG1vJ2YyvTxFWd5WG9nHjSR4UauhVECQZC3qy7mVVuQ054I5NWhKttHfKzYfoPzmUzAjw== + "@humanwhocodes/config-array@^0.5.0": version "0.5.0" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" @@ -8065,6 +8075,11 @@ hoist-non-react-statics@^3.1.0: dependencies: react-is "^16.7.0" +hono@^3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/hono/-/hono-3.12.0.tgz#889214938af36ac265a99864f4a0104c4942f3fa" + integrity sha512-UPEtZuLY7Wo7g0mqKWSOjLFdT8t7wJ60IYEcxKl3AQNU4u+R2QqU2fJMPmSu24C+/ag20Z8mOTQOErZzK4DMvA== + hosted-git-info@^2.1.4: version "2.8.9" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"