@getworkbench/core 0.4.0 → 0.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.
@@ -226,6 +226,18 @@ function buildRouteTable(core) {
226
226
  return { status: 200, body: { success: true } };
227
227
  }
228
228
  },
229
+ {
230
+ method: "post",
231
+ path: "/schedulers/:queue/:key/run",
232
+ handler: async ({ params }) => {
233
+ if (isReadonly()) return readonlyError;
234
+ const result = await qm.runSchedulerNow(params.queue, params.key);
235
+ if (!result) {
236
+ return { status: 400, body: { error: "Failed to run scheduler" } };
237
+ }
238
+ return { status: 200, body: result };
239
+ }
240
+ },
229
241
  {
230
242
  method: "get",
231
243
  path: "/search",
@@ -568,4 +580,4 @@ export {
568
580
  renderIndexHtml,
569
581
  buildWorkbenchApp
570
582
  };
571
- //# sourceMappingURL=chunk-O7DNWUZD.js.map
583
+ //# sourceMappingURL=chunk-4GN4APH7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/api/handlers.ts","../src/api/router.ts","../src/server/base-path.ts","../src/ui-dist.ts","../src/server/static-assets.ts","../src/server/hono-app.ts"],"sourcesContent":["import type {\n CreateFlowRequest,\n JobStatus,\n SortOptions,\n TestJobRequest,\n} from \"../core/types\";\nimport type { WorkbenchCore } from \"../core/workbench\";\n\n/**\n * Framework-agnostic HTTP method.\n */\nexport type HttpMethod = \"get\" | \"post\" | \"put\" | \"patch\" | \"delete\";\n\n/**\n * Normalized input passed to every handler. Adapters are responsible for\n * mapping their framework-specific request shape to this.\n */\nexport interface HandlerInput {\n params: Record<string, string>;\n query: Record<string, string | undefined>;\n body?: unknown;\n}\n\n/**\n * Normalized output returned by every handler. Adapters serialize this\n * onto their framework-specific response object.\n */\nexport interface HandlerResult {\n status: number;\n body: unknown;\n}\n\n/**\n * A framework-agnostic route handler. Closes over a `WorkbenchCore` and\n * takes a normalized request envelope.\n */\nexport type Handler = (input: HandlerInput) => Promise<HandlerResult>;\n\n/**\n * A framework-agnostic route definition.\n *\n * `path` uses `:param` syntax compatible with Hono, Express, and Fastify.\n * Paths are relative to `/api` — adapters mount them under that prefix.\n */\nexport interface RouteDef {\n method: HttpMethod;\n path: string;\n handler: Handler;\n}\n\n/**\n * Parse sort query param in format \"field:direction\" (e.g., \"timestamp:desc\")\n * Defaults to desc if direction not specified.\n */\nfunction parseSort(sort?: string): SortOptions | undefined {\n if (!sort) return undefined;\n const [field, dir] = sort.split(\":\");\n if (!field) return undefined;\n return {\n field,\n direction: dir === \"asc\" ? \"asc\" : \"desc\",\n };\n}\n\nconst readonlyError = {\n status: 403 as const,\n body: { error: \"Dashboard is in readonly mode\" },\n};\n\n/**\n * Build the framework-agnostic route table for the Workbench API.\n *\n * Adapters iterate this list and register each route on their host framework.\n * Paths are relative to `/api`.\n */\nexport function buildRouteTable(core: WorkbenchCore): RouteDef[] {\n const qm = core.queueManager;\n const isReadonly = () => !!core.options.readonly;\n\n return [\n {\n method: \"post\",\n path: \"/refresh\",\n handler: async () => {\n qm.clearCache();\n return { status: 200, body: { success: true } };\n },\n },\n\n {\n method: \"get\",\n path: \"/overview\",\n handler: async () => ({\n status: 200,\n body: await qm.getOverview(),\n }),\n },\n\n {\n method: \"get\",\n path: \"/counts\",\n handler: async () => ({\n status: 200,\n body: await qm.getQuickCounts(),\n }),\n },\n\n {\n method: \"get\",\n path: \"/runs\",\n handler: async ({ query }) => {\n const limit = Number(query.limit) || 50;\n const cursor = query.cursor;\n const start = cursor ? Number(cursor) : 0;\n const sort = parseSort(query.sort);\n\n const status = query.status as JobStatus | undefined;\n const q = query.q;\n const from = query.from;\n const to = query.to;\n const tagsParam = query.tags;\n\n let tags: Record<string, string> | undefined;\n if (tagsParam) {\n try {\n tags = JSON.parse(tagsParam);\n } catch {\n const tagPairs = tagsParam.split(\",\");\n tags = {};\n for (const pair of tagPairs) {\n const [key, value] = pair.split(\":\");\n if (key && value) {\n tags[key.trim()] = value.trim();\n }\n }\n }\n }\n\n let timeRange: { start: number; end: number } | undefined;\n if (from && to) {\n timeRange = {\n start: Number(from),\n end: Number(to),\n };\n }\n\n let text: string | undefined;\n if (q) {\n if (!q.includes(\":\")) {\n text = q;\n } else {\n const parts = q.split(\" \");\n const textParts = parts.filter((p) => !p.includes(\":\"));\n if (textParts.length > 0) {\n text = textParts.join(\" \");\n }\n }\n }\n\n const filters =\n status || tags || text || timeRange\n ? {\n status,\n tags,\n text,\n timeRange,\n }\n : undefined;\n\n return {\n status: 200,\n body: await qm.getAllRuns(limit, start, sort, filters),\n };\n },\n },\n\n {\n method: \"get\",\n path: \"/schedulers\",\n handler: async ({ query }) => {\n const repeatableSort = parseSort(query.repeatableSort);\n const delayedSort = parseSort(query.delayedSort);\n return {\n status: 200,\n body: await qm.getSchedulers(repeatableSort, delayedSort),\n };\n },\n },\n\n {\n method: \"post\",\n path: \"/test\",\n handler: async ({ body }) => {\n if (isReadonly()) return readonlyError;\n const req = body as TestJobRequest | undefined;\n\n if (!req?.queueName || !req.jobName) {\n return {\n status: 400,\n body: { error: \"queueName and jobName are required\" },\n };\n }\n\n try {\n const result = await qm.enqueueJob(req);\n return { status: 200, body: result };\n } catch (e) {\n return { status: 400, body: { error: (e as Error).message } };\n }\n },\n },\n\n {\n method: \"get\",\n path: \"/queue-names\",\n handler: async () => ({\n status: 200,\n body: qm.getQueueNames(),\n }),\n },\n\n {\n method: \"get\",\n path: \"/queues\",\n handler: async () => ({\n status: 200,\n body: await qm.getQueues(),\n }),\n },\n\n {\n method: \"get\",\n path: \"/metrics\",\n handler: async () => ({\n status: 200,\n body: await qm.getMetrics(),\n }),\n },\n\n {\n method: \"get\",\n path: \"/activity\",\n handler: async () => ({\n status: 200,\n body: await qm.getActivityStats(),\n }),\n },\n\n {\n method: \"get\",\n path: \"/queues/:name/jobs\",\n handler: async ({ params, query }) => {\n const name = params.name!;\n const status = query.status as JobStatus | undefined;\n const limit = Number(query.limit) || 50;\n const cursor = query.cursor;\n const start = cursor ? Number(cursor) : 0;\n const sort = parseSort(query.sort);\n\n return {\n status: 200,\n body: await qm.getJobs(name, status, limit, start, sort),\n };\n },\n },\n\n {\n method: \"get\",\n path: \"/jobs/:queue/:id\",\n handler: async ({ params }) => {\n const job = await qm.getJob(params.queue!, params.id!);\n if (!job) {\n return { status: 404, body: { error: \"Job not found\" } };\n }\n return { status: 200, body: job };\n },\n },\n\n {\n method: \"post\",\n path: \"/jobs/:queue/:id/retry\",\n handler: async ({ params }) => {\n if (isReadonly()) return readonlyError;\n const success = await qm.retryJob(params.queue!, params.id!);\n if (!success) {\n return { status: 400, body: { error: \"Failed to retry job\" } };\n }\n return { status: 200, body: { success: true } };\n },\n },\n\n {\n method: \"post\",\n path: \"/jobs/:queue/:id/remove\",\n handler: async ({ params }) => {\n if (isReadonly()) return readonlyError;\n const success = await qm.removeJob(params.queue!, params.id!);\n if (!success) {\n return { status: 400, body: { error: \"Failed to remove job\" } };\n }\n return { status: 200, body: { success: true } };\n },\n },\n\n {\n method: \"post\",\n path: \"/jobs/:queue/:id/promote\",\n handler: async ({ params }) => {\n if (isReadonly()) return readonlyError;\n const success = await qm.promoteJob(params.queue!, params.id!);\n if (!success) {\n return { status: 400, body: { error: \"Failed to promote job\" } };\n }\n return { status: 200, body: { success: true } };\n },\n },\n\n {\n method: \"post\",\n path: \"/schedulers/:queue/:key/run\",\n handler: async ({ params }) => {\n if (isReadonly()) return readonlyError;\n const result = await qm.runSchedulerNow(params.queue!, params.key!);\n if (!result) {\n return { status: 400, body: { error: \"Failed to run scheduler\" } };\n }\n return { status: 200, body: result };\n },\n },\n\n {\n method: \"get\",\n path: \"/search\",\n handler: async ({ query }) => {\n const q = query.q || \"\";\n const limit = Number(query.limit) || 20;\n if (!q) return { status: 200, body: { results: [] } };\n const results = await qm.search(q, limit);\n return { status: 200, body: { results } };\n },\n },\n\n {\n method: \"get\",\n path: \"/tags/:field/values\",\n handler: async ({ params, query }) => {\n const field = params.field!;\n const limit = Number(query.limit) || 50;\n\n const tagFields = qm.getTagFields();\n if (tagFields.length > 0 && !tagFields.includes(field)) {\n return {\n status: 400,\n body: {\n error: `Field \"${field}\" is not a configured tag field`,\n },\n };\n }\n\n const values = await qm.getTagValues(field, limit);\n return { status: 200, body: { field, values } };\n },\n },\n\n {\n method: \"post\",\n path: \"/queues/:name/clean\",\n handler: async ({ params, body }) => {\n if (isReadonly()) return readonlyError;\n const req = body as\n | { status: \"completed\" | \"failed\"; grace?: number }\n | undefined;\n if (!req) {\n return { status: 400, body: { error: \"Body required\" } };\n }\n const count = await qm.cleanJobs(\n params.name!,\n req.status,\n req.grace || 0,\n );\n return { status: 200, body: { removed: count } };\n },\n },\n\n {\n method: \"post\",\n path: \"/bulk/retry\",\n handler: async ({ body }) => {\n if (isReadonly()) return readonlyError;\n const req = body as\n | { jobs: { queueName: string; jobId: string }[] }\n | undefined;\n if (!req?.jobs) {\n return { status: 400, body: { error: \"jobs is required\" } };\n }\n return { status: 200, body: await qm.bulkRetry(req.jobs) };\n },\n },\n\n {\n method: \"post\",\n path: \"/bulk/delete\",\n handler: async ({ body }) => {\n if (isReadonly()) return readonlyError;\n const req = body as\n | { jobs: { queueName: string; jobId: string }[] }\n | undefined;\n if (!req?.jobs) {\n return { status: 400, body: { error: \"jobs is required\" } };\n }\n return { status: 200, body: await qm.bulkDelete(req.jobs) };\n },\n },\n\n {\n method: \"post\",\n path: \"/bulk/promote\",\n handler: async ({ body }) => {\n if (isReadonly()) return readonlyError;\n const req = body as\n | { jobs: { queueName: string; jobId: string }[] }\n | undefined;\n if (!req?.jobs) {\n return { status: 400, body: { error: \"jobs is required\" } };\n }\n return { status: 200, body: await qm.bulkPromote(req.jobs) };\n },\n },\n\n {\n method: \"post\",\n path: \"/queues/:name/pause\",\n handler: async ({ params }) => {\n if (isReadonly()) return readonlyError;\n try {\n await qm.pauseQueue(params.name!);\n return { status: 200, body: { success: true, paused: true } };\n } catch (error) {\n return {\n status: 404,\n body: {\n error:\n error instanceof Error\n ? error.message\n : \"Failed to pause queue\",\n },\n };\n }\n },\n },\n\n {\n method: \"post\",\n path: \"/queues/:name/resume\",\n handler: async ({ params }) => {\n if (isReadonly()) return readonlyError;\n try {\n await qm.resumeQueue(params.name!);\n return { status: 200, body: { success: true, paused: false } };\n } catch (error) {\n return {\n status: 404,\n body: {\n error:\n error instanceof Error\n ? error.message\n : \"Failed to resume queue\",\n },\n };\n }\n },\n },\n\n {\n method: \"get\",\n path: \"/flows\",\n handler: async ({ query }) => {\n const limit = Number(query.limit) || 50;\n const flows = await qm.getFlows(limit);\n return { status: 200, body: { flows } };\n },\n },\n\n {\n method: \"get\",\n path: \"/flows/:queueName/:jobId\",\n handler: async ({ params }) => {\n const flow = await qm.getFlow(params.queueName!, params.jobId!);\n if (!flow) {\n return { status: 404, body: { error: \"Flow not found\" } };\n }\n return { status: 200, body: flow };\n },\n },\n\n {\n method: \"post\",\n path: \"/flows\",\n handler: async ({ body }) => {\n if (isReadonly()) return readonlyError;\n const req = body as CreateFlowRequest | undefined;\n\n if (!req?.name || !req.queueName || !req.children?.length) {\n return {\n status: 400,\n body: { error: \"name, queueName, and children are required\" },\n };\n }\n\n try {\n const result = await qm.createFlow(req);\n return { status: 200, body: result };\n } catch (e) {\n return { status: 400, body: { error: (e as Error).message } };\n }\n },\n },\n ];\n}\n","import { Hono } from \"hono\";\nimport type { WorkbenchCore } from \"../core/workbench\";\nimport { buildRouteTable, type HandlerInput } from \"./handlers\";\n\n/**\n * Create API routes for Workbench as a Hono app.\n *\n * Iterates the framework-agnostic `buildRouteTable(core)` and registers\n * each route on a fresh Hono instance. Adapters that don't speak Hono can\n * use `buildRouteTable` directly — see `@getworkbench/express` and\n * `@getworkbench/fastify`.\n */\nexport function createApiRoutes(core: WorkbenchCore): Hono {\n const app = new Hono();\n\n for (const route of buildRouteTable(core)) {\n app[route.method](route.path, async (c) => {\n let body: unknown;\n const method = c.req.method;\n if (method !== \"GET\" && method !== \"HEAD\") {\n const contentType = c.req.header(\"content-type\") ?? \"\";\n if (contentType.includes(\"application/json\")) {\n try {\n body = await c.req.json();\n } catch {\n body = undefined;\n }\n }\n }\n\n const input: HandlerInput = {\n params: c.req.param() as Record<string, string>,\n query: c.req.query(),\n body,\n };\n\n const result = await route.handler(input);\n return new Response(JSON.stringify(result.body), {\n status: result.status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n });\n }\n\n return app;\n}\n","/**\n * Compute the dashboard's base path from an incoming request URL.\n *\n * The dashboard is mounted at some prefix (e.g. `/jobs`) and the client\n * router renders deep links like `/jobs/queues/email`, `/jobs/metrics`,\n * `/jobs/flows/email/123`, etc. The HTML `<base href>` needs to point at\n * the mount prefix so client-side asset URLs resolve correctly — strip\n * any client-side route segment from the pathname.\n */\nconst CLIENT_ROUTES: RegExp[] = [\n /\\/queues\\/[^/]+\\/jobs\\/[^/]+\\/?$/,\n /\\/queues\\/[^/]+\\/?$/,\n /\\/flows\\/[^/]+\\/[^/]+\\/?$/,\n /\\/schedulers\\/?$/,\n /\\/flows\\/?$/,\n /\\/metrics\\/?$/,\n /\\/test\\/?$/,\n];\n\nexport function computeBasePath(pathname: string): string {\n let basePath = pathname;\n for (const route of CLIENT_ROUTES) {\n basePath = basePath.replace(route, \"\");\n }\n if (!basePath.endsWith(\"/\")) {\n basePath = `${basePath}/`;\n }\n return basePath;\n}\n\n/**\n * Resolve the dashboard's base path, preferring an explicit override.\n *\n * Adapters where the host framework preserves the mount prefix on the\n * incoming URL (Hono `.route()`, Express `req.originalUrl`, Next.js route\n * files) can rely on auto-detection. Adapters where the prefix is stripped\n * before the handler runs (Elysia `.mount()`) require the user to pass\n * `basePath` so the dashboard's HTML still references assets under the\n * correct prefix.\n */\nexport function resolveBasePath(\n override: string | undefined,\n pathname: string,\n): string {\n if (override) {\n return override.endsWith(\"/\") ? override : `${override}/`;\n }\n return computeBasePath(pathname);\n}\n","import { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * Absolute filesystem path to the bundled UI assets (index.html + /assets).\n * Adapters that don't go through {@link createFetchHandler} serve static\n * files from this directory directly.\n */\nexport const UI_DIST_PATH = join(dirname(fileURLToPath(import.meta.url)), \"ui\");\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { UI_DIST_PATH } from \"../ui-dist\";\n\nexport interface StaticAssetResult {\n status: 200 | 404;\n body: Buffer | null;\n contentType: string;\n}\n\n/**\n * Read a bundled UI asset from `UI_DIST_PATH/assets/<filename>`.\n *\n * Returns a uniform `{ status, body, contentType }` shape so each adapter\n * can serialize it onto its framework-native response without re-implementing\n * the file lookup or content-type sniffing.\n */\nexport function serveStaticAsset(filename: string): StaticAssetResult {\n const filePath = join(UI_DIST_PATH, \"assets\", filename);\n\n if (!existsSync(filePath)) {\n return { status: 404, body: null, contentType: \"text/plain\" };\n }\n\n const body = readFileSync(filePath);\n const contentType = filename.endsWith(\".js\")\n ? \"application/javascript\"\n : filename.endsWith(\".css\")\n ? \"text/css\"\n : filename.endsWith(\".svg\")\n ? \"image/svg+xml\"\n : filename.endsWith(\".png\")\n ? \"image/png\"\n : filename.endsWith(\".woff2\")\n ? \"font/woff2\"\n : \"application/octet-stream\";\n\n return { status: 200, body, contentType };\n}\n\nexport interface IndexHtmlResult {\n body: string;\n contentType: \"text/html; charset=utf-8\";\n}\n\n/**\n * Read the bundled `index.html`, inject a `<base href>` matching the request's\n * mount path so client-side asset URLs resolve correctly, and return it.\n *\n * Falls back to a tiny \"UI assets not found\" stub when the core package has\n * not been built yet — useful for `bun run dev` against a fresh checkout.\n */\nexport function renderIndexHtml(\n basePath: string,\n title: string,\n): IndexHtmlResult {\n const indexPath = join(UI_DIST_PATH, \"index.html\");\n\n if (existsSync(indexPath)) {\n let html = readFileSync(indexPath, \"utf-8\");\n html = html.replace(\"<head>\", `<head>\\n <base href=\"${basePath}\">`);\n return { body: html, contentType: \"text/html; charset=utf-8\" };\n }\n\n return {\n body: fallbackHtml(title, basePath),\n contentType: \"text/html; charset=utf-8\",\n };\n}\n\nfunction fallbackHtml(title: string, basePath: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <base href=\"${basePath}\">\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>${title}</title>\n <style>\n body {\n font-family: system-ui, sans-serif;\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n margin: 0;\n background: #0a0a0a;\n color: #fafafa;\n }\n .message {\n text-align: center;\n padding: 2rem;\n }\n code {\n background: #1a1a1a;\n padding: 0.5rem 1rem;\n border-radius: 0.5rem;\n display: block;\n margin-top: 1rem;\n }\n </style>\n </head>\n <body>\n <div class=\"message\">\n <h1>${title}</h1>\n <p>UI assets not found. Build @getworkbench/core first:</p>\n <code>bun run --filter=@getworkbench/core build</code>\n </div>\n </body>\n</html>`;\n}\n","import { Hono } from \"hono\";\nimport { basicAuth } from \"hono/basic-auth\";\nimport { cors } from \"hono/cors\";\nimport { createApiRoutes } from \"../api/router\";\nimport type { WorkbenchCore } from \"../core/workbench\";\nimport { resolveBasePath } from \"./base-path\";\nimport { renderIndexHtml, serveStaticAsset } from \"./static-assets\";\n\n/**\n * Build a fully-wired Hono app for Workbench:\n *\n * - `POST /api/*`, `GET /api/*` etc. — JSON API\n * - `GET /config` — UI bootstrap config\n * - `GET /assets/:file` — static asset reader\n * - `GET *` — `index.html` with `<base href>`\n * - CORS on `/api/*`\n * - Basic auth on everything when `core.requiresAuth()` is true\n *\n * Used directly by `@getworkbench/hono` (returned as-is for `.route()`\n * mounting) and indirectly by `createFetchHandler` for non-Hono adapters.\n */\nexport function buildWorkbenchApp(core: WorkbenchCore): Hono {\n const app = new Hono();\n\n app.use(\"/api/*\", cors());\n\n if (core.requiresAuth()) {\n app.use(\n \"*\",\n basicAuth({\n username: core.options.auth!.username,\n password: core.options.auth!.password,\n }),\n );\n }\n\n app.route(\"/api\", createApiRoutes(core));\n\n app.get(\"/config\", (c) => c.json(core.getConfig()));\n\n app.get(\"/assets/:file\", (c) => {\n const fileName = c.req.param(\"file\");\n const asset = serveStaticAsset(fileName);\n if (asset.status === 404 || !asset.body) {\n return c.text(\"Not found\", 404);\n }\n return new Response(new Uint8Array(asset.body), {\n status: 200,\n headers: { \"Content-Type\": asset.contentType },\n });\n });\n\n app.get(\"*\", (c) => {\n const url = new URL(c.req.url);\n const basePath = resolveBasePath(core.options.basePath, url.pathname);\n const html = renderIndexHtml(basePath, core.options.title || \"Workbench\");\n return c.html(html.body);\n });\n\n return app;\n}\n"],"mappings":"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","names":["join","join","Hono","Hono"]}
package/dist/hono.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Hono } from 'hono';
2
- import { W as WorkbenchCore } from './workbench-Btnvit1t.js';
2
+ import { W as WorkbenchCore } from './workbench-CvJrF8Cl.js';
3
3
  import 'bullmq';
