shokupan 0.12.0 → 0.13.1

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.
Files changed (40) hide show
  1. package/README.md +1 -0
  2. package/dist/{analyzer-BkNQHWj4.js → analyzer-B0fMzeIo.js} +2 -2
  3. package/dist/{analyzer-BkNQHWj4.js.map → analyzer-B0fMzeIo.js.map} +1 -1
  4. package/dist/{analyzer-DM-OlRq8.cjs → analyzer-BOtveWL-.cjs} +2 -2
  5. package/dist/{analyzer-DM-OlRq8.cjs.map → analyzer-BOtveWL-.cjs.map} +1 -1
  6. package/dist/{analyzer.impl-CVJ8zfGQ.cjs → analyzer.impl-CUDO6vpn.cjs} +72 -5
  7. package/dist/analyzer.impl-CUDO6vpn.cjs.map +1 -0
  8. package/dist/{analyzer.impl-CsA1bS_s.js → analyzer.impl-DmHe92Oi.js} +72 -5
  9. package/dist/analyzer.impl-DmHe92Oi.js.map +1 -0
  10. package/dist/cli.cjs +1 -1
  11. package/dist/cli.js +1 -1
  12. package/dist/index.cjs +1849 -190
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.ts +9 -0
  15. package/dist/index.js +1892 -233
  16. package/dist/index.js.map +1 -1
  17. package/dist/plugins/application/error-view/index.d.ts +14 -0
  18. package/dist/plugins/application/error-view/monkeypatch.d.ts +9 -0
  19. package/dist/plugins/application/error-view/util/source-reader.d.ts +10 -0
  20. package/dist/plugins/application/error-view/views/error.d.ts +2 -0
  21. package/dist/plugins/application/error-view/views/status.d.ts +2 -0
  22. package/dist/plugins/application/htmx/index.d.ts +39 -0
  23. package/dist/plugins/application/mcp-server/plugin.d.ts +1 -2
  24. package/dist/plugins/application/openapi/test-setup.d.ts +1 -0
  25. package/dist/plugins/application/opentelemetry/index.d.ts +33 -0
  26. package/dist/plugins/middleware/session.d.ts +4 -4
  27. package/dist/plugins/resilience/decorators.d.ts +23 -0
  28. package/dist/plugins/resilience/factory.d.ts +5 -0
  29. package/dist/plugins/resilience/index.d.ts +2 -0
  30. package/dist/router.d.ts +21 -2
  31. package/dist/util/decorators.d.ts +38 -0
  32. package/dist/util/env-loader.d.ts +99 -0
  33. package/dist/util/mcp-protocol.d.ts +52 -0
  34. package/dist/util/promise.d.ts +16 -0
  35. package/dist/util/symbol.d.ts +4 -0
  36. package/dist/util/types.d.ts +10 -2
  37. package/package.json +36 -11
  38. package/dist/analyzer.impl-CVJ8zfGQ.cjs.map +0 -1
  39. package/dist/analyzer.impl-CsA1bS_s.js.map +0 -1
  40. package/dist/util/instrumentation.d.ts +0 -9
package/dist/index.js CHANGED
@@ -1,12 +1,11 @@
1
1
  import { nanoid } from "nanoid";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { inspect } from "node:util";
4
+ import { RecordId, Surreal } from "surrealdb";
4
5
  import { Eta } from "eta";
5
6
  import { stat, readdir, readFile as readFile$1 } from "fs/promises";
6
7
  import { resolve, join, sep, basename } from "path";
7
- import { trace, SpanKind, SpanStatusCode, context } from "@opentelemetry/api";
8
- import { RecordId, Surreal } from "surrealdb";
9
- import { dump } from "js-yaml";
8
+ import { retry, handleAll, ExponentialBackoff, ConstantBackoff, circuitBreaker, ConsecutiveBreaker, timeout, TimeoutStrategy, bulkhead, fallback, wrap } from "cockatiel";
10
9
  import * as http$1 from "node:http";
11
10
  import "node:https";
12
11
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -20,8 +19,10 @@ import * as os from "node:os";
20
19
  import os__default from "node:os";
21
20
  import { createRequire } from "node:module";
22
21
  import { monitorEventLoopDelay } from "node:perf_hooks";
22
+ import { file } from "bun";
23
+ import { OpenAPIAnalyzer } from "./analyzer.impl-DmHe92Oi.js";
23
24
  import { readFileSync } from "node:fs";
24
- import { OpenAPIAnalyzer } from "./analyzer-BkNQHWj4.js";
25
+ import { OpenAPIAnalyzer as OpenAPIAnalyzer$1 } from "./analyzer-B0fMzeIo.js";
25
26
  import { Readable } from "node:stream";
26
27
  import * as zlib from "node:zlib";
27
28
  import Ajv from "ajv";
@@ -330,6 +331,10 @@ const $cachedCookies = /* @__PURE__ */ Symbol.for("Shokupan.ctx.cachedCookies");
330
331
  const $ws = /* @__PURE__ */ Symbol.for("Shokupan.ctx.ws");
331
332
  const $socket = /* @__PURE__ */ Symbol.for("Shokupan.ctx.socket");
332
333
  const $io = /* @__PURE__ */ Symbol.for("Shokupan.ctx.io");
