@dunx/dashboard 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -102,6 +102,3 @@ var snapshotOf = (root, options) => ({
102
102
  });
103
103
 
104
104
  export { __decoratorStart, __decoratorMetadata, __runInitializers, __decorateElement, __require, metaOf, snapshotOf };
105
-
106
- //# debugId=D21070FBD0B8DA7064756E2164756E21
107
- //# sourceMappingURL=chunk-xps6r8jv.js.map
package/dist/index.js CHANGED
@@ -470,6 +470,3 @@ export {
470
470
  handleDashboard,
471
471
  normalizeMount
472
472
  };
473
-
474
- //# debugId=CFA504A7E130188D64756E2164756E21
475
- //# sourceMappingURL=index.js.map
package/dist/ui.js CHANGED
@@ -37,6 +37,3 @@ export {
37
37
  renderPage,
38
38
  renderShell
39
39
  };
40
-
41
- //# debugId=F568A3782F812A4164756E2164756E21
42
- //# sourceMappingURL=ui.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dunx/dashboard",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "An opt-in operations page for a running dunx app: routes, the provider graph, gateways, config and runtime health, with bull-board mounted for the queues",
5
5
  "keywords": [
6
6
  "bull-board",
@@ -66,8 +66,8 @@
66
66
  "@bull-board/api": "^8.6.0",
67
67
  "@bull-board/bun": "^8.6.0",
68
68
  "@bull-board/ui": "^8.6.0",
69
- "@dunx/core": "^2.4.0",
70
- "@dunx/http": "^2.4.0",
69
+ "@dunx/core": "^2.5.0",
70
+ "@dunx/http": "^2.5.0",
71
71
  "@types/bun": ">=1.3.0"
72
72
  },
73
73
  "peerDependenciesMeta": {
@@ -85,6 +85,6 @@
85
85
  }
86
86
  },
87
87
  "engines": {
88
- "bun": ">=1.3.0"
88
+ "bun": ">=1.4.0"
89
89
  }
90
90
  }
