shokupan 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +20 -5
  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.cjs CHANGED
@@ -25,12 +25,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
25
25
  const nanoid = require("nanoid");
26
26
  const promises = require("node:fs/promises");
27
27
  const node_util = require("node:util");
28
+ const surrealdb = require("surrealdb");
28
29
  const eta$1 = require("eta");
29
30
  const promises$1 = require("fs/promises");
30
31
  const path = require("path");
31
- const api = require("@opentelemetry/api");
32
- const surrealdb = require("surrealdb");
33
- const jsYaml = require("js-yaml");
32
+ const cockatiel = require("cockatiel");
34
33
  const http$1 = require("node:http");
35
34
  require("node:https");
36
35
  const node_async_hooks = require("node:async_hooks");
@@ -43,8 +42,10 @@ const net = require("node:net");
43
42
  const os = require("node:os");
44
43
  const node_module = require("node:module");
45
44
  const node_perf_hooks = require("node:perf_hooks");
45
+ const bun = require("bun");
46
+ const analyzer_impl = require("./analyzer.impl-CUDO6vpn.cjs");
46
47
  const fs$1 = require("node:fs");
47
- const analyzer = require("./analyzer-DM-OlRq8.cjs");
48
+ const analyzer = require("./analyzer-BOtveWL-.cjs");
48
49
  const node_stream = require("node:stream");
49
50
  const zlib = require("node:zlib");
50
51
  const Ajv = require("ajv");
@@ -373,6 +374,10 @@ const $cachedCookies = /* @__PURE__ */ Symbol.for("Shokupan.ctx.cachedCookies");
373
374
  const $ws = /* @__PURE__ */ Symbol.for("Shokupan.ctx.ws");
374
375
  const $socket = /* @__PURE__ */ Symbol.for("Shokupan.ctx.socket");
375
376
  const $io = /* @__PURE__ */ Symbol.for("Shokupan.ctx.io");
377
+ const $mcpTools = /* @__PURE__ */ Symbol.for("Shokupan.mcp.tools");
378
+ const $mcpPrompts = /* @__PURE__ */ Symbol.for("Shokupan.mcp.prompts");
379
+ const $mcpResources = /* @__PURE__ */ Symbol.for("Shokupan.mcp.resources");
380
+ const $resilienceConfig = /* @__PURE__ */ Symbol.for("Shokupan.resilience.config");
376
381
  function isValidCookieDomain(domain, currentHost) {
377
382
  const hostWithoutPort = currentHost.split(":")[0];
378
383
  if (domain === hostWithoutPort) return true;
@@ -1159,31 +1164,109 @@ const compose = (middleware) => {
1159
1164
  console.error(`[Middleware Error] Item at index ${i} is not a function! It is: ${typeof fn} (${name})`, fn);
1160
1165
  throw new TypeError(`Middleware at index ${i} must be a function, got ${name}`);
1161
1166
  }
1162
- if (!context[$debug]) {
1163
- return fn(context, () => runner(i + 1));
1167
+ const trackingEnabled = context.app?.applicationConfig?.enableMiddlewareTracking;
1168
+ const meta = fn.metadata;
1169
+ let trackingStartTime = 0;
1170
+ if (trackingEnabled && meta) {
1171
+ trackingStartTime = performance.now();
1172
+ context.handlerStack.push({
1173
+ name: meta.name || fn.name || "anonymous",
1174
+ file: meta.file,
1175
+ line: meta.line,
1176
+ isBuiltin: meta.isBuiltin,
1177
+ startTime: trackingStartTime,
1178
+ duration: -1
1179
+ });
1164
1180
  }
1165
1181
  const debug = context[$debug];
1166
- const debugId = fn._debugId || fn.name || "anonymous";
1167
- const previousNode = debug.getCurrentNode();
1168
- debug.trackEdge(previousNode, debugId);
1169
- debug.setNode(debugId);
1170
- const start = performance.now();
1182
+ let debugId;
1183
+ let previousNode;
1184
+ let debugStart = 0;
1185
+ if (debug) {
1186
+ debugId = fn._debugId || fn.name || "anonymous";
1187
+ previousNode = debug.getCurrentNode();
1188
+ debug.trackEdge(previousNode, debugId);
1189
+ debug.setNode(debugId);
1190
+ debugStart = performance.now();
1191
+ }
1171
1192
  try {
1172
- const res = await Promise.resolve(fn(context, () => runner(i + 1)));
1173
- debug.trackStep(debugId, "middleware", performance.now() - start, "success");
1193
+ const res = await fn(context, () => runner(i + 1));
1194
+ if (trackingEnabled && meta) {
1195
+ const duration = performance.now() - trackingStartTime;
1196
+ const stackItem = context.handlerStack[context.handlerStack.length - 1];
1197
+ if (stackItem) stackItem.duration = duration;
1198
+ Promise.resolve().then(async () => {
1199
+ try {
1200
+ const db = context.app?.db;
1201
+ if (!db) return;
1202
+ const timestamp = Date.now();
1203
+ await db.upsert(new surrealdb.RecordId("middleware_tracking", {
1204
+ timestamp,
1205
+ name: meta.name
1206
+ }), {
1207
+ name: meta.name,
1208
+ path: context.path,
1209
+ timestamp,
1210
+ duration,
1211
+ file: meta.file,
1212
+ line: meta.line,
1213
+ error: void 0,
1214
+ metadata: {
1215
+ isBuiltin: meta.isBuiltin,
1216
+ pluginName: meta.pluginName
1217
+ }
1218
+ });
1219
+ } catch (e) {
1220
+ }
1221
+ });
1222
+ }
1223
+ if (debug) {
1224
+ debug.trackStep(debugId, "middleware", performance.now() - debugStart, "success");
1225
+ }
1174
1226
  return res;
1175
1227
  } catch (err) {
1176
- debug.trackStep(debugId, "middleware", performance.now() - start, "error", err);
1177
- return Promise.reject(err);
1228
+ if (trackingEnabled && meta) {
1229
+ const duration = performance.now() - trackingStartTime;
1230
+ const stackItem = context.handlerStack[context.handlerStack.length - 1];
1231
+ if (stackItem) stackItem.duration = duration;
1232
+ Promise.resolve().then(async () => {
1233
+ try {
1234
+ const db = context.app?.db;
1235
+ if (!db) return;
1236
+ const timestamp = Date.now();
1237
+ await db.upsert(new surrealdb.RecordId("middleware_tracking", {
1238
+ timestamp,
1239
+ name: meta.name
1240
+ }), {
1241
+ name: meta.name,
1242
+ path: context.path,
1243
+ timestamp,
1244
+ duration,
1245
+ file: meta.file,
1246
+ line: meta.line,
1247
+ error: String(err),
1248
+ metadata: {
1249
+ isBuiltin: meta.isBuiltin,
1250
+ pluginName: meta.pluginName
1251
+ }
1252
+ });
1253
+ } catch (e) {
1254
+ }
1255
+ });
1256
+ }
1257
+ if (debug) {
1258
+ debug.trackStep(debugId, "middleware", performance.now() - debugStart, "error", err);
1259
+ }
1260
+ throw err;
1178
1261
  } finally {
1179
- if (previousNode) debug.setNode(previousNode);
1262
+ if (debug && previousNode) debug.setNode(previousNode);
1180
1263
  }
1181
1264
  }
1182
1265
  return runner(0);
1183
1266
  };
1184
1267
  };
1185
1268
  function isObject(item) {
1186
- return item && typeof item === "object" && !Array.isArray(item);
1269
+ return !!(item && typeof item === "object" && !Array.isArray(item));
1187
1270
  }
