@getworkbench/core 0.2.1 → 0.3.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.
package/dist/index.js CHANGED
@@ -1,3 +1,93 @@
1
+ import {
2
+ UI_DIST_PATH,
3
+ buildRouteTable,
4
+ buildWorkbenchApp,
5
+ computeBasePath,
6
+ renderIndexHtml,
7
+ resolveBasePath,
8
+ serveStaticAsset
9
+ } from "./chunk-O7DNWUZD.js";
10
+
11
+ // src/core/discover.ts
12
+ import { Queue } from "bullmq";
13
+ import { Redis } from "ioredis";
14
+ async function discoverQueues(connection, prefix = "bull") {
15
+ const normalized = normalizeConnection(connection);
16
+ const client = createScanClient(normalized);
17
+ const firstError = captureFirstError(client);
18
+ try {
19
+ await Promise.race([client.ping(), firstError.promise]);
20
+ const names = await scanQueueNames(client, prefix);
21
+ return names.map(
22
+ (name) => new Queue(name, {
23
+ connection: { ...normalized },
24
+ prefix
25
+ })
26
+ );
27
+ } finally {
28
+ firstError.dispose();
29
+ client.disconnect();
30
+ }
31
+ }
32
+ function captureFirstError(client) {
33
+ let onError = null;
34
+ const promise = new Promise((_, reject) => {
35
+ onError = (err) => reject(err);
36
+ client.once("error", onError);
37
+ });
38
+ client.on("error", () => {
39
+ });
40
+ return {
41
+ promise,
42
+ dispose: () => {
43
+ if (onError) client.off("error", onError);
44
+ }
45
+ };
46
+ }
47
+ function normalizeConnection(connection) {
48
+ if (typeof connection === "string") {
49
+ return { url: connection };
50
+ }
51
+ return { ...connection };
52
+ }
53
+ function createScanClient(opts) {
54
+ const { url, ...rest } = opts;
55
+ if (url) {
56
+ return new Redis(url, {
57
+ ...rest,
58
+ lazyConnect: false,
59
+ maxRetriesPerRequest: 1
60
+ });
61
+ }
62
+ return new Redis({ ...rest, lazyConnect: false, maxRetriesPerRequest: 1 });
63
+ }
64
+ async function scanQueueNames(client, prefix) {
65
+ const pattern = `${prefix}:*:meta`;
66
+ const names = /* @__PURE__ */ new Set();
67
+ let cursor = "0";
68
+ do {
69
+ const [next, batch] = await client.scan(
70
+ cursor,
71
+ "MATCH",
72
+ pattern,
73
+ "COUNT",
74
+ 500
75
+ );
76
+ cursor = next;
77
+ for (const key of batch) {
78
+ const name = parseQueueName(key, prefix);
79
+ if (name) names.add(name);
80
+ }
81
+ } while (cursor !== "0");
82
+ return Array.from(names).sort();
83
+ }
84
+ function parseQueueName(key, prefix) {
85
+ const head = `${prefix}:`;
86
+ const tail = ":meta";
87
+ if (!key.startsWith(head) || !key.endsWith(tail)) return null;
88
+ return key.slice(head.length, key.length - tail.length);
89
+ }
90
+
1
91
  // src/core/queue-manager.ts
2
92
  import { FlowProducer } from "bullmq";
3
93
  import { LRUCache } from "lru-cache";
@@ -1689,24 +1779,56 @@ var QueueManager = class {
1689
1779
  };
1690
1780
 
1691
1781
  // src/core/workbench.ts