334
+ const $mcpTools = /* @__PURE__ */ Symbol.for("Shokupan.mcp.tools");
335
+ const $mcpPrompts = /* @__PURE__ */ Symbol.for("Shokupan.mcp.prompts");
336
+ const $mcpResources = /* @__PURE__ */ Symbol.for("Shokupan.mcp.resources");
337
+ const $resilienceConfig = /* @__PURE__ */ Symbol.for("Shokupan.resilience.config");
333
338
  function isValidCookieDomain(domain, currentHost) {
334
339
  const hostWithoutPort = currentHost.split(":")[0];
335
340
  if (domain === hostWithoutPort) return true;
@@ -1098,11 +1103,11 @@ class ShokupanContext {
1098
1103
  }
1099
1104
  const compose = (middleware) => {
1100
1105
  if (!middleware.length) {
1101
- return (context2, next) => {
1106
+ return (context, next) => {
1102
1107
  return next ? next() : Promise.resolve();
1103
1108
  };
1104
1109
  }
1105
- return function dispatch(context2, next) {
1110
+ return function dispatch(context, next) {
1106
1111
  let index = -1;
1107
1112
  async function runner(i) {
1108
1113
  if (i <= index) return Promise.reject(new Error("next() called multiple times"));
@@ -1116,31 +1121,109 @@ const compose = (middleware) => {
1116
1121
  console.error(`[Middleware Error] Item at index ${i} is not a function! It is: ${typeof fn} (${name})`, fn);
1117
1122
  throw new TypeError(`Middleware at index ${i} must be a function, got ${name}`);
1118
1123
  }
1119
- if (!context2[$debug]) {
1120
- return fn(context2, () => runner(i + 1));
1124
+ const trackingEnabled = context.app?.applicationConfig?.enableMiddlewareTracking;
1125
+ const meta = fn.metadata;
1126
+ let trackingStartTime = 0;
1127
+ if (trackingEnabled && meta) {
1128
+ trackingStartTime = performance.now();
1129
+ context.handlerStack.push({
1130
+ name: meta.name || fn.name || "anonymous",
1131
+ file: meta.file,
1132
+ line: meta.line,
1133
+ isBuiltin: meta.isBuiltin,
1134
+ startTime: trackingStartTime,
1135
+ duration: -1
1136
+ });
1137
+ }
1138
+ const debug = context[$debug];
1139
+ let debugId;
1140
+ let previousNode;
1141
+ let debugStart = 0;
1142
+ if (debug) {
1143
+ debugId = fn._debugId || fn.name || "anonymous";
1144
+ previousNode = debug.getCurrentNode();
1145
+ debug.trackEdge(previousNode, debugId);
1146
+ debug.setNode(debugId);
1147
+ debugStart = performance.now();
1121
1148
  }
1122
- const debug = context2[$debug];
1123
- const debugId = fn._debugId || fn.name || "anonymous";
1124
- const previousNode = debug.getCurrentNode();
1125
- debug.trackEdge(previousNode, debugId);
1126
- debug.setNode(debugId);
1127
- const start = performance.now();
1128
1149
  try {
1129
- const res = await Promise.resolve(fn(context2, () => runner(i + 1)));
1130
- debug.trackStep(debugId, "middleware", performance.now() - start, "success");
1150
+ const res = await fn(context, () => runner(i + 1));
1151
+ if (trackingEnabled && meta) {
1152
+ const duration = performance.now() - trackingStartTime;
1153
+ const stackItem = context.handlerStack[context.handlerStack.length - 1];
1154
+ if (stackItem) stackItem.duration = duration;
1155
+ Promise.resolve().then(async () => {
1156
+ try {
1157
+ const db = context.app?.db;
1158
+ if (!db) return;
1159
+ const timestamp = Date.now();
1160
+ await db.upsert(new RecordId("middleware_tracking", {
1161
+ timestamp,
1162
+ name: meta.name
1163
+ }), {
1164
+ name: meta.name,
1165
+ path: context.path,
1166
+ timestamp,
1167
+ duration,
1168
+ file: meta.file,
1169
+ line: meta.line,
1170
+ error: void 0,
1171
+ metadata: {
1172
+ isBuiltin: meta.isBuiltin,
1173
+ pluginName: meta.pluginName
1174
+ }
1175
+ });
1176
+ } catch (e) {
1177
+ }
1178
+ });
1179
+ }
1180
+ if (debug) {
1181
+ debug.trackStep(debugId, "middleware", performance.now() - debugStart, "success");
1182
+ }
1131
1183
  return res;
1132
1184
  } catch (err) {
1133
- debug.trackStep(debugId, "middleware", performance.now() - start, "error", err);
1134
- return Promise.reject(err);
1185
+ if (trackingEnabled && meta) {
1186
+ const duration = performance.now() - trackingStartTime;
1187
+ const stackItem = context.handlerStack[context.handlerStack.length - 1];
1188
+ if (stackItem) stackItem.duration = duration;
1189
+ Promise.resolve().then(async () => {
1190
+ try {
1191
+ const db = context.app?.db;
1192
+ if (!db) return;
1193
+ const timestamp = Date.now();
1194
+ await db.upsert(new RecordId("middleware_tracking", {
1195
+ timestamp,
1196
+ name: meta.name
1197
+ }), {
1198
+ name: meta.name,
1199
+ path: context.path,
1200
+ timestamp,
1201
+ duration,
1202
+ file: meta.file,
1203
+ line: meta.line,
1204
+ error: String(err),
1205
+ metadata: {
1206
+ isBuiltin: meta.isBuiltin,
1207
+ pluginName: meta.pluginName
1208
+ }
1209
+ });
1210
+ } catch (e) {
1211
+ }
1212
+ });
1213
+ }
1214
+ if (debug) {
1215
+ debug.trackStep(debugId, "middleware", performance.now() - debugStart, "error", err);
1216
+ }
1217
+ throw err;
1135
1218
  } finally {
1136
- if (previousNode) debug.setNode(previousNode);
1219
+ if (debug && previousNode) debug.setNode(previousNode);
1137
1220
  }
1138
1221
  }
1139
1222
  return runner(0);
1140
1223
  };
1141
1224
  };
1142
1225
  function isObject(item) {
1143
- return item && typeof item === "object" && !Array.isArray(item);
1226
+ return !!(item && typeof item === "object" && !Array.isArray(item));
1144
1227
  }
1145
1228
  function deepMerge(target, ...sources) {
1146
1229
  if (!sources.length) return target;
@@ -1405,7 +1488,7 @@ async function generateOpenApi(rootRouter, options = {}) {
1405
1488
  let astMiddlewareRegistry = {};
1406
1489
  let applications = [];
1407
1490
  try {
1408
- const { OpenAPIAnalyzer: OpenAPIAnalyzer2 } = await import("./analyzer-BkNQHWj4.js");
1491
+ const { OpenAPIAnalyzer: OpenAPIAnalyzer2 } = await import("./analyzer-B0fMzeIo.js");
1409
1492
  const entrypoint = rootRouter.metadata?.file;
1410
1493
  const analyzer = new OpenAPIAnalyzer2(process.cwd(), entrypoint);
1411
1494
  const analysisResult = await analyzer.analyze();
@@ -1457,7 +1540,7 @@ async function generateOpenApi(rootRouter, options = {}) {
1457
1540
  isBuiltinPlugin = true;
1458
1541
  pluginName = router.metadata.pluginName;
1459
1542
  tag = pluginName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1460
- } else if (router.metadata?.file && router.metadata.file.includes("plugins/application/")) {
1543
+ } else if (router.metadata?.file && router.metadata.file.includes("plugins/application/") && !router.metadata.file.match(/\.(spec|test)\.ts$/)) {
1461
1544
  isBuiltinPlugin = true;
1462
1545
  const match = router.metadata.file.match(/plugins\/application\/([^/]+)/);
1463
1546
  if (match) {
@@ -1615,7 +1698,39 @@ async function generateOpenApi(rootRouter, options = {}) {
1615
1698
  const params = [];
1616
1699
  if (astMatch.requestTypes?.query) {
1617
1700
  for (const [name, _type] of Object.entries(astMatch.requestTypes.query)) {
1618
- params.push({ name, in: "query", schema: { type: "string" } });
1701
+ let type = "string";
1702
+ let format;
1703
+ if (_type === "integer") {
1704
+ type = "integer";
1705
+ format = "int32";
1706
+ } else if (_type === "number") {
1707
+ type = "number";
1708
+ format = "float";
1709
+ } else if (_type === "boolean") type = "boolean";
1710
+ const schema = { type };
1711
+ if (format) schema.format = format;
1712
+ params.push({ name, in: "query", schema });
1713
+ }
1714
+ }
1715
+ if (astMatch.requestTypes?.params) {
1716
+ for (const [name, _type] of Object.entries(astMatch.requestTypes.params)) {
1717
+ let type = "string";
1718
+ let format;
1719
+ if (_type === "integer") {
1720
+ type = "integer";
1721
+ format = "int32";
1722
+ } else if (_type === "number") {
1723
+ type = "number";
1724
+ format = "float";
1725
+ } else if (_type === "boolean") type = "boolean";
1726
+ const schema = { type };
1727
+ if (format) schema.format = format;
1728
+ params.push({ name, in: "path", required: true, schema });
1729
+ }
1730
+ }
1731
+ if (astMatch.requestTypes?.headers) {
1732
+ for (const [name, _type] of Object.entries(astMatch.requestTypes.headers)) {
1733
+ params.push({ name, in: "header", schema: { type: "string" } });
1619
1734
  }
1620
1735
  }
1621
1736
  if (params.length > 0) {
@@ -1631,20 +1746,20 @@ async function generateOpenApi(rootRouter, options = {}) {
1631
1746
  });
1632
1747
  }
1633
1748
  const runtimeSource = (route.handler.originalHandler || route.handler).toString();
1634
- let file;
1749
+ let file2;
1635
1750
  let line;
1636
1751
  if (route.metadata?.file) {
1637
- file = route.metadata.file;
1752
+ file2 = route.metadata.file;
1638
1753
  line = route.metadata.line || 1;
1639
1754
  }
1640
1755
  operation["x-source-info"] = {
1641
1756
  snippet: runtimeSource,
1642
1757
  isRuntime: true,
1643
- ...file ? { file, line: line || 1 } : {}
1758
+ ...file2 ? { file: file2, line: line || 1 } : {}
1644
1759
  };
1645
- if (file) {
1760
+ if (file2) {
1646
1761
  operation["x-shokupan-source"] = {
1647
- file,
1762
+ file: file2,
1648
1763
  line: line || 1,
1649
1764
  code: runtimeSource,
1650
1765
  pluginName: route.handler.pluginName
@@ -1673,9 +1788,7 @@ async function generateOpenApi(rootRouter, options = {}) {
1673
1788
  const mergedParams = [...existingParams];
1674
1789
  pathParams.forEach((p) => {
1675
1790
  const idx = mergedParams.findIndex((ep) => ep.in === "path" && ep.name === p.name);
1676
- if (idx >= 0) {
1677
- mergedParams[idx] = deepMerge(mergedParams[idx], p);
1678
- } else {
1791
+ if (idx === -1) {
1679
1792
  mergedParams.push(p);
1680
1793
  }
1681
1794
  });
@@ -1964,6 +2077,166 @@ function serveStatic(config, prefix) {
1964
2077
  serveStaticMiddleware.pluginName = "ServeStatic";
1965
2078
  return serveStaticMiddleware;
1966
2079
  }
2080
+ class OpenTelemetryPlugin {
2081
+ constructor(options = {}) {
2082
+ this.options = options;
2083
+ }
2084
+ api;
2085
+ sdk;
2086
+ async onInit(app) {
2087
+ try {
2088
+ this.api = await import("@opentelemetry/api");
2089
+ } catch (e) {
2090
+ console.warn("OpenTelemetry API not found. OpenTelemetryPlugin will be disabled.");
2091
+ return;
2092
+ }
2093
+ if (this.options.enableAutoInstrumentation !== false) {
2094
+ app.use(this.middleware());
2095
+ }
2096
+ }
2097
+ middleware() {
2098
+ return async (ctx, next) => {
2099
+ if (!this.api) return next();
2100
+ const tracer = this.api.trace.getTracer("shokupan");
2101
+ return tracer.startActiveSpan(`${ctx.req.method} ${ctx.req.path}`, {
2102
+ kind: this.api.SpanKind.SERVER,
2103
+ attributes: {
2104
+ "http.method": ctx.req.method,
2105
+ "http.url": ctx.req.url,
2106
+ "http.host": ctx.req.host,
2107
+ "http.user_agent": ctx.req.headers.get("user-agent") || void 0
2108
+ }
2109
+ }, async (span) => {
2110
+ try {
2111
+ const res = await next();
2112
+ span.setAttributes({
2113
+ "http.status_code": ctx.res.status
2114
+ });
2115
+ if (ctx.res.status >= 500) {
2116
+ span.setStatus({ code: this.api.SpanStatusCode.ERROR });
2117
+ } else {
2118
+ span.setStatus({ code: this.api.SpanStatusCode.OK });
2119
+ }
2120
+ return res;
2121
+ } catch (err) {
2122
+ span.recordException(err);
2123
+ span.setStatus({ code: this.api.SpanStatusCode.ERROR, message: err.message });
2124
+ throw err;
2125
+ } finally {
2126
+ span.end();
2127
+ }
2128
+ });
2129
+ };
2130
+ }
2131
+ }
2132
+ function traceMiddleware(fn, name) {
2133
+ let api;
2134
+ try {
2135
+ api = require("@opentelemetry/api");
2136
+ } catch {
2137
+ }
2138
+ if (!api) return fn;
2139
+ const tracer = api.trace.getTracer("shokupan.middleware");
2140
+ const middlewareName = name || fn.name || "anonymous middleware";
2141
+ return async (ctx, next) => {
2142
+ return tracer.startActiveSpan(`middleware - ${middlewareName}`, {
2143
+ kind: api.SpanKind.INTERNAL,
2144
+ attributes: {
2145
+ "code.function": middlewareName,
2146
+ "component": "shokupan.middleware"
2147
+ }
2148
+ }, async (span) => {
2149
+ try {
2150
+ const result = await fn(ctx, next);
2151
+ return result;
2152
+ } catch (err) {
2153
+ span.recordException(err);
2154
+ span.setStatus({ code: api.SpanStatusCode.ERROR, message: err.message });
2155
+ throw err;
2156
+ } finally {
2157
+ span.end();
2158
+ }
2159
+ });
2160
+ };
2161
+ }
2162
+ function traceHandler(fn, name) {
2163
+ let api;
2164
+ try {
2165
+ api = require("@opentelemetry/api");
2166
+ } catch {
2167
+ }
2168
+ if (!api) return fn;
2169
+ const tracer = api.trace.getTracer("shokupan.middleware");
2170
+ return async function(...args) {
2171
+ return tracer.startActiveSpan(`route handler - ${name}`, {
2172
+ kind: api.SpanKind.INTERNAL,
2173
+ attributes: {
2174
+ "http.route": name,
2175
+ "component": "shokupan.route"
2176
+ }
2177
+ }, async (span) => {
2178
+ try {
2179
+ const result = await fn.apply(this, args);
2180
+ return result;
2181
+ } catch (err) {
2182
+ span.recordException(err);
2183
+ span.setStatus({ code: api.SpanStatusCode.ERROR, message: err.message });
2184
+ throw err;
2185
+ } finally {
2186
+ span.end();
2187
+ }
2188
+ });
2189
+ };
2190
+ }
2191
+ class ResilienceFactory {
2192
+ static createPolicy(config) {
2193
+ const policies = [];
2194
+ if (config.retry) {
2195
+ const builder = handleAll;
2196
+ let retries = (config.retry.attempts ?? 3) - 1;
2197
+ if (retries < 0) retries = 0;
2198
+ let retryPolicy;
2199
+ if (config.retry.backoff === "exponential") {
2200
+ retryPolicy = retry(builder, {
2201
+ maxAttempts: retries,
2202
+ backoff: new ExponentialBackoff({
2203
+ initialDelay: config.retry.delay || 1e3,
2204
+ maxDelay: config.retry.maxDelay || 3e4
2205
+ })
2206
+ });
2207
+ } else {
2208
+ retryPolicy = retry(builder, {
2209
+ maxAttempts: retries,
2210
+ backoff: new ConstantBackoff(config.retry.delay || 1e3)
2211
+ });
2212
+ }
2213
+ policies.push(retryPolicy);
2214
+ }
2215
+ if (config.circuitBreaker) {
2216
+ const builder = handleAll;
2217
+ const breaker = circuitBreaker(builder, {
2218
+ halfOpenAfter: config.circuitBreaker.resetTimeout || 1e4,
2219
+ breaker: new ConsecutiveBreaker(config.circuitBreaker.threshold || 5)
2220
+ });
2221
+ policies.push(breaker);
2222
+ }
2223
+ if (config.timeout) {
2224
+ policies.push(timeout(config.timeout, { strategy: TimeoutStrategy.Aggressive, abortOnReturn: true }));
2225
+ }
2226
+ if (config.bulkhead) {
2227
+ policies.push(bulkhead(config.bulkhead));
2228
+ }
2229
+ if (config.fallback !== void 0) {
2230
+ const builder = handleAll;
2231
+ const fb = fallback(builder, config.fallback);
2232
+ policies.push(fb);
2233
+ }
2234
+ if (policies.length === 0) {
2235
+ return { execute: (fn) => fn() };
2236
+ }
2237
+ return wrap(...policies.reverse());
2238
+ }
2239
+ }
1967
2240
  const metadataStore = /* @__PURE__ */ new WeakMap();
1968
2241
  function defineMetadata(key, value, target, propertyKey) {
1969
2242
  let targetMetadata = metadataStore.get(target);
@@ -2060,31 +2333,8 @@ const di = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2060
2333
  __proto__: null,
2061
2334
  Container
2062
2335
  }, Symbol.toStringTag, { value: "Module" }));
2063
- const tracer = trace.getTracer("shokupan.middleware");
2064
- function traceHandler(fn, name) {
2065
- return async function(...args) {
2066
- return tracer.startActiveSpan(`route handler - ${name}`, {
2067
- kind: SpanKind.INTERNAL,
2068
- attributes: {
2069
- "http.route": name,
2070
- "component": "shokupan.route"
2071
- }
2072
- }, async (span) => {
2073
- try {
2074
- const result = await fn.apply(this, args);
2075
- return result;
2076
- } catch (err) {
2077
- span.recordException(err);
2078
- span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
2079
- throw err;
2080
- } finally {
2081
- span.end();
2082
- }
2083
- });
2084
- };
2085
- }
2086
2336
  function getCallerInfo(skipFrames = 1) {
2087
- let file = "unknown";
2337
+ let file2 = "unknown";
2088
2338
  let line = 0;
2089
2339
  try {
2090
2340
  const err = new Error();
@@ -2100,19 +2350,20 @@ function getCallerInfo(skipFrames = 1) {
2100
2350
  if (l.includes("src/router.ts")) continue;
2101
2351
  if (l.includes("src/util/decorators.ts")) continue;
2102
2352
  if (l.includes("src/shokupan.ts")) continue;
2353
+ if (l.includes("src/plugins/application/openapi/openapi.ts")) continue;
2103
2354
  found++;
2104
2355
  if (found >= skipFrames) {
2105
2356
  const match = l.match(/\((.*):(\d+):(\d+)\)/) || l.match(/at (.*):(\d+):(\d+)/);
2106
2357
  if (match) {
2107
- file = match[1];
2358
+ file2 = match[1];
2108
2359
  line = parseInt(match[2], 10);
2109
- return { file, line };
2360
+ return { file: file2, line };
2110
2361
  }
2111
2362
  }
2112
2363
  }
2113
2364
  } catch (e) {
2114
2365
  }
2115
- return { file, line };
2366
+ return { file: file2, line };
2116
2367
  }
2117
2368
  const HTTPMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "ALL"];
2118
2369
  var RouteParamType = /* @__PURE__ */ ((RouteParamType2) => {
@@ -2168,6 +2419,10 @@ class ControllerScanner {
2168
2419
  const decoratedArgs = instance[$routeArgs] || proto && proto[$routeArgs];
2169
2420
  const methodMiddlewareMap = instance[$middleware] || proto && proto[$middleware];
2170
2421
  const decoratedEvents = instance[$eventMethods] || proto && proto[$eventMethods];
2422
+ const mcpTools = instance[$mcpTools] || proto && proto[$mcpTools];
2423
+ const mcpPrompts = instance[$mcpPrompts] || proto && proto[$mcpPrompts];
2424
+ const mcpResources = instance[$mcpResources] || proto && proto[$mcpResources];
2425
+ const resilienceConfigMap = instance[$resilienceConfig] || proto && proto[$resilienceConfig];
2171
2426
  let routesAttached = 0;
2172
2427
  for (let i = 0; i < Array.from(methods).length; i++) {
2173
2428
  const name = Array.from(methods)[i];
@@ -2292,6 +2547,14 @@ class ControllerScanner {
2292
2547
  return composed(ctx, () => wrappedHandler(ctx));
2293
2548
  };
2294
2549
  }
2550
+ const config = resilienceConfigMap?.get(name);
2551
+ if (config) {
2552
+ const policy = ResilienceFactory.createPolicy(config);
2553
+ const baseHandler = finalHandler;
2554
+ finalHandler = async (ctx) => {
2555
+ return policy.execute(() => baseHandler(ctx));
2556
+ };
2557
+ }
2295
2558
  finalHandler.originalHandler = originalHandler;
2296
2559
  if (finalHandler !== wrappedHandler) {
2297
2560
  wrappedHandler.originalHandler = originalHandler;
@@ -2348,6 +2611,25 @@ class ControllerScanner {
2348
2611
  wrappedHandler.originalHandler = originalHandler;
2349
2612
  router.event(eventConfig.eventName, wrappedHandler);
2350
2613
  }
2614
+ const toolConfig = mcpTools?.get(name);
2615
+ if (toolConfig) {
2616
+ const handler = originalHandler.bind(instance);
2617
+ router.tool(toolConfig.name || name, toolConfig.inputSchema, handler);
2618
+ }
2619
+ const promptConfig = mcpPrompts?.get(name);
2620
+ if (promptConfig) {
2621
+ const handler = originalHandler.bind(instance);
2622
+ router.prompt(promptConfig.name || name, promptConfig.arguments, handler);
2623
+ }
2624
+ const resourceConfig = mcpResources?.get(name);
2625
+ if (resourceConfig) {
2626
+ const handler = originalHandler.bind(instance);
2627
+ router.resource(resourceConfig.uri, {
2628
+ name: resourceConfig.name || name,
2629
+ description: resourceConfig.description,
2630
+ mimeType: resourceConfig.mimeType
2631
+ }, handler);
2632
+ }
2351
2633
  }
2352
2634
  if (routesAttached === 0) {
2353
2635
  console.warn(`No routes attached to controller ${instance.constructor.name}`);
@@ -2378,71 +2660,177 @@ function getErrorStatus(err) {
2378
2660
  }
2379
2661
  return 500;
2380
2662
  }
2663
+ class NotFoundError extends HttpError {
2664
+ constructor(message = "Not Found") {
2665
+ super(message, 404);
2666
+ this.name = "NotFoundError";
2667
+ }
2668
+ }
2381
2669
  class EventError extends HttpError {
2382
2670
  constructor(message = "Event Error") {
2383
2671
  super(message, 500);
2384
2672
  this.name = "EventError";
2385
2673
  }
2386
2674
  }
2387
- class MiddlewareTracker {
2388
- static wrap(handler, context2) {
2389
- const { file, line, name, isBuiltin, pluginName } = context2;
2390
- const handlerName = name || handler.name || "anonymous";
2391
- const trackedHandler = async (ctx, next) => {
2392
- if (!ctx.app?.applicationConfig.enableMiddlewareTracking) {
2393
- return handler(ctx, next);
2394
- }
2395
- const startTime = performance.now();
2396
- let error = void 0;
2397
- try {
2398
- ctx.handlerStack.push({
2399
- name: handlerName,
2400
- file,
2401
- line,
2402
- isBuiltin,
2403
- startTime,
2404
- duration: -1
2405
- });
2406
- return await handler(ctx, next);
2407
- } catch (e) {
2408
- error = e;
2409
- throw e;
2410
- } finally {
2411
- const duration = performance.now() - startTime;
2412
- const stackItem = ctx.handlerStack[ctx.handlerStack.length - 1];
2413
- if (stackItem && stackItem.name === handlerName) {
2414
- stackItem.duration = duration;
2415
- }
2416
- Promise.resolve().then(async () => {
2675
+ class McpProtocol {
2676
+ tools = /* @__PURE__ */ new Map();
2677
+ prompts = /* @__PURE__ */ new Map();
2678
+ resources = /* @__PURE__ */ new Map();
2679
+ constructor(tools = [], prompts = [], resources = []) {
2680
+ tools.forEach((t) => this.tools.set(t.name, t));
2681
+ prompts.forEach((p) => this.prompts.set(p.name, p));
2682
+ resources.forEach((r) => this.resources.set(r.uri, r));
2683
+ }
2684
+ addTool(tool) {
2685
+ this.tools.set(tool.name, tool);
2686
+ }
2687
+ addPrompt(prompt) {
2688
+ this.prompts.set(prompt.name, prompt);
2689
+ }
2690
+ addResource(resource) {
2691
+ this.resources.set(resource.uri, resource);
2692
+ }
2693
+ merge(other) {
2694
+ other.tools.forEach((t) => this.tools.set(t.name, t));
2695
+ other.prompts.forEach((p) => this.prompts.set(p.name, p));
2696
+ other.resources.forEach((r) => this.resources.set(r.uri, r));
2697
+ }
2698
+ async handleMessage(message) {
2699
+ if (message.jsonrpc !== "2.0") {
2700
+ return this.error(message.id, -32600, "Invalid Request");
2701
+ }
2702
+ try {
2703
+ switch (message.method) {
2704
+ case "initialize":
2705
+ return this.success(message.id, {
2706
+ protocolVersion: "2024-11-05",
2707
+ serverInfo: {
2708
+ name: "Shokupan MCP",
2709
+ version: "1.0.0"
2710
+ },
2711
+ capabilities: {
2712
+ tools: this.tools.size > 0 ? {} : void 0,
2713
+ prompts: this.prompts.size > 0 ? {} : void 0,
2714
+ resources: this.resources.size > 0 ? {} : void 0
2715
+ }
2716
+ });
2717
+ case "ping":
2718
+ return this.success(message.id, {});
2719
+ case "tools/list":
2720
+ if (this.tools.size === 0) return this.success(message.id, { tools: [] });
2721
+ return this.success(message.id, {
2722
+ tools: Array.from(this.tools.values()).map((t) => ({
2723
+ name: t.name,
2724
+ description: t.description,
2725
+ inputSchema: t.inputSchema || { type: "object", properties: {} }
2726
+ }))
2727
+ });
2728
+ case "tools/call": {
2729
+ if (!message.params || !message.params.name) {
2730
+ return this.error(message.id, -32602, "Invalid params: name required");
2731
+ }
2732
+ const tool = this.tools.get(message.params.name);
2733
+ if (!tool) {
2734
+ return this.error(message.id, -32601, `Tool not found: ${message.params.name}`);
2735
+ }
2417
2736
  try {
2418
- const db = ctx.app?.db;
2419
- if (!db) return;
2420
- const timestamp = Date.now();
2421
- await db.upsert(new RecordId("middleware_tracking", {
2422
- timestamp,
2423
- name: handlerName
2424
- }), {
2425
- name: handlerName,
2426
- path: ctx.path,
2427
- timestamp,
2428
- duration,
2429
- file,
2430
- line,
2431
- error: error ? String(error) : void 0,
2432
- metadata: {
2433
- isBuiltin,
2434
- pluginName
2737
+ const result = await tool.handler(message.params.arguments || {});
2738
+ return this.success(message.id, result);
2739
+ } catch (e) {
2740
+ return {
2741
+ jsonrpc: "2.0",
2742
+ id: message.id ?? null,
2743
+ result: {
2744
+ isError: true,
2745
+ content: [{ type: "text", text: e.message || String(e) }]
2435
2746
  }
2436
- });
2437
- } catch (err) {
2747
+ };
2438
2748
  }
2439
- });
2749
+ }
2750
+ case "prompts/list":
2751
+ if (this.prompts.size === 0) return this.success(message.id, { prompts: [] });
2752
+ return this.success(message.id, {
2753
+ prompts: Array.from(this.prompts.values()).map((p) => ({
2754
+ name: p.name,
2755
+ description: p.description,
2756
+ arguments: p.arguments
2757
+ }))
2758
+ });
2759
+ case "prompts/get": {
2760
+ if (!message.params || !message.params.name) {
2761
+ return this.error(message.id, -32602, "Invalid params: name required");
2762
+ }
2763
+ const prompt = this.prompts.get(message.params.name);
2764
+ if (!prompt) {
2765
+ return this.error(message.id, -32601, `Prompt not found: ${message.params.name}`);
2766
+ }
2767
+ const result = await prompt.handler(message.params.arguments || {});
2768
+ return this.success(message.id, result);
2769
+ }
2770
+ case "resources/list":
2771
+ if (this.resources.size === 0) return this.success(message.id, { resources: [] });
2772
+ return this.success(message.id, {
2773
+ resources: Array.from(this.resources.values()).map((r) => ({
2774
+ uri: r.uri,
2775
+ name: r.name,
2776
+ description: r.description,
2777
+ mimeType: r.mimeType
2778
+ }))
2779
+ });
2780
+ case "resources/read": {
2781
+ if (!message.params || !message.params.uri) {
2782
+ return this.error(message.id, -32602, "Invalid params: uri required");
2783
+ }
2784
+ let resource = this.resources.get(message.params.uri);
2785
+ if (!resource) {
2786
+ return this.error(message.id, -32601, `Resource not found: ${message.params.uri}`);
2787
+ }
2788
+ const result = await resource.handler(message.params.uri);
2789
+ return this.success(message.id, result);
2790
+ }
2791
+ default:
2792
+ if (message.id === void 0) return null;
2793
+ return this.error(message.id, -32601, "Method not found");
2440
2794
  }
2795
+ } catch (err) {
2796
+ return this.error(message.id, -32603, "Internal Error", err.message);
2797
+ }
2798
+ }
2799
+ success(id, result) {
2800
+ return {
2801
+ jsonrpc: "2.0",
2802
+ id: id ?? null,
2803
+ result
2804
+ };
2805
+ }
2806
+ error(id, code, message, data) {
2807
+ return {
2808
+ jsonrpc: "2.0",
2809
+ id: id ?? null,
2810
+ error: { code, message, data }
2441
2811
  };
2442
- trackedHandler.metadata = handler.metadata || context2;
2443
- Object.defineProperty(trackedHandler, "name", { value: handlerName });
2444
- trackedHandler.originalHandler = handler.originalHandler || handler;
2445
- return trackedHandler;
2812
+ }
2813
+ }
2814
+ class MiddlewareTracker {
2815
+ static wrap(handler, context) {
2816
+ const { file: file2, line, name, isBuiltin, pluginName } = context;
2817
+ const handlerName = name || handler.name || "anonymous";
2818
+ try {
2819
+ handler.metadata = context;
2820
+ if (!handler.name || handler.name === "anonymous") {
2821
+ try {
2822
+ Object.defineProperty(handler, "name", { value: handlerName, configurable: true });
2823
+ } catch (e) {
2824
+ }
2825
+ }
2826
+ } catch (e) {
2827
+ const wrapped = handler.bind(null);
2828
+ wrapped.metadata = context;
2829
+ Object.defineProperty(wrapped, "name", { value: handlerName });
2830
+ wrapped.originalHandler = handler.originalHandler || handler;
2831
+ return wrapped;
2832
+ }
2833
+ return handler;
2446
2834
  }
2447
2835
  }
2448
2836
  class ShokupanRequestBase {
@@ -2656,6 +3044,7 @@ class ShokupanRouter {
2656
3044
  trie = new RouterTrie();
2657
3045
  metadata;
2658
3046
  // Metadata for the router itself
3047
+ mcpProtocol = new McpProtocol();
2659
3048
  currentGuards = [];
2660
3049
  eventHandlers = /* @__PURE__ */ new Map();
2661
3050
  /**
@@ -2781,6 +3170,39 @@ class ShokupanRouter {
2781
3170
  handlers.push(handler);
2782
3171
  return this;
2783
3172
  }
3173
+ /**
3174
+ * Registers an MCP Tool.
3175
+ */
3176
+ tool(name, schema, handler) {
3177
+ this.mcpProtocol.addTool({
3178
+ name,
3179
+ inputSchema: schema,
3180
+ handler
3181
+ });
3182
+ return this;
3183
+ }
3184
+ /**
3185
+ * Registers an MCP Prompt.
3186
+ */
3187
+ prompt(name, args, handler) {
3188
+ this.mcpProtocol.addPrompt({
3189
+ name,
3190
+ arguments: args,
3191
+ handler
3192
+ });
3193
+ return this;
3194
+ }
3195
+ /**
3196
+ * Registers an MCP Resource.
3197
+ */
3198
+ resource(uri, options, handler) {
3199
+ this.mcpProtocol.addResource({
3200
+ uri,
3201
+ handler,
3202
+ ...options
3203
+ });
3204
+ return this;
3205
+ }
2784
3206
  /**
2785
3207
  * Finds an event handler(s) by name.
2786
3208
  */
@@ -3079,63 +3501,52 @@ class ShokupanRouter {
3079
3501
  }
3080
3502
  }
3081
3503
  }
3082
- let wrappedHandler = async (ctx) => {
3083
- return handler(ctx);
3084
- };
3085
- wrappedHandler.originalHandler = handler.originalHandler || handler;
3086
- const routeGuards = [...this.currentGuards];
3087
3504
  const effectiveTimeout = requestTimeout ?? this.requestTimeout ?? this.rootConfig?.requestTimeout;
3088
- if (effectiveTimeout !== void 0 && effectiveTimeout > 0) {
3089
- const originalHandler = wrappedHandler;
3505
+ const effectiveRenderer = renderer ?? this.config?.renderer ?? this.rootConfig?.renderer;
3506
+ const routeGuards = [...this.currentGuards];
3507
+ let wrappedHandler = handler;
3508
+ if (effectiveTimeout && effectiveTimeout > 0 || effectiveRenderer || routeGuards.length > 0) {
3509
+ const originalHandler = handler;
3090
3510
  wrappedHandler = async (ctx) => {
3091
- if (ctx.server) {
3511
+ if (effectiveTimeout && effectiveTimeout > 0 && ctx.server) {
3092
3512
  ctx.server.timeout(ctx.req, effectiveTimeout / 1e3);
3093
3513
  }
3094
- return originalHandler(ctx);
3095
- };
3096
- wrappedHandler.originalHandler = originalHandler.originalHandler || originalHandler;
3097
- }
3098
- if (routeGuards.length > 0) {
3099
- const innerHandler = wrappedHandler;
3100
- wrappedHandler = async (ctx) => {
3101
- for (let i = 0; i < routeGuards.length; i++) {
3102
- const guard = routeGuards[i];
3103
- let guardPassed = false;
3104
- let nextCalled = false;
3105
- const next = () => {
3106
- nextCalled = true;
3107
- return Promise.resolve();
3108
- };
3109
- try {
3110
- const result = await guard.handler(ctx, next);
3111
- if (result === true || nextCalled) {
3112
- guardPassed = true;
3113
- } else if (result !== void 0 && result !== null && result !== false) {
3114
- return result;
3115
- } else {
3514
+ if (effectiveRenderer) {
3515
+ ctx.setRenderer(effectiveRenderer);
3516
+ }
3517
+ if (routeGuards.length > 0) {
3518
+ for (let i = 0; i < routeGuards.length; i++) {
3519
+ const guard = routeGuards[i];
3520
+ let guardPassed = false;
3521
+ let nextCalled = false;
3522
+ const next = () => {
3523
+ nextCalled = true;
3524
+ return Promise.resolve();
3525
+ };
3526
+ try {
3527
+ const result = await guard.handler(ctx, next);
3528
+ if (result === true || nextCalled) {
3529
+ guardPassed = true;
3530
+ } else if (result !== void 0 && result !== null && result !== false) {
3531
+ return result;
3532
+ } else {
3533
+ return ctx.json({ error: "Forbidden" }, 403);
3534
+ }
3535
+ } catch (error) {
3536
+ throw error;
3537
+ }
3538
+ if (!guardPassed) {
3116
3539
  return ctx.json({ error: "Forbidden" }, 403);
3117
3540
  }
3118
- } catch (error) {
3119
- throw error;
3120
- }
3121
- if (!guardPassed) {
3122
- return ctx.json({ error: "Forbidden" }, 403);
3123
3541
  }
3124
3542
  }
3125
- return innerHandler(ctx);
3126
- };
3127
- }
3128
- const effectiveRenderer = renderer ?? this.config?.renderer ?? this.rootConfig?.renderer;
3129
- if (effectiveRenderer) {
3130
- const innerHandler = wrappedHandler;
3131
- wrappedHandler = async (ctx) => {
3132
- ctx.setRenderer(effectiveRenderer);
3133
- return innerHandler(ctx);
3543
+ return originalHandler(ctx);
3134
3544
  };
3545
+ wrappedHandler.originalHandler = handler.originalHandler || handler;
3135
3546
  }
3136
- const { file, line } = metadata || getCallerInfo();
3547
+ const { file: file2, line } = metadata || getCallerInfo();
3137
3548
  wrappedHandler = MiddlewareTracker.wrap(wrappedHandler, {
3138
- file,
3549
+ file: file2,
3139
3550
  line,
3140
3551
  name: handler.name || "anonymous",
3141
3552
  isBuiltin: handler.isBuiltin,
@@ -3158,7 +3569,7 @@ class ShokupanRouter {
3158
3569
  requestTimeout,
3159
3570
  renderer,
3160
3571
  metadata: {
3161
- file,
3572
+ file: file2,
3162
3573
  line
3163
3574
  },
3164
3575
  controller,
@@ -3198,7 +3609,7 @@ class ShokupanRouter {
3198
3609
  guard(specOrHandler, handler) {
3199
3610
  const spec = typeof specOrHandler === "function" ? void 0 : specOrHandler;
3200
3611
  const guardHandler = typeof specOrHandler === "function" ? specOrHandler : handler;
3201
- let file = "unknown";
3612
+ let file2 = "unknown";
3202
3613
  let line = 0;
3203
3614
  try {
3204
3615
  const err = new Error();
@@ -3209,14 +3620,14 @@ class ShokupanRouter {
3209
3620
  if (callerLine) {
3210
3621
  const match = callerLine.match(/\((.{0,1000}):(\d{1,10}):(?:\d{1,10})\)/) || callerLine.match(/at (.{0,1000}):(\d{1,10}):(?:\d{1,10})/);
3211
3622
  if (match) {
3212
- file = match[1];
3623
+ file2 = match[1];
3213
3624
  line = parseInt(match[2], 10);
3214
3625
  }
3215
3626
  }
3216
3627
  } catch (e) {
3217
3628
  }
3218
3629
  const trackedGuard = MiddlewareTracker.wrap(guardHandler, {
3219
- file,
3630
+ file: file2,
3220
3631
  line,
3221
3632
  name: guardHandler.name || "guard"
3222
3633
  });
@@ -3662,10 +4073,10 @@ class DefaultFileSystemAdapter {
3662
4073
  }
3663
4074
  async stat(path) {
3664
4075
  if (typeof Bun !== "undefined") {
3665
- const file = Bun.file(path);
4076
+ const file2 = Bun.file(path);
3666
4077
  return {
3667
- size: file.size,
3668
- mtime: new Date(file.lastModified)
4078
+ size: file2.size,
4079
+ mtime: new Date(file2.lastModified)
3669
4080
  };
3670
4081
  } else {
3671
4082
  fs ??= await import("node:fs/promises");
@@ -3832,6 +4243,32 @@ class SurrealDatastore {
3832
4243
  return this.db.close();
3833
4244
  }
3834
4245
  }
4246
+ const kContext = /* @__PURE__ */ Symbol("kContext");
4247
+ let patched = false;
4248
+ function enablePromisePatch() {
4249
+ if (patched) return;
4250
+ patched = true;
4251
+ const OriginalPromise = global.Promise;
4252
+ global.Promise = class PatchedPromise extends OriginalPromise {
4253
+ [kContext];
4254
+ constructor(executor) {
4255
+ const store = asyncContext.getStore();
4256
+ const stack = new Error().stack || "No parent stack";
4257
+ super(executor);
4258
+ this[kContext] = {
4259
+ store,
4260
+ stack
4261
+ };
4262
+ }
4263
+ };
4264
+ for (const prop of Object.getOwnPropertyNames(OriginalPromise)) {
4265
+ if (prop !== "prototype" && prop !== "length" && prop !== "name") {
4266
+ if (typeof OriginalPromise[prop] === "function") {
4267
+ global.Promise[prop] = OriginalPromise[prop];
4268
+ }
4269
+ }
4270
+ }
4271
+ }
3835
4272
  const defaults = {
3836
4273
  port: 3e3,
3837
4274
  hostname: "localhost",
@@ -3869,21 +4306,38 @@ class Shokupan extends ShokupanRouter {
3869
4306
  this[$isApplication] = true;
3870
4307
  this[$appRoot] = this;
3871
4308
  this.applicationConfig = config;
3872
- const { file, line } = getCallerInfo();
4309
+ const { file: file2, line } = getCallerInfo();
3873
4310
  this.metadata = {
3874
- file,
4311
+ file: file2,
3875
4312
  line,
3876
4313
  name: "ShokupanApplication"
3877
4314
  };
3878
- if (this.applicationConfig.securityHeaders !== false) {
4315
+ if (this.applicationConfig.defaultSecurityHeaders) {
3879
4316
  const { SecurityHeaders: SecurityHeaders2 } = require("./plugins/middleware/security-headers");
3880
- this.use(SecurityHeaders2(this.applicationConfig.securityHeaders === true ? {} : this.applicationConfig.securityHeaders));
4317
+ this.use(SecurityHeaders2(this.applicationConfig.defaultSecurityHeaders === true ? {} : this.applicationConfig.defaultSecurityHeaders));
3881
4318
  }
3882
4319
  if (this.applicationConfig.adapter !== "wintercg") {
3883
4320
  this.dbPromise = this.initDatastore().catch((err) => {
3884
4321
  this.logger?.debug("Failed to initialize default datastore", { error: err });
3885
4322
  });
3886
4323
  }
4324
+ if (this.applicationConfig.enablePromiseMonkeypatch) {
4325
+ enablePromisePatch();
4326
+ const processRef = typeof process !== "undefined" ? process : void 0;
4327
+ if (processRef && processRef.on) {
4328
+ processRef.on("unhandledRejection", (reason, promise) => {
4329
+ const ctx = promise?.[kContext];
4330
+ if (ctx && ctx.store && ctx.store.app === this) {
4331
+ const { requestId } = ctx.store;
4332
+ this.logger.error("Unhandled Rejection in Shokupan Request", {
4333
+ error: reason,
4334
+ requestId,
4335
+ creationStack: ctx.stack
4336
+ });
4337
+ }
4338
+ });
4339
+ }
4340
+ }
3887
4341
  }
3888
4342
  async initDatastore() {
3889
4343
  let engines = this.applicationConfig.surreal?.engines;
@@ -3913,9 +4367,9 @@ class Shokupan extends ShokupanRouter {
3913
4367
  * Adds middleware to the application.
3914
4368
  */
3915
4369
  use(middleware) {
3916
- const { file, line } = getCallerInfo();
4370
+ const { file: file2, line } = getCallerInfo();
3917
4371
  const wrapped = MiddlewareTracker.wrap(middleware, {
3918
- file,
4372
+ file: file2,
3919
4373
  line,
3920
4374
  name: middleware.name || "middleware",
3921
4375
  isBuiltin: middleware.isBuiltin,
@@ -3969,6 +4423,7 @@ class Shokupan extends ShokupanRouter {
3969
4423
  this.get("/.well-known/openapi.yaml", async (ctx) => {
3970
4424
  try {
3971
4425
  await this.openApiSpecPromise;
4426
+ const { dump } = await import("js-yaml");
3972
4427
  const yaml = dump(this.openApiSpec);
3973
4428
  return ctx.send(yaml, { status: 200, headers: { "content-type": "application/yaml" } });
3974
4429
  } catch (e) {
@@ -4160,7 +4615,8 @@ class Shokupan extends ShokupanRouter {
4160
4615
  */
4161
4616
  async fetch(req, server) {
4162
4617
  if (this.applicationConfig.enableTracing) {
4163
- const tracer2 = trace.getTracer("shokupan.application");
4618
+ const { trace, context } = await import("@opentelemetry/api");
4619
+ const tracer = trace.getTracer("shokupan.application");
4164
4620
  const store = asyncContext.getStore();
4165
4621
  const attrs = {
4166
4622
  attributes: {
@@ -4170,7 +4626,7 @@ class Shokupan extends ShokupanRouter {
4170
4626
  };
4171
4627
  const parent = store?.span;
4172
4628
  const ctx = parent ? trace.setSpan(context.active(), parent) : void 0;
4173
- return tracer2.startActiveSpan(`${req.method} ${new URL(req.url).pathname}`, attrs, ctx, (span) => {
4629
+ return tracer.startActiveSpan(`${req.method} ${new URL(req.url).pathname}`, attrs, ctx, (span) => {
4174
4630
  const ctxStore = new RequestContextStore();
4175
4631
  ctxStore.span = span;
4176
4632
  ctxStore.request = req;
@@ -4178,16 +4634,19 @@ class Shokupan extends ShokupanRouter {
4178
4634
  });
4179
4635
  }
4180
4636
  if (this.applicationConfig.enableAsyncLocalStorage) {
4637
+ const requestId = this.applicationConfig.idGenerator?.() ?? nanoid();
4181
4638
  const ctxStore = new RequestContextStore();
4182
4639
  ctxStore.request = req;
4183
- return asyncContext.run(ctxStore, () => this.handleRequest(req, server));
4640
+ ctxStore["requestId"] = requestId;
4641
+ ctxStore["app"] = this;
4642
+ return asyncContext.run(ctxStore, () => this.handleRequest(req, server, requestId));
4184
4643
  }
4185
4644
  return this.handleRequest(req, server);
4186
4645
  }
4187
- async handleRequest(req, server) {
4646
+ async handleRequest(req, server, requestId) {
4188
4647
  const request = req;
4189
4648
  const controller = new AbortController();
4190
- const ctx = new ShokupanContext(request, server, void 0, this, controller.signal, this.applicationConfig.enableMiddlewareTracking);
4649
+ const ctx = new ShokupanContext(request, server, void 0, this, controller.signal, this.applicationConfig.enableMiddlewareTracking, requestId);
4191
4650
  const handle = async () => {
4192
4651
  if (this.cpuMonitor && this.cpuMonitor.getUsage() > (this.applicationConfig.autoBackpressureLevel ?? 60)) {
4193
4652
  const msg = "Too Many Requests (CPU Backpressure)";
@@ -4208,15 +4667,97 @@ class Shokupan extends ShokupanRouter {
4208
4667
  ctx[$routeMatched] = true;
4209
4668
  ctx.params = match.params;
4210
4669
  if (bodyParsing) await bodyParsing;
4211
- return match.handler(ctx);
4212
- }
4213
- return null;
4214
- });
4215
- let response;
4216
- if (result instanceof Response) {
4217
- response = result;
4218
- } else if ((result === null || result === void 0) && ctx[$finalResponse] instanceof Response) {
4219
- response = ctx[$finalResponse];
4670
+ if (this.applicationConfig.enableMiddlewareTracking) {
4671
+ const handler = match.handler;
4672
+ const meta = handler.metadata;
4673
+ if (meta) {
4674
+ const trackingStartTime = performance.now();
4675
+ const handlerName = meta.name || handler.name || "anonymous";
4676
+ ctx.handlerStack.push({
4677
+ name: handlerName,
4678
+ file: meta.file,
4679
+ line: meta.line,
4680
+ isBuiltin: meta.isBuiltin,
4681
+ startTime: trackingStartTime,
4682
+ duration: -1
4683
+ });
4684
+ try {
4685
+ const res = await handler(ctx);
4686
+ const duration = performance.now() - trackingStartTime;
4687
+ const stackItem = ctx.handlerStack[ctx.handlerStack.length - 1];
4688
+ if (stackItem) stackItem.duration = duration;
4689
+ Promise.resolve().then(async () => {
4690
+ try {
4691
+ const db = this.db;
4692
+ if (!db) return;
4693
+ const timestamp = Date.now();
4694
+ await db.upsert(new RecordId("middleware_tracking", {
4695
+ timestamp,
4696
+ name: handlerName
4697
+ }), {
4698
+ name: handlerName,
4699
+ path: ctx.path,
4700
+ timestamp,
4701
+ duration,
4702
+ file: meta.file,
4703
+ line: meta.line,
4704
+ error: void 0,
4705
+ metadata: {
4706
+ isBuiltin: meta.isBuiltin,
4707
+ pluginName: meta.pluginName
4708
+ }
4709
+ });
4710
+ } catch (e) {
4711
+ }
4712
+ });
4713
+ return res;
4714
+ } catch (err) {
4715
+ const duration = performance.now() - trackingStartTime;
4716
+ const stackItem = ctx.handlerStack[ctx.handlerStack.length - 1];
4717
+ if (stackItem) stackItem.duration = duration;
4718
+ Promise.resolve().then(async () => {
4719
+ try {
4720
+ const db = this.db;
4721
+ if (!db) return;
4722
+ const timestamp = Date.now();
4723
+ await db.upsert(new RecordId("middleware_tracking", {
4724
+ timestamp,
4725
+ name: handlerName
4726
+ }), {
4727
+ name: handlerName,
4728
+ path: ctx.path,
4729
+ timestamp,
4730
+ duration,
4731
+ file: meta.file,
4732
+ line: meta.line,
4733
+ error: String(err),
4734
+ metadata: {
4735
+ isBuiltin: meta.isBuiltin,
4736
+ pluginName: meta.pluginName
4737
+ }
4738
+ });
4739
+ } catch (e) {
4740
+ }
4741
+ });
4742
+ throw err;
4743
+ }
4744
+ }
4745
+ }
4746
+ return match.handler(ctx);
4747
+ }
4748
+ if (ctx.upgrade()) {
4749
+ return void 0;
4750
+ }
4751
+ if (ctx.response.status !== HTTP_STATUS.OK) {
4752
+ return ctx.send(null, { status: ctx.response.status, headers: ctx.response.headers });
4753
+ }
4754
+ throw new NotFoundError();
4755
+ });
4756
+ let response;
4757
+ if (result instanceof Response) {
4758
+ response = result;
4759
+ } else if ((result === null || result === void 0) && ctx[$finalResponse] instanceof Response) {
4760
+ response = ctx[$finalResponse];
4220
4761
  } else if (result === null || result === void 0) {
4221
4762
  if (ctx[$finalResponse] instanceof Response) {
4222
4763
  response = ctx[$finalResponse];
@@ -4229,14 +4770,7 @@ class Shokupan extends ShokupanRouter {
4229
4770
  }
4230
4771
  response = ctx.send(null, { status, headers: ctx.response.headers });
4231
4772
  } else {
4232
- if (ctx.upgrade()) {
4233
- return void 0;
4234
- }
4235
- if (ctx.response.status !== HTTP_STATUS.OK) {
4236
- response = ctx.send(null, { status: ctx.response.status, headers: ctx.response.headers });
4237
- } else {
4238
- response = ctx.text("Not Found", HTTP_STATUS.NOT_FOUND);
4239
- }
4773
+ throw new NotFoundError();
4240
4774
  }
4241
4775
  } else if (typeof result === "object") {
4242
4776
  response = ctx.json(result);
@@ -4603,6 +5137,37 @@ function Event(eventName) {
4603
5137
  function RateLimit(options) {
4604
5138
  return Use(RateLimitMiddleware(options));
4605
5139
  }
5140
+ function Tool(options) {
5141
+ return (target, propertyKey, descriptor) => {
5142
+ target[$mcpTools] ??= /* @__PURE__ */ new Map();
5143
+ target[$mcpTools].set(propertyKey, {
5144
+ name: options?.name,
5145
+ description: options?.description,
5146
+ inputSchema: options?.inputSchema
5147
+ });
5148
+ };
5149
+ }
5150
+ function Prompt(options) {
5151
+ return (target, propertyKey, descriptor) => {
5152
+ target[$mcpPrompts] ??= /* @__PURE__ */ new Map();
5153
+ target[$mcpPrompts].set(propertyKey, {
5154
+ name: options?.name,
5155
+ description: options?.description,
5156
+ arguments: options?.arguments
5157
+ });
5158
+ };
5159
+ }
5160
+ function Resource(uri, options) {
5161
+ return (target, propertyKey, descriptor) => {
5162
+ target[$mcpResources] ??= /* @__PURE__ */ new Map();
5163
+ target[$mcpResources].set(propertyKey, {
5164
+ uri,
5165
+ name: options?.name,
5166
+ description: options?.description,
5167
+ mimeType: options?.mimeType
5168
+ });
5169
+ };
5170
+ }
4606
5171
  function ApiExplorerApp({ spec, base, asyncSpec, config }) {
4607
5172
  const hierarchy = /* @__PURE__ */ new Map();
4608
5173
  const addRoute = (groupKey, route) => {
@@ -4970,8 +5535,8 @@ class ApiExplorerPlugin extends ShokupanRouter {
4970
5535
  return dir;
4971
5536
  }
4972
5537
  init() {
4973
- const serveFile = async (ctx, file, type) => {
4974
- const content = await readFile$1(join$1(ApiExplorerPlugin.getBasePath(), "static", file), "utf-8");
5538
+ const serveFile = async (ctx, file2, type) => {
5539
+ const content = await readFile$1(join$1(ApiExplorerPlugin.getBasePath(), "static", file2), "utf-8");
4975
5540
  ctx.set("Content-Type", type);
4976
5541
  return ctx.send(content);
4977
5542
  };
@@ -4994,11 +5559,11 @@ class ApiExplorerPlugin extends ShokupanRouter {
4994
5559
  this.get("/theme.css", (ctx) => serveFile(ctx, "theme.css", "text/css"));
4995
5560
  this.get("/explorer-client.mjs", (ctx) => serveFile(ctx, "explorer-client.mjs", "application/javascript"));
4996
5561
  this.get("/_source", async (ctx) => {
4997
- const file = ctx.query["file"];
4998
- if (!file) return ctx.text("Missing file parameter", 400);
5562
+ const file2 = ctx.query["file"];
5563
+ if (!file2) return ctx.text("Missing file parameter", 400);
4999
5564
  const { resolve: resolve2, normalize, isAbsolute } = await import("node:path");
5000
5565
  const cwd = process.cwd();
5001
- const resolvedPath = resolve2(cwd, file);
5566
+ const resolvedPath = resolve2(cwd, file2);
5002
5567
  if (!resolvedPath.startsWith(cwd)) {
5003
5568
  return ctx.text("Forbidden: File must be within project root", 403);
5004
5569
  }
@@ -5234,7 +5799,7 @@ async function generateAsyncApi(rootRouter, options = {}) {
5234
5799
  let astMiddlewareRegistry = {};
5235
5800
  let applications = [];
5236
5801
  try {
5237
- const { OpenAPIAnalyzer: OpenAPIAnalyzer2 } = await import("./analyzer-BkNQHWj4.js");
5802
+ const { OpenAPIAnalyzer: OpenAPIAnalyzer2 } = await import("./analyzer-B0fMzeIo.js");
5238
5803
  const entrypoint = globalThis.Bun?.main || require.main?.filename || process.argv[1];
5239
5804
  const analyzer = new OpenAPIAnalyzer2(process.cwd(), entrypoint);
5240
5805
  const analysisResult = await analyzer.analyze();
@@ -5607,8 +6172,8 @@ class AsyncApiPlugin extends ShokupanRouter {
5607
6172
  }
5608
6173
  }
5609
6174
  init() {
5610
- const serveFile = async (ctx, file, type) => {
5611
- const content = await readFile(join$1(AsyncApiPlugin.getBasePath(), "static", file), "utf-8");
6175
+ const serveFile = async (ctx, file2, type) => {
6176
+ const content = await readFile(join$1(AsyncApiPlugin.getBasePath(), "static", file2), "utf-8");
5612
6177
  ctx.set("Content-Type", type);
5613
6178
  return ctx.send(content);
5614
6179
  };
@@ -5640,13 +6205,13 @@ class AsyncApiPlugin extends ShokupanRouter {
5640
6205
  return ctx.json(spec);
5641
6206
  });
5642
6207
  this.get("/_code", async (ctx) => {
5643
- const file = ctx.query["file"];
5644
- if (!file || typeof file !== "string") {
6208
+ const file2 = ctx.query["file"];
6209
+ if (!file2 || typeof file2 !== "string") {
5645
6210
  return ctx.text("Missing file parameter", 400);
5646
6211
  }
5647
6212
  const { resolve: resolve2 } = await import("node:path");
5648
6213
  const cwd = process.cwd();
5649
- const resolvedPath = resolve2(cwd, file);
6214
+ const resolvedPath = resolve2(cwd, file2);
5650
6215
  if (!resolvedPath.startsWith(cwd)) {
5651
6216
  return ctx.text("Forbidden: File must be within project root", 403);
5652
6217
  }
@@ -7506,6 +8071,463 @@ class Dashboard {
7506
8071
  function unknownError(ctx) {
7507
8072
  return ctx.json({ error: "Unknown Error" }, 500);
7508
8073
  }
8074
+ let isPatched = false;
8075
+ function applyMonkeyPatch() {
8076
+ if (isPatched) return;
8077
+ isPatched = true;
8078
+ Error.stackTraceLimit = 50;
8079
+ }
8080
+ async function readSourceContext(filePath, line, contextLines = 5) {
8081
+ if (!filePath || filePath.startsWith("node:") || filePath.startsWith("bun:") || filePath.includes("node_modules")) {
8082
+ return null;
8083
+ }
8084
+ const path = filePath.startsWith("file://") ? filePath.slice(7) : filePath;
8085
+ try {
8086
+ const f = file(path);
8087
+ if (!await f.exists()) return null;
8088
+ const content = await f.text();
8089
+ const allLines = content.split("\n");
8090
+ const targetIndex = line - 1;
8091
+ if (targetIndex < 0 || targetIndex >= allLines.length) return null;
8092
+ const start = Math.max(0, targetIndex - contextLines);
8093
+ const end = Math.min(allLines.length, targetIndex + contextLines + 1);
8094
+ const subset = allLines.slice(start, end).map((code, i) => ({
8095
+ line: start + i + 1,
8096
+ code,
8097
+ isTarget: start + i + 1 === line
8098
+ }));
8099
+ return {
8100
+ lines: subset,
8101
+ startLine: start + 1,
8102
+ file: path
8103
+ };
8104
+ } catch (e) {
8105
+ return null;
8106
+ }
8107
+ }
8108
+ async function renderErrorView(ctx, error) {
8109
+ const frames = [];
8110
+ const cwd = process.cwd();
8111
+ const errorName = error?.name || "Error";
8112
+ const errorMessage = error?.message || "Unknown error occurred";
8113
+ const errorId = error?.id || ctx.requestId || "unknown-id";
8114
+ const errorTimestamp = error?.timestamp ? new Date(error.timestamp).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
8115
+ const errorScope = error?.scope || {};
8116
+ const lines = (error?.stack || "").split("\n").slice(1);
8117
+ for (const line of lines) {
8118
+ const match = line.match(/at (?:(.+?)\s+\()?(?:(.+?):(\d+):(\d+))\)?/);
8119
+ if (match) {
8120
+ const [_, method, file2, lineNo, colNo] = match;
8121
+ const fileName = file2 || "";
8122
+ let relativeFile = fileName;
8123
+ if (fileName.startsWith(cwd)) {
8124
+ relativeFile = fileName.slice(cwd.length + 1);
8125
+ }
8126
+ let isInternal = fileName.startsWith("node:") || fileName.startsWith("bun:") || fileName === "undefined" || fileName === "";
8127
+ if (isInternal && (method.includes("setTimeout") || method.includes("setInterval") || method.includes("setImmediate"))) {
8128
+ isInternal = false;
8129
+ }
8130
+ let isShokupan = false;
8131
+ if (fileName.includes("node_modules/@dotglitch/shokupan")) {
8132
+ isShokupan = true;
8133
+ } else if (relativeFile.startsWith("src/") || fileName.includes("/shokupan/dist/")) {
8134
+ isShokupan = true;
8135
+ }
8136
+ const isDependency = fileName.includes("node_modules") && !isShokupan;
8137
+ frames.push({
8138
+ method: method || "<anonymous>",
8139
+ file: fileName,
8140
+ line: parseInt(lineNo),
8141
+ column: parseInt(colNo),
8142
+ isNative: false,
8143
+ isInternal,
8144
+ isShokupan,
8145
+ isDependency,
8146
+ shortFile: fileName.split("/").pop() || fileName,
8147
+ relativeFile
8148
+ });
8149
+ }
8150
+ }
8151
+ let focusFrame = frames.find((f) => !f.isInternal && !f.isShokupan && !f.isDependency && !f.isNative);
8152
+ if (!focusFrame) focusFrame = frames[0];
8153
+ let sourceContext = null;
8154
+ if (focusFrame && focusFrame.file && !focusFrame.isInternal) {
8155
+ sourceContext = await readSourceContext(focusFrame.file, focusFrame.line, 8);
8156
+ }
8157
+ const renderFrames = frames.map((frame, index) => {
8158
+ const classes = [
8159
+ "stack-entry",
8160
+ frame.isInternal ? "internal" : "",
8161
+ frame.isShokupan ? "shokupan" : "",
8162
+ frame.isDependency ? "dependency" : "",
8163
+ frame === focusFrame ? "active" : ""
8164
+ ].join(" ");
8165
+ const fileLink = `vscode://file/${frame.file}:${frame.line}:${frame.column}`;
8166
+ return `
8167
+ <li class="${classes}">
8168
+ <a href="${fileLink}" style="text-decoration:none; color:inherit; display:block">
8169
+ <div class="stack-method">${frame.method === "<anonymous>" ? "Anonymous" : frame.method}</div>
8170
+ <div class="stack-file">${frame.relativeFile}:${frame.line}</div>
8171
+ </a>
8172
+ </li>
8173
+ `;
8174
+ }).join("");
8175
+ const highlightCode = (code) => {
8176
+ return code.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/(")(.*?)(")/g, '<span style="color:#a5d6ff">$1$2$3</span>').replace(/(')(.*?)(')/g, '<span style="color:#a5d6ff">$1$2$3</span>').replace(/(`)(.*?)(`)/g, '<span style="color:#a5d6ff">$1$2$3</span>').replace(/\b(const|let|var|function|class|import|export|from|return|if|else|switch|case|default|break|continue|try|catch|finally|throw|new|async|await|interface|type|extends|implements|public|private|protected|static|readonly|true|false|null|undefined)\b/g, '<span style="color:#ff7b72">$1</span>').replace(/(=>|===|==|!=|!==|\|\||&&|\+|\-|\*|\/|%|\+\+|\-\-)/g, '<span style="color:#ff7b72">$1</span>').replace(/\b([A-Z][a-zA-Z0-9_]*)\b/g, '<span style="color:#79c0ff">$1</span>').replace(/\b([a-zA-Z0-9_]+)(?=\()/g, '<span style="color:#d2a8ff">$1</span>').replace(/(\/\/.*)/g, '<span style="color:#8b949e; font-style:italic">$1</span>');
8177
+ };
8178
+ if (sourceContext) {
8179
+ sourceContext.lines.map((l) => `
8180
+ <div class="code-line ${l.isTarget ? "target" : ""}">
8181
+ <div class="line-number">${l.line}</div>
8182
+ <div class="line-content">${highlightCode(l.code)}</div>
8183
+ </div>
8184
+ `).join("");
8185
+ }
8186
+ const renderKV = (data) => {
8187
+ if (!data || Object.keys(data).length === 0) return '<div style="color:var(--text-muted)">None</div>';
8188
+ return `<table class="kv-table">
8189
+ ${Object.entries(data).map(([k, v]) => {
8190
+ let displayVal = String(v);
8191
+ let valClass = "";
8192
+ if (typeof v === "number") {
8193
+ valClass = "kv-val-number";
8194
+ } else if (typeof v === "boolean") {
8195
+ valClass = "kv-val-bool";
8196
+ } else if (typeof v === "object" && v !== null) {
8197
+ try {
8198
+ displayVal = JSON.stringify(v, null, 2);
8199
+ valClass = "kv-val-json";
8200
+ } catch (e) {
8201
+ displayVal = "[Circular]";
8202
+ }
8203
+ }
8204
+ return `
8205
+ <tr>
8206
+ <td class="kv-key">${k}</td>
8207
+ <td class="kv-val ${valClass}">${displayVal}</td>
8208
+ </tr>`;
8209
+ }).join("")}
8210
+ </table>`;
8211
+ };
8212
+ const ICON_COPY = `<svg class="icon" viewBox="0 0 24 24"><path d="M16 1H4C2.9 1 2 1.9 2 3V17H4V3H16V1ZM19 5H8C6.9 5 6 5.9 6 7V21C6 22.1 6.9 23 8 23H19C20.1 23 21 22.1 21 21V7C21 5.9 20.1 5 19 5ZM19 21H8V7H19V21Z"/></svg>`;
8213
+ return `<!DOCTYPE html>
8214
+ <html lang="en">
8215
+ <head>
8216
+ <meta charset="UTF-8">
8217
+ <title>${errorName}: ${errorMessage}</title>
8218
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.css" rel="stylesheet" />
8219
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-highlight/prism-line-highlight.min.css" rel="stylesheet" />
8220
+ <link href="/_shokupan/error-view/prismjs.theme.css" rel="stylesheet" />
8221
+ <link href="/_shokupan/error-view/styles.css" rel="stylesheet" />
8222
+ <link href="/_shokupan/error-view/theme.css" rel="stylesheet" />
8223
+
8224
+ </head>
8225
+ <body class="">
8226
+
8227
+ <div class="page">
8228
+ <!-- HEADER -->
8229
+ <header class="chapter-header">
8230
+ <div class="chapter-meta">
8231
+ <div class="meta-item">
8232
+ <span>${ctx.method}</span>
8233
+ </div>
8234
+ <div class="meta-item">
8235
+ <span>${ctx.url.pathname}</span>
8236
+ </div>
8237
+ <div class="meta-item">
8238
+ <span>${ctx.response.status || 500}</span>
8239
+ </div>
8240
+ <div class="meta-item" style="margin-left:auto">
8241
+ <span class="id-badge" onclick="copyText('${errorId}')" title="Copy ID">ID: ${errorId}</span>
8242
+ </div>
8243
+ </div>
8244
+
8245
+ <h1 class="error-title">${errorName}</h1>
8246
+
8247
+ <div class="error-message-container">
8248
+ <h2 class="error-message">${errorMessage}</h2>
8249
+ <button class="action-btn" onclick="copyText('${errorMessage.replace(/'/g, "\\'")}')" title="Copy Message" style="padding:4px 8px">
8250
+ ${ICON_COPY}
8251
+ </button>
8252
+ </div>
8253
+
8254
+ <div class="actions-bar">
8255
+ <button class="action-btn" onclick="copyText()">
8256
+ ${ICON_COPY} Copy Error
8257
+ </button>
8258
+ <button class="action-btn" onclick="document.getElementById('raw-modal').style.display='flex'">
8259
+ View Raw Error
8260
+ </button>
8261
+ </div>
8262
+ </header>
8263
+
8264
+ <!-- CODE FIGURE -->
8265
+ <section class="figure">
8266
+ <div class="figure-caption">
8267
+ ${focusFrame ? `<a href="vscode://file${focusFrame.file}:${focusFrame.line}" style="color:var(--text-muted); text-decoration:none">${focusFrame ? focusFrame.relativeFile : sourceContext?.file || "Unknown Source"}</a>` : ""}
8268
+ </div>
8269
+ <div class="figure-body">
8270
+ ${sourceContext ? `
8271
+ <pre class="line-numbers" data-line="${sourceContext.lines.find((l) => l.isTarget)?.line}" data-start="${sourceContext.lines[0].line}"><code class="language-typescript">${sourceContext.lines.map((l) => l.code.replace(/</g, "&lt;").replace(/>/g, "&gt;")).join("\n")}</code></pre>
8272
+ ` : `<div style="padding: 2rem; color: var(--text-muted); text-align: center;">Source code not available.</div>`}
8273
+ </div>
8274
+ </section>
8275
+
8276
+ <!-- NARRATIVE STACK -->
8277
+ <section class="narrative">
8278
+ <div class="section-title">
8279
+ <span>Stack Trace</span>
8280
+ <div class="filter-group">
8281
+ <span class="badge" onclick="this.classList.toggle('active'); document.body.classList.toggle('show-internals')">Internals</span>
8282
+ <span class="badge" onclick="this.classList.toggle('active'); document.body.classList.toggle('show-shokupan')">Framework</span>
8283
+ <span class="badge" onclick="this.classList.toggle('active'); document.body.classList.toggle('show-dependencies')">Dependencies</span>
8284
+ </div>
8285
+ </div>
8286
+ <ul class="stack-list">
8287
+ ${renderFrames}
8288
+ </ul>
8289
+ </section>
8290
+
8291
+ <!-- APPENDICES -->
8292
+ <section class="appendix">
8293
+ <div class="section-title">Context & Environment</div>
8294
+ <div class="appendix-grid">
8295
+ <div class="data-block">
8296
+ <h3>Request</h3>
8297
+ ${renderKV({
8298
+ id: errorId,
8299
+ timestamp: errorTimestamp,
8300
+ ...errorScope || {}
8301
+ })}
8302
+ </div>
8303
+ <div class="data-block">
8304
+ <h3>Headers</h3>
8305
+ ${renderKV(Object.fromEntries(ctx.headers))}
8306
+ </div>
8307
+ <div class="data-block">
8308
+ <h3>Query & Params</h3>
8309
+ ${renderKV({ ...ctx.params, ...ctx.query })}
8310
+ </div>
8311
+ </div>
8312
+ </section>
8313
+ </div>
8314
+
8315
+ <!-- RAW ERROR MODAL -->
8316
+ <div id="raw-modal" class="modal-overlay" onclick="if(event.target === this) this.style.display='none'">
8317
+ <div class="modal-content">
8318
+ <div class="modal-header">
8319
+ <span>Raw Error Object</span>
8320
+ <button class="action-btn" onclick="document.getElementById('raw-modal').style.display='none'">Close</button>
8321
+ </div>
8322
+ <div class="modal-body" id="raw-content"></div>
8323
+ </div>
8324
+ </div>
8325
+
8326
+ <!-- PrismJS Scripts -->
8327
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"><\/script>
8328
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-typescript.min.js"><\/script>
8329
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.js"><\/script>
8330
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-highlight/prism-line-highlight.min.js"><\/script>
8331
+
8332
+ <script>
8333
+ // Prepare Raw Error
8334
+ // circular ref safe stringify
8335
+ const getCircularReplacer = () => {
8336
+ const seen = new WeakSet();
8337
+ return (key, value) => {
8338
+ if (typeof value === "object" && value !== null) {
8339
+ if (seen.has(value)) {
8340
+ return "[Circular]";
8341
+ }
8342
+ seen.add(value);
8343
+ }
8344
+ return value;
8345
+ };
8346
+ };
8347
+
8348
+ // Inject error data from SERVER side
8349
+ const rawError = ${(() => {
8350
+ const serializeError = (err) => {
8351
+ const obj = {
8352
+ name: err.name,
8353
+ message: err.message,
8354
+ stack: err.stack,
8355
+ ...err
8356
+ // Spread enumerable props
8357
+ };
8358
+ if (err.cause) obj.cause = err.cause;
8359
+ if (err.code) obj.code = err.code;
8360
+ if (err.status) obj.status = err.status;
8361
+ if (err.statusCode) obj.statusCode = err.statusCode;
8362
+ return JSON.stringify(obj, (key, value) => {
8363
+ if (key === "structuredStack") return void 0;
8364
+ return value;
8365
+ }, 2);
8366
+ };
8367
+ return serializeError(error);
8368
+ })()};
8369
+
8370
+ // At this point 'rawError' is an Object in Client JS (because serializeError returned a JSON string)
8371
+ const RAW_ERROR_JSON = JSON.stringify(rawError, getCircularReplacer(), 2);
8372
+ // "Normally printed" usually means standard stacktrace string which includes name/message
8373
+ const RAW_ERROR_TEXT = rawError.stack || (rawError.name + ': ' + rawError.message);
8374
+
8375
+ document.getElementById('raw-content').innerText = RAW_ERROR_JSON;
8376
+
8377
+ function copyText(text) {
8378
+ if (!text) text = RAW_ERROR_TEXT; // Default to text representation (Message + Stack)
8379
+ navigator.clipboard.writeText(text).then(() => {
8380
+ console.log('Copied');
8381
+ });
8382
+ }
8383
+ <\/script>
8384
+ </body>
8385
+ </html>`;
8386
+ }
8387
+ function renderStatusView(ctx, status, error) {
8388
+ const title = `${status} ${error.message || "Error"}`;
8389
+ const method = ctx.method;
8390
+ const path = ctx.url.pathname;
8391
+ const css = `
8392
+ body {
8393
+ background: var(--bg-primary);
8394
+ color: var(--text-primary);
8395
+ font-family: var(--shokupan-font);
8396
+ display: flex;
8397
+ align-items: center;
8398
+ justify-content: center;
8399
+ height: 100vh;
8400
+ margin: 0;
8401
+ overflow: hidden;
8402
+ }
8403
+ .container {
8404
+ text-align: center;
8405
+ animation: fadeIn 0.3s ease-out;
8406
+ background: var(--bg-card);
8407
+ padding: 3rem 4rem;
8408
+ border-radius: 16px;
8409
+ border: 1px solid var(--card-border);
8410
+ box-shadow: 0 10px 30px rgba(0,0,0,0.3);
8411
+ max-width: 600px;
8412
+ }
8413
+ h1 {
8414
+ font-size: 6rem;
8415
+ margin: 0;
8416
+ color: var(--primary);
8417
+ line-height: 1;
8418
+ font-weight: 800;
8419
+ letter-spacing: -2px;
8420
+ text-shadow: 0 4px 20px rgba(255, 179, 128, 0.2);
8421
+ }
8422
+ h2 {
8423
+ font-size: 1.5rem;
8424
+ margin: 1rem 0 2rem 0;
8425
+ font-weight: 400;
8426
+ color: var(--text-secondary);
8427
+ }
8428
+ .meta {
8429
+ color: var(--text-muted);
8430
+ font-family: var(--shokupan-font-mono);
8431
+ font-size: 1rem;
8432
+ background: var(--bg-primary);
8433
+ padding: 0.75rem 1.5rem;
8434
+ border-radius: 8px;
8435
+ display: inline-block;
8436
+ border: 1px solid var(--border-color);
8437
+ }
8438
+ .method {
8439
+ font-weight: bold;
8440
+ margin-right: 0.5rem;
8441
+ padding: 0.2rem 0.5rem;
8442
+ border-radius: 4px;
8443
+ }
8444
+ .path {
8445
+ color: var(--text-primary);
8446
+ }
8447
+ @keyframes fadeIn {
8448
+ from { opacity: 0; transform: translateY(20px); }
8449
+ to { opacity: 1; transform: translateY(0); }
8450
+ }
8451
+ `;
8452
+ return `<!DOCTYPE html>
8453
+ <html lang="en">
8454
+ <head>
8455
+ <meta charset="UTF-8">
8456
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8457
+ <title>${title}</title>
8458
+ <link href="/_shokupan/error-view/theme.css" rel="stylesheet" />
8459
+ <style>${css}</style>
8460
+ </head>
8461
+ <body>
8462
+ <div class="container">
8463
+ <h1>${status}</h1>
8464
+ <h2>${error.message || "An error occurred"}</h2>
8465
+ <div class="meta">
8466
+ <span class="method badge-${method}">${method}</span>
8467
+ <span class="path">${path}</span>
8468
+ </div>
8469
+ </div>
8470
+ </body>
8471
+ </html>`;
8472
+ }
8473
+ class ErrorView {
8474
+ constructor(config = {}) {
8475
+ this.config = config;
8476
+ }
8477
+ name = "error-view";
8478
+ async onInit(app) {
8479
+ applyMonkeyPatch();
8480
+ const errorViewMiddleware = async (ctx, next) => {
8481
+ try {
8482
+ return await next();
8483
+ } catch (err) {
8484
+ const accept = ctx.get("accept") || "";
8485
+ if (!accept.includes("text/html")) {
8486
+ throw err;
8487
+ }
8488
+ if (!err.timestamp) {
8489
+ Object.defineProperty(err, "timestamp", {
8490
+ value: Date.now(),
8491
+ enumerable: false,
8492
+ writable: true,
8493
+ configurable: true
8494
+ });
8495
+ }
8496
+ if (!err.id) {
8497
+ Object.defineProperty(err, "id", {
8498
+ value: ctx.requestId,
8499
+ enumerable: false,
8500
+ writable: true,
8501
+ configurable: true
8502
+ });
8503
+ }
8504
+ if (!err.scope) {
8505
+ const store = asyncContext.getStore();
8506
+ if (store) {
8507
+ Object.defineProperty(err, "scope", {
8508
+ value: { ...store },
8509
+ enumerable: false,
8510
+ writable: true,
8511
+ configurable: true
8512
+ });
8513
+ }
8514
+ }
8515
+ const status = getErrorStatus(err);
8516
+ if (status === 404 || status === 401 || status === 403) {
8517
+ const html2 = await renderStatusView(ctx, status, err);
8518
+ return ctx.html(html2, status);
8519
+ }
8520
+ const html = await renderErrorView(ctx, err);
8521
+ return ctx.html(html, status);
8522
+ }
8523
+ };
8524
+ Object.defineProperty(errorViewMiddleware, "name", { value: "ErrorViewMiddleware" });
8525
+ const { join: join2 } = await import("path");
8526
+ const assetDir = join2(import.meta.dir, "assets");
8527
+ app.static("/_shokupan/error-view", assetDir);
8528
+ app.use(errorViewMiddleware);
8529
+ }
8530
+ }
7509
8531
  class GraphQLApolloPlugin extends ShokupanRouter {
7510
8532
  // Use generic any or verify type
7511
8533
  constructor(pluginOptions) {
@@ -7580,6 +8602,377 @@ class GraphQLApolloPlugin extends ShokupanRouter {
7580
8602
  });
7581
8603
  }
7582
8604
  }
8605
+ class GraphQLYogaPlugin extends ShokupanRouter {
8606
+ constructor(pluginOptions) {
8607
+ super();
8608
+ this.pluginOptions = pluginOptions;
8609
+ this.pluginOptions.path ??= "/graphql";
8610
+ }
8611
+ yoga;
8612
+ async onInit(app, options) {
8613
+ const { createYoga } = await import("graphql-yoga");
8614
+ const path = options?.path || this.pluginOptions.path || "/graphql";
8615
+ this.yoga = createYoga({
8616
+ ...this.pluginOptions.yogaConfig,
8617
+ graphqlEndpoint: path
8618
+ });
8619
+ app.mount(path, this);
8620
+ const handler = async (ctx) => {
8621
+ let body;
8622
+ if (ctx.req.method !== "GET" && ctx.req.method !== "HEAD") {
8623
+ body = await ctx.body();
8624
+ if (typeof body === "object" && body !== null) {
8625
+ body = JSON.stringify(body);
8626
+ }
8627
+ }
8628
+ const response = await this.yoga.fetch(
8629
+ new Request(ctx.req.url, {
8630
+ method: ctx.req.method,
8631
+ headers: ctx.req.headers,
8632
+ body
8633
+ }),
8634
+ {
8635
+ ...ctx
8636
+ }
8637
+ );
8638
+ response.headers.forEach((value, key) => {
8639
+ ctx.set(key, value);
8640
+ });
8641
+ const text = await response.text();
8642
+ return ctx.send(text, {
8643
+ status: response.status
8644
+ });
8645
+ };
8646
+ this.get("/", handler);
8647
+ this.post("/", handler);
8648
+ this.get("/*", handler);
8649
+ this.post("/*", handler);
8650
+ }
8651
+ }
8652
+ class HtmxPlugin {
8653
+ async onInit(app) {
8654
+ app.use(this.middleware());
8655
+ }
8656
+ middleware() {
8657
+ return async (ctx, next) => {
8658
+ Object.defineProperty(ctx, "isHtmx", {
8659
+ get: () => ctx.req.headers.has("hx-request")
8660
+ });
8661
+ Object.defineProperty(ctx, "isHtmxBoosted", {
8662
+ get: () => ctx.req.headers.has("hx-boosted")
8663
+ });
8664
+ ctx.trigger = (event, options) => {
8665
+ let headerName = "HX-Trigger";
8666
+ if (options?.after === "settle") headerName = "HX-Trigger-After-Settle";
8667
+ if (options?.after === "swap") headerName = "HX-Trigger-After-Swap";
8668
+ let value = JSON.stringify(event);
8669
+ if (typeof event === "string") {
8670
+ value = event;
8671
+ } else {
8672
+ value = JSON.stringify(event);
8673
+ }
8674
+ ctx.set(headerName, value);
8675
+ };
8676
+ ctx.pushUrl = (url) => {
8677
+ ctx.set("HX-Push-Url", url === false ? "false" : url);
8678
+ };
8679
+ ctx.htmxRedirect = (url) => {
8680
+ ctx.set("HX-Redirect", url);
8681
+ };
8682
+ ctx.refresh = () => {
8683
+ ctx.set("HX-Refresh", "true");
8684
+ };
8685
+ return next();
8686
+ };
8687
+ }
8688
+ }
8689
+ function Idempotency(options = {}) {
8690
+ const headerName = options.header || "Idempotency-Key";
8691
+ options.ttl || 24 * 60 * 60 * 1e3;
8692
+ let RecordIdClass;
8693
+ const idempotencyMiddleware = async function IdempotencyMiddleware(ctx, next) {
8694
+ const key = ctx.headers.get(headerName);
8695
+ if (!key) {
8696
+ return next();
8697
+ }
8698
+ try {
8699
+ if (!RecordIdClass) {
8700
+ const mod = await import("surrealdb");
8701
+ RecordIdClass = mod.RecordId;
8702
+ }
8703
+ const stored = await ctx.app.db.select(new RecordIdClass("idempotency", key));
8704
+ if (stored) {
8705
+ const responseHeaders = new Headers(stored.headers);
8706
+ responseHeaders.set("X-Idempotency-Hit", "true");
8707
+ return new Response(stored.body, {
8708
+ status: stored.status,
8709
+ headers: responseHeaders
8710
+ });
8711
+ }
8712
+ } catch (e) {
8713
+ console.error("Idempotency read error:", e);
8714
+ }
8715
+ const result = await next();
8716
+ let response;
8717
+ if (result instanceof Response) {
8718
+ response = result;
8719
+ } else if ((result === null || result === void 0) && ctx[$finalResponse] instanceof Response) {
8720
+ response = ctx[$finalResponse];
8721
+ } else if (result !== null && result !== void 0) {
8722
+ if (typeof result === "object") {
8723
+ response = await ctx.json(result);
8724
+ } else {
8725
+ response = await ctx.text(String(result));
8726
+ }
8727
+ }
8728
+ if (response instanceof Response) {
8729
+ const clone = response.clone();
8730
+ const bodyText = await clone.text();
8731
+ const headers = {};
8732
+ clone.headers.forEach((v, k) => {
8733
+ headers[k] = v;
8734
+ });
8735
+ const toStore = {
8736
+ status: clone.status,
8737
+ headers,
8738
+ body: bodyText,
8739
+ timestamp: Date.now()
8740
+ };
8741
+ try {
8742
+ await ctx.app.db.upsert(new RecordIdClass("idempotency", key), toStore);
8743
+ } catch (e) {
8744
+ console.error("Idempotency write error:", e);
8745
+ }
8746
+ return response;
8747
+ }
8748
+ return result;
8749
+ };
8750
+ idempotencyMiddleware.isBuiltin = true;
8751
+ idempotencyMiddleware.pluginName = "Idempotency";
8752
+ return idempotencyMiddleware;
8753
+ }
8754
+ class MCPServerPlugin {
8755
+ constructor(options = {}) {
8756
+ this.options = options;
8757
+ options.allowIntrospection ??= true;
8758
+ options.allowToolExecution ??= true;
8759
+ options.path ??= "/mcp";
8760
+ if (!options.path.startsWith("/")) {
8761
+ options.path = "/" + options.path;
8762
+ }
8763
+ options.rootDir ??= process.cwd();
8764
+ }
8765
+ router = new ShokupanRouter();
8766
+ analyzer;
8767
+ onInit(app) {
8768
+ this[$appRoot] = app;
8769
+ this.analyzer = new OpenAPIAnalyzer(this.options.rootDir);
8770
+ if (this.options.allowIntrospection) {
8771
+ this.registerTools();
8772
+ this.registerResources();
8773
+ this.registerPrompts();
8774
+ }
8775
+ app.onStart(async () => {
8776
+ app.mount(this.options.path, this.router);
8777
+ this.collectAppMcpItems(app);
8778
+ this.setupRoutes();
8779
+ this.router.metadata = {
8780
+ file: import.meta.file,
8781
+ line: 1,
8782
+ name: "MCPServerPlugin",
8783
+ pluginName: "MCP Server"
8784
+ };
8785
+ });
8786
+ }
8787
+ collectAppMcpItems(app) {
8788
+ const collect = (router) => {
8789
+ if (router.mcpProtocol) {
8790
+ this.router.mcpProtocol.merge(router.mcpProtocol);
8791
+ }
8792
+ router[$childRouters]?.forEach(collect);
8793
+ };
8794
+ collect(app);
8795
+ }
8796
+ setupRoutes() {
8797
+ this.router.get("", (ctx) => {
8798
+ const endpointUrl = `${ctx.protocol}://${ctx.host}${this.options.path}`;
8799
+ const enc = new TextEncoder();
8800
+ return new Response(
8801
+ new ReadableStream({
8802
+ start(controller) {
8803
+ controller.enqueue(enc.encode(`event: endpoint
8804
+ data: ${JSON.stringify(endpointUrl)}
8805
+
8806
+ `));
8807
+ },
8808
+ cancel() {
8809
+ }
8810
+ }),
8811
+ {
8812
+ headers: {
8813
+ "Content-Type": "text/event-stream",
8814
+ "Cache-Control": "no-cache",
8815
+ "Connection": "keep-alive"
8816
+ }
8817
+ }
8818
+ );
8819
+ });
8820
+ this.router.post("", async (ctx) => {
8821
+ let parsedBody;
8822
+ try {
8823
+ parsedBody = await ctx.body();
8824
+ } catch (e) {
8825
+ return ctx.json({
8826
+ jsonrpc: "2.0",
8827
+ id: null,
8828
+ error: { code: -32700, message: "Parse error" }
8829
+ }, 400);
8830
+ }
8831
+ const response = await this.router.mcpProtocol.handleMessage(parsedBody);
8832
+ if (response) {
8833
+ return ctx.json(response);
8834
+ }
8835
+ return ctx.text("", 204);
8836
+ });
8837
+ }
8838
+ registerTools() {
8839
+ const ensureExecutionAllowed = () => {
8840
+ if (!this.options.allowToolExecution) {
8841
+ throw new Error("Tool execution is disabled.");
8842
+ }
8843
+ };
8844
+ this.router.tool(
8845
+ "list_endpoints",
8846
+ {},
8847
+ async () => {
8848
+ ensureExecutionAllowed();
8849
+ const { applications } = await this.analyzer.analyze();
8850
+ const endpoints = applications.flatMap(
8851
+ (app) => app.routes.map((r) => ({
8852
+ method: r.method,
8853
+ path: r.path,
8854
+ handler: r.handlerName,
8855
+ summary: r.summary
8856
+ }))
8857
+ );
8858
+ return {
8859
+ content: [{
8860
+ type: "text",
8861
+ text: JSON.stringify(endpoints, null, 2)
8862
+ }]
8863
+ };
8864
+ }
8865
+ );
8866
+ this.router.tool(
8867
+ "get_endpoint_details",
8868
+ {
8869
+ type: "object",
8870
+ properties: {
8871
+ method: { type: "string" },
8872
+ path: { type: "string" }
8873
+ },
8874
+ required: ["method", "path"]
8875
+ },
8876
+ async ({ method, path }) => {
8877
+ ensureExecutionAllowed();
8878
+ const { applications } = await this.analyzer.analyze();
8879
+ const route = applications.flatMap((app) => app.routes).find((r) => r.method.toUpperCase() === method.toUpperCase() && r.path === path);
8880
+ if (!route) {
8881
+ return {
8882
+ content: [{ type: "text", text: `Endpoint ${method} ${path} not found.` }],
8883
+ isError: true
8884
+ };
8885
+ }
8886
+ return {
8887
+ content: [{
8888
+ type: "text",
8889
+ text: JSON.stringify(route, null, 2)
8890
+ }]
8891
+ };
8892
+ }
8893
+ );
8894
+ }
8895
+ registerResources() {
8896
+ this.router.resource(
8897
+ "mcp://api/openapi.json",
8898
+ {
8899
+ name: "openapi-spec",
8900
+ mimeType: "application/json"
8901
+ },
8902
+ async (uri) => {
8903
+ const { applications } = await this.analyzer.analyze();
8904
+ const endpoints = applications.flatMap(
8905
+ (app) => app.routes.map((r) => ({
8906
+ method: r.method,
8907
+ path: r.path,
8908
+ handler: r.handlerName,
8909
+ summary: r.summary,
8910
+ requestTypes: r.requestTypes,
8911
+ responseType: r.responseType
8912
+ }))
8913
+ );
8914
+ return {
8915
+ contents: [{
8916
+ uri,
8917
+ text: JSON.stringify(endpoints, null, 2)
8918
+ }]
8919
+ };
8920
+ }
8921
+ );
8922
+ this.router.resource(
8923
+ "mcp://api/routes/{method}/{path}/source",
8924
+ {
8925
+ name: "route-source",
8926
+ mimeType: "text/typescript"
8927
+ },
8928
+ async (uri) => {
8929
+ const parts = uri.replace("mcp://", "").split("/");
8930
+ parts[2];
8931
+ throw new Error("Dynamic resource reading not fully implemented in lightweight version yet.");
8932
+ }
8933
+ );
8934
+ }
8935
+ registerPrompts() {
8936
+ this.router.prompt(
8937
+ "generate-client",
8938
+ [
8939
+ { name: "method", required: true },
8940
+ { name: "path", required: true }
8941
+ ],
8942
+ async ({ method, path }) => {
8943
+ const { applications } = await this.analyzer.analyze();
8944
+ const route = applications.flatMap((app) => app.routes).find((r) => r.method.toUpperCase() === method.toUpperCase() && r.path === path);
8945
+ if (!route) {
8946
+ return {
8947
+ messages: [{
8948
+ role: "user",
8949
+ content: {
8950
+ type: "text",
8951
+ text: `Start a new task to create a client for ${method} ${path}. The endpoint was not found in the current analysis.`
8952
+ }
8953
+ }]
8954
+ };
8955
+ }
8956
+ return {
8957
+ messages: [{
8958
+ role: "user",
8959
+ content: {
8960
+ type: "text",
8961
+ text: `Please generate a TypeScript client function for the following endpoint:
8962
+ Method: ${route.method}
8963
+ Path: ${route.path}
8964
+ Summary: ${route.summary || "N/A"}
8965
+ Request Types: ${JSON.stringify(route.requestTypes, null, 2)}
8966
+ Response Type: ${route.responseType || "unknown"}
8967
+
8968
+ Use fetch or axios. Ensure proper typing.`
8969
+ }
8970
+ }]
8971
+ };
8972
+ }
8973
+ );
8974
+ }
8975
+ }
7583
8976
  class ScalarPlugin extends ShokupanRouter {
7584
8977
  constructor(pluginOptions = {}) {
7585
8978
  pluginOptions.config ??= {};
@@ -7755,7 +9148,7 @@ class ScalarPlugin extends ShokupanRouter {
7755
9148
  try {
7756
9149
  const entrypoint = process.argv[1];
7757
9150
  console.log(`[ScalarPlugin] Running eager static analysis on entrypoint: ${entrypoint}`);
7758
- const analyzer = new OpenAPIAnalyzer(process.cwd(), entrypoint);
9151
+ const analyzer = new OpenAPIAnalyzer$1(process.cwd(), entrypoint);
7759
9152
  let staticSpec = await analyzer.analyze();
7760
9153
  if (!this.pluginOptions.baseDocument) this.pluginOptions.baseDocument = {};
7761
9154
  deepMerge(this.pluginOptions.baseDocument, staticSpec);
@@ -7768,6 +9161,79 @@ class ScalarPlugin extends ShokupanRouter {
7768
9161
  }
7769
9162
  }
7770
9163
  }
9164
+ function attachSocketIOBridge(io, app) {
9165
+ io.on("connection", (socket) => {
9166
+ socket.onAny(async (event, ...args) => {
9167
+ if (event === "shokupan:request" || event === "http") {
9168
+ return;
9169
+ }
9170
+ const handler = app.findEvent(event);
9171
+ if (handler) {
9172
+ const data = args[0];
9173
+ const req = new ShokupanRequest({
9174
+ url: `socketio://${app.applicationConfig.hostname || "localhost"}/event/${event}`,
9175
+ method: "POST",
9176
+ headers: new Headers({ "content-type": "application/json" }),
9177
+ body: JSON.stringify(data)
9178
+ });
9179
+ const ctx = new ShokupanContext(req, app.server);
9180
+ ctx[$ws] = socket;
9181
+ ctx.io = io;
9182
+ try {
9183
+ for (let i = 0; i < handler.length; i++) {
9184
+ await handler[i](ctx);
9185
+ }
9186
+ } catch (e) {
9187
+ await app.runHooks("onError", ctx, e);
9188
+ if (app.applicationConfig["websocketErrorHandler"]) {
9189
+ await app.applicationConfig["websocketErrorHandler"](e, ctx);
9190
+ } else {
9191
+ console.error(`Error in event ${event}:`, e);
9192
+ }
9193
+ }
9194
+ }
9195
+ });
9196
+ if (app.applicationConfig["enableHttpBridge"]) {
9197
+ socket.on("shokupan:request", async (payload, callback) => {
9198
+ try {
9199
+ const { method, path, headers, body } = payload;
9200
+ const url = new URL(path, `http://${app.applicationConfig.hostname || "localhost"}:3000`);
9201
+ const req = new Request(url.toString(), {
9202
+ method,
9203
+ headers,
9204
+ body: typeof body === "object" ? JSON.stringify(body) : body
9205
+ });
9206
+ const res = await app.fetch(req);
9207
+ let resBody = await res.text();
9208
+ try {
9209
+ resBody = JSON.parse(resBody);
9210
+ } catch {
9211
+ }
9212
+ const resHeaders = {};
9213
+ res.headers.forEach((v, k) => resHeaders[k] = v);
9214
+ if (typeof callback === "function") {
9215
+ await callback({
9216
+ status: res.status,
9217
+ headers: resHeaders,
9218
+ body: resBody
9219
+ });
9220
+ } else {
9221
+ socket.emit("shokupan:response", {
9222
+ id: payload.id,
9223
+ status: res.status,
9224
+ headers: resHeaders,
9225
+ body: resBody
9226
+ });
9227
+ }
9228
+ } catch (e) {
9229
+ if (typeof callback === "function") {
9230
+ callback({ status: 500, body: { error: e.message } });
9231
+ }
9232
+ }
9233
+ });
9234
+ }
9235
+ });
9236
+ }
7771
9237
  function createLimitStream(maxSize) {
7772
9238
  let size = 0;
7773
9239
  return new TransformStream({
@@ -8410,6 +9876,168 @@ function enableOpenApiValidation(app) {
8410
9876
  precompileValidators(app, spec);
8411
9877
  });
8412
9878
  }
9879
+ function isPrivateIP(ip) {
9880
+ const ipv4Patterns = [
9881
+ /^10\./,
9882
+ // 10.0.0.0/8
9883
+ /^172\.(1[6-9]|2[0-9]|3[01])\./,
9884
+ // 172.16.0.0/12
9885
+ /^192\.168\./,
9886
+ // 192.168.0.0/16
9887
+ /^127\./,
9888
+ // 127.0.0.0/8 (loopback)
9889
+ /^169\.254\./,
9890
+ // 169.254.0.0/16 (link-local)
9891
+ /^0\.0\.0\.0$/
9892
+ // 0.0.0.0
9893
+ ];
9894
+ const ipv6Patterns = [
9895
+ /^::1$/,
9896
+ // loopback
9897
+ /^fe80:/,
9898
+ // link-local
9899
+ /^fc00:/,
9900
+ // unique local
9901
+ /^fd00:/
9902
+ // unique local
9903
+ ];
9904
+ for (const pattern of ipv4Patterns) {
9905
+ if (pattern.test(ip)) return true;
9906
+ }
9907
+ for (const pattern of ipv6Patterns) {
9908
+ if (pattern.test(ip.toLowerCase())) return true;
9909
+ }
9910
+ return false;
9911
+ }
9912
+ function Proxy$1(options) {
9913
+ const targetUrl = new URL(options.target);
9914
+ if (!["http:", "https:"].includes(targetUrl.protocol)) {
9915
+ throw new Error("Invalid proxy target protocol. Only http and https are allowed.");
9916
+ }
9917
+ if (options.allowedHosts && options.allowedHosts.length > 0) {
9918
+ if (!options.allowedHosts.includes(targetUrl.hostname)) {
9919
+ throw new Error(`Target hostname ${targetUrl.hostname} is not in the allowed hosts list.`);
9920
+ }
9921
+ }
9922
+ if (!options.allowPrivateIPs && isPrivateIP(targetUrl.hostname)) {
9923
+ throw new Error("Proxying to private IP addresses is not allowed.");
9924
+ }
9925
+ return async (ctx, next) => {
9926
+ const req = ctx.request;
9927
+ if (options.ws && req.headers.get("upgrade")?.toLowerCase() === "websocket") {
9928
+ const success = ctx.upgrade({
9929
+ data: {
9930
+ handler: {
9931
+ open: (ws) => handleWSOpen(ws, ctx, options, targetUrl),
9932
+ message: (ws, message) => handleWSMessage(ws, message),
9933
+ close: (ws, code, reason) => handleWSClose(ws, code, reason),
9934
+ drain: (ws) => handleWSDrain()
9935
+ }
9936
+ }
9937
+ });
9938
+ if (success) {
9939
+ return void 0;
9940
+ }
9941
+ }
9942
+ let path = ctx.url.pathname;
9943
+ if (options.pathRewrite) {
9944
+ path = options.pathRewrite(path);
9945
+ }
9946
+ const url = new URL(path + ctx.url.search, targetUrl);
9947
+ if (!["http:", "https:"].includes(url.protocol)) {
9948
+ return ctx.text("Invalid protocol in proxied URL", 400);
9949
+ }
9950
+ const headers = new Headers(req.headers);
9951
+ if (options.changeOrigin) {
9952
+ headers.set("host", targetUrl.host);
9953
+ }
9954
+ if (options.headers) {
9955
+ Object.entries(options.headers).forEach(([key, value]) => headers.set(key, value));
9956
+ }
9957
+ headers.delete("connection");
9958
+ headers.delete("keep-alive");
9959
+ headers.delete("proxy-authenticate");
9960
+ headers.delete("proxy-authorization");
9961
+ headers.delete("te");
9962
+ headers.delete("trailer");
9963
+ headers.delete("transfer-encoding");
9964
+ headers.delete("upgrade");
9965
+ const proxyReq = new Request(url.toString(), {
9966
+ method: req.method,
9967
+ headers,
9968
+ body: req.body,
9969
+ // @ts-ignore - duplex is needed for some node/bun versions for streaming bodies
9970
+ duplex: "half"
9971
+ });
9972
+ const res = await fetch(proxyReq);
9973
+ return new Response(res.body, {
9974
+ status: res.status,
9975
+ statusText: res.statusText,
9976
+ headers: res.headers
9977
+ });
9978
+ };
9979
+ }
9980
+ const wsMap = /* @__PURE__ */ new WeakMap();
9981
+ function handleWSOpen(ws, ctx, options, targetUrl) {
9982
+ let path = ctx.url.pathname;
9983
+ if (options.pathRewrite) {
9984
+ path = options.pathRewrite(path);
9985
+ }
9986
+ const url = new URL(path + ctx.url.search, targetUrl);
9987
+ url.protocol = targetUrl.protocol.replace("http", "ws");
9988
+ const headers = {};
9989
+ if (options.changeOrigin) {
9990
+ headers["Host"] = targetUrl.host;
9991
+ }
9992
+ ctx.request.headers.forEach((v, k) => {
9993
+ if (!["upgrade", "connection", "sec-websocket-key", "sec-websocket-version", "sec-websocket-extensions"].includes(k.toLowerCase())) {
9994
+ headers[k] = v;
9995
+ }
9996
+ });
9997
+ const upstream = new WebSocket(url.toString());
9998
+ wsMap.set(ws, upstream);
9999
+ const pendingMessages = [];
10000
+ let isConnected = false;
10001
+ upstream.onopen = () => {
10002
+ isConnected = true;
10003
+ while (pendingMessages.length > 0) {
10004
+ const msg = pendingMessages.shift();
10005
+ upstream.send(msg);
10006
+ }
10007
+ };
10008
+ upstream.onmessage = (event) => {
10009
+ ws.send(event.data);
10010
+ };
10011
+ upstream.onclose = (event) => {
10012
+ ws.close(event.code, event.reason);
10013
+ };
10014
+ upstream.onerror = (err) => {
10015
+ console.error("Upstream WebSocket error:", err);
10016
+ ws.close(1011, "Internal Error");
10017
+ };
10018
+ upstream._pendingRequestMessages = pendingMessages;
10019
+ upstream._isConnected = () => isConnected;
10020
+ }
10021
+ function handleWSMessage(ws, message) {
10022
+ const upstream = wsMap.get(ws);
10023
+ if (!upstream) return;
10024
+ if (upstream._isConnected && upstream._isConnected()) {
10025
+ upstream.send(message);
10026
+ } else {
10027
+ upstream._pendingRequestMessages.push(message);
10028
+ }
10029
+ }
10030
+ function handleWSClose(ws, code, reason) {
10031
+ const upstream = wsMap.get(ws);
10032
+ if (upstream) {
10033
+ if (upstream.readyState === WebSocket.OPEN) {
10034
+ upstream.close(code, reason);
10035
+ }
10036
+ wsMap.delete(ws);
10037
+ }
10038
+ }
10039
+ function handleWSDrain(ws) {
10040
+ }
8413
10041
  function SecurityHeaders(options = {}) {
8414
10042
  const securityHeadersMiddleware = async function SecurityHeadersMiddleware(ctx, next) {
8415
10043
  const set = (k, v) => ctx.response.set(k, v);
@@ -8622,43 +10250,56 @@ function Session(options) {
8622
10250
  }
8623
10251
  const sessObj = existing;
8624
10252
  Object.defineProperty(sessObj, "id", { value: sessionID, configurable: true });
8625
- sessObj.save = (cb) => {
8626
- store.set(sessObj.id, sessObj, cb);
10253
+ sessObj.save = () => {
10254
+ return new Promise((resolve2, reject) => {
10255
+ store.set(sessObj.id, sessObj, (err) => {
10256
+ if (err) reject(err);
10257
+ else resolve2();
10258
+ });
10259
+ });
8627
10260
  };
8628
- sessObj.destroy = (cb) => {
8629
- store.destroy(sessObj.id, (err) => {
8630
- if (cb) cb(err);
10261
+ sessObj.destroy = () => {
10262
+ return new Promise((resolve2, reject) => {
10263
+ store.destroy(sessObj.id, (err) => {
10264
+ if (err) reject(err);
10265
+ else resolve2();
10266
+ });
8631
10267
  });
8632
10268
  };
8633
- sessObj.regenerate = (cb) => {
8634
- store.destroy(sessObj.id, (err) => {
8635
- sessionID = generateId(ctx);
8636
- const keys = Object.keys(sessObj);
8637
- for (let i = 0; i < keys.length; i++) {
8638
- const key = keys[i];
8639
- if (key !== "cookie" && key !== "id" && typeof sessObj[key] !== "function") {
8640
- delete sessObj[key];
10269
+ sessObj.regenerate = () => {
10270
+ return new Promise((resolve2, reject) => {
10271
+ store.destroy(sessObj.id, (err) => {
10272
+ if (err) return reject(err);
10273
+ sessionID = generateId(ctx);
10274
+ const keys = Object.keys(sessObj);
10275
+ for (let i = 0; i < keys.length; i++) {
10276
+ const key = keys[i];
10277
+ if (key !== "cookie" && key !== "id" && typeof sessObj[key] !== "function") {
10278
+ delete sessObj[key];
10279
+ }
8641
10280
  }
8642
- }
8643
- Object.defineProperty(sessObj, "id", { value: sessionID, configurable: true });
8644
- if (cb) cb(err);
10281
+ Object.defineProperty(sessObj, "id", { value: sessionID, configurable: true });
10282
+ resolve2();
10283
+ });
8645
10284
  });
8646
10285
  };
8647
10286
  sessObj.undefined = () => {
8648
10287
  };
8649
- sessObj.reload = (cb) => {
8650
- store.get(sessObj.id, (err, sess2) => {
8651
- if (err) return cb(err);
8652
- if (!sess2) return cb(new Error("Session not found"));
8653
- const keys = Object.keys(sessObj);
8654
- for (let i = 0; i < keys.length; i++) {
8655
- const key = keys[i];
8656
- if (key !== "cookie" && key !== "id" && typeof sessObj[key] !== "function") {
8657
- delete sessObj[key];
10288
+ sessObj.reload = () => {
10289
+ return new Promise((resolve2, reject) => {
10290
+ store.get(sessObj.id, (err, sess2) => {
10291
+ if (err) return reject(err);
10292
+ if (!sess2) return reject(new Error("Session not found"));
10293
+ const keys = Object.keys(sessObj);
10294
+ for (let i = 0; i < keys.length; i++) {
10295
+ const key = keys[i];
10296
+ if (key !== "cookie" && key !== "id" && typeof sessObj[key] !== "function") {
10297
+ delete sessObj[key];
10298
+ }
8658
10299
  }
8659
- }
8660
- Object.assign(sessObj, sess2);
8661
- cb(null);
10300
+ Object.assign(sessObj, sess2);
10301
+ resolve2();
10302
+ });
8662
10303
  });
8663
10304
  };
8664
10305
  sessObj.touch = () => {
@@ -8751,11 +10392,15 @@ export {
8751
10392
  $isApplication,
8752
10393
  $isMounted,
8753
10394
  $isRouter,
10395
+ $mcpPrompts,
10396
+ $mcpResources,
10397
+ $mcpTools,
8754
10398
  $middleware,
8755
10399
  $mountPath,
8756
10400
  $parent,
8757
10401
  $rawBody,
8758
10402
  $requestId,
10403
+ $resilienceConfig,
8759
10404
  $routeArgs,
8760
10405
  $routeMatched,
8761
10406
  $routeMethods,
@@ -8777,24 +10422,33 @@ export {
8777
10422
  Ctx,
8778
10423
  Dashboard,
8779
10424
  Delete,
10425
+ ErrorView,
8780
10426
  Event,
8781
10427
  Get,
8782
10428
  GraphQLApolloPlugin,
10429
+ GraphQLYogaPlugin,
8783
10430
  HTTPMethods,
8784
10431
  Head,
8785
10432
  Headers$1 as Headers,
10433
+ HtmxPlugin,
10434
+ Idempotency,
8786
10435
  Inject,
8787
10436
  Injectable,
10437
+ MCPServerPlugin,
8788
10438
  MemoryStore,
10439
+ OpenTelemetryPlugin,
8789
10440
  Options,
8790
10441
  Param,
8791
10442
  Patch,
8792
10443
  Post,
10444
+ Prompt,
10445
+ Proxy$1 as Proxy,
8793
10446
  Put,
8794
10447
  Query,
8795
10448
  RateLimit,
8796
10449
  RateLimitMiddleware,
8797
10450
  Req,
10451
+ Resource,
8798
10452
  RouteParamType,
8799
10453
  RouterRegistry,
8800
10454
  ScalarPlugin,
@@ -8807,13 +10461,18 @@ export {
8807
10461
  ShokupanResponse,
8808
10462
  ShokupanRouter,
8809
10463
  Spec,
10464
+ Tool,
8810
10465
  Use,
8811
10466
  ValidationError,
10467
+ attachSocketIOBridge,
8812
10468
  compileValidators,
8813
10469
  compose,
8814
10470
  enableOpenApiValidation,
8815
10471
  openApiValidator,
8816
10472
  precompileValidators,
10473
+ serveStatic,
10474
+ traceHandler,
10475
+ traceMiddleware,
8817
10476
  useExpress,
8818
10477
  valibot,
8819
10478
  validate