1188
1271
  function deepMerge(target, ...sources) {
1189
1272
  if (!sources.length) return target;
@@ -1448,7 +1531,7 @@ async function generateOpenApi(rootRouter, options = {}) {
1448
1531
  let astMiddlewareRegistry = {};
1449
1532
  let applications = [];
1450
1533
  try {
1451
- const { OpenAPIAnalyzer } = await Promise.resolve().then(() => require("./analyzer-DM-OlRq8.cjs"));
1534
+ const { OpenAPIAnalyzer } = await Promise.resolve().then(() => require("./analyzer-BOtveWL-.cjs"));
1452
1535
  const entrypoint = rootRouter.metadata?.file;
1453
1536
  const analyzer2 = new OpenAPIAnalyzer(process.cwd(), entrypoint);
1454
1537
  const analysisResult = await analyzer2.analyze();
@@ -1500,7 +1583,7 @@ async function generateOpenApi(rootRouter, options = {}) {
1500
1583
  isBuiltinPlugin = true;
1501
1584
  pluginName = router.metadata.pluginName;
1502
1585
  tag = pluginName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1503
- } else if (router.metadata?.file && router.metadata.file.includes("plugins/application/")) {
1586
+ } else if (router.metadata?.file && router.metadata.file.includes("plugins/application/") && !router.metadata.file.match(/\.(spec|test)\.ts$/)) {
1504
1587
  isBuiltinPlugin = true;
1505
1588
  const match = router.metadata.file.match(/plugins\/application\/([^/]+)/);
1506
1589
  if (match) {
@@ -1658,7 +1741,39 @@ async function generateOpenApi(rootRouter, options = {}) {
1658
1741
  const params = [];
1659
1742
  if (astMatch.requestTypes?.query) {
1660
1743
  for (const [name, _type] of Object.entries(astMatch.requestTypes.query)) {
1661
- params.push({ name, in: "query", schema: { type: "string" } });
1744
+ let type = "string";
1745
+ let format;
1746
+ if (_type === "integer") {
1747
+ type = "integer";
1748
+ format = "int32";
1749
+ } else if (_type === "number") {
1750
+ type = "number";
1751
+ format = "float";
1752
+ } else if (_type === "boolean") type = "boolean";
1753
+ const schema = { type };
1754
+ if (format) schema.format = format;
1755
+ params.push({ name, in: "query", schema });
1756
+ }
1757
+ }
1758
+ if (astMatch.requestTypes?.params) {
1759
+ for (const [name, _type] of Object.entries(astMatch.requestTypes.params)) {
1760
+ let type = "string";
1761
+ let format;
1762
+ if (_type === "integer") {
1763
+ type = "integer";
1764
+ format = "int32";
1765
+ } else if (_type === "number") {
1766
+ type = "number";
1767
+ format = "float";
1768
+ } else if (_type === "boolean") type = "boolean";
1769
+ const schema = { type };
1770
+ if (format) schema.format = format;
1771
+ params.push({ name, in: "path", required: true, schema });
1772
+ }
1773
+ }
1774
+ if (astMatch.requestTypes?.headers) {
1775
+ for (const [name, _type] of Object.entries(astMatch.requestTypes.headers)) {
1776
+ params.push({ name, in: "header", schema: { type: "string" } });
1662
1777
  }
1663
1778
  }
1664
1779
  if (params.length > 0) {
@@ -1716,9 +1831,7 @@ async function generateOpenApi(rootRouter, options = {}) {
1716
1831
  const mergedParams = [...existingParams];
1717
1832
  pathParams.forEach((p) => {
1718
1833
  const idx = mergedParams.findIndex((ep) => ep.in === "path" && ep.name === p.name);
1719
- if (idx >= 0) {
1720
- mergedParams[idx] = deepMerge(mergedParams[idx], p);
1721
- } else {
1834
+ if (idx === -1) {
1722
1835
  mergedParams.push(p);
1723
1836
  }
1724
1837
  });
@@ -2007,6 +2120,166 @@ function serveStatic(config, prefix) {
2007
2120
  serveStaticMiddleware.pluginName = "ServeStatic";
2008
2121
  return serveStaticMiddleware;
2009
2122
  }
2123
+ class OpenTelemetryPlugin {
2124
+ constructor(options = {}) {
2125
+ this.options = options;
2126
+ }
2127
+ api;
2128
+ sdk;
2129
+ async onInit(app) {
2130
+ try {
2131
+ this.api = await import("@opentelemetry/api");
2132
+ } catch (e) {
2133
+ console.warn("OpenTelemetry API not found. OpenTelemetryPlugin will be disabled.");
2134
+ return;
2135
+ }
2136
+ if (this.options.enableAutoInstrumentation !== false) {
2137
+ app.use(this.middleware());
2138
+ }
2139
+ }
2140
+ middleware() {
2141
+ return async (ctx, next) => {
2142
+ if (!this.api) return next();
2143
+ const tracer = this.api.trace.getTracer("shokupan");
2144
+ return tracer.startActiveSpan(`${ctx.req.method} ${ctx.req.path}`, {
2145
+ kind: this.api.SpanKind.SERVER,
2146
+ attributes: {
2147
+ "http.method": ctx.req.method,
2148
+ "http.url": ctx.req.url,
2149
+ "http.host": ctx.req.host,
2150
+ "http.user_agent": ctx.req.headers.get("user-agent") || void 0
2151
+ }
2152
+ }, async (span) => {
2153
+ try {
2154
+ const res = await next();
2155
+ span.setAttributes({
2156
+ "http.status_code": ctx.res.status
2157
+ });
2158
+ if (ctx.res.status >= 500) {
2159
+ span.setStatus({ code: this.api.SpanStatusCode.ERROR });
2160
+ } else {
2161
+ span.setStatus({ code: this.api.SpanStatusCode.OK });
2162
+ }
2163
+ return res;
2164
+ } catch (err) {
2165
+ span.recordException(err);
2166
+ span.setStatus({ code: this.api.SpanStatusCode.ERROR, message: err.message });
2167
+ throw err;
2168
+ } finally {
2169
+ span.end();
2170
+ }
2171
+ });
2172
+ };
2173
+ }
2174
+ }
2175
+ function traceMiddleware(fn, name) {
2176
+ let api;
2177
+ try {
2178
+ api = require("@opentelemetry/api");
2179
+ } catch {
2180
+ }
2181
+ if (!api) return fn;
2182
+ const tracer = api.trace.getTracer("shokupan.middleware");
2183
+ const middlewareName = name || fn.name || "anonymous middleware";
2184
+ return async (ctx, next) => {
2185
+ return tracer.startActiveSpan(`middleware - ${middlewareName}`, {
2186
+ kind: api.SpanKind.INTERNAL,
2187
+ attributes: {
2188
+ "code.function": middlewareName,
2189
+ "component": "shokupan.middleware"
2190
+ }
2191
+ }, async (span) => {
2192
+ try {
2193
+ const result = await fn(ctx, next);
2194
+ return result;
2195
+ } catch (err) {
2196
+ span.recordException(err);
2197
+ span.setStatus({ code: api.SpanStatusCode.ERROR, message: err.message });
2198
+ throw err;
2199
+ } finally {
2200
+ span.end();
2201
+ }
2202
+ });
2203
+ };
2204
+ }
2205
+ function traceHandler(fn, name) {
2206
+ let api;
2207
+ try {
2208
+ api = require("@opentelemetry/api");
2209
+ } catch {
2210
+ }
2211
+ if (!api) return fn;
2212
+ const tracer = api.trace.getTracer("shokupan.middleware");
2213
+ return async function(...args) {
2214
+ return tracer.startActiveSpan(`route handler - ${name}`, {
2215
+ kind: api.SpanKind.INTERNAL,
2216
+ attributes: {
2217
+ "http.route": name,
2218
+ "component": "shokupan.route"
2219
+ }
2220
+ }, async (span) => {
2221
+ try {
2222
+ const result = await fn.apply(this, args);
2223
+ return result;
2224
+ } catch (err) {
2225
+ span.recordException(err);
2226
+ span.setStatus({ code: api.SpanStatusCode.ERROR, message: err.message });
2227
+ throw err;
2228
+ } finally {
2229
+ span.end();
2230
+ }
2231
+ });
2232
+ };
2233
+ }
2234
+ class ResilienceFactory {
2235
+ static createPolicy(config) {
2236
+ const policies = [];
2237
+ if (config.retry) {
2238
+ const builder = cockatiel.handleAll;
2239
+ let retries = (config.retry.attempts ?? 3) - 1;
2240
+ if (retries < 0) retries = 0;
2241
+ let retryPolicy;
2242
+ if (config.retry.backoff === "exponential") {
2243
+ retryPolicy = cockatiel.retry(builder, {
2244
+ maxAttempts: retries,
2245
+ backoff: new cockatiel.ExponentialBackoff({
2246
+ initialDelay: config.retry.delay || 1e3,
2247
+ maxDelay: config.retry.maxDelay || 3e4
2248
+ })
2249
+ });
2250
+ } else {
2251
+ retryPolicy = cockatiel.retry(builder, {
2252
+ maxAttempts: retries,
2253
+ backoff: new cockatiel.ConstantBackoff(config.retry.delay || 1e3)
2254
+ });
2255
+ }
2256
+ policies.push(retryPolicy);
2257
+ }
2258
+ if (config.circuitBreaker) {
2259
+ const builder = cockatiel.handleAll;
2260
+ const breaker = cockatiel.circuitBreaker(builder, {
2261
+ halfOpenAfter: config.circuitBreaker.resetTimeout || 1e4,
2262
+ breaker: new cockatiel.ConsecutiveBreaker(config.circuitBreaker.threshold || 5)
2263
+ });
2264
+ policies.push(breaker);
2265
+ }
2266
+ if (config.timeout) {
2267
+ policies.push(cockatiel.timeout(config.timeout, { strategy: cockatiel.TimeoutStrategy.Aggressive, abortOnReturn: true }));
2268
+ }
2269
+ if (config.bulkhead) {
2270
+ policies.push(cockatiel.bulkhead(config.bulkhead));
2271
+ }
2272
+ if (config.fallback !== void 0) {
2273
+ const builder = cockatiel.handleAll;
2274
+ const fb = cockatiel.fallback(builder, config.fallback);
2275
+ policies.push(fb);
2276
+ }
2277
+ if (policies.length === 0) {
2278
+ return { execute: (fn) => fn() };
2279
+ }
2280
+ return cockatiel.wrap(...policies.reverse());
2281
+ }
2282
+ }
2010
2283
  const metadataStore = /* @__PURE__ */ new WeakMap();
2011
2284
  function defineMetadata(key, value, target, propertyKey) {
2012
2285
  let targetMetadata = metadataStore.get(target);
@@ -2103,29 +2376,6 @@ const di = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2103
2376
  __proto__: null,
2104
2377
  Container
2105
2378
  }, Symbol.toStringTag, { value: "Module" }));
2106
- const tracer = api.trace.getTracer("shokupan.middleware");
2107
- function traceHandler(fn, name) {
2108
- return async function(...args) {
2109
- return tracer.startActiveSpan(`route handler - ${name}`, {
2110
- kind: api.SpanKind.INTERNAL,
2111
- attributes: {
2112
- "http.route": name,
2113
- "component": "shokupan.route"
2114
- }
2115
- }, async (span) => {
2116
- try {
2117
- const result = await fn.apply(this, args);
2118
- return result;
2119
- } catch (err) {
2120
- span.recordException(err);
2121
- span.setStatus({ code: api.SpanStatusCode.ERROR, message: err.message });
2122
- throw err;
2123
- } finally {
2124
- span.end();
2125
- }
2126
- });
2127
- };
2128
- }
2129
2379
  function getCallerInfo(skipFrames = 1) {
2130
2380
  let file = "unknown";
2131
2381
  let line = 0;
@@ -2143,6 +2393,7 @@ function getCallerInfo(skipFrames = 1) {
2143
2393
  if (l.includes("src/router.ts")) continue;
2144
2394
  if (l.includes("src/util/decorators.ts")) continue;
2145
2395
  if (l.includes("src/shokupan.ts")) continue;
2396
+ if (l.includes("src/plugins/application/openapi/openapi.ts")) continue;
2146
2397
  found++;
2147
2398
  if (found >= skipFrames) {
2148
2399
  const match = l.match(/\((.*):(\d+):(\d+)\)/) || l.match(/at (.*):(\d+):(\d+)/);
@@ -2211,6 +2462,10 @@ class ControllerScanner {
2211
2462
  const decoratedArgs = instance[$routeArgs] || proto && proto[$routeArgs];
2212
2463
  const methodMiddlewareMap = instance[$middleware] || proto && proto[$middleware];
2213
2464
  const decoratedEvents = instance[$eventMethods] || proto && proto[$eventMethods];
2465
+ const mcpTools = instance[$mcpTools] || proto && proto[$mcpTools];
2466
+ const mcpPrompts = instance[$mcpPrompts] || proto && proto[$mcpPrompts];
2467
+ const mcpResources = instance[$mcpResources] || proto && proto[$mcpResources];
2468
+ const resilienceConfigMap = instance[$resilienceConfig] || proto && proto[$resilienceConfig];
2214
2469
  let routesAttached = 0;
2215
2470
  for (let i = 0; i < Array.from(methods).length; i++) {
2216
2471
  const name = Array.from(methods)[i];
@@ -2335,6 +2590,14 @@ class ControllerScanner {
2335
2590
  return composed(ctx, () => wrappedHandler(ctx));
2336
2591
  };
2337
2592
  }
2593
+ const config = resilienceConfigMap?.get(name);
2594
+ if (config) {
2595
+ const policy = ResilienceFactory.createPolicy(config);
2596
+ const baseHandler = finalHandler;
2597
+ finalHandler = async (ctx) => {
2598
+ return policy.execute(() => baseHandler(ctx));
2599
+ };
2600
+ }
2338
2601
  finalHandler.originalHandler = originalHandler;
2339
2602
  if (finalHandler !== wrappedHandler) {
2340
2603
  wrappedHandler.originalHandler = originalHandler;
@@ -2391,6 +2654,25 @@ class ControllerScanner {
2391
2654
  wrappedHandler.originalHandler = originalHandler;
2392
2655
  router.event(eventConfig.eventName, wrappedHandler);
2393
2656
  }
2657
+ const toolConfig = mcpTools?.get(name);
2658
+ if (toolConfig) {
2659
+ const handler = originalHandler.bind(instance);
2660
+ router.tool(toolConfig.name || name, toolConfig.inputSchema, handler);
2661
+ }
2662
+ const promptConfig = mcpPrompts?.get(name);
2663
+ if (promptConfig) {
2664
+ const handler = originalHandler.bind(instance);
2665
+ router.prompt(promptConfig.name || name, promptConfig.arguments, handler);
2666
+ }
2667
+ const resourceConfig = mcpResources?.get(name);
2668
+ if (resourceConfig) {
2669
+ const handler = originalHandler.bind(instance);
2670
+ router.resource(resourceConfig.uri, {
2671
+ name: resourceConfig.name || name,
2672
+ description: resourceConfig.description,
2673
+ mimeType: resourceConfig.mimeType
2674
+ }, handler);
2675
+ }
2394
2676
  }
2395
2677
  if (routesAttached === 0) {
2396
2678
  console.warn(`No routes attached to controller ${instance.constructor.name}`);
@@ -2421,71 +2703,177 @@ function getErrorStatus(err) {
2421
2703
  }
2422
2704
  return 500;
2423
2705
  }
2706
+ class NotFoundError extends HttpError {
2707
+ constructor(message = "Not Found") {
2708
+ super(message, 404);
2709
+ this.name = "NotFoundError";
2710
+ }
2711
+ }
2424
2712
  class EventError extends HttpError {
2425
2713
  constructor(message = "Event Error") {
2426
2714
  super(message, 500);
2427
2715
  this.name = "EventError";
2428
2716
  }
2429
2717
  }
2718
+ class McpProtocol {
2719
+ tools = /* @__PURE__ */ new Map();
2720
+ prompts = /* @__PURE__ */ new Map();
2721
+ resources = /* @__PURE__ */ new Map();
2722
+ constructor(tools = [], prompts = [], resources = []) {
2723
+ tools.forEach((t) => this.tools.set(t.name, t));
2724
+ prompts.forEach((p) => this.prompts.set(p.name, p));
2725
+ resources.forEach((r) => this.resources.set(r.uri, r));
2726
+ }
2727
+ addTool(tool) {
2728
+ this.tools.set(tool.name, tool);
2729
+ }
2730
+ addPrompt(prompt) {
2731
+ this.prompts.set(prompt.name, prompt);
2732
+ }
2733
+ addResource(resource) {
2734
+ this.resources.set(resource.uri, resource);
2735
+ }
2736
+ merge(other) {
2737
+ other.tools.forEach((t) => this.tools.set(t.name, t));
2738
+ other.prompts.forEach((p) => this.prompts.set(p.name, p));
2739
+ other.resources.forEach((r) => this.resources.set(r.uri, r));
2740
+ }
2741
+ async handleMessage(message) {
2742
+ if (message.jsonrpc !== "2.0") {
2743
+ return this.error(message.id, -32600, "Invalid Request");
2744
+ }
2745
+ try {
2746
+ switch (message.method) {
2747
+ case "initialize":
2748
+ return this.success(message.id, {
2749
+ protocolVersion: "2024-11-05",
2750
+ serverInfo: {
2751
+ name: "Shokupan MCP",
2752
+ version: "1.0.0"
2753
+ },
2754
+ capabilities: {
2755
+ tools: this.tools.size > 0 ? {} : void 0,
2756
+ prompts: this.prompts.size > 0 ? {} : void 0,
2757
+ resources: this.resources.size > 0 ? {} : void 0
2758
+ }
2759
+ });
2760
+ case "ping":
2761
+ return this.success(message.id, {});
2762
+ case "tools/list":
2763
+ if (this.tools.size === 0) return this.success(message.id, { tools: [] });
2764
+ return this.success(message.id, {
2765
+ tools: Array.from(this.tools.values()).map((t) => ({
2766
+ name: t.name,
2767
+ description: t.description,
2768
+ inputSchema: t.inputSchema || { type: "object", properties: {} }
2769
+ }))
2770
+ });
2771
+ case "tools/call": {
2772
+ if (!message.params || !message.params.name) {
2773
+ return this.error(message.id, -32602, "Invalid params: name required");
2774
+ }
2775
+ const tool = this.tools.get(message.params.name);
2776
+ if (!tool) {
2777
+ return this.error(message.id, -32601, `Tool not found: ${message.params.name}`);
2778
+ }
2779
+ try {
2780
+ const result = await tool.handler(message.params.arguments || {});
2781
+ return this.success(message.id, result);
2782
+ } catch (e) {
2783
+ return {
2784
+ jsonrpc: "2.0",
2785
+ id: message.id ?? null,
2786
+ result: {
2787
+ isError: true,
2788
+ content: [{ type: "text", text: e.message || String(e) }]
2789
+ }
2790
+ };
2791
+ }
2792
+ }
2793
+ case "prompts/list":
2794
+ if (this.prompts.size === 0) return this.success(message.id, { prompts: [] });
2795
+ return this.success(message.id, {
2796
+ prompts: Array.from(this.prompts.values()).map((p) => ({
2797
+ name: p.name,
2798
+ description: p.description,
2799
+ arguments: p.arguments
2800
+ }))
2801
+ });
2802
+ case "prompts/get": {
2803
+ if (!message.params || !message.params.name) {
2804
+ return this.error(message.id, -32602, "Invalid params: name required");
2805
+ }
2806
+ const prompt = this.prompts.get(message.params.name);
2807
+ if (!prompt) {
2808
+ return this.error(message.id, -32601, `Prompt not found: ${message.params.name}`);
2809
+ }
2810
+ const result = await prompt.handler(message.params.arguments || {});
2811
+ return this.success(message.id, result);
2812
+ }
2813
+ case "resources/list":
2814
+ if (this.resources.size === 0) return this.success(message.id, { resources: [] });
2815
+ return this.success(message.id, {
2816
+ resources: Array.from(this.resources.values()).map((r) => ({
2817
+ uri: r.uri,
2818
+ name: r.name,
2819
+ description: r.description,
2820
+ mimeType: r.mimeType
2821
+ }))
2822
+ });
2823
+ case "resources/read": {
2824
+ if (!message.params || !message.params.uri) {
2825
+ return this.error(message.id, -32602, "Invalid params: uri required");
2826
+ }
2827
+ let resource = this.resources.get(message.params.uri);
2828
+ if (!resource) {
2829
+ return this.error(message.id, -32601, `Resource not found: ${message.params.uri}`);
2830
+ }
2831
+ const result = await resource.handler(message.params.uri);
2832
+ return this.success(message.id, result);
2833
+ }
2834
+ default:
2835
+ if (message.id === void 0) return null;
2836
+ return this.error(message.id, -32601, "Method not found");
2837
+ }
2838
+ } catch (err) {
2839
+ return this.error(message.id, -32603, "Internal Error", err.message);
2840
+ }
2841
+ }
2842
+ success(id, result) {
2843
+ return {
2844
+ jsonrpc: "2.0",
2845
+ id: id ?? null,
2846
+ result
2847
+ };
2848
+ }
2849
+ error(id, code, message, data) {
2850
+ return {
2851
+ jsonrpc: "2.0",
2852
+ id: id ?? null,
2853
+ error: { code, message, data }
2854
+ };
2855
+ }
2856
+ }
2430
2857
  class MiddlewareTracker {
2431
2858
  static wrap(handler, context) {
2432
2859
  const { file, line, name, isBuiltin, pluginName } = context;
2433
2860
  const handlerName = name || handler.name || "anonymous";
2434
- const trackedHandler = async (ctx, next) => {
2435
- if (!ctx.app?.applicationConfig.enableMiddlewareTracking) {
2436
- return handler(ctx, next);
2437
- }
2438
- const startTime = performance.now();
2439
- let error = void 0;
2440
- try {
2441
- ctx.handlerStack.push({
2442
- name: handlerName,
2443
- file,
2444
- line,
2445
- isBuiltin,
2446
- startTime,
2447
- duration: -1
2448
- });
2449
- return await handler(ctx, next);
2450
- } catch (e) {
2451
- error = e;
2452
- throw e;
2453
- } finally {
2454
- const duration = performance.now() - startTime;
2455
- const stackItem = ctx.handlerStack[ctx.handlerStack.length - 1];
2456
- if (stackItem && stackItem.name === handlerName) {
2457
- stackItem.duration = duration;
2861
+ try {
2862
+ handler.metadata = context;
2863
+ if (!handler.name || handler.name === "anonymous") {
2864
+ try {
2865
+ Object.defineProperty(handler, "name", { value: handlerName, configurable: true });
2866
+ } catch (e) {
2458
2867
  }
2459
- Promise.resolve().then(async () => {
2460
- try {
2461
- const db = ctx.app?.db;
2462
- if (!db) return;
2463
- const timestamp = Date.now();
2464
- await db.upsert(new surrealdb.RecordId("middleware_tracking", {
2465
- timestamp,
2466
- name: handlerName
2467
- }), {
2468
- name: handlerName,
2469
- path: ctx.path,
2470
- timestamp,
2471
- duration,
2472
- file,
2473
- line,
2474
- error: error ? String(error) : void 0,
2475
- metadata: {
2476
- isBuiltin,
2477
- pluginName
2478
- }
2479
- });
2480
- } catch (err) {
2481
- }
2482
- });
2483
2868
  }
2484
- };
2485
- trackedHandler.metadata = handler.metadata || context;
2486
- Object.defineProperty(trackedHandler, "name", { value: handlerName });
2487
- trackedHandler.originalHandler = handler.originalHandler || handler;
2488
- return trackedHandler;
2869
+ } catch (e) {
2870
+ const wrapped = handler.bind(null);
2871
+ wrapped.metadata = context;
2872
+ Object.defineProperty(wrapped, "name", { value: handlerName });
2873
+ wrapped.originalHandler = handler.originalHandler || handler;
2874
+ return wrapped;
2875
+ }
2876
+ return handler;
2489
2877
  }
2490
2878
  }
2491
2879
  class ShokupanRequestBase {
@@ -2699,6 +3087,7 @@ class ShokupanRouter {
2699
3087
  trie = new RouterTrie();
2700
3088
  metadata;
2701
3089
  // Metadata for the router itself
3090
+ mcpProtocol = new McpProtocol();
2702
3091
  currentGuards = [];
2703
3092
  eventHandlers = /* @__PURE__ */ new Map();
2704
3093
  /**
@@ -2824,6 +3213,39 @@ class ShokupanRouter {
2824
3213
  handlers.push(handler);
2825
3214
  return this;
2826
3215
  }
3216
+ /**
3217
+ * Registers an MCP Tool.
3218
+ */
3219
+ tool(name, schema, handler) {
3220
+ this.mcpProtocol.addTool({
3221
+ name,
3222
+ inputSchema: schema,
3223
+ handler
3224
+ });
3225
+ return this;
3226
+ }
3227
+ /**
3228
+ * Registers an MCP Prompt.
3229
+ */
3230
+ prompt(name, args, handler) {
3231
+ this.mcpProtocol.addPrompt({
3232
+ name,
3233
+ arguments: args,
3234
+ handler
3235
+ });
3236
+ return this;
3237
+ }
3238
+ /**
3239
+ * Registers an MCP Resource.
3240
+ */
3241
+ resource(uri, options, handler) {
3242
+ this.mcpProtocol.addResource({
3243
+ uri,
3244
+ handler,
3245
+ ...options
3246
+ });
3247
+ return this;
3248
+ }
2827
3249
  /**
2828
3250
  * Finds an event handler(s) by name.
2829
3251
  */
@@ -3122,59 +3544,48 @@ class ShokupanRouter {
3122
3544
  }
3123
3545
  }
3124
3546
  }
3125
- let wrappedHandler = async (ctx) => {
3126
- return handler(ctx);
3127
- };
3128
- wrappedHandler.originalHandler = handler.originalHandler || handler;
3129
- const routeGuards = [...this.currentGuards];
3130
3547
  const effectiveTimeout = requestTimeout ?? this.requestTimeout ?? this.rootConfig?.requestTimeout;
3131
- if (effectiveTimeout !== void 0 && effectiveTimeout > 0) {
3132
- const originalHandler = wrappedHandler;
3548
+ const effectiveRenderer = renderer ?? this.config?.renderer ?? this.rootConfig?.renderer;
3549
+ const routeGuards = [...this.currentGuards];
3550
+ let wrappedHandler = handler;
3551
+ if (effectiveTimeout && effectiveTimeout > 0 || effectiveRenderer || routeGuards.length > 0) {
3552
+ const originalHandler = handler;
3133
3553
  wrappedHandler = async (ctx) => {
3134
- if (ctx.server) {
3554
+ if (effectiveTimeout && effectiveTimeout > 0 && ctx.server) {
3135
3555
  ctx.server.timeout(ctx.req, effectiveTimeout / 1e3);
3136
3556
  }
3137
- return originalHandler(ctx);
3138
- };
3139
- wrappedHandler.originalHandler = originalHandler.originalHandler || originalHandler;
3140
- }
3141
- if (routeGuards.length > 0) {
3142
- const innerHandler = wrappedHandler;
3143
- wrappedHandler = async (ctx) => {
3144
- for (let i = 0; i < routeGuards.length; i++) {
3145
- const guard = routeGuards[i];
3146
- let guardPassed = false;
3147
- let nextCalled = false;
3148
- const next = () => {
3149
- nextCalled = true;
3150
- return Promise.resolve();
3151
- };
3152
- try {
3153
- const result = await guard.handler(ctx, next);
3154
- if (result === true || nextCalled) {
3155
- guardPassed = true;
3156
- } else if (result !== void 0 && result !== null && result !== false) {
3157
- return result;
3158
- } else {
3557
+ if (effectiveRenderer) {
3558
+ ctx.setRenderer(effectiveRenderer);
3559
+ }
3560
+ if (routeGuards.length > 0) {
3561
+ for (let i = 0; i < routeGuards.length; i++) {
3562
+ const guard = routeGuards[i];
3563
+ let guardPassed = false;
3564
+ let nextCalled = false;
3565
+ const next = () => {
3566
+ nextCalled = true;
3567
+ return Promise.resolve();
3568
+ };
3569
+ try {
3570
+ const result = await guard.handler(ctx, next);
3571
+ if (result === true || nextCalled) {
3572
+ guardPassed = true;
3573
+ } else if (result !== void 0 && result !== null && result !== false) {
3574
+ return result;
3575
+ } else {
3576
+ return ctx.json({ error: "Forbidden" }, 403);
3577
+ }
3578
+ } catch (error) {
3579
+ throw error;
3580
+ }
3581
+ if (!guardPassed) {
3159
3582
  return ctx.json({ error: "Forbidden" }, 403);
3160
3583
  }
3161
- } catch (error) {
3162
- throw error;
3163
- }
3164
- if (!guardPassed) {
3165
- return ctx.json({ error: "Forbidden" }, 403);
3166
3584
  }
3167
3585
  }
3168
- return innerHandler(ctx);
3169
- };
3170
- }
3171
- const effectiveRenderer = renderer ?? this.config?.renderer ?? this.rootConfig?.renderer;
3172
- if (effectiveRenderer) {
3173
- const innerHandler = wrappedHandler;
3174
- wrappedHandler = async (ctx) => {
3175
- ctx.setRenderer(effectiveRenderer);
3176
- return innerHandler(ctx);
3586
+ return originalHandler(ctx);
3177
3587
  };
3588
+ wrappedHandler.originalHandler = handler.originalHandler || handler;
3178
3589
  }
3179
3590
  const { file, line } = metadata || getCallerInfo();
3180
3591
  wrappedHandler = MiddlewareTracker.wrap(wrappedHandler, {
@@ -3875,6 +4286,32 @@ class SurrealDatastore {
3875
4286
  return this.db.close();
3876
4287
  }
3877
4288
  }
4289
+ const kContext = /* @__PURE__ */ Symbol("kContext");
4290
+ let patched = false;
4291
+ function enablePromisePatch() {
4292
+ if (patched) return;
4293
+ patched = true;
4294
+ const OriginalPromise = global.Promise;
4295
+ global.Promise = class PatchedPromise extends OriginalPromise {
4296
+ [kContext];
4297
+ constructor(executor) {
4298
+ const store = asyncContext.getStore();
4299
+ const stack = new Error().stack || "No parent stack";
4300
+ super(executor);
4301
+ this[kContext] = {
4302
+ store,
4303
+ stack
4304
+ };
4305
+ }
4306
+ };
4307
+ for (const prop of Object.getOwnPropertyNames(OriginalPromise)) {
4308
+ if (prop !== "prototype" && prop !== "length" && prop !== "name") {
4309
+ if (typeof OriginalPromise[prop] === "function") {
4310
+ global.Promise[prop] = OriginalPromise[prop];
4311
+ }
4312
+ }
4313
+ }
4314
+ }
3878
4315
  const defaults = {
3879
4316
  port: 3e3,
3880
4317
  hostname: "localhost",
@@ -3918,15 +4355,32 @@ class Shokupan extends ShokupanRouter {
3918
4355
  line,
3919
4356
  name: "ShokupanApplication"
3920
4357
  };
3921
- if (this.applicationConfig.securityHeaders !== false) {
4358
+ if (this.applicationConfig.defaultSecurityHeaders) {
3922
4359
  const { SecurityHeaders: SecurityHeaders2 } = require("./plugins/middleware/security-headers");
3923
- this.use(SecurityHeaders2(this.applicationConfig.securityHeaders === true ? {} : this.applicationConfig.securityHeaders));
4360
+ this.use(SecurityHeaders2(this.applicationConfig.defaultSecurityHeaders === true ? {} : this.applicationConfig.defaultSecurityHeaders));
3924
4361
  }
3925
4362
  if (this.applicationConfig.adapter !== "wintercg") {
3926
4363
  this.dbPromise = this.initDatastore().catch((err) => {
3927
4364
  this.logger?.debug("Failed to initialize default datastore", { error: err });
3928
4365
  });
3929
4366
  }
4367
+ if (this.applicationConfig.enablePromiseMonkeypatch) {
4368
+ enablePromisePatch();
4369
+ const processRef = typeof process !== "undefined" ? process : void 0;
4370
+ if (processRef && processRef.on) {
4371
+ processRef.on("unhandledRejection", (reason, promise) => {
4372
+ const ctx = promise?.[kContext];
4373
+ if (ctx && ctx.store && ctx.store.app === this) {
4374
+ const { requestId } = ctx.store;
4375
+ this.logger.error("Unhandled Rejection in Shokupan Request", {
4376
+ error: reason,
4377
+ requestId,
4378
+ creationStack: ctx.stack
4379
+ });
4380
+ }
4381
+ });
4382
+ }
4383
+ }
3930
4384
  }
3931
4385
  async initDatastore() {
3932
4386
  let engines = this.applicationConfig.surreal?.engines;
@@ -4012,7 +4466,8 @@ class Shokupan extends ShokupanRouter {
4012
4466
  this.get("/.well-known/openapi.yaml", async (ctx) => {
4013
4467
  try {
4014
4468
  await this.openApiSpecPromise;
4015
- const yaml = jsYaml.dump(this.openApiSpec);
4469
+ const { dump } = await import("js-yaml");
4470
+ const yaml = dump(this.openApiSpec);
4016
4471
  return ctx.send(yaml, { status: 200, headers: { "content-type": "application/yaml" } });
4017
4472
  } catch (e) {
4018
4473
  this.logger?.error("Failed to generate OpenAPI YAML", { error: e });
@@ -4203,7 +4658,8 @@ class Shokupan extends ShokupanRouter {
4203
4658
  */
4204
4659
  async fetch(req, server) {
4205
4660
  if (this.applicationConfig.enableTracing) {
4206
- const tracer2 = api.trace.getTracer("shokupan.application");
4661
+ const { trace, context } = await import("@opentelemetry/api");
4662
+ const tracer = trace.getTracer("shokupan.application");
4207
4663
  const store = asyncContext.getStore();
4208
4664
  const attrs = {
4209
4665
  attributes: {
@@ -4212,8 +4668,8 @@ class Shokupan extends ShokupanRouter {
4212
4668
  }
4213
4669
  };
4214
4670
  const parent = store?.span;
4215
- const ctx = parent ? api.trace.setSpan(api.context.active(), parent) : void 0;
4216
- return tracer2.startActiveSpan(`${req.method} ${new URL(req.url).pathname}`, attrs, ctx, (span) => {
4671
+ const ctx = parent ? trace.setSpan(context.active(), parent) : void 0;
4672
+ return tracer.startActiveSpan(`${req.method} ${new URL(req.url).pathname}`, attrs, ctx, (span) => {
4217
4673
  const ctxStore = new RequestContextStore();
4218
4674
  ctxStore.span = span;
4219
4675
  ctxStore.request = req;
@@ -4221,16 +4677,19 @@ class Shokupan extends ShokupanRouter {
4221
4677
  });
4222
4678
  }
4223
4679
  if (this.applicationConfig.enableAsyncLocalStorage) {
4680
+ const requestId = this.applicationConfig.idGenerator?.() ?? nanoid.nanoid();
4224
4681
  const ctxStore = new RequestContextStore();
4225
4682
  ctxStore.request = req;
4226
- return asyncContext.run(ctxStore, () => this.handleRequest(req, server));
4683
+ ctxStore["requestId"] = requestId;
4684
+ ctxStore["app"] = this;
4685
+ return asyncContext.run(ctxStore, () => this.handleRequest(req, server, requestId));
4227
4686
  }
4228
4687
  return this.handleRequest(req, server);
4229
4688
  }
4230
- async handleRequest(req, server) {
4689
+ async handleRequest(req, server, requestId) {
4231
4690
  const request = req;
4232
4691
  const controller = new AbortController();
4233
- const ctx = new ShokupanContext(request, server, void 0, this, controller.signal, this.applicationConfig.enableMiddlewareTracking);
4692
+ const ctx = new ShokupanContext(request, server, void 0, this, controller.signal, this.applicationConfig.enableMiddlewareTracking, requestId);
4234
4693
  const handle = async () => {
4235
4694
  if (this.cpuMonitor && this.cpuMonitor.getUsage() > (this.applicationConfig.autoBackpressureLevel ?? 60)) {
4236
4695
  const msg = "Too Many Requests (CPU Backpressure)";
@@ -4251,9 +4710,91 @@ class Shokupan extends ShokupanRouter {
4251
4710
  ctx[$routeMatched] = true;
4252
4711
  ctx.params = match.params;
4253
4712
  if (bodyParsing) await bodyParsing;
4713
+ if (this.applicationConfig.enableMiddlewareTracking) {
4714
+ const handler = match.handler;
4715
+ const meta = handler.metadata;
4716
+ if (meta) {
4717
+ const trackingStartTime = performance.now();
4718
+ const handlerName = meta.name || handler.name || "anonymous";
4719
+ ctx.handlerStack.push({
4720
+ name: handlerName,
4721
+ file: meta.file,
4722
+ line: meta.line,
4723
+ isBuiltin: meta.isBuiltin,
4724
+ startTime: trackingStartTime,
4725
+ duration: -1
4726
+ });
4727
+ try {
4728
+ const res = await handler(ctx);
4729
+ const duration = performance.now() - trackingStartTime;
4730
+ const stackItem = ctx.handlerStack[ctx.handlerStack.length - 1];
4731
+ if (stackItem) stackItem.duration = duration;
4732
+ Promise.resolve().then(async () => {
4733
+ try {
4734
+ const db = this.db;
4735
+ if (!db) return;
4736
+ const timestamp = Date.now();
4737
+ await db.upsert(new surrealdb.RecordId("middleware_tracking", {
4738
+ timestamp,
4739
+ name: handlerName
4740
+ }), {
4741
+ name: handlerName,
4742
+ path: ctx.path,
4743
+ timestamp,
4744
+ duration,
4745
+ file: meta.file,
4746
+ line: meta.line,
4747
+ error: void 0,
4748
+ metadata: {
4749
+ isBuiltin: meta.isBuiltin,
4750
+ pluginName: meta.pluginName
4751
+ }
4752
+ });
4753
+ } catch (e) {
4754
+ }
4755
+ });
4756
+ return res;
4757
+ } catch (err) {
4758
+ const duration = performance.now() - trackingStartTime;
4759
+ const stackItem = ctx.handlerStack[ctx.handlerStack.length - 1];
4760
+ if (stackItem) stackItem.duration = duration;
4761
+ Promise.resolve().then(async () => {
4762
+ try {
4763
+ const db = this.db;
4764
+ if (!db) return;
4765
+ const timestamp = Date.now();
4766
+ await db.upsert(new surrealdb.RecordId("middleware_tracking", {
4767
+ timestamp,
4768
+ name: handlerName
4769
+ }), {
4770
+ name: handlerName,
4771
+ path: ctx.path,
4772
+ timestamp,
4773
+ duration,
4774
+ file: meta.file,
4775
+ line: meta.line,
4776
+ error: String(err),
4777
+ metadata: {
4778
+ isBuiltin: meta.isBuiltin,
4779
+ pluginName: meta.pluginName
4780
+ }
4781
+ });
4782
+ } catch (e) {
4783
+ }
4784
+ });
4785
+ throw err;
4786
+ }
4787
+ }
4788
+ }
4254
4789
  return match.handler(ctx);
4255
4790
  }
4256
- return null;
4791
+ if (ctx.upgrade()) {
4792
+ return void 0;
4793
+ }
4794
+ if (ctx.response.status !== HTTP_STATUS.OK) {
4795
+ return ctx.send(null, { status: ctx.response.status, headers: ctx.response.headers });
4796
+ }
4797
+ throw new NotFoundError();
4257
4798
  });
4258
4799
  let response;
4259
4800
  if (result instanceof Response) {
@@ -4272,14 +4813,7 @@ class Shokupan extends ShokupanRouter {
4272
4813
  }
4273
4814
  response = ctx.send(null, { status, headers: ctx.response.headers });
4274
4815
  } else {
4275
- if (ctx.upgrade()) {
4276
- return void 0;
4277
- }
4278
- if (ctx.response.status !== HTTP_STATUS.OK) {
4279
- response = ctx.send(null, { status: ctx.response.status, headers: ctx.response.headers });
4280
- } else {
4281
- response = ctx.text("Not Found", HTTP_STATUS.NOT_FOUND);
4282
- }
4816
+ throw new NotFoundError();
4283
4817
  }
4284
4818
  } else if (typeof result === "object") {
4285
4819
  response = ctx.json(result);
@@ -4646,6 +5180,37 @@ function Event(eventName) {
4646
5180
  function RateLimit(options) {
4647
5181
  return Use(RateLimitMiddleware(options));
4648
5182
  }
5183
+ function Tool(options) {
5184
+ return (target, propertyKey, descriptor) => {
5185
+ target[$mcpTools] ??= /* @__PURE__ */ new Map();
5186
+ target[$mcpTools].set(propertyKey, {
5187
+ name: options?.name,
5188
+ description: options?.description,
5189
+ inputSchema: options?.inputSchema
5190
+ });
5191
+ };
5192
+ }
5193
+ function Prompt(options) {
5194
+ return (target, propertyKey, descriptor) => {
5195
+ target[$mcpPrompts] ??= /* @__PURE__ */ new Map();
5196
+ target[$mcpPrompts].set(propertyKey, {
5197
+ name: options?.name,
5198
+ description: options?.description,
5199
+ arguments: options?.arguments
5200
+ });
5201
+ };
5202
+ }
5203
+ function Resource(uri, options) {
5204
+ return (target, propertyKey, descriptor) => {
5205
+ target[$mcpResources] ??= /* @__PURE__ */ new Map();
5206
+ target[$mcpResources].set(propertyKey, {
5207
+ uri,
5208
+ name: options?.name,
5209
+ description: options?.description,
5210
+ mimeType: options?.mimeType
5211
+ });
5212
+ };
5213
+ }
4649
5214
  function ApiExplorerApp({ spec, base, asyncSpec, config }) {
4650
5215
  const hierarchy = /* @__PURE__ */ new Map();
4651
5216
  const addRoute = (groupKey, route) => {
@@ -5277,7 +5842,7 @@ async function generateAsyncApi(rootRouter, options = {}) {
5277
5842
  let astMiddlewareRegistry = {};
5278
5843
  let applications = [];
5279
5844
  try {
5280
- const { OpenAPIAnalyzer } = await Promise.resolve().then(() => require("./analyzer-DM-OlRq8.cjs"));
5845
+ const { OpenAPIAnalyzer } = await Promise.resolve().then(() => require("./analyzer-BOtveWL-.cjs"));
5281
5846
  const entrypoint = globalThis.Bun?.main || require.main?.filename || process.argv[1];
5282
5847
  const analyzer2 = new OpenAPIAnalyzer(process.cwd(), entrypoint);
5283
5848
  const analysisResult = await analyzer2.analyze();
@@ -7549,6 +8114,463 @@ class Dashboard {
7549
8114
  function unknownError(ctx) {
7550
8115
  return ctx.json({ error: "Unknown Error" }, 500);
7551
8116
  }
8117
+ let isPatched = false;
8118
+ function applyMonkeyPatch() {
8119
+ if (isPatched) return;
8120
+ isPatched = true;
8121
+ Error.stackTraceLimit = 50;
8122
+ }
8123
+ async function readSourceContext(filePath, line, contextLines = 5) {
8124
+ if (!filePath || filePath.startsWith("node:") || filePath.startsWith("bun:") || filePath.includes("node_modules")) {
8125
+ return null;
8126
+ }
8127
+ const path2 = filePath.startsWith("file://") ? filePath.slice(7) : filePath;
8128
+ try {
8129
+ const f = bun.file(path2);
8130
+ if (!await f.exists()) return null;
8131
+ const content = await f.text();
8132
+ const allLines = content.split("\n");
8133
+ const targetIndex = line - 1;
8134
+ if (targetIndex < 0 || targetIndex >= allLines.length) return null;
8135
+ const start = Math.max(0, targetIndex - contextLines);
8136
+ const end = Math.min(allLines.length, targetIndex + contextLines + 1);
8137
+ const subset = allLines.slice(start, end).map((code, i) => ({
8138
+ line: start + i + 1,
8139
+ code,
8140
+ isTarget: start + i + 1 === line
8141
+ }));
8142
+ return {
8143
+ lines: subset,
8144
+ startLine: start + 1,
8145
+ file: path2
8146
+ };
8147
+ } catch (e) {
8148
+ return null;
8149
+ }
8150
+ }
8151
+ async function renderErrorView(ctx, error) {
8152
+ const frames = [];
8153
+ const cwd = process.cwd();
8154
+ const errorName = error?.name || "Error";
8155
+ const errorMessage = error?.message || "Unknown error occurred";
8156
+ const errorId = error?.id || ctx.requestId || "unknown-id";
8157
+ const errorTimestamp = error?.timestamp ? new Date(error.timestamp).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
8158
+ const errorScope = error?.scope || {};
8159
+ const lines = (error?.stack || "").split("\n").slice(1);
8160
+ for (const line of lines) {
8161
+ const match = line.match(/at (?:(.+?)\s+\()?(?:(.+?):(\d+):(\d+))\)?/);
8162
+ if (match) {
8163
+ const [_, method, file, lineNo, colNo] = match;
8164
+ const fileName = file || "";
8165
+ let relativeFile = fileName;
8166
+ if (fileName.startsWith(cwd)) {
8167
+ relativeFile = fileName.slice(cwd.length + 1);
8168
+ }
8169
+ let isInternal = fileName.startsWith("node:") || fileName.startsWith("bun:") || fileName === "undefined" || fileName === "";
8170
+ if (isInternal && (method.includes("setTimeout") || method.includes("setInterval") || method.includes("setImmediate"))) {
8171
+ isInternal = false;
8172
+ }
8173
+ let isShokupan = false;
8174
+ if (fileName.includes("node_modules/@dotglitch/shokupan")) {
8175
+ isShokupan = true;
8176
+ } else if (relativeFile.startsWith("src/") || fileName.includes("/shokupan/dist/")) {
8177
+ isShokupan = true;
8178
+ }
8179
+ const isDependency = fileName.includes("node_modules") && !isShokupan;
8180
+ frames.push({
8181
+ method: method || "<anonymous>",
8182
+ file: fileName,
8183
+ line: parseInt(lineNo),
8184
+ column: parseInt(colNo),
8185
+ isNative: false,
8186
+ isInternal,
8187
+ isShokupan,
8188
+ isDependency,
8189
+ shortFile: fileName.split("/").pop() || fileName,
8190
+ relativeFile
8191
+ });
8192
+ }
8193
+ }
8194
+ let focusFrame = frames.find((f) => !f.isInternal && !f.isShokupan && !f.isDependency && !f.isNative);
8195
+ if (!focusFrame) focusFrame = frames[0];
8196
+ let sourceContext = null;
8197
+ if (focusFrame && focusFrame.file && !focusFrame.isInternal) {
8198
+ sourceContext = await readSourceContext(focusFrame.file, focusFrame.line, 8);
8199
+ }
8200
+ const renderFrames = frames.map((frame, index) => {
8201
+ const classes = [
8202
+ "stack-entry",
8203
+ frame.isInternal ? "internal" : "",
8204
+ frame.isShokupan ? "shokupan" : "",
8205
+ frame.isDependency ? "dependency" : "",
8206
+ frame === focusFrame ? "active" : ""
8207
+ ].join(" ");
8208
+ const fileLink = `vscode://file/${frame.file}:${frame.line}:${frame.column}`;
8209
+ return `
8210
+ <li class="${classes}">
8211
+ <a href="${fileLink}" style="text-decoration:none; color:inherit; display:block">
8212
+ <div class="stack-method">${frame.method === "<anonymous>" ? "Anonymous" : frame.method}</div>
8213
+ <div class="stack-file">${frame.relativeFile}:${frame.line}</div>
8214
+ </a>
8215
+ </li>
8216
+ `;
8217
+ }).join("");
8218
+ const highlightCode = (code) => {
8219
+ 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>');
8220
+ };
8221
+ if (sourceContext) {
8222
+ sourceContext.lines.map((l) => `
8223
+ <div class="code-line ${l.isTarget ? "target" : ""}">
8224
+ <div class="line-number">${l.line}</div>
8225
+ <div class="line-content">${highlightCode(l.code)}</div>
8226
+ </div>
8227
+ `).join("");
8228
+ }
8229
+ const renderKV = (data) => {
8230
+ if (!data || Object.keys(data).length === 0) return '<div style="color:var(--text-muted)">None</div>';
8231
+ return `<table class="kv-table">
8232
+ ${Object.entries(data).map(([k, v]) => {
8233
+ let displayVal = String(v);
8234
+ let valClass = "";
8235
+ if (typeof v === "number") {
8236
+ valClass = "kv-val-number";
8237
+ } else if (typeof v === "boolean") {
8238
+ valClass = "kv-val-bool";
8239
+ } else if (typeof v === "object" && v !== null) {
8240
+ try {
8241
+ displayVal = JSON.stringify(v, null, 2);
8242
+ valClass = "kv-val-json";
8243
+ } catch (e) {
8244
+ displayVal = "[Circular]";
8245
+ }
8246
+ }
8247
+ return `
8248
+ <tr>
8249
+ <td class="kv-key">${k}</td>
8250
+ <td class="kv-val ${valClass}">${displayVal}</td>
8251
+ </tr>`;
8252
+ }).join("")}
8253
+ </table>`;
8254
+ };
8255
+ 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>`;
8256
+ return `<!DOCTYPE html>
8257
+ <html lang="en">
8258
+ <head>
8259
+ <meta charset="UTF-8">
8260
+ <title>${errorName}: ${errorMessage}</title>
8261
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.css" rel="stylesheet" />
8262
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-highlight/prism-line-highlight.min.css" rel="stylesheet" />
8263
+ <link href="/_shokupan/error-view/prismjs.theme.css" rel="stylesheet" />
8264
+ <link href="/_shokupan/error-view/styles.css" rel="stylesheet" />
8265
+ <link href="/_shokupan/error-view/theme.css" rel="stylesheet" />
8266
+
8267
+ </head>
8268
+ <body class="">
8269
+
8270
+ <div class="page">
8271
+ <!-- HEADER -->
8272
+ <header class="chapter-header">
8273
+ <div class="chapter-meta">
8274
+ <div class="meta-item">
8275
+ <span>${ctx.method}</span>
8276
+ </div>
8277
+ <div class="meta-item">
8278
+ <span>${ctx.url.pathname}</span>
8279
+ </div>
8280
+ <div class="meta-item">
8281
+ <span>${ctx.response.status || 500}</span>
8282
+ </div>
8283
+ <div class="meta-item" style="margin-left:auto">
8284
+ <span class="id-badge" onclick="copyText('${errorId}')" title="Copy ID">ID: ${errorId}</span>
8285
+ </div>
8286
+ </div>
8287
+
8288
+ <h1 class="error-title">${errorName}</h1>
8289
+
8290
+ <div class="error-message-container">
8291
+ <h2 class="error-message">${errorMessage}</h2>
8292
+ <button class="action-btn" onclick="copyText('${errorMessage.replace(/'/g, "\\'")}')" title="Copy Message" style="padding:4px 8px">
8293
+ ${ICON_COPY}
8294
+ </button>
8295
+ </div>
8296
+
8297
+ <div class="actions-bar">
8298
+ <button class="action-btn" onclick="copyText()">
8299
+ ${ICON_COPY} Copy Error
8300
+ </button>
8301
+ <button class="action-btn" onclick="document.getElementById('raw-modal').style.display='flex'">
8302
+ View Raw Error
8303
+ </button>
8304
+ </div>
8305
+ </header>
8306
+
8307
+ <!-- CODE FIGURE -->
8308
+ <section class="figure">
8309
+ <div class="figure-caption">
8310
+ ${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>` : ""}
8311
+ </div>
8312
+ <div class="figure-body">
8313
+ ${sourceContext ? `
8314
+ <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>
8315
+ ` : `<div style="padding: 2rem; color: var(--text-muted); text-align: center;">Source code not available.</div>`}
8316
+ </div>
8317
+ </section>
8318
+
8319
+ <!-- NARRATIVE STACK -->
8320
+ <section class="narrative">
8321
+ <div class="section-title">
8322
+ <span>Stack Trace</span>
8323
+ <div class="filter-group">
8324
+ <span class="badge" onclick="this.classList.toggle('active'); document.body.classList.toggle('show-internals')">Internals</span>
8325
+ <span class="badge" onclick="this.classList.toggle('active'); document.body.classList.toggle('show-shokupan')">Framework</span>
8326
+ <span class="badge" onclick="this.classList.toggle('active'); document.body.classList.toggle('show-dependencies')">Dependencies</span>
8327
+ </div>
8328
+ </div>
8329
+ <ul class="stack-list">
8330
+ ${renderFrames}
8331
+ </ul>
8332
+ </section>
8333
+
8334
+ <!-- APPENDICES -->
8335
+ <section class="appendix">
8336
+ <div class="section-title">Context & Environment</div>
8337
+ <div class="appendix-grid">
8338
+ <div class="data-block">
8339
+ <h3>Request</h3>
8340
+ ${renderKV({
8341
+ id: errorId,
8342
+ timestamp: errorTimestamp,
8343
+ ...errorScope || {}
8344
+ })}
8345
+ </div>
8346
+ <div class="data-block">
8347
+ <h3>Headers</h3>
8348
+ ${renderKV(Object.fromEntries(ctx.headers))}
8349
+ </div>
8350
+ <div class="data-block">
8351
+ <h3>Query & Params</h3>
8352
+ ${renderKV({ ...ctx.params, ...ctx.query })}
8353
+ </div>
8354
+ </div>
8355
+ </section>
8356
+ </div>
8357
+
8358
+ <!-- RAW ERROR MODAL -->
8359
+ <div id="raw-modal" class="modal-overlay" onclick="if(event.target === this) this.style.display='none'">
8360
+ <div class="modal-content">
8361
+ <div class="modal-header">
8362
+ <span>Raw Error Object</span>
8363
+ <button class="action-btn" onclick="document.getElementById('raw-modal').style.display='none'">Close</button>
8364
+ </div>
8365
+ <div class="modal-body" id="raw-content"></div>
8366
+ </div>
8367
+ </div>
8368
+
8369
+ <!-- PrismJS Scripts -->
8370
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"><\/script>
8371
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-typescript.min.js"><\/script>
8372
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.js"><\/script>
8373
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-highlight/prism-line-highlight.min.js"><\/script>
8374
+
8375
+ <script>
8376
+ // Prepare Raw Error
8377
+ // circular ref safe stringify
8378
+ const getCircularReplacer = () => {
8379
+ const seen = new WeakSet();
8380
+ return (key, value) => {
8381
+ if (typeof value === "object" && value !== null) {
8382
+ if (seen.has(value)) {
8383
+ return "[Circular]";
8384
+ }
8385
+ seen.add(value);
8386
+ }
8387
+ return value;
8388
+ };
8389
+ };
8390
+
8391
+ // Inject error data from SERVER side
8392
+ const rawError = ${(() => {
8393
+ const serializeError = (err) => {
8394
+ const obj = {
8395
+ name: err.name,
8396
+ message: err.message,
8397
+ stack: err.stack,
8398
+ ...err
8399
+ // Spread enumerable props
8400
+ };
8401
+ if (err.cause) obj.cause = err.cause;
8402
+ if (err.code) obj.code = err.code;
8403
+ if (err.status) obj.status = err.status;
8404
+ if (err.statusCode) obj.statusCode = err.statusCode;
8405
+ return JSON.stringify(obj, (key, value) => {
8406
+ if (key === "structuredStack") return void 0;
8407
+ return value;
8408
+ }, 2);
8409
+ };
8410
+ return serializeError(error);
8411
+ })()};
8412
+
8413
+ // At this point 'rawError' is an Object in Client JS (because serializeError returned a JSON string)
8414
+ const RAW_ERROR_JSON = JSON.stringify(rawError, getCircularReplacer(), 2);
8415
+ // "Normally printed" usually means standard stacktrace string which includes name/message
8416
+ const RAW_ERROR_TEXT = rawError.stack || (rawError.name + ': ' + rawError.message);
8417
+
8418
+ document.getElementById('raw-content').innerText = RAW_ERROR_JSON;
8419
+
8420
+ function copyText(text) {
8421
+ if (!text) text = RAW_ERROR_TEXT; // Default to text representation (Message + Stack)
8422
+ navigator.clipboard.writeText(text).then(() => {
8423
+ console.log('Copied');
8424
+ });
8425
+ }
8426
+ <\/script>
8427
+ </body>
8428
+ </html>`;
8429
+ }
8430
+ function renderStatusView(ctx, status, error) {
8431
+ const title = `${status} ${error.message || "Error"}`;
8432
+ const method = ctx.method;
8433
+ const path2 = ctx.url.pathname;
8434
+ const css = `
8435
+ body {
8436
+ background: var(--bg-primary);
8437
+ color: var(--text-primary);
8438
+ font-family: var(--shokupan-font);
8439
+ display: flex;
8440
+ align-items: center;
8441
+ justify-content: center;
8442
+ height: 100vh;
8443
+ margin: 0;
8444
+ overflow: hidden;
8445
+ }
8446
+ .container {
8447
+ text-align: center;
8448
+ animation: fadeIn 0.3s ease-out;
8449
+ background: var(--bg-card);
8450
+ padding: 3rem 4rem;
8451
+ border-radius: 16px;
8452
+ border: 1px solid var(--card-border);
8453
+ box-shadow: 0 10px 30px rgba(0,0,0,0.3);
8454
+ max-width: 600px;
8455
+ }
8456
+ h1 {
8457
+ font-size: 6rem;
8458
+ margin: 0;
8459
+ color: var(--primary);
8460
+ line-height: 1;
8461
+ font-weight: 800;
8462
+ letter-spacing: -2px;
8463
+ text-shadow: 0 4px 20px rgba(255, 179, 128, 0.2);
8464
+ }
8465
+ h2 {
8466
+ font-size: 1.5rem;
8467
+ margin: 1rem 0 2rem 0;
8468
+ font-weight: 400;
8469
+ color: var(--text-secondary);
8470
+ }
8471
+ .meta {
8472
+ color: var(--text-muted);
8473
+ font-family: var(--shokupan-font-mono);
8474
+ font-size: 1rem;
8475
+ background: var(--bg-primary);
8476
+ padding: 0.75rem 1.5rem;
8477
+ border-radius: 8px;
8478
+ display: inline-block;
8479
+ border: 1px solid var(--border-color);
8480
+ }
8481
+ .method {
8482
+ font-weight: bold;
8483
+ margin-right: 0.5rem;
8484
+ padding: 0.2rem 0.5rem;
8485
+ border-radius: 4px;
8486
+ }
8487
+ .path {
8488
+ color: var(--text-primary);
8489
+ }
8490
+ @keyframes fadeIn {
8491
+ from { opacity: 0; transform: translateY(20px); }
8492
+ to { opacity: 1; transform: translateY(0); }
8493
+ }
8494
+ `;
8495
+ return `<!DOCTYPE html>
8496
+ <html lang="en">
8497
+ <head>
8498
+ <meta charset="UTF-8">
8499
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8500
+ <title>${title}</title>
8501
+ <link href="/_shokupan/error-view/theme.css" rel="stylesheet" />
8502
+ <style>${css}</style>
8503
+ </head>
8504
+ <body>
8505
+ <div class="container">
8506
+ <h1>${status}</h1>
8507
+ <h2>${error.message || "An error occurred"}</h2>
8508
+ <div class="meta">
8509
+ <span class="method badge-${method}">${method}</span>
8510
+ <span class="path">${path2}</span>
8511
+ </div>
8512
+ </div>
8513
+ </body>
8514
+ </html>`;
8515
+ }
8516
+ class ErrorView {
8517
+ constructor(config = {}) {
8518
+ this.config = config;
8519
+ }
8520
+ name = "error-view";
8521
+ async onInit(app) {
8522
+ applyMonkeyPatch();
8523
+ const errorViewMiddleware = async (ctx, next) => {
8524
+ try {
8525
+ return await next();
8526
+ } catch (err) {
8527
+ const accept = ctx.get("accept") || "";
8528
+ if (!accept.includes("text/html")) {
8529
+ throw err;
8530
+ }
8531
+ if (!err.timestamp) {
8532
+ Object.defineProperty(err, "timestamp", {
8533
+ value: Date.now(),
8534
+ enumerable: false,
8535
+ writable: true,
8536
+ configurable: true
8537
+ });
8538
+ }
8539
+ if (!err.id) {
8540
+ Object.defineProperty(err, "id", {
8541
+ value: ctx.requestId,
8542
+ enumerable: false,
8543
+ writable: true,
8544
+ configurable: true
8545
+ });
8546
+ }
8547
+ if (!err.scope) {
8548
+ const store = asyncContext.getStore();
8549
+ if (store) {
8550
+ Object.defineProperty(err, "scope", {
8551
+ value: { ...store },
8552
+ enumerable: false,
8553
+ writable: true,
8554
+ configurable: true
8555
+ });
8556
+ }
8557
+ }
8558
+ const status = getErrorStatus(err);
8559
+ if (status === 404 || status === 401 || status === 403) {
8560
+ const html2 = await renderStatusView(ctx, status, err);
8561
+ return ctx.html(html2, status);
8562
+ }
8563
+ const html = await renderErrorView(ctx, err);
8564
+ return ctx.html(html, status);
8565
+ }
8566
+ };
8567
+ Object.defineProperty(errorViewMiddleware, "name", { value: "ErrorViewMiddleware" });
8568
+ const { join } = await import("path");
8569
+ const assetDir = join(void 0, "assets");
8570
+ app.static("/_shokupan/error-view", assetDir);
8571
+ app.use(errorViewMiddleware);
8572
+ }
8573
+ }
7552
8574
  class GraphQLApolloPlugin extends ShokupanRouter {
7553
8575
  // Use generic any or verify type
7554
8576
  constructor(pluginOptions) {
@@ -7623,6 +8645,377 @@ class GraphQLApolloPlugin extends ShokupanRouter {
7623
8645
  });
7624
8646
  }
7625
8647
  }
8648
+ class GraphQLYogaPlugin extends ShokupanRouter {
8649
+ constructor(pluginOptions) {
8650
+ super();
8651
+ this.pluginOptions = pluginOptions;
8652
+ this.pluginOptions.path ??= "/graphql";
8653
+ }
8654
+ yoga;
8655
+ async onInit(app, options) {
8656
+ const { createYoga } = await import("graphql-yoga");
8657
+ const path2 = options?.path || this.pluginOptions.path || "/graphql";
8658
+ this.yoga = createYoga({
8659
+ ...this.pluginOptions.yogaConfig,
8660
+ graphqlEndpoint: path2
8661
+ });
8662
+ app.mount(path2, this);
8663
+ const handler = async (ctx) => {
8664
+ let body;
8665
+ if (ctx.req.method !== "GET" && ctx.req.method !== "HEAD") {
8666
+ body = await ctx.body();
8667
+ if (typeof body === "object" && body !== null) {
8668
+ body = JSON.stringify(body);
8669
+ }
8670
+ }
8671
+ const response = await this.yoga.fetch(
8672
+ new Request(ctx.req.url, {
8673
+ method: ctx.req.method,
8674
+ headers: ctx.req.headers,
8675
+ body
8676
+ }),
8677
+ {
8678
+ ...ctx
8679
+ }
8680
+ );
8681
+ response.headers.forEach((value, key) => {
8682
+ ctx.set(key, value);
8683
+ });
8684
+ const text = await response.text();
8685
+ return ctx.send(text, {
8686
+ status: response.status
8687
+ });
8688
+ };
8689
+ this.get("/", handler);
8690
+ this.post("/", handler);
8691
+ this.get("/*", handler);
8692
+ this.post("/*", handler);
8693
+ }
8694
+ }
8695
+ class HtmxPlugin {
8696
+ async onInit(app) {
8697
+ app.use(this.middleware());
8698
+ }
8699
+ middleware() {
8700
+ return async (ctx, next) => {
8701
+ Object.defineProperty(ctx, "isHtmx", {
8702
+ get: () => ctx.req.headers.has("hx-request")
8703
+ });
8704
+ Object.defineProperty(ctx, "isHtmxBoosted", {
8705
+ get: () => ctx.req.headers.has("hx-boosted")
8706
+ });
8707
+ ctx.trigger = (event, options) => {
8708
+ let headerName = "HX-Trigger";
8709
+ if (options?.after === "settle") headerName = "HX-Trigger-After-Settle";
8710
+ if (options?.after === "swap") headerName = "HX-Trigger-After-Swap";
8711
+ let value = JSON.stringify(event);
8712
+ if (typeof event === "string") {
8713
+ value = event;
8714
+ } else {
8715
+ value = JSON.stringify(event);
8716
+ }
8717
+ ctx.set(headerName, value);
8718
+ };
8719
+ ctx.pushUrl = (url) => {
8720
+ ctx.set("HX-Push-Url", url === false ? "false" : url);
8721
+ };
8722
+ ctx.htmxRedirect = (url) => {
8723
+ ctx.set("HX-Redirect", url);
8724
+ };
8725
+ ctx.refresh = () => {
8726
+ ctx.set("HX-Refresh", "true");
8727
+ };
8728
+ return next();
8729
+ };
8730
+ }
8731
+ }
8732
+ function Idempotency(options = {}) {
8733
+ const headerName = options.header || "Idempotency-Key";
8734
+ options.ttl || 24 * 60 * 60 * 1e3;
8735
+ let RecordIdClass;
8736
+ const idempotencyMiddleware = async function IdempotencyMiddleware(ctx, next) {
8737
+ const key = ctx.headers.get(headerName);
8738
+ if (!key) {
8739
+ return next();
8740
+ }
8741
+ try {
8742
+ if (!RecordIdClass) {
8743
+ const mod = await import("surrealdb");
8744
+ RecordIdClass = mod.RecordId;
8745
+ }
8746
+ const stored = await ctx.app.db.select(new RecordIdClass("idempotency", key));
8747
+ if (stored) {
8748
+ const responseHeaders = new Headers(stored.headers);
8749
+ responseHeaders.set("X-Idempotency-Hit", "true");
8750
+ return new Response(stored.body, {
8751
+ status: stored.status,
8752
+ headers: responseHeaders
8753
+ });
8754
+ }
8755
+ } catch (e) {
8756
+ console.error("Idempotency read error:", e);
8757
+ }
8758
+ const result = await next();
8759
+ let response;
8760
+ if (result instanceof Response) {
8761
+ response = result;
8762
+ } else if ((result === null || result === void 0) && ctx[$finalResponse] instanceof Response) {
8763
+ response = ctx[$finalResponse];
8764
+ } else if (result !== null && result !== void 0) {
8765
+ if (typeof result === "object") {
8766
+ response = await ctx.json(result);
8767
+ } else {
8768
+ response = await ctx.text(String(result));
8769
+ }
8770
+ }
8771
+ if (response instanceof Response) {
8772
+ const clone = response.clone();
8773
+ const bodyText = await clone.text();
8774
+ const headers = {};
8775
+ clone.headers.forEach((v, k) => {
8776
+ headers[k] = v;
8777
+ });
8778
+ const toStore = {
8779
+ status: clone.status,
8780
+ headers,
8781
+ body: bodyText,
8782
+ timestamp: Date.now()
8783
+ };
8784
+ try {
8785
+ await ctx.app.db.upsert(new RecordIdClass("idempotency", key), toStore);
8786
+ } catch (e) {
8787
+ console.error("Idempotency write error:", e);
8788
+ }
8789
+ return response;
8790
+ }
8791
+ return result;
8792
+ };
8793
+ idempotencyMiddleware.isBuiltin = true;
8794
+ idempotencyMiddleware.pluginName = "Idempotency";
8795
+ return idempotencyMiddleware;
8796
+ }
8797
+ class MCPServerPlugin {
8798
+ constructor(options = {}) {
8799
+ this.options = options;
8800
+ options.allowIntrospection ??= true;
8801
+ options.allowToolExecution ??= true;
8802
+ options.path ??= "/mcp";
8803
+ if (!options.path.startsWith("/")) {
8804
+ options.path = "/" + options.path;
8805
+ }
8806
+ options.rootDir ??= process.cwd();
8807
+ }
8808
+ router = new ShokupanRouter();
8809
+ analyzer;
8810
+ onInit(app) {
8811
+ this[$appRoot] = app;
8812
+ this.analyzer = new analyzer_impl.OpenAPIAnalyzer(this.options.rootDir);
8813
+ if (this.options.allowIntrospection) {
8814
+ this.registerTools();
8815
+ this.registerResources();
8816
+ this.registerPrompts();
8817
+ }
8818
+ app.onStart(async () => {
8819
+ app.mount(this.options.path, this.router);
8820
+ this.collectAppMcpItems(app);
8821
+ this.setupRoutes();
8822
+ this.router.metadata = {
8823
+ file: void 0,
8824
+ line: 1,
8825
+ name: "MCPServerPlugin",
8826
+ pluginName: "MCP Server"
8827
+ };
8828
+ });
8829
+ }
8830
+ collectAppMcpItems(app) {
8831
+ const collect = (router) => {
8832
+ if (router.mcpProtocol) {
8833
+ this.router.mcpProtocol.merge(router.mcpProtocol);
8834
+ }
8835
+ router[$childRouters]?.forEach(collect);
8836
+ };
8837
+ collect(app);
8838
+ }
8839
+ setupRoutes() {
8840
+ this.router.get("", (ctx) => {
8841
+ const endpointUrl = `${ctx.protocol}://${ctx.host}${this.options.path}`;
8842
+ const enc = new TextEncoder();
8843
+ return new Response(
8844
+ new ReadableStream({
8845
+ start(controller) {
8846
+ controller.enqueue(enc.encode(`event: endpoint
8847
+ data: ${JSON.stringify(endpointUrl)}
8848
+
8849
+ `));
8850
+ },
8851
+ cancel() {
8852
+ }
8853
+ }),
8854
+ {
8855
+ headers: {
8856
+ "Content-Type": "text/event-stream",
8857
+ "Cache-Control": "no-cache",
8858
+ "Connection": "keep-alive"
8859
+ }
8860
+ }
8861
+ );
8862
+ });
8863
+ this.router.post("", async (ctx) => {
8864
+ let parsedBody;
8865
+ try {
8866
+ parsedBody = await ctx.body();
8867
+ } catch (e) {
8868
+ return ctx.json({
8869
+ jsonrpc: "2.0",
8870
+ id: null,
8871
+ error: { code: -32700, message: "Parse error" }
8872
+ }, 400);
8873
+ }
8874
+ const response = await this.router.mcpProtocol.handleMessage(parsedBody);
8875
+ if (response) {
8876
+ return ctx.json(response);
8877
+ }
8878
+ return ctx.text("", 204);
8879
+ });
8880
+ }
8881
+ registerTools() {
8882
+ const ensureExecutionAllowed = () => {
8883
+ if (!this.options.allowToolExecution) {
8884
+ throw new Error("Tool execution is disabled.");
8885
+ }
8886
+ };
8887
+ this.router.tool(
8888
+ "list_endpoints",
8889
+ {},
8890
+ async () => {
8891
+ ensureExecutionAllowed();
8892
+ const { applications } = await this.analyzer.analyze();
8893
+ const endpoints = applications.flatMap(
8894
+ (app) => app.routes.map((r) => ({
8895
+ method: r.method,
8896
+ path: r.path,
8897
+ handler: r.handlerName,
8898
+ summary: r.summary
8899
+ }))
8900
+ );
8901
+ return {
8902
+ content: [{
8903
+ type: "text",
8904
+ text: JSON.stringify(endpoints, null, 2)
8905
+ }]
8906
+ };
8907
+ }
8908
+ );
8909
+ this.router.tool(
8910
+ "get_endpoint_details",
8911
+ {
8912
+ type: "object",
8913
+ properties: {
8914
+ method: { type: "string" },
8915
+ path: { type: "string" }
8916
+ },
8917
+ required: ["method", "path"]
8918
+ },
8919
+ async ({ method, path: path2 }) => {
8920
+ ensureExecutionAllowed();
8921
+ const { applications } = await this.analyzer.analyze();
8922
+ const route = applications.flatMap((app) => app.routes).find((r) => r.method.toUpperCase() === method.toUpperCase() && r.path === path2);
8923
+ if (!route) {
8924
+ return {
8925
+ content: [{ type: "text", text: `Endpoint ${method} ${path2} not found.` }],
8926
+ isError: true
8927
+ };
8928
+ }
8929
+ return {
8930
+ content: [{
8931
+ type: "text",
8932
+ text: JSON.stringify(route, null, 2)
8933
+ }]
8934
+ };
8935
+ }
8936
+ );
8937
+ }
8938
+ registerResources() {
8939
+ this.router.resource(
8940
+ "mcp://api/openapi.json",
8941
+ {
8942
+ name: "openapi-spec",
8943
+ mimeType: "application/json"
8944
+ },
8945
+ async (uri) => {
8946
+ const { applications } = await this.analyzer.analyze();
8947
+ const endpoints = applications.flatMap(
8948
+ (app) => app.routes.map((r) => ({
8949
+ method: r.method,
8950
+ path: r.path,
8951
+ handler: r.handlerName,
8952
+ summary: r.summary,
8953
+ requestTypes: r.requestTypes,
8954
+ responseType: r.responseType
8955
+ }))
8956
+ );
8957
+ return {
8958
+ contents: [{
8959
+ uri,
8960
+ text: JSON.stringify(endpoints, null, 2)
8961
+ }]
8962
+ };
8963
+ }
8964
+ );
8965
+ this.router.resource(
8966
+ "mcp://api/routes/{method}/{path}/source",
8967
+ {
8968
+ name: "route-source",
8969
+ mimeType: "text/typescript"
8970
+ },
8971
+ async (uri) => {
8972
+ const parts = uri.replace("mcp://", "").split("/");
8973
+ parts[2];
8974
+ throw new Error("Dynamic resource reading not fully implemented in lightweight version yet.");
8975
+ }
8976
+ );
8977
+ }
8978
+ registerPrompts() {
8979
+ this.router.prompt(
8980
+ "generate-client",
8981
+ [
8982
+ { name: "method", required: true },
8983
+ { name: "path", required: true }
8984
+ ],
8985
+ async ({ method, path: path2 }) => {
8986
+ const { applications } = await this.analyzer.analyze();
8987
+ const route = applications.flatMap((app) => app.routes).find((r) => r.method.toUpperCase() === method.toUpperCase() && r.path === path2);
8988
+ if (!route) {
8989
+ return {
8990
+ messages: [{
8991
+ role: "user",
8992
+ content: {
8993
+ type: "text",
8994
+ text: `Start a new task to create a client for ${method} ${path2}. The endpoint was not found in the current analysis.`
8995
+ }
8996
+ }]
8997
+ };
8998
+ }
8999
+ return {
9000
+ messages: [{
9001
+ role: "user",
9002
+ content: {
9003
+ type: "text",
9004
+ text: `Please generate a TypeScript client function for the following endpoint:
9005
+ Method: ${route.method}
9006
+ Path: ${route.path}
9007
+ Summary: ${route.summary || "N/A"}
9008
+ Request Types: ${JSON.stringify(route.requestTypes, null, 2)}
9009
+ Response Type: ${route.responseType || "unknown"}
9010
+
9011
+ Use fetch or axios. Ensure proper typing.`
9012
+ }
9013
+ }]
9014
+ };
9015
+ }
9016
+ );
9017
+ }
9018
+ }
7626
9019
  class ScalarPlugin extends ShokupanRouter {
7627
9020
  constructor(pluginOptions = {}) {
7628
9021
  pluginOptions.config ??= {};
@@ -7811,6 +9204,79 @@ class ScalarPlugin extends ShokupanRouter {
7811
9204
  }
7812
9205
  }
7813
9206
  }
9207
+ function attachSocketIOBridge(io, app) {
9208
+ io.on("connection", (socket) => {
9209
+ socket.onAny(async (event, ...args) => {
9210
+ if (event === "shokupan:request" || event === "http") {
9211
+ return;
9212
+ }
9213
+ const handler = app.findEvent(event);
9214
+ if (handler) {
9215
+ const data = args[0];
9216
+ const req = new ShokupanRequest({
9217
+ url: `socketio://${app.applicationConfig.hostname || "localhost"}/event/${event}`,
9218
+ method: "POST",
9219
+ headers: new Headers({ "content-type": "application/json" }),
9220
+ body: JSON.stringify(data)
9221
+ });
9222
+ const ctx = new ShokupanContext(req, app.server);
9223
+ ctx[$ws] = socket;
9224
+ ctx.io = io;
9225
+ try {
9226
+ for (let i = 0; i < handler.length; i++) {
9227
+ await handler[i](ctx);
9228
+ }
9229
+ } catch (e) {
9230
+ await app.runHooks("onError", ctx, e);
9231
+ if (app.applicationConfig["websocketErrorHandler"]) {
9232
+ await app.applicationConfig["websocketErrorHandler"](e, ctx);
9233
+ } else {
9234
+ console.error(`Error in event ${event}:`, e);
9235
+ }
9236
+ }
9237
+ }
9238
+ });
9239
+ if (app.applicationConfig["enableHttpBridge"]) {
9240
+ socket.on("shokupan:request", async (payload, callback) => {
9241
+ try {
9242
+ const { method, path: path2, headers, body } = payload;
9243
+ const url = new URL(path2, `http://${app.applicationConfig.hostname || "localhost"}:3000`);
9244
+ const req = new Request(url.toString(), {
9245
+ method,
9246
+ headers,
9247
+ body: typeof body === "object" ? JSON.stringify(body) : body
9248
+ });
9249
+ const res = await app.fetch(req);
9250
+ let resBody = await res.text();
9251
+ try {
9252
+ resBody = JSON.parse(resBody);
9253
+ } catch {
9254
+ }
9255
+ const resHeaders = {};
9256
+ res.headers.forEach((v, k) => resHeaders[k] = v);
9257
+ if (typeof callback === "function") {
9258
+ await callback({
9259
+ status: res.status,
9260
+ headers: resHeaders,
9261
+ body: resBody
9262
+ });
9263
+ } else {
9264
+ socket.emit("shokupan:response", {
9265
+ id: payload.id,
9266
+ status: res.status,
9267
+ headers: resHeaders,
9268
+ body: resBody
9269
+ });
9270
+ }
9271
+ } catch (e) {
9272
+ if (typeof callback === "function") {
9273
+ callback({ status: 500, body: { error: e.message } });
9274
+ }
9275
+ }
9276
+ });
9277
+ }
9278
+ });
9279
+ }
7814
9280
  function createLimitStream(maxSize) {
7815
9281
  let size = 0;
7816
9282
  return new TransformStream({
@@ -8453,6 +9919,168 @@ function enableOpenApiValidation(app) {
8453
9919
  precompileValidators(app, spec);
8454
9920
  });
8455
9921
  }
9922
+ function isPrivateIP(ip) {
9923
+ const ipv4Patterns = [
9924
+ /^10\./,
9925
+ // 10.0.0.0/8
9926
+ /^172\.(1[6-9]|2[0-9]|3[01])\./,
9927
+ // 172.16.0.0/12
9928
+ /^192\.168\./,
9929
+ // 192.168.0.0/16
9930
+ /^127\./,
9931
+ // 127.0.0.0/8 (loopback)
9932
+ /^169\.254\./,
9933
+ // 169.254.0.0/16 (link-local)
9934
+ /^0\.0\.0\.0$/
9935
+ // 0.0.0.0
9936
+ ];
9937
+ const ipv6Patterns = [
9938
+ /^::1$/,
9939
+ // loopback
9940
+ /^fe80:/,
9941
+ // link-local
9942
+ /^fc00:/,
9943
+ // unique local
9944
+ /^fd00:/
9945
+ // unique local
9946
+ ];
9947
+ for (const pattern of ipv4Patterns) {
9948
+ if (pattern.test(ip)) return true;
9949
+ }
9950
+ for (const pattern of ipv6Patterns) {
9951
+ if (pattern.test(ip.toLowerCase())) return true;
9952
+ }
9953
+ return false;
9954
+ }
9955
+ function Proxy$1(options) {
9956
+ const targetUrl = new URL(options.target);
9957
+ if (!["http:", "https:"].includes(targetUrl.protocol)) {
9958
+ throw new Error("Invalid proxy target protocol. Only http and https are allowed.");
9959
+ }
9960
+ if (options.allowedHosts && options.allowedHosts.length > 0) {
9961
+ if (!options.allowedHosts.includes(targetUrl.hostname)) {
9962
+ throw new Error(`Target hostname ${targetUrl.hostname} is not in the allowed hosts list.`);
9963
+ }
9964
+ }
9965
+ if (!options.allowPrivateIPs && isPrivateIP(targetUrl.hostname)) {
9966
+ throw new Error("Proxying to private IP addresses is not allowed.");
9967
+ }
9968
+ return async (ctx, next) => {
9969
+ const req = ctx.request;
9970
+ if (options.ws && req.headers.get("upgrade")?.toLowerCase() === "websocket") {
9971
+ const success = ctx.upgrade({
9972
+ data: {
9973
+ handler: {
9974
+ open: (ws) => handleWSOpen(ws, ctx, options, targetUrl),
9975
+ message: (ws, message) => handleWSMessage(ws, message),
9976
+ close: (ws, code, reason) => handleWSClose(ws, code, reason),
9977
+ drain: (ws) => handleWSDrain()
9978
+ }
9979
+ }
9980
+ });
9981
+ if (success) {
9982
+ return void 0;
9983
+ }
9984
+ }
9985
+ let path2 = ctx.url.pathname;
9986
+ if (options.pathRewrite) {
9987
+ path2 = options.pathRewrite(path2);
9988
+ }
9989
+ const url = new URL(path2 + ctx.url.search, targetUrl);
9990
+ if (!["http:", "https:"].includes(url.protocol)) {
9991
+ return ctx.text("Invalid protocol in proxied URL", 400);
9992
+ }
9993
+ const headers = new Headers(req.headers);
9994
+ if (options.changeOrigin) {
9995
+ headers.set("host", targetUrl.host);
9996
+ }
9997
+ if (options.headers) {
9998
+ Object.entries(options.headers).forEach(([key, value]) => headers.set(key, value));
9999
+ }
10000
+ headers.delete("connection");
10001
+ headers.delete("keep-alive");
10002
+ headers.delete("proxy-authenticate");
10003
+ headers.delete("proxy-authorization");
10004
+ headers.delete("te");
10005
+ headers.delete("trailer");
10006
+ headers.delete("transfer-encoding");
10007
+ headers.delete("upgrade");
10008
+ const proxyReq = new Request(url.toString(), {
10009
+ method: req.method,
10010
+ headers,
10011
+ body: req.body,
10012
+ // @ts-ignore - duplex is needed for some node/bun versions for streaming bodies
10013
+ duplex: "half"
10014
+ });
10015
+ const res = await fetch(proxyReq);
10016
+ return new Response(res.body, {
10017
+ status: res.status,
10018
+ statusText: res.statusText,
10019
+ headers: res.headers
10020
+ });
10021
+ };
10022
+ }
10023
+ const wsMap = /* @__PURE__ */ new WeakMap();
10024
+ function handleWSOpen(ws, ctx, options, targetUrl) {
10025
+ let path2 = ctx.url.pathname;
10026
+ if (options.pathRewrite) {
10027
+ path2 = options.pathRewrite(path2);
10028
+ }
10029
+ const url = new URL(path2 + ctx.url.search, targetUrl);
10030
+ url.protocol = targetUrl.protocol.replace("http", "ws");
10031
+ const headers = {};
10032
+ if (options.changeOrigin) {
10033
+ headers["Host"] = targetUrl.host;
10034
+ }
10035
+ ctx.request.headers.forEach((v, k) => {
10036
+ if (!["upgrade", "connection", "sec-websocket-key", "sec-websocket-version", "sec-websocket-extensions"].includes(k.toLowerCase())) {
10037
+ headers[k] = v;
10038
+ }
10039
+ });
10040
+ const upstream = new WebSocket(url.toString());
10041
+ wsMap.set(ws, upstream);
10042
+ const pendingMessages = [];
10043
+ let isConnected = false;
10044
+ upstream.onopen = () => {
10045
+ isConnected = true;
10046
+ while (pendingMessages.length > 0) {
10047
+ const msg = pendingMessages.shift();
10048
+ upstream.send(msg);
10049
+ }
10050
+ };
10051
+ upstream.onmessage = (event) => {
10052
+ ws.send(event.data);
10053
+ };
10054
+ upstream.onclose = (event) => {
10055
+ ws.close(event.code, event.reason);
10056
+ };
10057
+ upstream.onerror = (err) => {
10058
+ console.error("Upstream WebSocket error:", err);
10059
+ ws.close(1011, "Internal Error");
10060
+ };
10061
+ upstream._pendingRequestMessages = pendingMessages;
10062
+ upstream._isConnected = () => isConnected;
10063
+ }
10064
+ function handleWSMessage(ws, message) {
10065
+ const upstream = wsMap.get(ws);
10066
+ if (!upstream) return;
10067
+ if (upstream._isConnected && upstream._isConnected()) {
10068
+ upstream.send(message);
10069
+ } else {
10070
+ upstream._pendingRequestMessages.push(message);
10071
+ }
10072
+ }
10073
+ function handleWSClose(ws, code, reason) {
10074
+ const upstream = wsMap.get(ws);
10075
+ if (upstream) {
10076
+ if (upstream.readyState === WebSocket.OPEN) {
10077
+ upstream.close(code, reason);
10078
+ }
10079
+ wsMap.delete(ws);
10080
+ }
10081
+ }
10082
+ function handleWSDrain(ws) {
10083
+ }
8456
10084
  function SecurityHeaders(options = {}) {
8457
10085
  const securityHeadersMiddleware = async function SecurityHeadersMiddleware(ctx, next) {
8458
10086
  const set = (k, v) => ctx.response.set(k, v);
@@ -8665,43 +10293,56 @@ function Session(options) {
8665
10293
  }
8666
10294
  const sessObj = existing;
8667
10295
  Object.defineProperty(sessObj, "id", { value: sessionID, configurable: true });
8668
- sessObj.save = (cb) => {
8669
- store.set(sessObj.id, sessObj, cb);
10296
+ sessObj.save = () => {
10297
+ return new Promise((resolve, reject) => {
10298
+ store.set(sessObj.id, sessObj, (err) => {
10299
+ if (err) reject(err);
10300
+ else resolve();
10301
+ });
10302
+ });
8670
10303
  };
8671
- sessObj.destroy = (cb) => {
8672
- store.destroy(sessObj.id, (err) => {
8673
- if (cb) cb(err);
10304
+ sessObj.destroy = () => {
10305
+ return new Promise((resolve, reject) => {
10306
+ store.destroy(sessObj.id, (err) => {
10307
+ if (err) reject(err);
10308
+ else resolve();
10309
+ });
8674
10310
  });
8675
10311
  };
8676
- sessObj.regenerate = (cb) => {
8677
- store.destroy(sessObj.id, (err) => {
8678
- sessionID = generateId(ctx);
8679
- const keys = Object.keys(sessObj);
8680
- for (let i = 0; i < keys.length; i++) {
8681
- const key = keys[i];
8682
- if (key !== "cookie" && key !== "id" && typeof sessObj[key] !== "function") {
8683
- delete sessObj[key];
10312
+ sessObj.regenerate = () => {
10313
+ return new Promise((resolve, reject) => {
10314
+ store.destroy(sessObj.id, (err) => {
10315
+ if (err) return reject(err);
10316
+ sessionID = generateId(ctx);
10317
+ const keys = Object.keys(sessObj);
10318
+ for (let i = 0; i < keys.length; i++) {
10319
+ const key = keys[i];
10320
+ if (key !== "cookie" && key !== "id" && typeof sessObj[key] !== "function") {
10321
+ delete sessObj[key];
10322
+ }
8684
10323
  }
8685
- }
8686
- Object.defineProperty(sessObj, "id", { value: sessionID, configurable: true });
8687
- if (cb) cb(err);
10324
+ Object.defineProperty(sessObj, "id", { value: sessionID, configurable: true });
10325
+ resolve();
10326
+ });
8688
10327
  });
8689
10328
  };
8690
10329
  sessObj.undefined = () => {
8691
10330
  };
8692
- sessObj.reload = (cb) => {
8693
- store.get(sessObj.id, (err, sess2) => {
8694
- if (err) return cb(err);
8695
- if (!sess2) return cb(new Error("Session not found"));
8696
- const keys = Object.keys(sessObj);
8697
- for (let i = 0; i < keys.length; i++) {
8698
- const key = keys[i];
8699
- if (key !== "cookie" && key !== "id" && typeof sessObj[key] !== "function") {
8700
- delete sessObj[key];
10331
+ sessObj.reload = () => {
10332
+ return new Promise((resolve, reject) => {
10333
+ store.get(sessObj.id, (err, sess2) => {
10334
+ if (err) return reject(err);
10335
+ if (!sess2) return reject(new Error("Session not found"));
10336
+ const keys = Object.keys(sessObj);
10337
+ for (let i = 0; i < keys.length; i++) {
10338
+ const key = keys[i];
10339
+ if (key !== "cookie" && key !== "id" && typeof sessObj[key] !== "function") {
10340
+ delete sessObj[key];
10341
+ }
8701
10342
  }
8702
- }
8703
- Object.assign(sessObj, sess2);
8704
- cb(null);
10343
+ Object.assign(sessObj, sess2);
10344
+ resolve();
10345
+ });
8705
10346
  });
8706
10347
  };
8707
10348
  sessObj.touch = () => {
@@ -8793,11 +10434,15 @@ exports.$io = $io;
8793
10434
  exports.$isApplication = $isApplication;
8794
10435
  exports.$isMounted = $isMounted;
8795
10436
  exports.$isRouter = $isRouter;
10437
+ exports.$mcpPrompts = $mcpPrompts;
10438
+ exports.$mcpResources = $mcpResources;
10439
+ exports.$mcpTools = $mcpTools;
8796
10440
  exports.$middleware = $middleware;
8797
10441
  exports.$mountPath = $mountPath;
8798
10442
  exports.$parent = $parent;
8799
10443
  exports.$rawBody = $rawBody;
8800
10444
  exports.$requestId = $requestId;
10445
+ exports.$resilienceConfig = $resilienceConfig;
8801
10446
  exports.$routeArgs = $routeArgs;
8802
10447
  exports.$routeMatched = $routeMatched;
8803
10448
  exports.$routeMethods = $routeMethods;
@@ -8819,24 +10464,33 @@ exports.Cors = Cors;
8819
10464
  exports.Ctx = Ctx;
8820
10465
  exports.Dashboard = Dashboard;
8821
10466
  exports.Delete = Delete;
10467
+ exports.ErrorView = ErrorView;
8822
10468
  exports.Event = Event;
8823
10469
  exports.Get = Get;
8824
10470
  exports.GraphQLApolloPlugin = GraphQLApolloPlugin;
10471
+ exports.GraphQLYogaPlugin = GraphQLYogaPlugin;
8825
10472
  exports.HTTPMethods = HTTPMethods;
8826
10473
  exports.Head = Head;
8827
10474
  exports.Headers = Headers$1;
10475
+ exports.HtmxPlugin = HtmxPlugin;
10476
+ exports.Idempotency = Idempotency;
8828
10477
  exports.Inject = Inject;
8829
10478
  exports.Injectable = Injectable;
10479
+ exports.MCPServerPlugin = MCPServerPlugin;
8830
10480
  exports.MemoryStore = MemoryStore;
10481
+ exports.OpenTelemetryPlugin = OpenTelemetryPlugin;
8831
10482
  exports.Options = Options;
8832
10483
  exports.Param = Param;
8833
10484
  exports.Patch = Patch;
8834
10485
  exports.Post = Post;
10486
+ exports.Prompt = Prompt;
10487
+ exports.Proxy = Proxy$1;
8835
10488
  exports.Put = Put;
8836
10489
  exports.Query = Query;
8837
10490
  exports.RateLimit = RateLimit;
8838
10491
  exports.RateLimitMiddleware = RateLimitMiddleware;
8839
10492
  exports.Req = Req;
10493
+ exports.Resource = Resource;
8840
10494
  exports.RouteParamType = RouteParamType;
8841
10495
  exports.RouterRegistry = RouterRegistry;
8842
10496
  exports.ScalarPlugin = ScalarPlugin;
@@ -8849,13 +10503,18 @@ exports.ShokupanRequest = ShokupanRequest;
8849
10503
  exports.ShokupanResponse = ShokupanResponse;
8850
10504
  exports.ShokupanRouter = ShokupanRouter;
8851
10505
  exports.Spec = Spec;
10506
+ exports.Tool = Tool;
8852
10507
  exports.Use = Use;
8853
10508
  exports.ValidationError = ValidationError;
10509
+ exports.attachSocketIOBridge = attachSocketIOBridge;
8854
10510
  exports.compileValidators = compileValidators;
8855
10511
  exports.compose = compose;
8856
10512
  exports.enableOpenApiValidation = enableOpenApiValidation;
8857
10513
  exports.openApiValidator = openApiValidator;
8858
10514
  exports.precompileValidators = precompileValidators;
10515
+ exports.serveStatic = serveStatic;
10516
+ exports.traceHandler = traceHandler;
10517
+ exports.traceMiddleware = traceMiddleware;
8859
10518
  exports.useExpress = useExpress;
8860
10519
  exports.valibot = valibot;
8861
10520
  exports.validate = validate;