shokupan 0.10.0 → 0.10.2

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.cjs CHANGED
@@ -25,18 +25,18 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
25
25
  const nanoid = require("nanoid");
26
26
  const promises = require("node:fs/promises");
27
27
  const node_util = require("node:util");
28
- const api = require("@opentelemetry/api");
29
- const jsYaml = require("js-yaml");
30
- const node_async_hooks = require("node:async_hooks");
31
28
  const surrealdb = require("surrealdb");
32
29
  const eta$1 = require("eta");
33
30
  const promises$1 = require("fs/promises");
34
31
  const path = require("path");
32
+ const api = require("@opentelemetry/api");
33
+ const jsYaml = require("js-yaml");
34
+ const node_async_hooks = require("node:async_hooks");
35
35
  const os = require("node:os");
36
- const path$1 = require("node:path");
37
- const node_url = require("node:url");
38
36
  const renderToString = require("preact-render-to-string");
39
37
  const jsxRuntime = require("preact/jsx-runtime");
38
+ const path$1 = require("node:path");
39
+ const node_url = require("node:url");
40
40
  const cluster = require("node:cluster");
41
41
  const net = require("node:net");
42
42
  const node_perf_hooks = require("node:perf_hooks");
@@ -1359,40 +1359,6 @@ async function generateOpenApi(rootRouter, options = {}) {
1359
1359
  "x-tagGroups": xTagGroups
1360
1360
  };
1361
1361
  }
1362
- class RequestContextStore {
1363
- request;
1364
- span;
1365
- }
1366
- const asyncContext = new node_async_hooks.AsyncLocalStorage();
1367
- class HttpError extends Error {
1368
- status;
1369
- constructor(message, status) {
1370
- super(message);
1371
- this.name = "HttpError";
1372
- this.status = status;
1373
- if (Error.captureStackTrace) {
1374
- Error.captureStackTrace(this, HttpError);
1375
- }
1376
- }
1377
- }
1378
- function getErrorStatus(err) {
1379
- if (!err || typeof err !== "object") {
1380
- return 500;
1381
- }
1382
- if (typeof err.status === "number") {
1383
- return err.status;
1384
- }
1385
- if (typeof err.statusCode === "number") {
1386
- return err.statusCode;
1387
- }
1388
- return 500;
1389
- }
1390
- class EventError extends HttpError {
1391
- constructor(message = "Event Error") {
1392
- super(message, 500);
1393
- this.name = "EventError";
1394
- }
1395
- }
1396
1362
  const eta = new eta$1.Eta();
1397
1363
  function serveStatic(config, prefix) {
1398
1364
  const rootPath = path.resolve(config.root || ".");
@@ -1578,6 +1544,35 @@ function Inject(token) {
1578
1544
  });
1579
1545
  };
1580
1546
  }
1547
+ class HttpError extends Error {
1548
+ status;
1549
+ constructor(message, status) {
1550
+ super(message);
1551
+ this.name = "HttpError";
1552
+ this.status = status;
1553
+ if (Error.captureStackTrace) {
1554
+ Error.captureStackTrace(this, HttpError);
1555
+ }
1556
+ }
1557
+ }
1558
+ function getErrorStatus(err) {
1559
+ if (!err || typeof err !== "object") {
1560
+ return 500;
1561
+ }
1562
+ if (typeof err.status === "number") {
1563
+ return err.status;
1564
+ }
1565
+ if (typeof err.statusCode === "number") {
1566
+ return err.statusCode;
1567
+ }
1568
+ return 500;
1569
+ }
1570
+ class EventError extends HttpError {
1571
+ constructor(message = "Event Error") {
1572
+ super(message, 500);
1573
+ this.name = "EventError";
1574
+ }
1575
+ }
1581
1576
  const tracer = api.trace.getTracer("shokupan.middleware");