1692
- var WorkbenchCore = class {
1782
+ var WorkbenchCore = class _WorkbenchCore {
1693
1783
  options;
1694
1784
  queueManager;
1695
- constructor(options) {
1785
+ discovery;
1786
+ constructor(options, discovery = null) {
1696
1787
  const opts = Array.isArray(options) ? { queues: options } : options;
1697
1788
  this.options = {
1698
1789
  title: "Workbench",
1699
1790
  readonly: false,
1700
1791
  ...opts
1701
1792
  };
1702
- if (!this.options.queues || this.options.queues.length === 0) {
1793
+ this.discovery = discovery;
1794
+ const explicit = this.options.queues ?? [];
1795
+ const allowEmpty = !!this.options.redis;
1796
+ if (explicit.length === 0 && !allowEmpty) {
1703
1797
  throw new Error(
1704
1798
  "Workbench requires at least one queue. Pass queues directly or provide a redis connection for auto-discovery."
1705
1799
  );
1706
1800
  }
1707
- this.queueManager = new QueueManager(
1708
- this.options.queues,
1709
- this.options.tags || []
1801
+ this.queueManager = new QueueManager(explicit, this.options.tags || []);
1802
+ }
1803
+ /**
1804
+ * Async factory: build a `WorkbenchCore` from `WorkbenchOptions`, performing
1805
+ * BullMQ queue auto-discovery via `SCAN <prefix>:*:meta` when `queues` is
1806
+ * not provided.
1807
+ *
1808
+ * - When `queues` is set explicitly, behaves like `new WorkbenchCore(opts)`.
1809
+ * - When only `redis` is set, scans the connection for queues, caps at
1810
+ * `maxQueues` (default 100) to avoid connection storms with very large
1811
+ * deployments, and constructs the core with the resulting list.
1812
+ * - When no queues are discovered, the core is constructed with an empty
1813
+ * queue map so the dashboard can render an "empty" state instead of
1814
+ * erroring out.
1815
+ */
1816
+ static async fromOptions(opts) {
1817
+ if (opts.queues?.length) {
1818
+ return new _WorkbenchCore(opts);
1819
+ }
1820
+ if (!opts.redis) {
1821
+ throw new Error(
1822
+ "WorkbenchCore.fromOptions requires either `queues` or `redis`"
1823
+ );
1824
+ }
1825
+ const prefix = opts.prefix ?? "bull";
1826
+ const cap = opts.maxQueues ?? 100;
1827
+ const all = await discoverQueues(opts.redis, prefix);
1828
+ const queues = all.slice(0, cap);
1829
+ return new _WorkbenchCore(
1830
+ { ...opts, queues },
1831
+ { total: all.length, capped: all.length > cap, cap }
1710
1832
  );
1711
1833
  }
1712
1834
  /**
@@ -1737,577 +1859,12 @@ var WorkbenchCore = class {
1737
1859
  logo: this.options.logo,
1738
1860
  readonly: this.options.readonly,
1739
1861
  queues: this.queueManager.getQueueNames(),
1740
- tags: this.queueManager.getTagFields()
1862
+ tags: this.queueManager.getTagFields(),
1863
+ discovery: this.discovery
1741
1864
  };
1742
1865
  }
1743
1866
  };
1744
1867
 
1745
- // src/server/hono-app.ts
1746
- import { Hono as Hono2 } from "hono";
1747
- import { basicAuth } from "hono/basic-auth";
1748
- import { cors } from "hono/cors";
1749
-
1750
- // src/api/router.ts
1751
- import { Hono } from "hono";
1752
-
1753
- // src/api/handlers.ts
1754
- function parseSort(sort) {
1755
- if (!sort) return void 0;
1756
- const [field, dir] = sort.split(":");
1757
- if (!field) return void 0;
1758
- return {
1759
- field,
1760
- direction: dir === "asc" ? "asc" : "desc"
1761
- };
1762
- }
1763
- var readonlyError = {
1764
- status: 403,
1765
- body: { error: "Dashboard is in readonly mode" }
1766
- };
1767
- function buildRouteTable(core) {
1768
- const qm = core.queueManager;
1769
- const isReadonly = () => !!core.options.readonly;
1770
- return [
1771
- {
1772
- method: "post",
1773
- path: "/refresh",
1774
- handler: async () => {
1775
- qm.clearCache();
1776
- return { status: 200, body: { success: true } };
1777
- }
1778
- },
1779
- {
1780
- method: "get",
1781
- path: "/overview",
1782
- handler: async () => ({
1783
- status: 200,
1784
- body: await qm.getOverview()
1785
- })
1786
- },
1787
- {
1788
- method: "get",
1789
- path: "/counts",
1790
- handler: async () => ({
1791
- status: 200,
1792
- body: await qm.getQuickCounts()
1793
- })
1794
- },
1795
- {
1796
- method: "get",
1797
- path: "/runs",
1798
- handler: async ({ query }) => {
1799
- const limit = Number(query.limit) || 50;
1800
- const cursor = query.cursor;
1801
- const start = cursor ? Number(cursor) : 0;
1802
- const sort = parseSort(query.sort);
1803
- const status = query.status;
1804
- const q = query.q;
1805
- const from = query.from;
1806
- const to = query.to;
1807
- const tagsParam = query.tags;
1808
- let tags;
1809
- if (tagsParam) {
1810
- try {
1811
- tags = JSON.parse(tagsParam);
1812
- } catch {
1813
- const tagPairs = tagsParam.split(",");
1814
- tags = {};
1815
- for (const pair of tagPairs) {
1816
- const [key, value] = pair.split(":");
1817
- if (key && value) {
1818
- tags[key.trim()] = value.trim();
1819
- }
1820
- }
1821
- }
1822
- }
1823
- let timeRange;
1824
- if (from && to) {
1825
- timeRange = {
1826
- start: Number(from),
1827
- end: Number(to)
1828
- };
1829
- }
1830
- let text;
1831
- if (q) {
1832
- if (!q.includes(":")) {
1833
- text = q;
1834
- } else {
1835
- const parts = q.split(" ");
1836
- const textParts = parts.filter((p) => !p.includes(":"));
1837
- if (textParts.length > 0) {
1838
- text = textParts.join(" ");
1839
- }
1840
- }
1841
- }
1842
- const filters = status || tags || text || timeRange ? {
1843
- status,
1844
- tags,
1845
- text,
1846
- timeRange
1847
- } : void 0;
1848
- return {
1849
- status: 200,
1850
- body: await qm.getAllRuns(limit, start, sort, filters)
1851
- };
1852
- }
1853
- },
1854
- {
1855
- method: "get",
1856
- path: "/schedulers",
1857
- handler: async ({ query }) => {
1858
- const repeatableSort = parseSort(query.repeatableSort);
1859
- const delayedSort = parseSort(query.delayedSort);
1860
- return {
1861
- status: 200,
1862
- body: await qm.getSchedulers(repeatableSort, delayedSort)
1863
- };
1864
- }
1865
- },
1866
- {
1867
- method: "post",
1868
- path: "/test",
1869
- handler: async ({ body }) => {
1870
- if (isReadonly()) return readonlyError;
1871
- const req = body;
1872
- if (!req?.queueName || !req.jobName) {
1873
- return {
1874
- status: 400,
1875
- body: { error: "queueName and jobName are required" }
1876
- };
1877
- }
1878
- try {
1879
- const result = await qm.enqueueJob(req);
1880
- return { status: 200, body: result };
1881
- } catch (e) {
1882
- return { status: 400, body: { error: e.message } };
1883
- }
1884
- }
1885
- },
1886
- {
1887
- method: "get",
1888
- path: "/queue-names",
1889
- handler: async () => ({
1890
- status: 200,
1891
- body: qm.getQueueNames()
1892
- })
1893
- },
1894
- {
1895
- method: "get",
1896
- path: "/queues",
1897
- handler: async () => ({
1898
- status: 200,
1899
- body: await qm.getQueues()
1900
- })
1901
- },
1902
- {
1903
- method: "get",
1904
- path: "/metrics",
1905
- handler: async () => ({
1906
- status: 200,
1907
- body: await qm.getMetrics()
1908
- })
1909
- },
1910
- {
1911
- method: "get",
1912
- path: "/activity",
1913
- handler: async () => ({
1914
- status: 200,
1915
- body: await qm.getActivityStats()
1916
- })
1917
- },
1918
- {
1919
- method: "get",
1920
- path: "/queues/:name/jobs",
1921
- handler: async ({ params, query }) => {
1922
- const name = params.name;
1923
- const status = query.status;
1924
- const limit = Number(query.limit) || 50;
1925
- const cursor = query.cursor;
1926
- const start = cursor ? Number(cursor) : 0;
1927
- const sort = parseSort(query.sort);
1928
- return {
1929
- status: 200,
1930
- body: await qm.getJobs(name, status, limit, start, sort)
1931
- };
1932
- }
1933
- },
1934
- {
1935
- method: "get",
1936
- path: "/jobs/:queue/:id",
1937
- handler: async ({ params }) => {
1938
- const job = await qm.getJob(params.queue, params.id);
1939
- if (!job) {
1940
- return { status: 404, body: { error: "Job not found" } };
1941
- }
1942
- return { status: 200, body: job };
1943
- }
1944
- },
1945
- {
1946
- method: "post",
1947
- path: "/jobs/:queue/:id/retry",
1948
- handler: async ({ params }) => {
1949
- if (isReadonly()) return readonlyError;
1950
- const success = await qm.retryJob(params.queue, params.id);
1951
- if (!success) {
1952
- return { status: 400, body: { error: "Failed to retry job" } };
1953
- }
1954
- return { status: 200, body: { success: true } };
1955
- }
1956
- },
1957
- {
1958
- method: "post",
1959
- path: "/jobs/:queue/:id/remove",
1960
- handler: async ({ params }) => {
1961
- if (isReadonly()) return readonlyError;
1962
- const success = await qm.removeJob(params.queue, params.id);
1963
- if (!success) {
1964
- return { status: 400, body: { error: "Failed to remove job" } };
1965
- }
1966
- return { status: 200, body: { success: true } };
1967
- }
1968
- },
1969
- {
1970
- method: "post",
1971
- path: "/jobs/:queue/:id/promote",
1972
- handler: async ({ params }) => {
1973
- if (isReadonly()) return readonlyError;
1974
- const success = await qm.promoteJob(params.queue, params.id);
1975
- if (!success) {
1976
- return { status: 400, body: { error: "Failed to promote job" } };
1977
- }
1978
- return { status: 200, body: { success: true } };
1979
- }
1980
- },
1981
- {
1982
- method: "get",
1983
- path: "/search",
1984
- handler: async ({ query }) => {
1985
- const q = query.q || "";
1986
- const limit = Number(query.limit) || 20;
1987
- if (!q) return { status: 200, body: { results: [] } };
1988
- const results = await qm.search(q, limit);
1989
- return { status: 200, body: { results } };
1990
- }
1991
- },
1992
- {
1993
- method: "get",
1994
- path: "/tags/:field/values",
1995
- handler: async ({ params, query }) => {
1996
- const field = params.field;
1997
- const limit = Number(query.limit) || 50;
1998
- const tagFields = qm.getTagFields();
1999
- if (tagFields.length > 0 && !tagFields.includes(field)) {
2000
- return {
2001
- status: 400,
2002
- body: {
2003
- error: `Field "${field}" is not a configured tag field`
2004
- }
2005
- };
2006
- }
2007
- const values = await qm.getTagValues(field, limit);
2008
- return { status: 200, body: { field, values } };
2009
- }
2010
- },
2011
- {
2012
- method: "post",
2013
- path: "/queues/:name/clean",
2014
- handler: async ({ params, body }) => {
2015
- if (isReadonly()) return readonlyError;
2016
- const req = body;
2017
- if (!req) {
2018
- return { status: 400, body: { error: "Body required" } };
2019
- }
2020
- const count = await qm.cleanJobs(
2021
- params.name,
2022
- req.status,
2023
- req.grace || 0
2024
- );
2025
- return { status: 200, body: { removed: count } };
2026
- }
2027
- },
2028
- {
2029
- method: "post",
2030
- path: "/bulk/retry",
2031
- handler: async ({ body }) => {
2032
- if (isReadonly()) return readonlyError;
2033
- const req = body;
2034
- if (!req?.jobs) {
2035
- return { status: 400, body: { error: "jobs is required" } };
2036
- }
2037
- return { status: 200, body: await qm.bulkRetry(req.jobs) };
2038
- }
2039
- },
2040
- {
2041
- method: "post",
2042
- path: "/bulk/delete",
2043
- handler: async ({ body }) => {
2044
- if (isReadonly()) return readonlyError;
2045
- const req = body;
2046
- if (!req?.jobs) {
2047
- return { status: 400, body: { error: "jobs is required" } };
2048
- }
2049
- return { status: 200, body: await qm.bulkDelete(req.jobs) };
2050
- }
2051
- },
2052
- {
2053
- method: "post",
2054
- path: "/bulk/promote",
2055
- handler: async ({ body }) => {
2056
- if (isReadonly()) return readonlyError;
2057
- const req = body;
2058
- if (!req?.jobs) {
2059
- return { status: 400, body: { error: "jobs is required" } };
2060
- }
2061
- return { status: 200, body: await qm.bulkPromote(req.jobs) };
2062
- }
2063
- },
2064
- {
2065
- method: "post",
2066
- path: "/queues/:name/pause",
2067
- handler: async ({ params }) => {
2068
- if (isReadonly()) return readonlyError;
2069
- try {
2070
- await qm.pauseQueue(params.name);
2071
- return { status: 200, body: { success: true, paused: true } };
2072
- } catch (error) {
2073
- return {
2074
- status: 404,
2075
- body: {
2076
- error: error instanceof Error ? error.message : "Failed to pause queue"
2077
- }
2078
- };
2079
- }
2080
- }
2081
- },
2082
- {
2083
- method: "post",
2084
- path: "/queues/:name/resume",
2085
- handler: async ({ params }) => {
2086
- if (isReadonly()) return readonlyError;
2087
- try {
2088
- await qm.resumeQueue(params.name);
2089
- return { status: 200, body: { success: true, paused: false } };
2090
- } catch (error) {
2091
- return {
2092
- status: 404,
2093
- body: {
2094
- error: error instanceof Error ? error.message : "Failed to resume queue"
2095
- }
2096
- };
2097
- }
2098
- }
2099
- },
2100
- {
2101
- method: "get",
2102
- path: "/flows",
2103
- handler: async ({ query }) => {
2104
- const limit = Number(query.limit) || 50;
2105
- const flows = await qm.getFlows(limit);
2106
- return { status: 200, body: { flows } };
2107
- }
2108
- },
2109
- {
2110
- method: "get",
2111
- path: "/flows/:queueName/:jobId",
2112
- handler: async ({ params }) => {
2113
- const flow = await qm.getFlow(params.queueName, params.jobId);
2114
- if (!flow) {
2115
- return { status: 404, body: { error: "Flow not found" } };
2116
- }
2117
- return { status: 200, body: flow };
2118
- }
2119
- },
2120
- {
2121
- method: "post",
2122
- path: "/flows",
2123
- handler: async ({ body }) => {
2124
- if (isReadonly()) return readonlyError;
2125
- const req = body;
2126
- if (!req?.name || !req.queueName || !req.children?.length) {
2127
- return {
2128
- status: 400,
2129
- body: { error: "name, queueName, and children are required" }
2130
- };
2131
- }
2132
- try {
2133
- const result = await qm.createFlow(req);
2134
- return { status: 200, body: result };
2135
- } catch (e) {
2136
- return { status: 400, body: { error: e.message } };
2137
- }
2138
- }
2139
- }
2140
- ];
2141
- }
2142
-
2143
- // src/api/router.ts
2144
- function createApiRoutes(core) {
2145
- const app = new Hono();
2146
- for (const route of buildRouteTable(core)) {
2147
- app[route.method](route.path, async (c) => {
2148
- let body;
2149
- const method = c.req.method;
2150
- if (method !== "GET" && method !== "HEAD") {
2151
- const contentType = c.req.header("content-type") ?? "";
2152
- if (contentType.includes("application/json")) {
2153
- try {
2154
- body = await c.req.json();
2155
- } catch {
2156
- body = void 0;
2157
- }
2158
- }
2159
- }
2160
- const input = {
2161
- params: c.req.param(),
2162
- query: c.req.query(),
2163
- body
2164
- };
2165
- const result = await route.handler(input);
2166
- return new Response(JSON.stringify(result.body), {
2167
- status: result.status,
2168
- headers: { "Content-Type": "application/json" }
2169
- });
2170
- });
2171
- }
2172
- return app;
2173
- }
2174
-
2175
- // src/server/base-path.ts
2176
- var CLIENT_ROUTES = [
2177
- /\/queues\/[^/]+\/jobs\/[^/]+\/?$/,
2178
- /\/queues\/[^/]+\/?$/,
2179
- /\/flows\/[^/]+\/[^/]+\/?$/,
2180
- /\/schedulers\/?$/,
2181
- /\/flows\/?$/,
2182
- /\/metrics\/?$/,
2183
- /\/test\/?$/
2184
- ];
2185
- function computeBasePath(pathname) {
2186
- let basePath = pathname;
2187
- for (const route of CLIENT_ROUTES) {
2188
- basePath = basePath.replace(route, "");
2189
- }
2190
- if (!basePath.endsWith("/")) {
2191
- basePath = `${basePath}/`;
2192
- }
2193
- return basePath;
2194
- }
2195
- function resolveBasePath(override, pathname) {
2196
- if (override) {
2197
- return override.endsWith("/") ? override : `${override}/`;
2198
- }
2199
- return computeBasePath(pathname);
2200
- }
2201
-
2202
- // src/server/static-assets.ts
2203
- import { existsSync, readFileSync } from "fs";
2204
- import { join as join2 } from "path";
2205
-
2206
- // src/ui-dist.ts
2207
- import { dirname, join } from "path";
2208
- import { fileURLToPath } from "url";
2209
- var UI_DIST_PATH = join(dirname(fileURLToPath(import.meta.url)), "ui");
2210
-
2211
- // src/server/static-assets.ts
2212
- function serveStaticAsset(filename) {
2213
- const filePath = join2(UI_DIST_PATH, "assets", filename);
2214
- if (!existsSync(filePath)) {
2215
- return { status: 404, body: null, contentType: "text/plain" };
2216
- }
2217
- const body = readFileSync(filePath);
2218
- const contentType = filename.endsWith(".js") ? "application/javascript" : filename.endsWith(".css") ? "text/css" : filename.endsWith(".svg") ? "image/svg+xml" : filename.endsWith(".png") ? "image/png" : filename.endsWith(".woff2") ? "font/woff2" : "application/octet-stream";
2219
- return { status: 200, body, contentType };
2220
- }
2221
- function renderIndexHtml(basePath, title) {
2222
- const indexPath = join2(UI_DIST_PATH, "index.html");
2223
- if (existsSync(indexPath)) {
2224
- let html = readFileSync(indexPath, "utf-8");
2225
- html = html.replace("<head>", `<head>
2226
- <base href="${basePath}">`);
2227
- return { body: html, contentType: "text/html; charset=utf-8" };
2228
- }
2229
- return {
2230
- body: fallbackHtml(title, basePath),
2231
- contentType: "text/html; charset=utf-8"
2232
- };
2233
- }
2234
- function fallbackHtml(title, basePath) {
2235
- return `<!DOCTYPE html>
2236
- <html lang="en">
2237
- <head>
2238
- <base href="${basePath}">
2239
- <meta charset="UTF-8" />
2240
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
2241
- <title>${title}</title>
2242
- <style>
2243
- body {
2244
- font-family: system-ui, sans-serif;
2245
- display: flex;
2246
- align-items: center;
2247
- justify-content: center;
2248
- min-height: 100vh;
2249
- margin: 0;
2250
- background: #0a0a0a;
2251
- color: #fafafa;
2252
- }
2253
- .message {
2254
- text-align: center;
2255
- padding: 2rem;
2256
- }
2257
- code {
2258
- background: #1a1a1a;
2259
- padding: 0.5rem 1rem;
2260
- border-radius: 0.5rem;
2261
- display: block;
2262
- margin-top: 1rem;
2263
- }
2264
- </style>
2265
- </head>
2266
- <body>
2267
- <div class="message">
2268
- <h1>${title}</h1>
2269
- <p>UI assets not found. Build @getworkbench/core first:</p>
2270
- <code>bun run --filter=@getworkbench/core build</code>
2271
- </div>
2272
- </body>
2273
- </html>`;
2274
- }
2275
-
2276
- // src/server/hono-app.ts
2277
- function buildWorkbenchApp(core) {
2278
- const app = new Hono2();
2279
- app.use("/api/*", cors());
2280
- if (core.requiresAuth()) {
2281
- app.use(
2282
- "*",
2283
- basicAuth({
2284
- username: core.options.auth.username,
2285
- password: core.options.auth.password
2286
- })
2287
- );
2288
- }
2289
- app.route("/api", createApiRoutes(core));
2290
- app.get("/config", (c) => c.json(core.getConfig()));
2291
- app.get("/assets/:file", (c) => {
2292
- const fileName = c.req.param("file");
2293
- const asset = serveStaticAsset(fileName);
2294
- if (asset.status === 404 || !asset.body) {
2295
- return c.text("Not found", 404);
2296
- }
2297
- return new Response(new Uint8Array(asset.body), {
2298
- status: 200,
2299
- headers: { "Content-Type": asset.contentType }
2300
- });
2301
- });
2302
- app.get("*", (c) => {
2303
- const url = new URL(c.req.url);
2304
- const basePath = resolveBasePath(core.options.basePath, url.pathname);
2305
- const html = renderIndexHtml(basePath, core.options.title || "Workbench");
2306
- return c.html(html.body);
2307
- });
2308
- return app;
2309
- }
2310
-
2311
1868
  // src/api/fetch-handler.ts
2312
1869
  function createFetchHandler(options) {
2313
1870
  const core = new WorkbenchCore(options);
@@ -2380,11 +1937,10 @@ export {
2380
1937
  UI_DIST_PATH,
2381
1938
  WorkbenchCore,
2382
1939
  buildRouteTable,
2383
- buildWorkbenchApp,
2384
1940
  checkBasicAuth,
2385
1941
  computeBasePath,
2386
- createApiRoutes,
2387
1942
  createFetchHandler,
1943
+ discoverQueues,
2388
1944
  renderIndexHtml,
2389
1945
  resolveBasePath,
2390
1946
  serveStaticAsset