@@ -1,10 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/api/snapshot.ts"],
4
- "sourcesContent": [
5
- "import { modulesOf, providersOf, type ModuleRef } from '@dunx/core';\nimport { gatewaysOf, isGateway, routesOf } from '@dunx/http';\nimport type { DashboardOptions } from '../options.js';\nimport type { ConfigEntry, Meta, Snapshot } from './types.js';\n\n/**\n * The static half of the page, built from the same readers `@dunx/mcp` answers\n * with. Nothing here constructs anything: `providersOf` and `routesOf` walk\n * prototypes, so this would answer identically before the app booted.\n *\n * That is the deliberate inversion of MCP's rule. MCP refuses runtime questions\n * because booting an app to answer them would open databases and bind sockets;\n * this package is *already inside* a booted app, so the reason does not apply and\n * the live panels ask the container directly. Splitting it per panel rather than\n * per package is what stops this file growing a `boot()`.\n */\n\nconst typeOf = (value: unknown): string => {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'array';\n return typeof value;\n};\n\n/**\n * Keys, types, and a value only where the app's `reveal` predicate said so.\n *\n * Sorted, because a config panel is read by scanning for a key rather than in\n * declaration order, and `validate` returns whatever object literal order the app\n * happened to write.\n */\nexport const configEntries = (\n values: object,\n reveal: DashboardOptions['reveal'],\n): readonly ConfigEntry[] =>\n Object.entries(values)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([key, value]: [string, unknown]) => ({\n key,\n type: typeOf(value),\n ...(reveal(key, value) ? { value } : {}),\n }));\n\nexport const metaOf = (options: DashboardOptions): Meta => ({\n title: options.title,\n basePath: options.path,\n openApiPath: options.openApiPath,\n pollMs: options.pollMs,\n queuesPath: `${options.path}/queues`,\n});\n\nexport const snapshotOf = (\n root: ModuleRef,\n options: DashboardOptions,\n): Snapshot => ({\n meta: metaOf(options),\n routes: routesOf(root),\n gateways: gatewaysOf(root),\n // Core cannot import `@dunx/http`, so the gateway marker arrives as an option.\n // Without it a gateway would be listed as an ordinary provider here while the\n // gateways panel showed it as one, and the two panels would disagree.\n modules: modulesOf(root, { isGateway }),\n providers: providersOf(root, { isGateway }),\n // Absent when the app passed no `config`, which the panel reports as such - a\n // different fact from a configuration with no keys in it.\n config:\n options.config === undefined\n ? undefined\n : configEntries(options.config.values, options.reveal),\n});\n"
6
- ],
7
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AACA;AAgBA,IAAM,SAAS,CAAC,UAA2B;AAAA,EACzC,IAAI,UAAU;AAAA,IAAM,OAAO;AAAA,EAC3B,IAAI,MAAM,QAAQ,KAAK;AAAA,IAAG,OAAO;AAAA,EACjC,OAAO,OAAO;AAAA;AAUT,IAAM,gBAAgB,CAC3B,QACA,WAEA,OAAO,QAAQ,MAAM,EAClB,KAAK,EAAE,KAAK,OAAO,EAAE,cAAc,CAAC,CAAC,EACrC,IAAI,EAAE,KAAK,YAA+B;AAAA,EACzC;AAAA,EACA,MAAM,OAAO,KAAK;AAAA,KACd,OAAO,KAAK,KAAK,IAAI,EAAE,MAAM,IAAI,CAAC;AACxC,EAAE;AAEC,IAAM,SAAS,CAAC,aAAqC;AAAA,EAC1D,OAAO,QAAQ;AAAA,EACf,UAAU,QAAQ;AAAA,EAClB,aAAa,QAAQ;AAAA,EACrB,QAAQ,QAAQ;AAAA,EAChB,YAAY,GAAG,QAAQ;AACzB;AAEO,IAAM,aAAa,CACxB,MACA,aACc;AAAA,EACd,MAAM,OAAO,OAAO;AAAA,EACpB,QAAQ,SAAS,IAAI;AAAA,EACrB,UAAU,WAAW,IAAI;AAAA,EAIzB,SAAS,UAAU,MAAM,EAAE,UAAU,CAAC;AAAA,EACtC,WAAW,YAAY,MAAM,EAAE,UAAU,CAAC;AAAA,EAG1C,QACE,QAAQ,WAAW,YACf,YACA,cAAc,QAAQ,OAAO,QAAQ,QAAQ,MAAM;AAC3D;",
8
- "debugId": "D21070FBD0B8DA7064756E2164756E21",
9
- "names": []
10
- }
package/dist/index.js.map DELETED
@@ -1,17 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/module.ts", "../src/middleware.ts", "../src/board.ts", "../src/options.ts", "../src/api/bounded.ts", "../src/api/redis.ts", "../src/api/runtime.ts", "../src/router.ts"],
4
- "sourcesContent": [
5
- "import {\n Logger,\n Module,\n provide,\n ROOT_MODULE,\n type AsyncModuleConfig,\n type Deps,\n type DynamicModule,\n type ModuleRef,\n type Registration,\n} from '@dunx/core';\nimport { DashboardMiddleware } from './middleware.js';\nimport { DashboardOptions, type DashboardOptionsInit } from './options.js';\n\n/**\n * `useFactory` rather than listing the class bare, which would work too - it just\n * makes the binding explicit and independent of `@dunx/transform`, the same choice\n * `RedisModule` makes for `Redis`. It matters slightly more here: this package's\n * own test suite runs from `src` with no compiler preload, like every other\n * package's.\n *\n * `ROOT_MODULE` is core's, bound into the global scope by `AppFactory.create`. It\n * is how the middleware names the graph it reports on - the readers all take a\n * `ModuleRef`, and asking the app to pass its own root module into its own\n * `imports` would be a circular reference to write out by hand.\n */\nconst middleware = (): Registration =>\n provide(DashboardMiddleware, {\n useFactory: (options: DashboardOptions, root: ModuleRef, logger: Logger) =>\n new DashboardMiddleware(options, root, logger),\n inject: [DashboardOptions, ROOT_MODULE, Logger] as const,\n });\n\n/**\n * Binds the options and the middleware. **It does not register the middleware** -\n * the app does, with `app.use(DashboardMiddleware)`, and that is deliberate:\n * position in the chain is the whole security property here.\n *\n * `@Module({ middleware })` scopes middleware to that module's own controllers, of\n * which this module has none, so it would never run. Registering globally from\n * inside the module would put it wherever the module happened to be imported, and\n * it has to be **ahead of any session guard** - measured in `dunx-template`, where a\n * guard running first answered every dashboard request `401` before `authorize`\n * ran, defeating the 404 contract. Two lines in the app, one of which is a\n * decision:\n *\n * ```ts\n * const app = await HttpFactory.create(AppModule);\n * app.use(DashboardMiddleware, SessionGuard);\n * ```\n */\n@Module({})\nexport class DashboardModule {\n static forRoot(init: DashboardOptionsInit = {}): DynamicModule {\n return {\n module: DashboardModule,\n exports: [DashboardOptions, DashboardMiddleware],\n providers: [\n provide(DashboardOptions, { useValue: new DashboardOptions(init) }),\n middleware(),\n ],\n };\n }\n\n /**\n * `forRoot` with the options behind a factory - which is the **normal** way to\n * mount this, not the exotic one: `queues` wants `JobPublisher` and `redis` wants\n * `RedisConnection`, and both come out of the container.\n *\n * ```ts\n * DashboardModule.forRootAsync({\n * useFactory: (queues: JobPublisher, redis: RedisConnection) => ({\n * queues,\n * redis,\n * authorize: (req) => req.headers.get('x-admin-key') === process.env.ADMIN,\n * }),\n * inject: [JobPublisher, RedisConnection],\n * });\n * ```\n *\n * There is no separate async machinery: the container resolves eagerly and awaits\n * factories before any constructor runs.\n */\n static forRootAsync(\n load: () => DashboardOptionsInit | Promise<DashboardOptionsInit>,\n ): DynamicModule;\n static forRootAsync<const D extends Deps>(\n config: AsyncModuleConfig<DashboardOptionsInit, D>,\n ): DynamicModule;\n static forRootAsync(\n source:\n | (() => DashboardOptionsInit | Promise<DashboardOptionsInit>)\n | AsyncModuleConfig<DashboardOptionsInit, Deps>,\n ): DynamicModule {\n const load = typeof source === 'function' ? source : source.useFactory;\n const inject = typeof source === 'function' ? [] : (source.inject ?? []);\n // The container is scoped: this dynamic module is its own scope, so a factory\n // injecting `JobPublisher` needs the module that exports it in *these*\n // imports. Importing it into whatever module calls `forRootAsync` does not\n // reach here - that is a boot error naming both modules, clear but only once.\n // Forwarding is what makes the documented wiring work.\n const imports = typeof source === 'function' ? [] : (source.imports ?? []);\n\n return {\n module: DashboardModule,\n imports,\n exports: [DashboardOptions, DashboardMiddleware],\n providers: [\n provide(DashboardOptions, {\n useFactory: async (...deps: readonly unknown[]) =>\n new DashboardOptions(await load(...deps)),\n inject,\n }),\n middleware(),\n ],\n };\n }\n}\n",
6
- "import { Logger, type ModuleRef } from '@dunx/core';\nimport type { Middleware, Next, RouteContext } from '@dunx/http';\nimport type { BunRequest } from 'bun';\nimport { buildBoard, type Board } from './board.js';\nimport { DashboardOptions } from './options.js';\nimport { handleDashboard, type RouterDeps } from './router.js';\n\n/**\n * A **global middleware**, not a controller, and that is not a stylistic choice.\n *\n * Middleware registered with `app.use` runs in front of the unmatched-path\n * fallback, which is exactly where the dashboard's paths land because the app\n * declares none of them. Declaring them as dunx routes would mean generating\n * controllers for a route table handed over at runtime, and every panel would need\n * its own `@Get`.\n *\n * Two consequences of that, both learned the hard way and both worth keeping:\n *\n * - **Register it ahead of any session guard.** Measured in `dunx-template`: with\n * this last in the chain, `SessionGuard` answered every dashboard request `401`\n * before `authorize` ran, which defeats the 404 contract entirely. That works\n * only because `authorize` gets the raw `Request` and can ask the auth library\n * itself, so keep it self-sufficient.\n * - **Anything outside the mount falls through untouched**, so the app's own\n * routes and its 404 behave exactly as before.\n *\n * The page bundle is built on the **first request** and memoised on the promise,\n * so two concurrent first requests build one page and importing this package pulls\n * in none of its 400-odd KB.\n */\nexport class DashboardMiddleware implements Middleware {\n readonly #options: DashboardOptions;\n readonly #deps: RouterDeps;\n readonly #prefix: string;\n #page: Promise<string> | undefined;\n #board: Promise<Board> | undefined;\n\n constructor(options: DashboardOptions, root: ModuleRef, logger: Logger) {\n this.#options = options;\n // With the trailing slash, so `/_dunxious` cannot match a `/_dunx` mount. The\n // bare mount is matched separately.\n this.#prefix = `${options.path}/`;\n this.#deps = {\n root,\n options,\n startedAt: performance.now(),\n page: () => this.#renderPage(),\n board: () => this.#buildBoard(),\n };\n\n if (options.authorize === undefined) {\n logger.warn(\n `The dashboard at ${options.path} has no authorize function, so it is ` +\n 'served to anyone who can reach this port - including the route table, ' +\n 'the provider graph and the config keys. Pass ' +\n 'DashboardModule.forRoot({ authorize }) unless this port is private.',\n );\n }\n }\n\n async handle(\n req: BunRequest,\n _ctx: RouteContext,\n next: Next,\n ): Promise<Response> {\n const url = new URL(req.url);\n const { pathname } = url;\n if (pathname !== this.#options.path && !pathname.startsWith(this.#prefix)) {\n return next();\n }\n\n // 404, never 403. A dashboard that announces itself to an unauthenticated\n // caller has told them where to keep knocking - so a rejected request is\n // indistinguishable from a mount that is not there.\n if (this.#options.authorize && !(await this.#options.authorize(req))) {\n return Response.json(\n { error: 'NOT_FOUND', status: 404 },\n { status: 404 },\n );\n }\n\n const rest = pathname.slice(this.#options.path.length);\n return handleDashboard(this.#deps, req, rest);\n }\n\n /**\n * bull-board, built on the **first request for the queues page** and memoised on\n * the promise.\n *\n * Lazily, because building it calls `QueueSource.queue(name)` for every queue,\n * and that opens a connection to the broker. An app that mounts the dashboard and\n * never opens the queues page must not hold a socket for it - which is also what\n * lets a process exit cleanly against an absent Redis.\n */\n #buildBoard(): Promise<Board> {\n // The favicon comes out of the same lazily-imported module as the page\n // bundle, so bull-board wears the dunx mark without this file loading 400 KB\n // to find out what it is.\n this.#board ??= import('./ui.js').then(({ FAVICON }) =>\n buildBoard(this.#options, `${this.#options.path}/queues`, FAVICON),\n );\n return this.#board;\n }\n\n /**\n * The bundle lives behind `@dunx/dashboard/ui` and is reached with a dynamic\n * import, so an app that never opens the page never parses it. The **promise** is\n * memoised rather than the string, which is what makes two concurrent first\n * requests build one page.\n */\n #renderPage(): Promise<string> {\n this.#page ??= import('./ui.js').then(({ renderPage }) =>\n renderPage(this.#options),\n );\n return this.#page;\n }\n}\nObject.defineProperty(DashboardMiddleware, Symbol.for('dunx.deps'), {\n value: () => [DashboardOptions, { unresolved: \"root: ModuleRef\", typeOnly: \"ModuleRef\" }, Logger],\n});\n",
7
- "import type { DashboardOptions } from './options.js';\n\n/**\n * bull-board, mounted. **dunx renders no queue UI of its own.**\n *\n * This package briefly did - a table over `getJobCounts`, `getJobs` and seven\n * commands - and that was the wrong call under Rule 1's second half: never invent\n * what a mature library already solves. bull-board is years of edge cases with\n * flows, job logs, the repeatable-job editor, per-queue metrics, redis stats and a\n * pause/retry/clean surface nobody is going to re-derive correctly.\n *\n * The reason it was hand-rolled was that mounting bull-board on `Bun.serve` meant\n * writing a `BunServeAdapter` to satisfy its interface, which the deleted\n * `@dunx/queue-dashboard` did and which was a liability. **That reason expired:**\n * bull-board 8.6.0 ships `@bull-board/bun`, an official `BunAdapter`. So the\n * integration is now three calls and dunx owns none of the queue surface - which is\n * also what surfaced the `getWorkers()` bug: bull-board asks the question dunx's own\n * panel had decided not to, and the answer turned out to be wrong for a reason in\n * `@dunx/infra/queue`. See `QueueConnection.#handleErrors`.\n *\n * All three packages are **optional peers**. An app with no queues installs none of\n * them and the board is simply absent, with the queues nav item explaining why.\n */\n\n/** bull-board's `BaseAdapter`, restated to the one thing this file does with it. */\ninterface QueueAdapter {\n readonly name?: string;\n}\n\n/** The Bun route table `BunAdapter.getRoutes()` produces. */\nexport type BoardRoutes = Record<\n string,\n Record<string, (req: Request) => Response | Promise<Response>>\n>;\n\ninterface BullBoardModules {\n readonly createBullBoard: (config: {\n queues: readonly QueueAdapter[];\n serverAdapter: unknown;\n options?: { uiConfig: BoardUiConfig };\n }) => unknown;\n readonly BullMQAdapter: new (queue: unknown) => QueueAdapter;\n readonly BunAdapter: new () => {\n setBasePath(path: string): unknown;\n setQueues(queues: unknown): unknown;\n getRoutes(): BoardRoutes;\n };\n}\n\n/**\n * Loaded with `await import` inside a `try`, because all three are optional peers\n * and \"not installed\" is a normal state rather than a failure. The message names\n * the exact install line, since a missing optional peer is otherwise a stack trace\n * about a module specifier.\n */\nconst load = async (): Promise<BullBoardModules | undefined> => {\n try {\n const [api, bullmq, bun] = await Promise.all([\n import('@bull-board/api'),\n import('@bull-board/api/bullMQAdapter'),\n import('@bull-board/bun'),\n ]);\n // `as unknown as` throughout, and deliberately. These are **optional peers**\n // whose types are present when installed and absent when not, so this module\n // has to describe the surface it uses rather than import theirs - and the\n // structural distance between a hand-written description and bull-board's\n // real generics is exactly what a direct assertion refuses.\n return {\n createBullBoard:\n api.createBullBoard as unknown as BullBoardModules['createBullBoard'],\n BullMQAdapter:\n bullmq.BullMQAdapter as unknown as BullBoardModules['BullMQAdapter'],\n BunAdapter: bun.BunAdapter as unknown as BullBoardModules['BunAdapter'],\n };\n } catch {\n return undefined;\n }\n};\n\nexport interface Board {\n /** Why there is no board, for the page to show. Absent when there is one. */\n readonly unavailable?: string;\n readonly routes?: BoardRoutes;\n /** The entry route, so an unmatched client-side path serves the page. */\n readonly entry?: (req: Request) => Response | Promise<Response>;\n /** The queue names the board was built with, for the nav. */\n readonly queues: readonly string[];\n}\n\n/**\n * bull-board wants **queue objects**, not names, and `QueueSource.queue(name)`\n * opens one - so this is the one place the dashboard touches a broker. It happens\n * on the first request for the board and is memoised by the caller, not at boot: an\n * app that mounts the dashboard and never opens the queues page must not hold a\n * socket for it.\n */\n/**\n * The names alone, answerable **without opening anything**.\n *\n * That separation is the whole reason `/_dunx/api/queues` and `/_dunx/queues` are\n * different endpoints: the page asks this one on every poll to decide whether to\n * offer the link, and it must not open a socket to answer. Only somebody actually\n * opening the board does that.\n */\nexport const boardNames = (\n options: DashboardOptions,\n): { readonly names: readonly string[]; readonly unavailable?: string } => {\n if (options.queues === undefined) {\n return {\n names: [],\n unavailable:\n 'This app passed no `queues` to DashboardModule. `JobPublisher` from ' +\n '@dunx/infra/queue satisfies it as written.',\n };\n }\n\n const opened = options.queues.opened;\n const names = [\n ...opened,\n ...options.queueNames.filter((name) => !opened.includes(name)),\n ].sort();\n\n if (names.length === 0) {\n return {\n names,\n unavailable:\n 'This process has opened no queues. A queue it only consumes has never ' +\n 'been opened by the publisher - name it in DashboardOptions.queueNames.',\n };\n }\n return { names };\n};\n\n/**\n * bull-board's own `uiConfig`, which is where its title and tab icon come from.\n *\n * Set rather than left at \"Bull Dashboard\", because the board is reached from\n * inside this app's dashboard and a page that suddenly changes name and logo reads\n * as having left the site. The mark is `@dunx/ui`'s single declaration of it,\n * passed as a `data:` URI so bull-board fetches nothing for it either.\n *\n * `boardLogo` **and** `favIcon`: the first is the header, the second the tab, and\n * setting only one leaves the page half-branded.\n */\ninterface BoardUiConfig {\n readonly readOnlyMode: boolean;\n readonly boardTitle: string;\n /** The mark **in the board's own header**, beside the title. */\n readonly boardLogo: { path: string; width?: number; height?: number };\n /** The tab icon. Two cuts, because bull-board offers an SVG/PNG pair. */\n readonly favIcon: { default: string; alternative: string };\n}\n\nconst uiConfigFor = (\n options: DashboardOptions,\n favicon: string,\n): BoardUiConfig => ({\n readOnlyMode: !options.commands,\n boardTitle: `${options.title} queues`,\n // Both, not just the tab: `boardLogo` is the mark in bull-board's own header,\n // which is what actually makes the page look like part of this app rather than\n // a different product someone linked to.\n boardLogo: { path: favicon, width: 26, height: 26 },\n favIcon: { default: favicon, alternative: favicon },\n});\n\nexport const buildBoard = async (\n options: DashboardOptions,\n basePath: string,\n favicon: string,\n): Promise<Board> => {\n const { names, unavailable } = boardNames(options);\n\n if (unavailable !== undefined || options.queues === undefined) {\n return { unavailable: unavailable ?? 'no queue source', queues: names };\n }\n\n const modules = await load();\n if (modules === undefined) {\n return {\n unavailable:\n 'bull-board is not installed. It is an optional peer, so add it with ' +\n '`bun add @bull-board/api @bull-board/ui @bull-board/bun`.',\n queues: names,\n };\n }\n\n const { createBullBoard, BullMQAdapter, BunAdapter } = modules;\n const source = options.queues;\n const serverAdapter = new BunAdapter();\n serverAdapter.setBasePath(basePath);\n\n createBullBoard({\n queues: names.map((name) => new BullMQAdapter(source.queue(name))),\n serverAdapter,\n // `commands: false` maps straight onto bull-board's own `readOnlyMode` rather\n // than dunx refusing the POSTs itself. Enforcing it here would be a second\n // implementation of a switch the library already has, and one that disagreed\n // the moment bull-board grew an operation dunx had not heard of.\n options: { uiConfig: uiConfigFor(options, favicon) },\n });\n\n const routes = serverAdapter.getRoutes();\n // bull-board is a single-page app with its own router, so a path it renders\n // client-side - `/queue/emails?status=failed` - has no entry in the table. Its\n // own base route is what serves that, exactly as the dashboard's mount serves\n // its own panels.\n const entry = routes[basePath]?.['GET'];\n return {\n routes,\n queues: names,\n ...(entry === undefined ? {} : { entry }),\n };\n};\n\n/**\n * Bun's route patterns, matched against a path.\n *\n * bull-board's table is not a list of literals - it carries `:queueName`,\n * `:jobId`, `:queueStatus` and a `static/*` prefix, thirty-odd patterns in all.\n * The first version of this did exact-match plus one wildcard and 404'd every\n * job view and every per-queue API call, which is the bug that produced\n * `{\"error\":\"no such bull-board route\"}` on a page bull-board itself renders.\n *\n * This **is** a small path matcher, and dunx bans writing one - for the app's\n * routes, where `Bun.serve({ routes })` does it natively and better. It cannot\n * here: the board is built on the first request for it, long after `listen()`\n * folded the route table, so there is nothing to hand these to. The compensation\n * is that it never sees an app's routes, only the table bull-board handed over.\n */\ninterface Pattern {\n readonly segments: readonly string[];\n readonly wildcard: boolean;\n /** Literal segments, so a literal route beats a parameterised one. */\n readonly literals: number;\n readonly handlers: Record<\n string,\n (req: Request) => Response | Promise<Response>\n >;\n}\n\nconst compile = (routes: BoardRoutes): readonly Pattern[] =>\n Object.entries(routes)\n .map(([pattern, handlers]) => {\n const raw = pattern.split('/').filter(Boolean);\n const wildcard = raw.at(-1) === '*';\n const segments = wildcard ? raw.slice(0, -1) : raw;\n return {\n segments,\n wildcard,\n literals: segments.filter((s) => !s.startsWith(':')).length,\n handlers,\n };\n })\n // Most literal first, then longest. `/api/queues` must win over\n // `/api/queues/:queueName` for a path both could claim, and a wildcard is\n // the last thing tried.\n .sort(\n (a, b) =>\n b.literals - a.literals ||\n b.segments.length - a.segments.length ||\n Number(a.wildcard) - Number(b.wildcard),\n );\n\n/**\n * The params if it matches, `undefined` if it does not - an empty object is a\n * match with no parameters, which is a different answer.\n */\nconst matchParams = (\n pattern: Pattern,\n path: readonly string[],\n): Record<string, string> | undefined => {\n if (pattern.wildcard) {\n if (path.length < pattern.segments.length) return undefined;\n } else if (path.length !== pattern.segments.length) {\n return undefined;\n }\n\n const params: Record<string, string> = {};\n for (const [index, segment] of pattern.segments.entries()) {\n const value = path[index] ?? '';\n if (segment.startsWith(':')) {\n // Decoded, because a queue name may legitimately carry a space or a slash\n // and arrives percent-encoded. Bun does the same for its own params.\n params[segment.slice(1)] = decodeURIComponent(value);\n } else if (segment !== value) {\n return undefined;\n }\n }\n return params;\n};\n\n/**\n * Dispatches against bull-board's **own** route table - an exact path match, plus\n * the one `/*` prefix its static assets are served under.\n *\n * This is not the JavaScript router dunx bans. It never sees an app's routes: Bun\n * still matches everything real, this runs only inside the dashboard mount, and the\n * table it walks is the one bull-board handed over. Twenty lines here is the price\n * of not asking `HttpFactory` to accept a foreign route table at boot.\n */\nexport interface BoardMatch {\n readonly handler: (req: Request) => Response | Promise<Response>;\n /**\n * What `Bun.serve` would have put on `BunRequest.params`.\n *\n * bull-board's handlers read `request.params` - Bun populates it when *it*\n * matches a route pattern, and nothing populates it when the dispatch is\n * manual. Without this every parameterised route answered\n * `{\"error\":{\"key\":\"ERRORS.QUEUE_NOT_FOUND\"}}`: the route was found, the queue\n * name was not.\n */\n readonly params: Record<string, string>;\n}\n\nexport const matchBoard = (\n routes: BoardRoutes,\n method: string,\n pathname: string,\n): BoardMatch | undefined => {\n // The literal case is most of the traffic and costs one lookup.\n const exact = routes[pathname]?.[method];\n if (exact) return { handler: exact, params: {} };\n\n const path = pathname.split('/').filter(Boolean);\n for (const pattern of compile(routes)) {\n const params = matchParams(pattern, path);\n if (params === undefined) continue;\n const handler = pattern.handlers[method];\n if (handler) return { handler, params };\n }\n return undefined;\n};\n",
8
- "import type { BunRequest } from 'bun';\nimport type {\n ConfigValues,\n DashboardProbe,\n QueueSource,\n RedisProbe,\n} from './contracts.js';\n\n/**\n * Decides whether a request may see the dashboard at all.\n *\n * It receives the **raw `Request`**, not an `AuthContext` some earlier middleware\n * wrote, and that is load bearing rather than incidental. The dashboard middleware\n * has to be registered ahead of any session guard - measured in `dunx-template`,\n * where a guard running first answered every dashboard request `401` before\n * `authorize` was reached, which defeats the 404 contract below entirely. Running\n * first means there is nothing upstream to have written a context, so this has to\n * be able to ask the auth library itself.\n *\n * A rejected request gets **404, not 403**. A dashboard that announces itself to\n * an unauthenticated caller has told them where to keep knocking.\n */\nexport type Authorize = (req: BunRequest) => boolean | Promise<boolean>;\n\n/**\n * Whether a config value may be shown.\n *\n * **The default reveals nothing**, and that is the answer to the open question the\n * design left. `ConfigService` holds whatever the app's `validate` returned, which\n * includes every secret it has; a deny-list of the usual suspects - `SECRET`,\n * `PASSWORD`, `TOKEN` - looks careful and leaks the first key nobody thought of.\n * A deny-list that quietly misses one is worse than no config panel at all.\n *\n * So the panel shows **keys and types** by default, which is most of what it was\n * wanted for (\"is FEATURE_X actually set here\"), and a value appears only when this\n * predicate says so:\n *\n * ```ts\n * reveal: (key) => key.startsWith('PUBLIC_') || key === 'NODE_ENV'\n * ```\n *\n * There is no \"reveal\" affordance on the page. Redaction is decided at boot by the\n * app, not per click by whoever reached the page.\n */\nexport type Reveal = (key: string, value: unknown) => boolean;\n\nexport interface DashboardOptionsInit {\n /**\n * Where the page is mounted. `/_dunx` by default - the underscore keeps it\n * clear of an app's own routes, and the name is the framework's rather than\n * `/queues`, because queues are one panel of six.\n *\n * **`app.setGlobalPrefix('api')` does not move it.** That prefixes the routes\n * discovered from controllers, and this is not one of those - it is a middleware\n * matching a path, which is the whole reason the dashboard needs no controllers\n * for a table handed over at runtime. An app with a global prefix that wants the\n * page beside its routes writes `path: '/api/_dunx'` here.\n */\n readonly path?: string;\n /**\n * **There is no default, and leaving it out serves the page to anyone who can\n * reach the port.** That is fine behind a private network and bad everywhere\n * else, so it is stated either way rather than guessed: omitting it logs a\n * warning naming the mount path at boot.\n */\n readonly authorize?: Authorize;\n /** Shown in the header and the `<title>`. @default 'dunx' */\n readonly title?: string;\n /**\n * `JobPublisher` goes here. Absent means the queues panel says this process has\n * no queue source rather than that it has no queues.\n */\n readonly queues?: QueueSource;\n /**\n * Queues to show beyond the ones the source has opened. A process that\n * **consumes** a queue never publishes to it, so the publisher has never opened\n * it and it would otherwise be invisible on the page that exists to show it.\n */\n readonly queueNames?: readonly string[];\n /** `RedisConnection` goes here; it drives the Redis panel and one probe. */\n readonly redis?: RedisProbe;\n /** Anything else worth a light: a database, an upstream, a leader lease. */\n readonly probes?: readonly DashboardProbe[];\n /**\n * `ConfigService` goes here, and the panel is absent without it - showing an\n * app's configuration is something the app says yes to, not something this\n * package reaches into the container for.\n */\n readonly config?: ConfigValues;\n /** See {@link Reveal}. The default reveals nothing, even with `config` set. */\n readonly reveal?: Reveal;\n /**\n * Where `@dunx/openapi` serves its explorer, so the routes panel can link a row\n * to the operation that documents it. A string, not a dependency: the two\n * packages describe the same routes for different audiences and a link is free,\n * where importing one into the other is not.\n */\n readonly openApiPath?: string;\n /**\n * How often the live panels re-fetch, in milliseconds. `0` turns polling off and\n * leaves the refresh button.\n *\n * Polling rather than a websocket, deliberately: the page is stateless, a gateway\n * would put the dashboard in the app's own upgrade table, and 5 s is well inside\n * what \"how many jobs are failing\" needs.\n *\n * @default 5000\n */\n readonly pollMs?: number;\n /**\n * How long a probe may take before it is reported `unknown`. A hung probe must\n * cost one light, not the page.\n *\n * @default 2000\n */\n readonly probeTimeoutMs?: number;\n /**\n * Whether the queue board may change anything.\n *\n * Passed through to **bull-board's own `readOnlyMode`** rather than enforced\n * here: it already has the switch, and a second implementation would disagree\n * with it the moment bull-board grew an operation dunx had not heard of.\n *\n * The rest of the dashboard is read-only regardless - it reports on the process\n * and never acts on it - so this is entirely about the queues page. `authorize`\n * gates who reaches the mount; this gates what they can do once there.\n *\n * @default true\n */\n readonly commands?: boolean;\n}\n\n/**\n * A class, not an interface, so it is a runtime value and can therefore be a\n * constructor parameter type that `@dunx/transform` records - the same reason\n * `QueueOptions` and `RedisOptions` are classes.\n */\nexport class DashboardOptions {\n readonly path: string;\n readonly authorize: Authorize | undefined;\n readonly title: string;\n readonly queues: QueueSource | undefined;\n readonly queueNames: readonly string[];\n readonly redis: RedisProbe | undefined;\n readonly probes: readonly DashboardProbe[];\n readonly config: ConfigValues | undefined;\n readonly reveal: Reveal;\n readonly openApiPath: string | undefined;\n readonly pollMs: number;\n readonly probeTimeoutMs: number;\n readonly commands: boolean;\n\n constructor(init: DashboardOptionsInit = {}) {\n this.path = normalizeMount(init.path ?? '/_dunx');\n this.authorize = init.authorize;\n this.title = init.title ?? 'dunx';\n this.queues = init.queues;\n this.queueNames = init.queueNames ?? [];\n this.redis = init.redis;\n this.probes = init.probes ?? [];\n this.config = init.config;\n this.reveal = init.reveal ?? (() => false);\n this.openApiPath = init.openApiPath;\n this.pollMs = init.pollMs ?? 5000;\n this.probeTimeoutMs = init.probeTimeoutMs ?? 2000;\n this.commands = init.commands ?? true;\n }\n}\nObject.defineProperty(DashboardOptions, Symbol.for('dunx.deps'), {\n value: () => [{ unresolved: \"init: DashboardOptionsInit = {}\" }],\n});\n\n/**\n * A leading slash and no trailing one, so `${path}/api/...` is never `//api`.\n *\n * `/` itself is rejected: mounting the dashboard at the root would swallow every\n * unmatched path in the app, and the middleware's whole contract is that anything\n * outside its mount falls through untouched.\n */\nexport const normalizeMount = (path: string): string => {\n const trimmed = `/${path.split('/').filter(Boolean).join('/')}`;\n if (trimmed === '/') {\n throw new Error(\n 'DashboardOptions.path cannot be \"/\": the dashboard is a middleware that ' +\n 'claims every path under its mount, so mounting it at the root would ' +\n 'answer every request in the app. Use \"/_dunx\" or another prefix.',\n );\n }\n return trimmed;\n};\n",
9
- "/**\n * Every read the dashboard makes off-process is bounded, and this is the one\n * implementation of that.\n *\n * The failure it exists for is specific and was measured: with Redis unreachable,\n * a queue's `getJobCounts` waits out the connection timeout - 5 s by default in\n * `@dunx/infra/queue` - so opening the dashboard on a broken broker hung the page\n * for exactly as long as the thing you opened it to look at was broken. A\n * dependency being down must cost one panel, not the page.\n *\n * The fallback is a **value**, not a rejection: a queue that could not be reached\n * still gets a row saying so, which is the whole point of looking.\n */\nexport const bounded = async <T>(\n work: () => Promise<T>,\n ms: number,\n onTimeout: () => T,\n): Promise<T> => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n work(),\n new Promise<T>((resolve) => {\n timer = setTimeout(() => resolve(onTimeout()), ms);\n }),\n ]);\n } finally {\n // Unconditional: the winner is usually the work, and leaving the timer armed\n // would hold a handle per poll - which on a 5 s interval is a leak.\n if (timer !== undefined) clearTimeout(timer);\n }\n};\n",
10
- "import type { RedisProbe } from '../contracts.js';\nimport { bounded } from './bounded.js';\nimport type { RedisReport } from './types.js';\n\n/**\n * Whether the broker is up and what it is doing.\n *\n * This survived the move to bull-board while the queue table did not, and the line\n * between them is worth stating: bull-board answers **what is in the queues**, and\n * this answers **whether the thing underneath them is healthy** - which is a\n * question an app with a cache and no queues at all still has. It is one `PING` and\n * one `INFO`, not a Redis client.\n */\nconst message = (error: unknown): string =>\n error instanceof Error ? error.message : String(error);\n\n/** The handful of `INFO` fields worth a panel, out of roughly two hundred. */\nconst INFO_FIELDS = [\n 'redis_version',\n 'redis_mode',\n 'os',\n 'uptime_in_seconds',\n 'connected_clients',\n 'blocked_clients',\n 'used_memory_human',\n 'used_memory_peak_human',\n 'maxmemory_human',\n 'keyspace_hits',\n 'keyspace_misses',\n 'total_commands_processed',\n 'rejected_connections',\n] as const;\n\n/**\n * `INFO` replies as a text blob of `key:value` lines with `# Section` headers.\n * Parsed here rather than adding a method per field to `RedisProbe`, which is how a\n * structural restatement turns into a client library.\n */\nexport const parseInfo = (raw: string): Readonly<Record<string, string>> => {\n const wanted = new Set<string>(INFO_FIELDS);\n const out: Record<string, string> = {};\n for (const line of raw.split(/\\r?\\n/)) {\n if (line === '' || line.startsWith('#')) continue;\n const colon = line.indexOf(':');\n if (colon < 1) continue;\n const key = line.slice(0, colon);\n if (wanted.has(key)) out[key] = line.slice(colon + 1);\n }\n return out;\n};\n\nexport const redisReport = async (\n redis: RedisProbe,\n timeoutMs: number,\n): Promise<RedisReport> => {\n const started = performance.now();\n const failed = (error: string): RedisReport => ({\n configured: true,\n connected: redis.connected,\n pingMs: undefined,\n info: {},\n error,\n });\n\n return bounded(\n async () => {\n try {\n await redis.ping();\n const pingMs = Math.round(performance.now() - started);\n const raw = await redis.send('INFO', []);\n return {\n configured: true as const,\n connected: redis.connected,\n pingMs,\n info: typeof raw === 'string' ? parseInfo(raw) : {},\n };\n } catch (error) {\n return failed(message(error));\n }\n },\n timeoutMs,\n // A broker that is merely *slow* never rejects - it waits out the connection\n // timeout, 5 s by default - so the row has to be produced by the clock.\n () => failed(`no answer in ${timeoutMs}ms`),\n );\n};\n",
11
- "import type { DashboardProbe, ProbeResult } from '../contracts.js';\nimport type { DashboardOptions } from '../options.js';\nimport { bounded } from './bounded.js';\nimport type { ProbeReport, RuntimeReport } from './types.js';\n\n/**\n * `unknown` on a timeout, **not** `down`. A probe that did not answer in two\n * seconds has told us nothing about the service, and saying `down` would send\n * somebody to restart something healthy. A probe that *threw* did tell us\n * something, so that one is `down`.\n */\nconst withTimeout = (probe: DashboardProbe, ms: number): Promise<ProbeResult> =>\n bounded(\n async () => {\n try {\n return await probe.check();\n } catch (error) {\n return {\n state: 'down',\n detail: error instanceof Error ? error.message : String(error),\n } as const;\n }\n },\n ms,\n () => ({ state: 'unknown', detail: `no answer in ${ms}ms` }),\n );\n\nexport const runProbe = async (\n probe: DashboardProbe,\n timeoutMs: number,\n): Promise<ProbeReport> => {\n const started = performance.now();\n const result = await withTimeout(probe, timeoutMs);\n return {\n name: probe.name,\n state: result.state,\n ...(result.detail === undefined ? {} : { detail: result.detail }),\n ms: Math.round(performance.now() - started),\n };\n};\n\n/**\n * The Redis handle, if the app passed one, as a probe like any other - so the\n * lights row has one shape and the panel does not special-case its own dependency.\n *\n * `ping` rather than `connected`: the flag says whether a socket is up, and a\n * round trip says whether the server is answering, which is the question.\n */\nexport const redisProbe = (\n redis: NonNullable<DashboardOptions['redis']>,\n): DashboardProbe => ({\n name: 'redis',\n check: async (): Promise<ProbeResult> => {\n const started = performance.now();\n await redis.ping();\n return {\n state: 'up',\n detail: `PING ${Math.round(performance.now() - started)}ms`,\n };\n },\n});\n\nconst memory = (): RuntimeReport['memory'] => {\n const usage = process.memoryUsage();\n return {\n rss: usage.rss,\n heapUsed: usage.heapUsed,\n heapTotal: usage.heapTotal,\n external: usage.external,\n };\n};\n\nexport const runtimeReport = async (\n options: DashboardOptions,\n startedAt: number,\n): Promise<RuntimeReport> => {\n const probes = [\n ...(options.redis ? [redisProbe(options.redis)] : []),\n ...options.probes,\n ];\n\n return {\n pid: process.pid,\n // From when the middleware was constructed rather than `process.uptime()`,\n // which counts from the interpreter starting - a difference that matters in a\n // process that spent thirty seconds running migrations before it served.\n uptimeMs: Math.round(performance.now() - startedAt),\n bun: Bun.version,\n platform: process.platform,\n arch: process.arch,\n memory: memory(),\n // Concurrent, and each already bounded, so the endpoint costs the slowest\n // probe rather than their sum.\n probes: await Promise.all(\n probes.map((probe) => runProbe(probe, options.probeTimeoutMs)),\n ),\n now: Date.now(),\n };\n};\n",
12
- "import type { ModuleRef } from '@dunx/core';\nimport { boardNames, matchBoard, type Board } from './board.js';\nimport { redisReport } from './api/redis.js';\nimport { runtimeReport } from './api/runtime.js';\nimport { snapshotOf } from './api/snapshot.js';\nimport type { DashboardOptions } from './options.js';\n\n/**\n * What the mount answers, and nothing else. A path outside it never reaches here -\n * the middleware calls `next()` - which is what keeps the app's own routes and its\n * 404 behaving exactly as before.\n *\n * Three kinds of thing live under the mount:\n *\n * - `/` and any other non-`api`, non-`queues` path serve the page, so a client-side\n * route survives a reload.\n * - `/api/*` are dunx's own JSON endpoints. Every panel has one, and they are\n * supported rather than an implementation detail of the page - `curl` is a real\n * way to read this.\n * - `/queues/*` is **bull-board's**, dispatched against its own route table. dunx\n * renders no queue UI; see `board.ts`.\n */\n\nconst json = (body: unknown, status = 200): Response =>\n Response.json(body, {\n status,\n // The page and its data are the same origin and the same process; a cached\n // runtime report is a lie with a timestamp on it.\n headers: { 'cache-control': 'no-store' },\n });\n\n/** The one shape every failure inside the mount takes. */\nconst fail = (status: number, error: string): Response =>\n json({ error }, status);\n\nexport interface RouterDeps {\n readonly root: ModuleRef;\n readonly options: DashboardOptions;\n readonly startedAt: number;\n /** The HTML page, built lazily so importing this package does not load it. */\n readonly page: () => Promise<string>;\n /** bull-board, built lazily so an app that never opens it holds no socket. */\n readonly board: () => Promise<Board>;\n}\n\nconst handleApi = async (\n deps: RouterDeps,\n method: string,\n segments: readonly string[],\n): Promise<Response> => {\n if (method !== 'GET') return fail(405, `${method} is not allowed here`);\n\n switch (segments[0]) {\n case 'snapshot':\n return json(snapshotOf(deps.root, deps.options));\n case 'runtime':\n return json(await runtimeReport(deps.options, deps.startedAt));\n case 'redis':\n return json(\n deps.options.redis === undefined\n ? { configured: false }\n : await redisReport(deps.options.redis, deps.options.probeTimeoutMs),\n );\n case 'queues': {\n // Names only, read straight off the options - **not** through `deps.board()`,\n // which would open a connection per queue. The page polls this to decide\n // whether to offer the link, and a poll must not have side effects.\n const { names, unavailable } = boardNames(deps.options);\n return json({\n queues: names,\n ...(unavailable === undefined ? {} : { unavailable }),\n });\n }\n default:\n return fail(404, 'no such dashboard endpoint');\n }\n};\n\n/**\n * `rest` is the path with the mount already stripped: `''` for the page itself,\n * `api/runtime` for a data call, `queues/...` for bull-board.\n */\nexport const handleDashboard = async (\n deps: RouterDeps,\n request: Request,\n rest: string,\n): Promise<Response> => {\n const segments = rest.split('/').filter(Boolean);\n const { method } = request;\n\n if (segments[0] === 'api') {\n try {\n return await handleApi(deps, method, segments.slice(1));\n } catch (error) {\n // Nothing inside the mount may throw into the app's error mapper: a\n // dashboard read that failed is a dashboard problem, and a 500 shaped like\n // the app's own errors would send someone looking in the wrong place.\n return fail(500, error instanceof Error ? error.message : String(error));\n }\n }\n\n if (segments[0] === 'queues') {\n const board = await deps.board();\n if (board.routes === undefined) {\n return fail(503, board.unavailable ?? 'no queue board');\n }\n const match = matchBoard(\n board.routes,\n method,\n new URL(request.url).pathname,\n );\n if (match !== undefined) {\n // bull-board reads `request.params`, which `Bun.serve` fills in when it\n // does the matching. This dispatch is manual, so the field has to be put\n // there or every `:queueName` route answers QUEUE_NOT_FOUND.\n Object.defineProperty(request, 'params', {\n value: match.params,\n configurable: true,\n });\n return match.handler(request);\n }\n\n // Nothing in its table, so it is one of bull-board's own client-side routes -\n // `/queue/emails?status=failed` is rendered by its router, not its server.\n // Its entry route serves those, which is the same thing the dashboard's mount\n // does for its own panels. Only for a GET: a write to a path nothing declares\n // is a real 404.\n if (method === 'GET' && board.entry) return board.entry(request);\n return fail(404, 'no such bull-board route');\n }\n\n if (method !== 'GET') return fail(405, `${method} is not allowed here`);\n\n return new Response(await deps.page(), {\n headers: {\n 'content-type': 'text/html; charset=utf-8',\n 'cache-control': 'no-store',\n },\n });\n};\n"
13
- ],
14
- "mappings": "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",
15
- "debugId": "CFA504A7E130188D64756E2164756E21",
16
- "names": []
17
- }