4
4
 
5
5
  /**
package/dist/hono.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  buildWorkbenchApp,
3
3
  createApiRoutes
4
- } from "./chunk-O7DNWUZD.js";
4
+ } from "./chunk-4GN4APH7.js";
5
5
 
6
6
  // src/server/hono-api-app.ts
7
7
  import { Hono } from "hono";
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Queue, RedisOptions } from 'bullmq';
2
- import { W as WorkbenchCore, r as WorkbenchOptions } from './workbench-Btnvit1t.js';
3
- export { A as ActivityBucket, a as ActivityStatsResponse, C as CreateFlowChildRequest, b as CreateFlowRequest, D as DelayedJobInfo, c as DelayedSortField, d as DiscoveryMeta, F as FailingJobType, e as FlowNode, f as FlowSummary, H as HourlyBucket, J as JobInfo, g as JobStatus, h as JobTags, M as MetricsResponse, O as OverviewStats, P as PaginatedResponse, Q as QueueInfo, i as QueueManager, j as QueueMetrics, R as RepeatableSortField, k as RunInfo, l as RunInfoList, m as RunSortField, S as SchedulerInfo, n as SearchResult, o as SlowestJob, p as SortDirection, q as SortOptions, T as TestJobRequest, s as WorkerInfo } from './workbench-Btnvit1t.js';
2
+ import { W as WorkbenchCore, r as WorkbenchOptions } from './workbench-CvJrF8Cl.js';
3
+ export { A as ActivityBucket, a as ActivityStatsResponse, C as CreateFlowChildRequest, b as CreateFlowRequest, D as DelayedJobInfo, c as DelayedSortField, d as DiscoveryMeta, F as FailingJobType, e as FlowNode, f as FlowSummary, H as HourlyBucket, J as JobInfo, g as JobStatus, h as JobTags, M as MetricsResponse, O as OverviewStats, P as PaginatedResponse, Q as QueueInfo, i as QueueManager, j as QueueMetrics, R as RepeatableSortField, k as RunInfo, l as RunInfoList, m as RunSortField, S as SchedulerInfo, n as SearchResult, o as SlowestJob, p as SortDirection, q as SortOptions, T as TestJobRequest, s as WorkerInfo } from './workbench-CvJrF8Cl.js';
4
4
 
5
5
  interface FetchHandlerResult {
6
6
  /**
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  renderIndexHtml,
7
7
  resolveBasePath,
8
8
  serveStaticAsset
9
- } from "./chunk-O7DNWUZD.js";
9
+ } from "./chunk-4GN4APH7.js";
10
10
 
11
11
  // src/core/discover.ts
12
12
  import { Queue } from "bullmq";
@@ -1228,7 +1228,9 @@ var QueueManager = class {
1228
1228
  const results = await Promise.all(
1229
1229
  queueEntries.map(async ([queueName, queue]) => {
1230
1230
  const [repeatableJobs, delayedJobs] = await Promise.all([
1231
- queue.getRepeatableJobs(),
1231
+ // getJobSchedulers replaces the deprecated getRepeatableJobs (removed
1232
+ // in BullMQ v6) and exposes the scheduler id + template "Run now" needs.
1233
+ queue.getJobSchedulers(),
1232
1234
  queue.getJobs("delayed", 0, 50)
1233
1235
  ]);
1234
1236
  return { queueName, repeatableJobs, delayedJobs };
@@ -1241,13 +1243,14 @@ var QueueManager = class {
1241
1243
  name: job.name || "unnamed",
1242
1244
  queueName,
1243
1245
  pattern: job.pattern ?? void 0,
1244
- every: job.every ? Number(job.every) : void 0,
1246
+ every: job.every ?? void 0,
1245
1247
  next: job.next ?? void 0,
1246
1248
  endDate: job.endDate ?? void 0,
1247
1249
  tz: job.tz ?? void 0
1248
1250
  });
1249
1251
  }
1250
1252
  for (const job of delayedJobs) {
1253
+ if (job.repeatJobKey) continue;
1251
1254
  const delay = job.opts.delay || 0;
1252
1255
  delayed.push({
1253
1256
  id: job.id || "",
@@ -1294,6 +1297,57 @@ var QueueManager = class {
1294
1297
  });
1295
1298
  return { id: job.id || "" };
1296
1299
  }
1300
+ /**
1301
+ * Trigger an immediate, one-off run of a repeatable job scheduler.
1302
+ *
1303
+ * Enqueues a clone of the scheduler's job (name + data + opts) so the run
1304
+ * behaves like a scheduled execution, but as a standalone job. The repeat
1305
+ * schedule is left untouched, and scheduling internals are stripped so it
1306
+ * runs now and never collides with the deterministic ids of scheduled
1307
+ * iterations.
1308
+ *
1309
+ * `schedulerKey` is the scheduler's `key` from {@link getSchedulers} (the id
1310
+ * passed to upsertJobScheduler). We resolve it via getJobSchedulers rather
1311
+ * than the singular getJobScheduler, which mis-parses some keys.
1312
+ *
1313
+ * Data/opts source: modern schedulers (upsertJobScheduler) carry a `template`.
1314
+ * Legacy repeatables — `queue.add(name, data, { repeat })` — store NO template,
1315
+ * so we fall back to the next pending iteration (a delayed job tagged with this
1316
+ * scheduler's key), which carries the real payload and options.
1317
+ */
1318
+ async runSchedulerNow(queueName, schedulerKey) {
1319
+ const queue = this.queues.get(queueName);
1320
+ if (!queue) return null;
1321
+ const scheduler = (await queue.getJobSchedulers()).find(
1322
+ (s) => s.key === schedulerKey
1323
+ );
1324
+ if (!scheduler) return null;
1325
+ let data = scheduler.template?.data;
1326
+ let rawOpts = { ...scheduler.template?.opts };
1327
+ if (data === void 0) {
1328
+ const delayed = await queue.getJobs("delayed", 0, -1);
1329
+ const next = delayed.find((j) => j.repeatJobKey === schedulerKey);
1330
+ if (next) {
1331
+ data = next.data;
1332
+ rawOpts = { ...next.opts };
1333
+ }
1334
+ }
1335
+ const {
1336
+ repeat: _repeat,
1337
+ jobId: _jobId,
1338
+ delay: _delay,
1339
+ repeatJobKey: _repeatJobKey,
1340
+ prevMillis: _prevMillis,
1341
+ timestamp: _timestamp,
1342
+ ...opts
1343
+ } = rawOpts;
1344
+ const job = await queue.add(
1345
+ scheduler.name,
1346
+ data ?? {},
1347
+ opts
1348
+ );
1349
+ return { id: job.id ?? "" };
1350
+ }
1297
1351
  /**
1298
1352
  * Extract tag values from job data based on configured tag fields
1299
1353
  */