1582
1577
  function traceHandler(fn, name) {
1583
1578
  return async function(...args) {
@@ -1767,6 +1762,8 @@ var RouteParamType = /* @__PURE__ */ ((RouteParamType2) => {
1767
1762
  RouteParamType2["CONTEXT"] = "CONTEXT";
1768
1763
  return RouteParamType2;
1769
1764
  })(RouteParamType || {});
1765
+ const RouterRegistry = /* @__PURE__ */ new Map();
1766
+ const ShokupanApplicationTree = {};
1770
1767
  class ShokupanRouter {
1771
1768
  constructor(config) {
1772
1769
  this.config = config;
@@ -2761,6 +2758,11 @@ class ShokupanRouter {
2761
2758
  }
2762
2759
  }
2763
2760
  }
2761
+ class RequestContextStore {
2762
+ request;
2763
+ span;
2764
+ }
2765
+ const asyncContext = new node_async_hooks.AsyncLocalStorage();
2764
2766
  class SystemCpuMonitor {
2765
2767
  constructor(intervalMs = 1e3) {
2766
2768
  this.intervalMs = intervalMs;
@@ -3565,6 +3567,281 @@ function Event(eventName) {
3565
3567
  function RateLimit(options) {
3566
3568
  return Use(RateLimitMiddleware(options));
3567
3569
  }
3570
+ function ApiExplorerApp({ spec, asyncSpec, config }) {
3571
+ const hierarchy = /* @__PURE__ */ new Map();
3572
+ const addRoute = (groupKey, route) => {
3573
+ if (!hierarchy.has(groupKey)) {
3574
+ hierarchy.set(groupKey, []);
3575
+ }
3576
+ hierarchy.get(groupKey).push(route);
3577
+ };
3578
+ const getGroupKey = (op, source) => {
3579
+ if (op.tags && op.tags.length > 0) {
3580
+ const tag = typeof op.tags[0] === "string" ? op.tags[0] : op.tags[0].name;
3581
+ if (!tag.startsWith("/")) return tag;
3582
+ }
3583
+ if (source?.className) return source.className;
3584
+ if (source?.file) {
3585
+ const parts = source.file.split("/");
3586
+ const filename = parts[parts.length - 1].replace(/\.(ts|js)$/, "");
3587
+ return filename.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
3588
+ }
3589
+ if (op.tags && op.tags.length > 0) {
3590
+ const tag = typeof op.tags[0] === "string" ? op.tags[0] : op.tags[0].name;
3591
+ return tag;
3592
+ }
3593
+ return "Ungrouped";
3594
+ };
3595
+ const createSubgroups = (routes, depth = 0) => {
3596
+ if (routes.length < 3 || depth > 5) {
3597
+ return routes.map((route) => ({
3598
+ name: route.path,
3599
+ type: "route",
3600
+ path: route.path,
3601
+ routes: [route]
3602
+ }));
3603
+ }
3604
+ const pathSegments = routes.map((r) => {
3605
+ const cleaned = r.path.replace(/^\/|\/$/g, "");
3606
+ return cleaned.split("/");
3607
+ });
3608
+ const prefixGroups = /* @__PURE__ */ new Map();
3609
+ const ungrouped = [];
3610
+ routes.forEach((route, idx) => {
3611
+ const segments = pathSegments[idx];
3612
+ if (segments.length <= depth) {
3613
+ ungrouped.push(route);
3614
+ return;
3615
+ }
3616
+ const prefix = segments.slice(0, depth + 1).join("/");
3617
+ if (!prefixGroups.has(prefix)) {
3618
+ prefixGroups.set(prefix, []);
3619
+ }
3620
+ prefixGroups.get(prefix).push(route);
3621
+ });
3622
+ const result = [];
3623
+ prefixGroups.forEach((groupRoutes, prefix) => {
3624
+ if (groupRoutes.length >= 3) {
3625
+ const prefixName = prefix.split("/").pop() || prefix;
3626
+ result.push({
3627
+ name: prefixName,
3628
+ type: "subgroup",
3629
+ path: "/" + prefix,
3630
+ children: createSubgroups(groupRoutes, depth + 1)
3631
+ });
3632
+ } else {
3633
+ ungrouped.push(...groupRoutes);
3634
+ }
3635
+ });
3636
+ ungrouped.forEach((route) => {
3637
+ result.push({
3638
+ name: route.path,
3639
+ type: "route",
3640
+ path: route.path,
3641
+ routes: [route]
3642
+ });
3643
+ });
3644
+ result.sort((a, b) => {
3645
+ if (a.type === "subgroup" && b.type !== "subgroup") return -1;
3646
+ if (a.type !== "subgroup" && b.type === "subgroup") return 1;
3647
+ return a.name.localeCompare(b.name);
3648
+ });
3649
+ return result;
3650
+ };
3651
+ Object.entries(spec.paths || {}).forEach(([path2, methods]) => {
3652
+ Object.entries(methods).forEach(([method, op]) => {
3653
+ if (!op.operationId) {
3654
+ op.operationId = `${method}-${path2.replace(/\//g, "-").replace(/[{}:]/g, "")}`;
3655
+ }
3656
+ const route = { method, path: path2, op };
3657
+ const source = op["x-shokupan-source"];
3658
+ const groupKey = getGroupKey(op, source);
3659
+ addRoute(groupKey, route);
3660
+ });
3661
+ });
3662
+ Object.entries(asyncSpec?.channels || {}).forEach(([name, ch]) => {
3663
+ const operations = [];
3664
+ if (ch.publish) operations.push({ method: "recv", op: ch.publish });
3665
+ if (ch.subscribe) operations.push({ method: "send", op: ch.subscribe });
3666
+ operations.forEach(({ method, op }) => {
3667
+ if (!op.operationId) op.operationId = `${method}-${name.replace(/[^a-zA-Z0-9]/g, "-")}`;
3668
+ const route = { method, path: name, op };
3669
+ const source = op["x-shokupan-source"] || op["x-source-info"];
3670
+ const groupKey = getGroupKey(op, source);
3671
+ addRoute(groupKey, route);
3672
+ });
3673
+ });
3674
+ const hierarchicalGroups = Array.from(hierarchy.entries()).map(([name, routes]) => {
3675
+ routes.sort((a, b) => a.path.localeCompare(b.path));
3676
+ const children = createSubgroups(routes);
3677
+ return {
3678
+ name,
3679
+ type: "group",
3680
+ children
3681
+ };
3682
+ }).sort((a, b) => {
3683
+ if (a.name === "Ungrouped") return 1;
3684
+ if (b.name === "Ungrouped") return -1;
3685
+ return a.name.localeCompare(b.name);
3686
+ });
3687
+ const allRoutes = Array.from(hierarchy.values()).flat();
3688
+ return /* @__PURE__ */ jsxRuntime.jsxs("html", { lang: "en", children: [
3689
+ /* @__PURE__ */ jsxRuntime.jsxs("head", { children: [
3690
+ /* @__PURE__ */ jsxRuntime.jsx("meta", { charSet: "UTF-8" }),
3691
+ /* @__PURE__ */ jsxRuntime.jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0" }),
3692
+ /* @__PURE__ */ jsxRuntime.jsx("title", { children: spec.info?.title || "API Explorer" }),
3693
+ /* @__PURE__ */ jsxRuntime.jsx("link", { rel: "stylesheet", href: "style.css" }),
3694
+ /* @__PURE__ */ jsxRuntime.jsx("link", { rel: "stylesheet", href: "theme.css" }),
3695
+ /* @__PURE__ */ jsxRuntime.jsx("script", { src: "https://cdn.jsdelivr.net/npm/marked@4.3.0/marked.min.js" }),
3696
+ /* @__PURE__ */ jsxRuntime.jsx("script", { src: "https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs/loader.js" }),
3697
+ /* @__PURE__ */ jsxRuntime.jsx("script", { dangerouslySetInnerHTML: {
3698
+ __html: `
3699
+ (function() {
3700
+ if (!window.location.pathname.endsWith('/') && !window.location.pathname.split('/').pop().includes('.')) {
3701
+ var newUrl = window.location.pathname + '/' + window.location.search + window.location.hash;
3702
+ window.history.replaceState(null, null, newUrl);
3703
+ window.location.reload();
3704
+ }
3705
+ })();
3706
+ `
3707
+ } })
3708
+ ] }),
3709
+ /* @__PURE__ */ jsxRuntime.jsxs("body", { class: "dark-theme", children: [
3710
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { id: "app", children: [
3711
+ /* @__PURE__ */ jsxRuntime.jsx(Sidebar$1, { spec, hierarchicalGroups }),
3712
+ /* @__PURE__ */ jsxRuntime.jsx(MainContent$1, { allRoutes, config, spec })
3713
+ ] }),
3714
+ /* @__PURE__ */ jsxRuntime.jsx("script", { src: "explorer-client.mjs", type: "module" })
3715
+ ] })
3716
+ ] });
3717
+ }
3718
+ function Sidebar$1({ spec, hierarchicalGroups }) {
3719
+ const renderNavNode = (node, depth = 0) => {
3720
+ if (node.type === "route") {
3721
+ const route = node.routes[0];
3722
+ const source = route.op["x-shokupan-source"] || route.op["x-source-info"];
3723
+ const isRuntime = route.op["x-source-info"]?.isRuntime;
3724
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { class: "nav-item-wrapper", style: `padding-left: ${depth * 12}px;`, children: [
3725
+ /* @__PURE__ */ jsxRuntime.jsxs(
3726
+ "a",
3727
+ {
3728
+ href: `#${route.op.operationId}`,
3729
+ class: "nav-item",
3730
+ "data-id": route.op.operationId,
3731
+ title: route.path,
3732
+ children: [
3733
+ /* @__PURE__ */ jsxRuntime.jsx("span", { class: `badge badge-${route.method.toUpperCase()}`, children: route.method.toUpperCase() }),
3734
+ /* @__PURE__ */ jsxRuntime.jsx("span", { class: "nav-label", children: node.name }),
3735
+ isRuntime && /* @__PURE__ */ jsxRuntime.jsx("span", { class: "nav-warning", title: "Static Analysis Failed", children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", children: [
3736
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" }),
3737
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "9", x2: "12", y2: "13" }),
3738
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
3739
+ ] }) })
3740
+ ]
3741
+ },
3742
+ route.op.operationId
3743
+ ),
3744
+ source?.file && /* @__PURE__ */ jsxRuntime.jsx(
3745
+ "a",
3746
+ {
3747
+ href: `vscode://file/${source.file}:${source.line || 1}`,
3748
+ class: "nav-source-link",
3749
+ title: `${source.file}:${source.line || 1}`,
3750
+ children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
3751
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "16 18 22 12 16 6" }),
3752
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "8 6 2 12 8 18" })
3753
+ ] })
3754
+ }
3755
+ )
3756
+ ] });
3757
+ } else if (node.type === "subgroup") {
3758
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { class: "nav-subgroup collapsed", style: `padding-left: ${depth * 12}px;`, children: [
3759
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { class: "nav-subgroup-title", children: [
3760
+ /* @__PURE__ */ jsxRuntime.jsx("span", { class: "chevron", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "9 18 15 12 9 6" }) }) }),
3761
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: node.name })
3762
+ ] }),
3763
+ /* @__PURE__ */ jsxRuntime.jsx("div", { class: "nav-subgroup-items", children: node.children?.map((child) => renderNavNode(child, depth + 1)) })
3764
+ ] });
3765
+ }
3766
+ };
3767
+ return /* @__PURE__ */ jsxRuntime.jsxs("aside", { class: "sidebar", children: [
3768
+ /* @__PURE__ */ jsxRuntime.jsx("div", { class: "resize-handle" }),
3769
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { class: "sidebar-header", children: [
3770
+ /* @__PURE__ */ jsxRuntime.jsx("button", { class: "toggle-sidebar", children: "☰" }),
3771
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { children: spec.info?.title }),
3772
+ /* @__PURE__ */ jsxRuntime.jsx("div", { class: "version", children: spec.info?.version })
3773
+ ] }),
3774
+ /* @__PURE__ */ jsxRuntime.jsx("div", { class: "sidebar-collapse-trigger", children: "➔" }),
3775
+ /* @__PURE__ */ jsxRuntime.jsx("nav", { class: "nav-groups", children: hierarchicalGroups.map((group) => /* @__PURE__ */ jsxRuntime.jsxs("div", { class: "nav-group collapsed", children: [
3776
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { class: "nav-group-title", children: [
3777
+ /* @__PURE__ */ jsxRuntime.jsx("span", { class: "chevron", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "9 18 15 12 9 6" }) }) }),
3778
+ " ",
3779
+ group.name
3780
+ ] }),
3781
+ /* @__PURE__ */ jsxRuntime.jsx("div", { class: "nav-items", children: group.children?.map((child) => renderNavNode(child, 0)) })
3782
+ ] }, group.name)) })
3783
+ ] });
3784
+ }
3785
+ function MainContent$1({ allRoutes, config, spec }) {
3786
+ const explorerData = JSON.stringify({
3787
+ routes: allRoutes,
3788
+ config,
3789
+ info: spec.info
3790
+ });
3791
+ const safeJson = explorerData.replace(/<\/script>/g, "<\\/script>");
3792
+ return /* @__PURE__ */ jsxRuntime.jsxs("main", { class: "content", id: "main-content", children: [
3793
+ /* @__PURE__ */ jsxRuntime.jsx("div", { id: "ide-container", children: /* @__PURE__ */ jsxRuntime.jsx("div", { class: "empty-state", children: "Select a request to view details" }) }),
3794
+ /* @__PURE__ */ jsxRuntime.jsx("script", { id: "explorer-data", type: "application/json", dangerouslySetInnerHTML: { __html: safeJson } })
3795
+ ] });
3796
+ }
3797
+ class ApiExplorerPlugin extends ShokupanRouter {
3798
+ constructor(pluginOptions) {
3799
+ super({ renderer: renderToString });
3800
+ this.pluginOptions = pluginOptions;
3801
+ pluginOptions.path ??= "/explorer";
3802
+ const serveFile = async (ctx, file, type) => {
3803
+ const content = await promises$1.readFile(path.join(__dirname, "static", file), "utf-8");
3804
+ ctx.set("Content-Type", type);
3805
+ return ctx.send(content);
3806
+ };
3807
+ const stripSourceCode = (spec) => {
3808
+ if (!spec || !spec.paths) return spec;
3809
+ Object.values(spec.paths).forEach((methods) => {
3810
+ Object.values(methods).forEach((op) => {
3811
+ if (op["x-source-info"]?.snippet) {
3812
+ delete op["x-source-info"].snippet;
3813
+ }
3814
+ if (op["x-shokupan-source"]?.code) {
3815
+ delete op["x-shokupan-source"].code;
3816
+ }
3817
+ });
3818
+ });
3819
+ return spec;
3820
+ };
3821
+ this.get("/style.css", (ctx) => serveFile(ctx, "style.css", "text/css"));
3822
+ this.get("/theme.css", (ctx) => serveFile(ctx, "theme.css", "text/css"));
3823
+ this.get("/explorer-client.mjs", (ctx) => serveFile(ctx, "explorer-client.mjs", "application/javascript"));
3824
+ this.get("/_source", async (ctx) => {
3825
+ const file = ctx.query["file"];
3826
+ if (!file) return ctx.text("Missing file parameter", 400);
3827
+ try {
3828
+ const content = await promises$1.readFile(file, "utf-8");
3829
+ return ctx.text(content);
3830
+ } catch (err) {
3831
+ return ctx.text("File not found", 404);
3832
+ }
3833
+ });
3834
+ this.get("/openapi.json", async (ctx) => {
3835
+ const spec = this.root.openApiSpec ? structuredClone(this.root.openApiSpec) : await (this.root || this).generateApiSpec();
3836
+ return ctx.json(stripSourceCode(spec));
3837
+ });
3838
+ this.get("/", async (ctx) => {
3839
+ const spec = this.root.openApiSpec ? structuredClone(this.root.openApiSpec) : await (this.root || this).generateApiSpec();
3840
+ const asyncSpec = ctx.app.asyncApiSpec;
3841
+ return ctx.jsx(ApiExplorerApp({ spec: stripSourceCode(spec), asyncSpec }));
3842
+ });
3843
+ }
3844
+ }
3568
3845
  function AsyncApiApp({ spec, serverUrl, base, disableSourceView, navTree }) {
3569
3846
  return /* @__PURE__ */ jsxRuntime.jsxs("html", { lang: "en", children: [
3570
3847
  /* @__PURE__ */ jsxRuntime.jsxs("head", { children: [
@@ -6405,6 +6682,7 @@ exports.$socket = $socket;
6405
6682
  exports.$url = $url;
6406
6683
  exports.$ws = $ws;
6407
6684
  exports.All = All;
6685
+ exports.ApiExplorerPlugin = ApiExplorerPlugin;
6408
6686
  exports.AsyncApiPlugin = AsyncApiPlugin;
6409
6687
  exports.AuthPlugin = AuthPlugin;
6410
6688
  exports.Body = Body;
@@ -6435,13 +6713,16 @@ exports.RateLimit = RateLimit;
6435
6713
  exports.RateLimitMiddleware = RateLimitMiddleware;
6436
6714
  exports.Req = Req;
6437
6715
  exports.RouteParamType = RouteParamType;
6716
+ exports.RouterRegistry = RouterRegistry;
6438
6717
  exports.ScalarPlugin = ScalarPlugin;
6439
6718
  exports.SecurityHeaders = SecurityHeaders;
6440
6719
  exports.Session = Session;
6441
6720
  exports.Shokupan = Shokupan;
6721
+ exports.ShokupanApplicationTree = ShokupanApplicationTree;
6442
6722
  exports.ShokupanContext = ShokupanContext;
6443
6723
  exports.ShokupanRequest = ShokupanRequest;
6444
6724
  exports.ShokupanResponse = ShokupanResponse;
6725
+ exports.ShokupanRouter = ShokupanRouter;
6445
6726
  exports.Spec = Spec;
6446
6727
  exports.Use = Use;
6447
6728
  exports.ValidationError = ValidationError;