shokupan 0.6.1 → 0.7.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 (61) hide show
  1. package/README.md +2 -2
  2. package/dist/{openapi-analyzer-Bei1sVWp.cjs → analyzer-Bei1sVWp.cjs} +1 -1
  3. package/dist/analyzer-Bei1sVWp.cjs.map +1 -0
  4. package/dist/{openapi-analyzer-Ce_7JxZh.js → analyzer-Ce_7JxZh.js} +1 -1
  5. package/dist/analyzer-Ce_7JxZh.js.map +1 -0
  6. package/dist/cli.cjs +2 -2
  7. package/dist/cli.cjs.map +1 -1
  8. package/dist/cli.js +1 -1
  9. package/dist/cli.js.map +1 -1
  10. package/dist/context.d.ts +9 -9
  11. package/dist/{server-adapter-0xH174zz.js → http-server-0xH174zz.js} +1 -1
  12. package/dist/http-server-0xH174zz.js.map +1 -0
  13. package/dist/{server-adapter-DFhwlK8e.cjs → http-server-DFhwlK8e.cjs} +1 -1
  14. package/dist/http-server-DFhwlK8e.cjs.map +1 -0
  15. package/dist/index.cjs +928 -767
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.ts +17 -17
  18. package/dist/index.js +953 -791
  19. package/dist/index.js.map +1 -1
  20. package/dist/middleware.d.ts +1 -1
  21. package/dist/plugins/{auth.d.ts → application/auth.d.ts} +72 -3
  22. package/dist/plugins/application/cluster.d.ts +33 -0
  23. package/dist/plugins/{failed-request-recorder.d.ts → application/dashboard/failed-request-recorder.d.ts} +1 -1
  24. package/dist/plugins/{debugview → application/dashboard}/plugin.d.ts +13 -6
  25. package/dist/plugins/{server-adapter.d.ts → application/http-server.d.ts} +1 -1
  26. package/dist/plugins/{idempotency → application/idempotency}/plugin.d.ts +7 -1
  27. package/dist/plugins/{openapi.d.ts → application/openapi/openapi.d.ts} +2 -2
  28. package/dist/plugins/application/scalar.d.ts +36 -0
  29. package/dist/plugins/middleware/compression.d.ts +17 -0
  30. package/dist/plugins/middleware/cors.d.ts +34 -0
  31. package/dist/plugins/{express.d.ts → middleware/express.d.ts} +1 -1
  32. package/dist/plugins/{openapi-validator.d.ts → middleware/openapi-validator.d.ts} +2 -2
  33. package/dist/plugins/middleware/proxy.d.ts +37 -0
  34. package/dist/plugins/middleware/rate-limit.d.ts +58 -0
  35. package/dist/plugins/{security-headers.d.ts → middleware/security-headers.d.ts} +51 -1
  36. package/dist/plugins/{serve-static.d.ts → middleware/serve-static.d.ts} +1 -1
  37. package/dist/plugins/{session.d.ts → middleware/session.d.ts} +89 -3
  38. package/dist/plugins/{validation.d.ts → middleware/validation.d.ts} +6 -1
  39. package/dist/router.d.ts +5 -5
  40. package/dist/shokupan.d.ts +10 -4
  41. package/dist/util/async-hooks.d.ts +8 -2
  42. package/dist/{decorators.d.ts → util/decorators.d.ts} +1 -1
  43. package/dist/util/http-status.d.ts +2 -0
  44. package/dist/util/instrumentation.d.ts +1 -1
  45. package/dist/{router → util}/trie.d.ts +1 -1
  46. package/dist/{types.d.ts → util/types.d.ts} +8 -1
  47. package/package.json +4 -4
  48. package/dist/openapi-analyzer-Bei1sVWp.cjs.map +0 -1
  49. package/dist/openapi-analyzer-Ce_7JxZh.js.map +0 -1
  50. package/dist/plugins/compression.d.ts +0 -5
  51. package/dist/plugins/cors.d.ts +0 -11
  52. package/dist/plugins/proxy.d.ts +0 -11
  53. package/dist/plugins/rate-limit.d.ts +0 -15
  54. package/dist/plugins/scalar.d.ts +0 -15
  55. package/dist/server-adapter-0xH174zz.js.map +0 -1
  56. package/dist/server-adapter-DFhwlK8e.cjs.map +0 -1
  57. /package/dist/{analysis/openapi-analyzer.d.ts → plugins/application/openapi/analyzer.d.ts} +0 -0
  58. /package/dist/{di.d.ts → util/di.d.ts} +0 -0
  59. /package/dist/{request.d.ts → util/request.d.ts} +0 -0
  60. /package/dist/{response.d.ts → util/response.d.ts} +0 -0
  61. /package/dist/{symbol.d.ts → util/symbol.d.ts} +0 -0
package/dist/index.cjs CHANGED
@@ -23,18 +23,20 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
25
25
  const promises = require("node:fs/promises");
26
+ const api = require("@opentelemetry/api");
27
+ const node_async_hooks = require("node:async_hooks");
26
28
  const eta$2 = require("eta");
27
29
  const promises$1 = require("fs/promises");
28
30
  const path = require("path");
29
- const node_async_hooks = require("node:async_hooks");
30
- const api = require("@opentelemetry/api");
31
31
  const os = require("node:os");
32
32
  const arctic = require("arctic");
33
33
  const jose = require("jose");
34
+ const cluster = require("node:cluster");
35
+ const net = require("node:net");
36
+ const analyzer = require("./analyzer-Bei1sVWp.cjs");
34
37
  const zlib = require("node:zlib");
35
38
  const Ajv = require("ajv");
36
39
  const addFormats = require("ajv-formats");
37
- const openapiAnalyzer = require("./openapi-analyzer-Bei1sVWp.cjs");
38
40
  const crypto = require("crypto");
39
41
  const events = require("events");
40
42
  function _interopNamespaceDefault(e) {
@@ -56,6 +58,72 @@ function _interopNamespaceDefault(e) {
56
58
  const os__namespace = /* @__PURE__ */ _interopNamespaceDefault(os);
57
59
  const jose__namespace = /* @__PURE__ */ _interopNamespaceDefault(jose);
58
60
  const zlib__namespace = /* @__PURE__ */ _interopNamespaceDefault(zlib);
61
+ const VALID_HTTP_STATUSES = /* @__PURE__ */ new Set([
62
+ 100,
63
+ 101,
64
+ 102,
65
+ 103,
66
+ 200,
67
+ 201,
68
+ 202,
69
+ 203,
70
+ 204,
71
+ 205,
72
+ 206,
73
+ 207,
74
+ 208,
75
+ 226,
76
+ 300,
77
+ 301,
78
+ 302,
79
+ 303,
80
+ 304,
81
+ 305,
82
+ 306,
83
+ 307,
84
+ 308,
85
+ 400,
86
+ 401,
87
+ 402,
88
+ 403,
89
+ 404,
90
+ 405,
91
+ 406,
92
+ 407,
93
+ 408,
94
+ 409,
95
+ 410,
96
+ 411,
97
+ 412,
98
+ 413,
99
+ 414,
100
+ 415,
101
+ 416,
102
+ 417,
103
+ 418,
104
+ 421,
105
+ 422,
106
+ 423,
107
+ 424,
108
+ 425,
109
+ 426,
110
+ 428,
111
+ 429,
112
+ 431,
113
+ 451,
114
+ 500,
115
+ 501,
116
+ 502,
117
+ 503,
118
+ 504,
119
+ 505,
120
+ 506,
121
+ 507,
122
+ 508,
123
+ 510,
124
+ 511
125
+ ]);
126
+ const VALID_REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
59
127
  class ShokupanResponse {
60
128
  _headers = null;
61
129
  _status = 200;
@@ -128,72 +196,6 @@ function isValidCookieDomain(domain, currentHost) {
128
196
  }
129
197
  return false;
130
198
  }
131
- const VALID_HTTP_STATUSES = /* @__PURE__ */ new Set([
132
- 100,
133
- 101,
134
- 102,
135
- 103,
136
- 200,
137
- 201,
138
- 202,
139
- 203,
140
- 204,
141
- 205,
142
- 206,
143
- 207,
144
- 208,
145
- 226,
146
- 300,
147
- 301,
148
- 302,
149
- 303,
150
- 304,
151
- 305,
152
- 306,
153
- 307,
154
- 308,
155
- 400,
156
- 401,
157
- 402,
158
- 403,
159
- 404,
160
- 405,
161
- 406,
162
- 407,
163
- 408,
164
- 409,
165
- 410,
166
- 411,
167
- 412,
168
- 413,
169
- 414,
170
- 415,
171
- 416,
172
- 417,
173
- 418,
174
- 421,
175
- 422,
176
- 423,
177
- 424,
178
- 425,
179
- 426,
180
- 428,
181
- 429,
182
- 431,
183
- 451,
184
- 500,
185
- 501,
186
- 502,
187
- 503,
188
- 504,
189
- 505,
190
- 506,
191
- 507,
192
- 508,
193
- 510,
194
- 511
195
- ]);
196
- const VALID_REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
197
199
  class ShokupanContext {
198
200
  constructor(request, server, state, app, signal, enableMiddlewareTracking = false) {
199
201
  this.request = request;
@@ -232,12 +234,20 @@ class ShokupanContext {
232
234
  _bodyType;
233
235
  _bodyParsed = false;
234
236
  _bodyParseError;
237
+ _routeMatched = false;
235
238
  // Cached URL properties to avoid repeated parsing
236
239
  _cachedHostname;
237
240
  _cachedProtocol;
238
241
  _cachedHost;
239
242
  _cachedOrigin;
240
243
  _cachedQuery;
244
+ /**
245
+ * JSX Rendering Function
246
+ */
247
+ renderer;
248
+ setRenderer(renderer) {
249
+ this.renderer = renderer;
250
+ }
241
251
  get url() {
242
252
  if (!this._url) {
243
253
  const urlString = this.request.url || "http://localhost/";
@@ -455,11 +465,15 @@ class ShokupanContext {
455
465
  }
456
466
  const contentType = this.request.headers.get("content-type") || "";
457
467
  if (contentType.includes("application/json") || contentType.includes("+json")) {
458
- const rawText = await this.readRawBody();
459
468
  const parserType = this.app?.applicationConfig?.jsonParser || "native";
460
469
  if (parserType === "native") {
461
- this._cachedBody = JSON.parse(rawText);
470
+ try {
471
+ this._cachedBody = await this.request.json();
472
+ } catch (e) {
473
+ throw e;
474
+ }
462
475
  } else {
476
+ const rawText = await this.request.text();
463
477
  const { getJSONParser } = await Promise.resolve().then(() => require("./json-parser-COdZ0fqY.cjs"));
464
478
  const parser = getJSONParser(parserType);
465
479
  this._cachedBody = parser(rawText);
@@ -469,7 +483,7 @@ class ShokupanContext {
469
483
  this._cachedBody = await this.request.formData();
470
484
  this._bodyType = "formData";
471
485
  } else {
472
- this._cachedBody = await this.readRawBody();
486
+ this._cachedBody = await this.request.text();
473
487
  this._bodyType = "text";
474
488
  }
475
489
  this._bodyParsed = true;
@@ -647,10 +661,6 @@ class ShokupanContext {
647
661
  return this._finalResponse;
648
662
  }
649
663
  }
650
- /**
651
- * JSX Rendering Function
652
- */
653
- renderer;
654
664
  /**
655
665
  * Render a JSX element
656
666
  * @param element JSX Element
@@ -669,221 +679,6 @@ class ShokupanContext {
669
679
  return this.html(html, status, headers);
670
680
  }
671
681
  }
672
- function RateLimitMiddleware(options = {}) {
673
- const windowMs = options.windowMs || 60 * 1e3;
674
- const max = options.limit || options.max || 5;
675
- const message = options.message || "Too many requests, please try again later.";
676
- const statusCode = options.statusCode || 429;
677
- const headers = options.headers !== false;
678
- const mode = options.mode || "user";
679
- const trustedProxies = options.trustedProxies || [];
680
- const keyGenerator = options.keyGenerator || ((ctx) => {
681
- if (mode === "absolute") {
682
- return "global";
683
- }
684
- const xForwardedFor = ctx.headers.get("x-forwarded-for");
685
- if (xForwardedFor && trustedProxies.length > 0) {
686
- const ips = xForwardedFor.split(",").map((ip) => ip.trim());
687
- for (let i = ips.length - 1; i >= 0; i--) {
688
- const ip = ips[i];
689
- if (!trustedProxies.includes(ip)) {
690
- if (/^[\d.:a-fA-F]+$/.test(ip)) {
691
- return ip;
692
- }
693
- }
694
- }
695
- }
696
- return ctx.server?.requestIP?.(ctx.request)?.address || "unknown";
697
- });
698
- const skip = options.skip || (() => false);
699
- const hits = /* @__PURE__ */ new Map();
700
- const interval = setInterval(() => {
701
- const now = Date.now();
702
- const entries = Array.from(hits.entries());
703
- for (let i = 0; i < entries.length; i++) {
704
- const [key, record] = entries[i];
705
- if (record.resetTime <= now) {
706
- hits.delete(key);
707
- }
708
- }
709
- }, windowMs);
710
- if (interval.unref) interval.unref();
711
- const rateLimitMiddleware = async function RateLimitMiddleware2(ctx, next) {
712
- if (skip(ctx)) return next();
713
- const key = keyGenerator(ctx);
714
- const now = Date.now();
715
- let record = hits.get(key);
716
- if (!record || record.resetTime <= now) {
717
- record = {
718
- hits: 0,
719
- resetTime: now + windowMs
720
- };
721
- hits.set(key, record);
722
- }
723
- record.hits++;
724
- const remaining = Math.max(0, max - record.hits);
725
- const resetTime = Math.ceil(record.resetTime / 1e3);
726
- const retryAfter = Math.ceil((record.resetTime - now) / 1e3);
727
- const setHeaders = (res) => {
728
- if (!headers || !res || !res.headers) return;
729
- try {
730
- res.headers.set("X-RateLimit-Limit", String(max));
731
- res.headers.set("X-RateLimit-Remaining", String(remaining));
732
- res.headers.set("X-RateLimit-Reset", String(resetTime));
733
- } catch (e) {
734
- }
735
- };
736
- if (record.hits > max) {
737
- typeof message === "object" ? JSON.stringify(message) : String(message);
738
- const res = typeof message === "object" ? ctx.json(message, statusCode) : ctx.text(String(message), statusCode);
739
- if (headers) {
740
- setHeaders(res);
741
- res.headers.set("Retry-After", String(retryAfter));
742
- }
743
- return res;
744
- }
745
- const response = await next();
746
- if (response instanceof Response && headers) {
747
- setHeaders(response);
748
- }
749
- return response;
750
- };
751
- rateLimitMiddleware.isBuiltin = true;
752
- rateLimitMiddleware.pluginName = "RateLimit";
753
- return rateLimitMiddleware;
754
- }
755
- const $isApplication = /* @__PURE__ */ Symbol.for("Shokupan.app");
756
- const $appRoot = /* @__PURE__ */ Symbol.for("Shokupan.app-root");
757
- const $isMounted = /* @__PURE__ */ Symbol("Shokupan.isMounted");
758
- const $routeMethods = /* @__PURE__ */ Symbol("Shokupan.routeMethods");
759
- const $routeArgs = /* @__PURE__ */ Symbol("Shokupan.routeArgs");
760
- const $controllerPath = /* @__PURE__ */ Symbol("Shokupan.controllerPath");
761
- const $middleware = /* @__PURE__ */ Symbol("Shokupan.middleware");
762
- const $isRouter = /* @__PURE__ */ Symbol.for("Shokupan.router");
763
- const $parent = /* @__PURE__ */ Symbol.for("Shokupan.parent");
764
- const $childRouters = /* @__PURE__ */ Symbol.for("Shokupan.child-routers");
765
- const $childControllers = /* @__PURE__ */ Symbol.for("Shokupan.child-controllers");
766
- const $mountPath = /* @__PURE__ */ Symbol.for("Shokupan.mount-path");
767
- const $dispatch = /* @__PURE__ */ Symbol.for("Shokupan.dispatch");
768
- const $routes = /* @__PURE__ */ Symbol.for("Shokupan.routes");
769
- const $routeSpec = /* @__PURE__ */ Symbol.for("Shokupan.routeSpec");
770
- const HTTPMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "ALL"];
771
- var RouteParamType = /* @__PURE__ */ ((RouteParamType2) => {
772
- RouteParamType2["BODY"] = "BODY";
773
- RouteParamType2["PARAM"] = "PARAM";
774
- RouteParamType2["QUERY"] = "QUERY";
775
- RouteParamType2["HEADER"] = "HEADER";
776
- RouteParamType2["REQUEST"] = "REQUEST";
777
- RouteParamType2["CONTEXT"] = "CONTEXT";
778
- return RouteParamType2;
779
- })(RouteParamType || {});
780
- function Controller(path2 = "/") {
781
- return (target) => {
782
- target[$controllerPath] = path2;
783
- };
784
- }
785
- function Use(...middleware) {
786
- return (target, propertyKey, descriptor) => {
787
- if (!propertyKey) {
788
- const existing = target[$middleware] || [];
789
- target[$middleware] = [...existing, ...middleware];
790
- } else {
791
- if (!target[$middleware]) {
792
- target[$middleware] = /* @__PURE__ */ new Map();
793
- }
794
- const existing = target[$middleware].get(propertyKey) || [];
795
- target[$middleware].set(propertyKey, [...existing, ...middleware]);
796
- }
797
- };
798
- }
799
- function createParamDecorator(type) {
800
- return (name) => {
801
- return (target, propertyKey, parameterIndex) => {
802
- if (!target[$routeArgs]) {
803
- target[$routeArgs] = /* @__PURE__ */ new Map();
804
- }
805
- if (!target[$routeArgs].has(propertyKey)) {
806
- target[$routeArgs].set(propertyKey, []);
807
- }
808
- target[$routeArgs].get(propertyKey).push({
809
- index: parameterIndex,
810
- type,
811
- name
812
- });
813
- };
814
- };
815
- }
816
- const Body = createParamDecorator(RouteParamType.BODY);
817
- const Param = createParamDecorator(RouteParamType.PARAM);
818
- const Query = createParamDecorator(RouteParamType.QUERY);
819
- const Headers$1 = createParamDecorator(RouteParamType.HEADER);
820
- const Req = createParamDecorator(RouteParamType.REQUEST);
821
- const Ctx = createParamDecorator(RouteParamType.CONTEXT);
822
- function Spec(spec) {
823
- return (target, propertyKey, descriptor) => {
824
- if (!target[$routeSpec]) {
825
- target[$routeSpec] = /* @__PURE__ */ new Map();
826
- }
827
- target[$routeSpec].set(propertyKey, spec);
828
- };
829
- }
830
- function createMethodDecorator(method) {
831
- return (path2 = "/") => {
832
- return (target, propertyKey, descriptor) => {
833
- if (!target[$routeMethods]) {
834
- target[$routeMethods] = /* @__PURE__ */ new Map();
835
- }
836
- target[$routeMethods].set(propertyKey, {
837
- method,
838
- path: path2
839
- });
840
- };
841
- };
842
- }
843
- const Get = createMethodDecorator("GET");
844
- const Post = createMethodDecorator("POST");
845
- const Put = createMethodDecorator("PUT");
846
- const Delete = createMethodDecorator("DELETE");
847
- const Patch = createMethodDecorator("PATCH");
848
- const Options = createMethodDecorator("OPTIONS");
849
- const Head = createMethodDecorator("HEAD");
850
- const All = createMethodDecorator("ALL");
851
- function RateLimit(options) {
852
- return Use(RateLimitMiddleware(options));
853
- }
854
- class Container {
855
- static services = /* @__PURE__ */ new Map();
856
- static register(target, instance) {
857
- this.services.set(target, instance);
858
- }
859
- static get(target) {
860
- return this.services.get(target);
861
- }
862
- static has(target) {
863
- return this.services.has(target);
864
- }
865
- static resolve(target) {
866
- if (this.services.has(target)) {
867
- return this.services.get(target);
868
- }
869
- const instance = new target();
870
- this.services.set(target, instance);
871
- return instance;
872
- }
873
- }
874
- function Injectable() {
875
- return (target) => {
876
- };
877
- }
878
- function Inject(token) {
879
- return (target, key) => {
880
- Object.defineProperty(target, key, {
881
- get: () => Container.resolve(token),
882
- enumerable: true,
883
- configurable: true
884
- });
885
- };
886
- }
887
682
  const compose = (middleware) => {
888
683
  if (!middleware.length) {
889
684
  return (context, next) => {
@@ -922,31 +717,29 @@ const compose = (middleware) => {
922
717
  return runner(0);
923
718
  };
924
719
  };
925
- class ShokupanRequestBase {
926
- method;
927
- url;
928
- headers;
929
- body;
930
- async json() {
931
- return JSON.parse(this.body);
932
- }
933
- async text() {
934
- return this.body;
935
- }
936
- async formData() {
937
- if (this.body instanceof FormData) {
938
- return this.body;
939
- }
940
- return new Response(this.body, { headers: this.headers }).formData();
941
- }
942
- constructor(props) {
943
- Object.assign(this, props);
944
- if (!(this.headers instanceof Headers)) {
945
- this.headers = new Headers(this.headers);
946
- }
947
- }
720
+ const tracer = api.trace.getTracer("shokupan.middleware");
721
+ function traceHandler(fn, name) {
722
+ return async function(...args) {
723
+ return tracer.startActiveSpan(`route handler - ${name}`, {
724
+ kind: api.SpanKind.INTERNAL,
725
+ attributes: {
726
+ "http.route": name,
727
+ "component": "shokupan.route"
728
+ }
729
+ }, async (span) => {
730
+ try {
731
+ const result = await fn.apply(this, args);
732
+ return result;
733
+ } catch (err) {
734
+ span.recordException(err);
735
+ span.setStatus({ code: api.SpanStatusCode.ERROR, message: err.message });
736
+ throw err;
737
+ } finally {
738
+ span.end();
739
+ }
740
+ });
741
+ };
948
742
  }
949
- const ShokupanRequest = ShokupanRequestBase;
950
743
  function isObject(item) {
951
744
  return item && typeof item === "object" && !Array.isArray(item);
952
745
  }
@@ -980,6 +773,21 @@ function deepMerge(target, ...sources) {
980
773
  }
981
774
  return deepMerge(target, ...sources);
982
775
  }
776
+ const $isApplication = /* @__PURE__ */ Symbol.for("Shokupan.app");
777
+ const $appRoot = /* @__PURE__ */ Symbol.for("Shokupan.app-root");
778
+ const $isMounted = /* @__PURE__ */ Symbol("Shokupan.isMounted");
779
+ const $routeMethods = /* @__PURE__ */ Symbol("Shokupan.routeMethods");
780
+ const $routeArgs = /* @__PURE__ */ Symbol("Shokupan.routeArgs");
781
+ const $controllerPath = /* @__PURE__ */ Symbol("Shokupan.controllerPath");
782
+ const $middleware = /* @__PURE__ */ Symbol("Shokupan.middleware");
783
+ const $isRouter = /* @__PURE__ */ Symbol.for("Shokupan.router");
784
+ const $parent = /* @__PURE__ */ Symbol.for("Shokupan.parent");
785
+ const $childRouters = /* @__PURE__ */ Symbol.for("Shokupan.child-routers");
786
+ const $childControllers = /* @__PURE__ */ Symbol.for("Shokupan.child-controllers");
787
+ const $mountPath = /* @__PURE__ */ Symbol.for("Shokupan.mount-path");
788
+ const $dispatch = /* @__PURE__ */ Symbol.for("Shokupan.dispatch");
789
+ const $routes = /* @__PURE__ */ Symbol.for("Shokupan.routes");
790
+ const $routeSpec = /* @__PURE__ */ Symbol.for("Shokupan.routeSpec");
983
791
  const REGEX_PATTERNS = {
984
792
  QUERY_INT: /parseInt\(ctx\.query\.(\w+)\)/g,
985
793
  QUERY_FLOAT: /parseFloat\(ctx\.query\.(\w+)\)/g,
@@ -1172,9 +980,9 @@ async function generateOpenApi(rootRouter, options = {}) {
1172
980
  const defaultTagName = options.defaultTag || "Application";
1173
981
  let astRoutes = [];
1174
982
  try {
1175
- const { OpenAPIAnalyzer } = await Promise.resolve().then(() => require("./openapi-analyzer-Bei1sVWp.cjs"));
1176
- const analyzer = new OpenAPIAnalyzer(process.cwd());
1177
- const { applications } = await analyzer.analyze();
983
+ const { OpenAPIAnalyzer } = await Promise.resolve().then(() => require("./analyzer-Bei1sVWp.cjs"));
984
+ const analyzer2 = new OpenAPIAnalyzer(process.cwd());
985
+ const { applications } = await analyzer2.analyze();
1178
986
  astRoutes = await getAstRoutes(applications);
1179
987
  } catch (e) {
1180
988
  }
@@ -1361,6 +1169,11 @@ async function generateOpenApi(rootRouter, options = {}) {
1361
1169
  "x-tagGroups": xTagGroups
1362
1170
  };
1363
1171
  }
1172
+ class RequestContextStore {
1173
+ request;
1174
+ span;
1175
+ }
1176
+ const asyncContext = new node_async_hooks.AsyncLocalStorage();
1364
1177
  const eta$1 = new eta$2.Eta();
1365
1178
  function serveStatic(config, prefix) {
1366
1179
  const rootPath = path.resolve(config.root || ".");
@@ -1513,107 +1326,6 @@ function serveStatic(config, prefix) {
1513
1326
  serveStaticMiddleware.pluginName = "ServeStatic";
1514
1327
  return serveStaticMiddleware;
1515
1328
  }
1516
- class RouterTrie {
1517
- root;
1518
- constructor() {
1519
- this.root = this.createNode();
1520
- }
1521
- createNode() {
1522
- return {
1523
- children: {}
1524
- };
1525
- }
1526
- insert(method, path2, handler) {
1527
- let node = this.root;
1528
- const segments = this.splitPath(path2);
1529
- for (let i = 0; i < segments.length; i++) {
1530
- const segment = segments[i];
1531
- if (segment === "**") {
1532
- if (!node.recursiveChild) {
1533
- node.recursiveChild = this.createNode();
1534
- }
1535
- node = node.recursiveChild;
1536
- } else if (segment === "*") {
1537
- if (!node.wildcardChild) {
1538
- node.wildcardChild = this.createNode();
1539
- }
1540
- node = node.wildcardChild;
1541
- } else if (segment.startsWith(":")) {
1542
- const paramName = segment.slice(1);
1543
- if (!node.paramChild) {
1544
- node.paramChild = this.createNode();
1545
- node.paramChild.paramName = paramName;
1546
- }
1547
- node = node.paramChild;
1548
- node.paramName = paramName;
1549
- } else {
1550
- if (!node.children[segment]) {
1551
- node.children[segment] = this.createNode();
1552
- }
1553
- node = node.children[segment];
1554
- }
1555
- }
1556
- if (!node.handlers) {
1557
- node.handlers = {};
1558
- }
1559
- node.handlers[method] = handler;
1560
- }
1561
- search(method, path2) {
1562
- const segments = this.splitPath(path2);
1563
- const params = {};
1564
- const match = this.findNode(this.root, segments, 0, params);
1565
- if (match && match.handlers) {
1566
- const handler = match.handlers[method] || match.handlers["ALL"];
1567
- if (handler) {
1568
- return { handler, params };
1569
- }
1570
- if (method === "HEAD" && match.handlers["GET"]) {
1571
- return { handler: match.handlers["GET"], params };
1572
- }
1573
- }
1574
- return null;
1575
- }
1576
- findNode(node, segments, index, params) {
1577
- if (index === segments.length) {
1578
- if (node.handlers) return node;
1579
- if (node.recursiveChild && node.recursiveChild.handlers) {
1580
- return node.recursiveChild;
1581
- }
1582
- return null;
1583
- }
1584
- const segment = segments[index];
1585
- const child = node.children[segment];
1586
- if (child) {
1587
- const result = this.findNode(child, segments, index + 1, params);
1588
- if (result) return result;
1589
- }
1590
- if (node.paramChild) {
1591
- params[node.paramChild.paramName] = segment;
1592
- const result = this.findNode(node.paramChild, segments, index + 1, params);
1593
- if (result) return result;
1594
- delete params[node.paramChild.paramName];
1595
- }
1596
- if (node.wildcardChild) {
1597
- const result = this.findNode(node.wildcardChild, segments, index + 1, params);
1598
- if (result) return result;
1599
- }
1600
- if (node.recursiveChild) {
1601
- const remaining = segments.length - index;
1602
- for (let k = 0; k <= remaining; k++) {
1603
- const result = this.findNode(node.recursiveChild, segments, index + k, params);
1604
- if (result) return result;
1605
- }
1606
- }
1607
- return null;
1608
- }
1609
- splitPath(path2) {
1610
- if (path2 === "/" || path2 === "") return [];
1611
- const s = path2.startsWith("/") ? path2.slice(1) : path2;
1612
- if (s === "") return [];
1613
- return s.split("/");
1614
- }
1615
- }
1616
- const asyncContext = new node_async_hooks.AsyncLocalStorage();
1617
1329
  let db;
1618
1330
  let dbPromise = null;
1619
1331
  let RecordId;
@@ -1677,29 +1389,64 @@ const datastore = {
1677
1389
  process.on("exit", async () => {
1678
1390
  if (db) await db.close();
1679
1391
  });
1680
- const tracer = api.trace.getTracer("shokupan.middleware");
1681
- function traceHandler(fn, name) {
1682
- return async function(...args) {
1683
- return tracer.startActiveSpan(`route handler - ${name}`, {
1684
- kind: api.SpanKind.INTERNAL,
1685
- attributes: {
1686
- "http.route": name,
1687
- "component": "shokupan.route"
1688
- }
1689
- }, async (span) => {
1690
- try {
1691
- const result = await fn.apply(this, args);
1692
- return result;
1693
- } catch (err) {
1694
- span.recordException(err);
1695
- span.setStatus({ code: api.SpanStatusCode.ERROR, message: err.message });
1696
- throw err;
1697
- } finally {
1698
- span.end();
1699
- }
1392
+ class Container {
1393
+ static services = /* @__PURE__ */ new Map();
1394
+ static register(target, instance) {
1395
+ this.services.set(target, instance);
1396
+ }
1397
+ static get(target) {
1398
+ return this.services.get(target);
1399
+ }
1400
+ static has(target) {
1401
+ return this.services.has(target);
1402
+ }
1403
+ static resolve(target) {
1404
+ if (this.services.has(target)) {
1405
+ return this.services.get(target);
1406
+ }
1407
+ const instance = new target();
1408
+ this.services.set(target, instance);
1409
+ return instance;
1410
+ }
1411
+ }
1412
+ function Injectable() {
1413
+ return (target) => {
1414
+ };
1415
+ }
1416
+ function Inject(token) {
1417
+ return (target, key) => {
1418
+ Object.defineProperty(target, key, {
1419
+ get: () => Container.resolve(token),
1420
+ enumerable: true,
1421
+ configurable: true
1700
1422
  });
1701
1423
  };
1702
1424
  }
1425
+ class ShokupanRequestBase {
1426
+ method;
1427
+ url;
1428
+ headers;
1429
+ body;
1430
+ async json() {
1431
+ return JSON.parse(this.body);
1432
+ }
1433
+ async text() {
1434
+ return this.body;
1435
+ }
1436
+ async formData() {
1437
+ if (this.body instanceof FormData) {
1438
+ return this.body;
1439
+ }
1440
+ return new Response(this.body, { headers: this.headers }).formData();
1441
+ }
1442
+ constructor(props) {
1443
+ Object.assign(this, props);
1444
+ if (!(this.headers instanceof Headers)) {
1445
+ this.headers = new Headers(this.headers);
1446
+ }
1447
+ }
1448
+ }
1449
+ const ShokupanRequest = ShokupanRequestBase;
1703
1450
  function getCallerInfo(skipFrames = 1) {
1704
1451
  let file = "unknown";
1705
1452
  let line = 0;
@@ -1725,12 +1472,120 @@ function getCallerInfo(skipFrames = 1) {
1725
1472
  }
1726
1473
  }
1727
1474
  }
1728
- } catch (e) {
1475
+ } catch (e) {
1476
+ }
1477
+ return { file, line };
1478
+ }
1479
+ class RouterTrie {
1480
+ root;
1481
+ constructor() {
1482
+ this.root = this.createNode();
1483
+ }
1484
+ createNode() {
1485
+ return {
1486
+ children: {}
1487
+ };
1488
+ }
1489
+ insert(method, path2, handler) {
1490
+ let node = this.root;
1491
+ const segments = this.splitPath(path2);
1492
+ for (let i = 0; i < segments.length; i++) {
1493
+ const segment = segments[i];
1494
+ if (segment === "**") {
1495
+ if (!node.recursiveChild) {
1496
+ node.recursiveChild = this.createNode();
1497
+ }
1498
+ node = node.recursiveChild;
1499
+ } else if (segment === "*") {
1500
+ if (!node.wildcardChild) {
1501
+ node.wildcardChild = this.createNode();
1502
+ }
1503
+ node = node.wildcardChild;
1504
+ } else if (segment.startsWith(":")) {
1505
+ const paramName = segment.slice(1);
1506
+ if (!node.paramChild) {
1507
+ node.paramChild = this.createNode();
1508
+ node.paramChild.paramName = paramName;
1509
+ }
1510
+ node = node.paramChild;
1511
+ node.paramName = paramName;
1512
+ } else {
1513
+ if (!node.children[segment]) {
1514
+ node.children[segment] = this.createNode();
1515
+ }
1516
+ node = node.children[segment];
1517
+ }
1518
+ }
1519
+ if (!node.handlers) {
1520
+ node.handlers = {};
1521
+ }
1522
+ node.handlers[method] = handler;
1523
+ }
1524
+ search(method, path2) {
1525
+ const segments = this.splitPath(path2);
1526
+ const params = {};
1527
+ const match = this.findNode(this.root, segments, 0, params);
1528
+ if (match && match.handlers) {
1529
+ const handler = match.handlers[method] || match.handlers["ALL"];
1530
+ if (handler) {
1531
+ return { handler, params };
1532
+ }
1533
+ if (method === "HEAD" && match.handlers["GET"]) {
1534
+ return { handler: match.handlers["GET"], params };
1535
+ }
1536
+ }
1537
+ return null;
1538
+ }
1539
+ findNode(node, segments, index, params) {
1540
+ if (index === segments.length) {
1541
+ if (node.handlers) return node;
1542
+ if (node.recursiveChild && node.recursiveChild.handlers) {
1543
+ return node.recursiveChild;
1544
+ }
1545
+ return null;
1546
+ }
1547
+ const segment = segments[index];
1548
+ const child = node.children[segment];
1549
+ if (child) {
1550
+ const result = this.findNode(child, segments, index + 1, params);
1551
+ if (result) return result;
1552
+ }
1553
+ if (node.paramChild) {
1554
+ params[node.paramChild.paramName] = segment;
1555
+ const result = this.findNode(node.paramChild, segments, index + 1, params);
1556
+ if (result) return result;
1557
+ delete params[node.paramChild.paramName];
1558
+ }
1559
+ if (node.wildcardChild) {
1560
+ const result = this.findNode(node.wildcardChild, segments, index + 1, params);
1561
+ if (result) return result;
1562
+ }
1563
+ if (node.recursiveChild) {
1564
+ const remaining = segments.length - index;
1565
+ for (let k = 0; k <= remaining; k++) {
1566
+ const result = this.findNode(node.recursiveChild, segments, index + k, params);
1567
+ if (result) return result;
1568
+ }
1569
+ }
1570
+ return null;
1571
+ }
1572
+ splitPath(path2) {
1573
+ if (path2 === "/" || path2 === "") return [];
1574
+ const s = path2.startsWith("/") ? path2.slice(1) : path2;
1575
+ if (s === "") return [];
1576
+ return s.split("/");
1729
1577
  }
1730
- return { file, line };
1731
1578
  }
1732
- const RouterRegistry = /* @__PURE__ */ new Map();
1733
- const ShokupanApplicationTree = {};
1579
+ const HTTPMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "ALL"];
1580
+ var RouteParamType = /* @__PURE__ */ ((RouteParamType2) => {
1581
+ RouteParamType2["BODY"] = "BODY";
1582
+ RouteParamType2["PARAM"] = "PARAM";
1583
+ RouteParamType2["QUERY"] = "QUERY";
1584
+ RouteParamType2["HEADER"] = "HEADER";
1585
+ RouteParamType2["REQUEST"] = "REQUEST";
1586
+ RouteParamType2["CONTEXT"] = "CONTEXT";
1587
+ return RouteParamType2;
1588
+ })(RouteParamType || {});
1734
1589
  class ShokupanRouter {
1735
1590
  constructor(config) {
1736
1591
  this.config = config;
@@ -1841,216 +1696,9 @@ class ShokupanRouter {
1841
1696
  throw new Error(`[Shokupan] strict controller check failed: ${controller.constructor.name || typeof controller} is not a class constructor.`);
1842
1697
  }
1843
1698
  if (this.isRouterInstance(controller)) {
1844
- if (controller[$isMounted]) {
1845
- throw new Error("Router is already mounted");
1846
- }
1847
- controller[$mountPath] = prefix;
1848
- if (!controller.metadata) {
1849
- const info = getCallerInfo();
1850
- controller.metadata = {
1851
- file: info.file,
1852
- line: info.line,
1853
- name: "MountedRouter"
1854
- };
1855
- }
1856
- this[$childRouters].push(controller);
1857
- controller[$parent] = this;
1858
- const setRouterContext = (router) => {
1859
- router[$appRoot] = this.root;
1860
- router[$childRouters].forEach((child) => setRouterContext(child));
1861
- };
1862
- setRouterContext(controller);
1863
- if (this[$appRoot]) ;
1864
- controller[$appRoot] = this.root;
1865
- controller[$isMounted] = true;
1699
+ this.mountRouter(prefix, controller);
1866
1700
  } else {
1867
- let instance = controller;
1868
- if (typeof controller === "function") {
1869
- instance = Container.resolve(controller);
1870
- const controllerPath = controller[$controllerPath];
1871
- if (controllerPath) {
1872
- const p1 = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
1873
- const p2 = controllerPath.startsWith("/") ? controllerPath : "/" + controllerPath;
1874
- prefix = p1 + p2;
1875
- if (!prefix) prefix = "/";
1876
- }
1877
- } else {
1878
- const ctor = instance.constructor;
1879
- const controllerPath = ctor[$controllerPath];
1880
- if (controllerPath) {
1881
- const p1 = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
1882
- const p2 = controllerPath.startsWith("/") ? controllerPath : "/" + controllerPath;
1883
- prefix = p1 + p2;
1884
- if (!prefix) prefix = "/";
1885
- }
1886
- }
1887
- instance[$mountPath] = prefix;
1888
- const info = getCallerInfo();
1889
- instance.metadata = {
1890
- file: info.file,
1891
- line: info.line,
1892
- name: instance.constructor.name
1893
- };
1894
- this[$childControllers].push(instance);
1895
- const controllerMiddleware = (typeof controller === "function" ? controller[$middleware] : instance[$middleware]) || [];
1896
- const proto = Object.getPrototypeOf(instance);
1897
- const methods = /* @__PURE__ */ new Set();
1898
- let current = proto;
1899
- while (current && current !== Object.prototype) {
1900
- Object.getOwnPropertyNames(current).forEach((name) => methods.add(name));
1901
- current = Object.getPrototypeOf(current);
1902
- }
1903
- Object.getOwnPropertyNames(instance).forEach((name) => methods.add(name));
1904
- const decoratedRoutes = instance[$routeMethods] || proto && proto[$routeMethods];
1905
- const decoratedArgs = instance[$routeArgs] || proto && proto[$routeArgs];
1906
- const methodMiddlewareMap = instance[$middleware] || proto && proto[$middleware];
1907
- let routesAttached = 0;
1908
- for (let i = 0; i < Array.from(methods).length; i++) {
1909
- const name = Array.from(methods)[i];
1910
- if (name === "constructor") continue;
1911
- if (["arguments", "caller", "callee"].includes(name)) continue;
1912
- const originalHandler = instance[name];
1913
- if (typeof originalHandler !== "function") continue;
1914
- let method;
1915
- let subPath = "";
1916
- if (decoratedRoutes && decoratedRoutes.has(name)) {
1917
- const config = decoratedRoutes.get(name);
1918
- method = config.method;
1919
- subPath = config.path;
1920
- } else {
1921
- for (let j = 0; j < HTTPMethods.length; j++) {
1922
- const m = HTTPMethods[j];
1923
- if (name.toUpperCase().startsWith(m)) {
1924
- method = m;
1925
- const rest = name.slice(m.length);
1926
- if (rest.length === 0) {
1927
- subPath = "/";
1928
- } else {
1929
- subPath = "";
1930
- let buffer = "";
1931
- const flush = () => {
1932
- if (buffer.length > 0) {
1933
- subPath += "/" + buffer.toLowerCase();
1934
- buffer = "";
1935
- }
1936
- };
1937
- for (let i2 = 0; i2 < rest.length; i2++) {
1938
- const char = rest[i2];
1939
- if (char === "$") {
1940
- flush();
1941
- subPath += "/:";
1942
- continue;
1943
- }
1944
- }
1945
- subPath = rest.replace(/\$/g, "/:").replace(/([a-z0-9])([A-Z])/g, "$1/$2").toLowerCase();
1946
- if (!subPath.startsWith("/")) {
1947
- subPath = "/" + subPath;
1948
- }
1949
- }
1950
- break;
1951
- }
1952
- }
1953
- }
1954
- if (method) {
1955
- routesAttached++;
1956
- const cleanPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
1957
- const cleanSubPath = subPath === "/" ? "" : subPath;
1958
- let joined;
1959
- if (cleanSubPath.length === 0) {
1960
- joined = cleanPrefix;
1961
- } else if (cleanSubPath.startsWith("/")) {
1962
- joined = cleanPrefix + cleanSubPath;
1963
- } else {
1964
- joined = cleanPrefix + "/" + cleanSubPath;
1965
- }
1966
- const fullPath = joined || "/";
1967
- const normalizedPath = fullPath.replace(/\/+/g, "/");
1968
- const methodMw = methodMiddlewareMap instanceof Map ? methodMiddlewareMap.get(name) || [] : [];
1969
- const allMiddleware = [...controllerMiddleware, ...methodMw];
1970
- const routeArgs = decoratedArgs && decoratedArgs.get(name);
1971
- const wrappedHandler = async (ctx) => {
1972
- let args = [ctx];
1973
- if (routeArgs?.length > 0) {
1974
- args = [];
1975
- const sortedArgs = [...routeArgs].sort((a, b) => a.index - b.index);
1976
- for (let k = 0; k < sortedArgs.length; k++) {
1977
- const arg = sortedArgs[k];
1978
- switch (arg.type) {
1979
- case RouteParamType.BODY:
1980
- try {
1981
- if (ctx.req.headers.get("content-type")?.includes("application/json")) {
1982
- args[arg.index] = await ctx.req.json();
1983
- } else {
1984
- const text = await ctx.req.text();
1985
- if (!text) {
1986
- args[arg.index] = {};
1987
- } else {
1988
- args[arg.index] = JSON.parse(text);
1989
- }
1990
- }
1991
- } catch (e) {
1992
- const err = new Error("Invalid JSON body");
1993
- err.status = 400;
1994
- throw err;
1995
- }
1996
- break;
1997
- case RouteParamType.PARAM:
1998
- args[arg.index] = arg.name ? ctx.params[arg.name] : ctx.params;
1999
- break;
2000
- case RouteParamType.QUERY: {
2001
- const url = new URL(ctx.req.url);
2002
- if (arg.name) {
2003
- const vals = url.searchParams.getAll(arg.name);
2004
- args[arg.index] = vals.length > 1 ? vals : vals[0];
2005
- } else {
2006
- const query = {};
2007
- const keys = Object.keys(url.searchParams);
2008
- for (let k2 = 0; k2 < keys.length; k2++) {
2009
- const key = keys[k2];
2010
- const vals = url.searchParams.getAll(key);
2011
- query[key] = vals.length > 1 ? vals : vals[0];
2012
- }
2013
- args[arg.index] = query;
2014
- }
2015
- break;
2016
- }
2017
- case RouteParamType.HEADER:
2018
- args[arg.index] = arg.name ? ctx.req.headers.get(arg.name) : ctx.req.headers;
2019
- break;
2020
- case RouteParamType.REQUEST:
2021
- args[arg.index] = ctx.req;
2022
- break;
2023
- case RouteParamType.CONTEXT:
2024
- args[arg.index] = ctx;
2025
- break;
2026
- }
2027
- }
2028
- }
2029
- const tracedOriginalHandler = ctx.app?.applicationConfig.enableTracing ? traceHandler(originalHandler, normalizedPath) : originalHandler;
2030
- return tracedOriginalHandler.apply(instance, args);
2031
- };
2032
- let finalHandler = wrappedHandler;
2033
- if (allMiddleware.length > 0) {
2034
- const composed = compose(allMiddleware);
2035
- finalHandler = async (ctx) => {
2036
- return composed(ctx, () => wrappedHandler(ctx));
2037
- };
2038
- }
2039
- finalHandler.originalHandler = originalHandler;
2040
- if (finalHandler !== wrappedHandler) {
2041
- wrappedHandler.originalHandler = originalHandler;
2042
- }
2043
- const tagName = instance.constructor.name;
2044
- const decoratedSpecs = instance[$routeSpec] || proto && proto[$routeSpec];
2045
- const userSpec = decoratedSpecs && decoratedSpecs.get(name);
2046
- const spec = { tags: [tagName], ...userSpec };
2047
- this.add({ method, path: normalizedPath, handler: finalHandler, spec, controller: instance });
2048
- }
2049
- }
2050
- if (routesAttached === 0) {
2051
- console.warn(`No routes attached to controller ${instance.constructor.name}`);
2052
- }
2053
- instance[$isMounted] = true;
1701
+ this.scanControllerRoutes(prefix, controller);
2054
1702
  }
2055
1703
  return this;
2056
1704
  }
@@ -2088,8 +1736,6 @@ class ShokupanRouter {
2088
1736
  */
2089
1737
  async internalRequest(arg) {
2090
1738
  const options = typeof arg === "string" ? { path: arg } : arg;
2091
- const store = asyncContext.getStore();
2092
- store?.get("req");
2093
1739
  let url = options.path;
2094
1740
  if (!url.startsWith("http")) {
2095
1741
  const base = `http://${this.rootConfig?.hostname || "localhost"}:${this.rootConfig.port || 3e3}`;
@@ -2201,6 +1847,220 @@ class ShokupanRouter {
2201
1847
  wrapped.originalHandler = originalHandler.originalHandler ?? originalHandler;
2202
1848
  return wrapped;
2203
1849
  }
1850
+ mountRouter(prefix, router) {
1851
+ if (router[$isMounted]) {
1852
+ throw new Error("Router is already mounted");
1853
+ }
1854
+ router[$mountPath] = prefix;
1855
+ if (!router.metadata) {
1856
+ const info = getCallerInfo();
1857
+ router.metadata = {
1858
+ file: info.file,
1859
+ line: info.line,
1860
+ name: "MountedRouter"
1861
+ };
1862
+ }
1863
+ this[$childRouters].push(router);
1864
+ router[$parent] = this;
1865
+ const setRouterContext = (router2) => {
1866
+ router2[$appRoot] = this.root;
1867
+ router2[$childRouters].forEach((child) => setRouterContext(child));
1868
+ };
1869
+ setRouterContext(router);
1870
+ router[$appRoot] = this.root;
1871
+ router[$isMounted] = true;
1872
+ }
1873
+ scanControllerRoutes(prefix, controller) {
1874
+ let instance = controller;
1875
+ if (typeof controller === "function") {
1876
+ instance = Container.resolve(controller);
1877
+ const controllerPath = controller[$controllerPath];
1878
+ if (controllerPath) {
1879
+ const p1 = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
1880
+ const p2 = controllerPath.startsWith("/") ? controllerPath : "/" + controllerPath;
1881
+ prefix = p1 + p2;
1882
+ if (!prefix) prefix = "/";
1883
+ }
1884
+ } else {
1885
+ const ctor = instance.constructor;
1886
+ const controllerPath = ctor[$controllerPath];
1887
+ if (controllerPath) {
1888
+ const p1 = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
1889
+ const p2 = controllerPath.startsWith("/") ? controllerPath : "/" + controllerPath;
1890
+ prefix = p1 + p2;
1891
+ if (!prefix) prefix = "/";
1892
+ }
1893
+ }
1894
+ instance[$mountPath] = prefix;
1895
+ const info = getCallerInfo();
1896
+ instance.metadata = {
1897
+ file: info.file,
1898
+ line: info.line,
1899
+ name: instance.constructor.name
1900
+ };
1901
+ this[$childControllers].push(instance);
1902
+ const controllerMiddleware = (typeof controller === "function" ? controller[$middleware] : instance[$middleware]) || [];
1903
+ const proto = Object.getPrototypeOf(instance);
1904
+ const methods = /* @__PURE__ */ new Set();
1905
+ let current = proto;
1906
+ while (current && current !== Object.prototype) {
1907
+ Object.getOwnPropertyNames(current).forEach((name) => methods.add(name));
1908
+ current = Object.getPrototypeOf(current);
1909
+ }
1910
+ Object.getOwnPropertyNames(instance).forEach((name) => methods.add(name));
1911
+ const decoratedRoutes = instance[$routeMethods] || proto && proto[$routeMethods];
1912
+ const decoratedArgs = instance[$routeArgs] || proto && proto[$routeArgs];
1913
+ const methodMiddlewareMap = instance[$middleware] || proto && proto[$middleware];
1914
+ let routesAttached = 0;
1915
+ for (let i = 0; i < Array.from(methods).length; i++) {
1916
+ const name = Array.from(methods)[i];
1917
+ if (name === "constructor") continue;
1918
+ if (["arguments", "caller", "callee"].includes(name)) continue;
1919
+ const originalHandler = instance[name];
1920
+ if (typeof originalHandler !== "function") continue;
1921
+ let method;
1922
+ let subPath = "";
1923
+ if (decoratedRoutes && decoratedRoutes.has(name)) {
1924
+ const config = decoratedRoutes.get(name);
1925
+ method = config.method;
1926
+ subPath = config.path;
1927
+ } else {
1928
+ for (let j = 0; j < HTTPMethods.length; j++) {
1929
+ const m = HTTPMethods[j];
1930
+ if (name.toUpperCase().startsWith(m)) {
1931
+ method = m;
1932
+ const rest = name.slice(m.length);
1933
+ if (rest.length === 0) {
1934
+ subPath = "/";
1935
+ } else {
1936
+ subPath = "";
1937
+ let buffer = "";
1938
+ const flush = () => {
1939
+ if (buffer.length > 0) {
1940
+ subPath += "/" + buffer.toLowerCase();
1941
+ buffer = "";
1942
+ }
1943
+ };
1944
+ for (let i2 = 0; i2 < rest.length; i2++) {
1945
+ const char = rest[i2];
1946
+ if (char === "$") {
1947
+ flush();
1948
+ subPath += "/:";
1949
+ continue;
1950
+ }
1951
+ buffer += char;
1952
+ }
1953
+ if (buffer.length > 0) flush();
1954
+ subPath = rest.replace(/\$/g, "/:").replace(/([a-z0-9])([A-Z])/g, "$1/$2").toLowerCase();
1955
+ if (!subPath.startsWith("/")) {
1956
+ subPath = "/" + subPath;
1957
+ }
1958
+ }
1959
+ break;
1960
+ }
1961
+ }
1962
+ }
1963
+ if (method) {
1964
+ routesAttached++;
1965
+ const cleanPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
1966
+ const cleanSubPath = subPath === "/" ? "" : subPath;
1967
+ let joined;
1968
+ if (cleanSubPath.length === 0) {
1969
+ joined = cleanPrefix;
1970
+ } else if (cleanSubPath.startsWith("/")) {
1971
+ joined = cleanPrefix + cleanSubPath;
1972
+ } else {
1973
+ joined = cleanPrefix + "/" + cleanSubPath;
1974
+ }
1975
+ const fullPath = joined || "/";
1976
+ const normalizedPath = fullPath.replace(/\/+/g, "/");
1977
+ const methodMw = methodMiddlewareMap instanceof Map ? methodMiddlewareMap.get(name) || [] : [];
1978
+ const allMiddleware = [...controllerMiddleware, ...methodMw];
1979
+ const routeArgs = decoratedArgs && decoratedArgs.get(name);
1980
+ const wrappedHandler = async (ctx) => {
1981
+ let args = [ctx];
1982
+ if (routeArgs?.length > 0) {
1983
+ args = [];
1984
+ const sortedArgs = [...routeArgs].sort((a, b) => a.index - b.index);
1985
+ for (let k = 0; k < sortedArgs.length; k++) {
1986
+ const arg = sortedArgs[k];
1987
+ switch (arg.type) {
1988
+ case RouteParamType.BODY:
1989
+ try {
1990
+ if (ctx.req.headers.get("content-type")?.includes("application/json")) {
1991
+ args[arg.index] = await ctx.req.json();
1992
+ } else {
1993
+ const text = await ctx.req.text();
1994
+ if (!text) {
1995
+ args[arg.index] = {};
1996
+ } else {
1997
+ args[arg.index] = JSON.parse(text);
1998
+ }
1999
+ }
2000
+ } catch (e) {
2001
+ const err = new Error("Invalid JSON body");
2002
+ err.status = 400;
2003
+ throw err;
2004
+ }
2005
+ break;
2006
+ case RouteParamType.PARAM:
2007
+ args[arg.index] = arg.name ? ctx.params[arg.name] : ctx.params;
2008
+ break;
2009
+ case RouteParamType.QUERY: {
2010
+ const url = new URL(ctx.req.url);
2011
+ if (arg.name) {
2012
+ const vals = url.searchParams.getAll(arg.name);
2013
+ args[arg.index] = vals.length > 1 ? vals : vals[0];
2014
+ } else {
2015
+ const query = {};
2016
+ const keys = Object.keys(url.searchParams);
2017
+ for (let k2 = 0; k2 < keys.length; k2++) {
2018
+ const key = keys[k2];
2019
+ const vals = url.searchParams.getAll(key);
2020
+ query[key] = vals.length > 1 ? vals : vals[0];
2021
+ }
2022
+ args[arg.index] = query;
2023
+ }
2024
+ break;
2025
+ }
2026
+ case RouteParamType.HEADER:
2027
+ args[arg.index] = arg.name ? ctx.req.headers.get(arg.name) : ctx.req.headers;
2028
+ break;
2029
+ case RouteParamType.REQUEST:
2030
+ args[arg.index] = ctx.req;
2031
+ break;
2032
+ case RouteParamType.CONTEXT:
2033
+ args[arg.index] = ctx;
2034
+ break;
2035
+ }
2036
+ }
2037
+ }
2038
+ const tracedOriginalHandler = ctx.app?.applicationConfig.enableTracing ? traceHandler(originalHandler, normalizedPath) : originalHandler;
2039
+ return tracedOriginalHandler.apply(instance, args);
2040
+ };
2041
+ let finalHandler = wrappedHandler;
2042
+ if (allMiddleware.length > 0) {
2043
+ const composed = compose(allMiddleware);
2044
+ finalHandler = async (ctx) => {
2045
+ return composed(ctx, () => wrappedHandler(ctx));
2046
+ };
2047
+ }
2048
+ finalHandler.originalHandler = originalHandler;
2049
+ if (finalHandler !== wrappedHandler) {
2050
+ wrappedHandler.originalHandler = originalHandler;
2051
+ }
2052
+ const tagName = instance.constructor.name;
2053
+ const decoratedSpecs = instance[$routeSpec] || proto && proto[$routeSpec];
2054
+ const userSpec = decoratedSpecs && decoratedSpecs.get(name);
2055
+ const spec = { tags: [tagName], ...userSpec };
2056
+ this.add({ method, path: normalizedPath, handler: finalHandler, spec, controller: instance });
2057
+ }
2058
+ }
2059
+ if (routesAttached === 0) {
2060
+ console.warn(`No routes attached to controller ${instance.constructor.name}`);
2061
+ }
2062
+ instance[$isMounted] = true;
2063
+ }
2204
2064
  /**
2205
2065
  * Find a route matching the given method and path.
2206
2066
  * @param method HTTP method
@@ -2327,7 +2187,7 @@ class ShokupanRouter {
2327
2187
  if (effectiveRenderer) {
2328
2188
  const innerHandler = wrappedHandler;
2329
2189
  wrappedHandler = async (ctx) => {
2330
- ctx.renderer = effectiveRenderer;
2190
+ ctx.setRenderer(effectiveRenderer);
2331
2191
  return innerHandler(ctx);
2332
2192
  };
2333
2193
  }
@@ -2729,6 +2589,13 @@ class Shokupan extends ShokupanRouter {
2729
2589
  }
2730
2590
  return this;
2731
2591
  }
2592
+ /**
2593
+ * Registers a plugin.
2594
+ */
2595
+ register(plugin, options) {
2596
+ plugin.onInit(this, options);
2597
+ return this;
2598
+ }
2732
2599
  startupHooks = [];
2733
2600
  /**
2734
2601
  * Registers a callback to be executed before the server starts listening.
@@ -2791,7 +2658,7 @@ class Shokupan extends ShokupanRouter {
2791
2658
  };
2792
2659
  let factory = this.applicationConfig.serverFactory;
2793
2660
  if (!factory && typeof Bun === "undefined") {
2794
- const { createHttpServer } = await Promise.resolve().then(() => require("./server-adapter-DFhwlK8e.cjs"));
2661
+ const { createHttpServer } = await Promise.resolve().then(() => require("./http-server-DFhwlK8e.cjs"));
2795
2662
  factory = createHttpServer();
2796
2663
  }
2797
2664
  const server = factory ? await factory(serveOptions) : Bun.serve(serveOptions);
@@ -2860,19 +2727,19 @@ class Shokupan extends ShokupanRouter {
2860
2727
  "http.method": req.method
2861
2728
  }
2862
2729
  };
2863
- const parent = store?.get("span");
2730
+ const parent = store?.span;
2864
2731
  const ctx = parent ? api.trace.setSpan(api.context.active(), parent) : void 0;
2865
2732
  return tracer2.startActiveSpan(`${req.method} ${new URL(req.url).pathname}`, attrs, ctx, (span) => {
2866
- const ctxMap = /* @__PURE__ */ new Map();
2867
- ctxMap.set("span", span);
2868
- ctxMap.set("request", req);
2869
- return asyncContext.run(ctxMap, () => this.handleRequest(req, server).finally(() => span.end()));
2733
+ const ctxStore = new RequestContextStore();
2734
+ ctxStore.span = span;
2735
+ ctxStore.request = req;
2736
+ return asyncContext.run(ctxStore, () => this.handleRequest(req, server).finally(() => span.end()));
2870
2737
  });
2871
2738
  }
2872
2739
  if (this.applicationConfig.enableAsyncLocalStorage) {
2873
- const ctxMap = /* @__PURE__ */ new Map();
2874
- ctxMap.set("request", req);
2875
- return asyncContext.run(ctxMap, () => this.handleRequest(req, server));
2740
+ const ctxStore = new RequestContextStore();
2741
+ ctxStore.request = req;
2742
+ return asyncContext.run(ctxStore, () => this.handleRequest(req, server));
2876
2743
  }
2877
2744
  return this.handleRequest(req, server);
2878
2745
  }
@@ -2894,6 +2761,7 @@ class Shokupan extends ShokupanRouter {
2894
2761
  const bodyParsing = ["POST", "PUT", "PATCH", "DELETE"].includes(req.method) ? ctx.parseBody() : Promise.resolve();
2895
2762
  const match = this.find(req.method, ctx.path);
2896
2763
  if (match) {
2764
+ ctx._routeMatched = true;
2897
2765
  ctx.params = match.params;
2898
2766
  await bodyParsing;
2899
2767
  return match.handler(ctx);
@@ -2908,10 +2776,14 @@ class Shokupan extends ShokupanRouter {
2908
2776
  } else if (result === null || result === void 0) {
2909
2777
  if (ctx._finalResponse instanceof Response) {
2910
2778
  response = ctx._finalResponse;
2911
- } else if (ctx.response.status !== 200 || ctx.response.hasPopulatedHeaders) {
2779
+ } else if (ctx._routeMatched) {
2912
2780
  response = ctx.send(null, { status: ctx.response.status, headers: ctx.response.headers });
2913
2781
  } else {
2914
- response = ctx.text("Not Found", 404);
2782
+ if (ctx.response.status !== 200) {
2783
+ response = ctx.send(null, { status: ctx.response.status, headers: ctx.response.headers });
2784
+ } else {
2785
+ response = ctx.text("Not Found", 404);
2786
+ }
2915
2787
  }
2916
2788
  } else if (typeof result === "object") {
2917
2789
  response = ctx.json(result);
@@ -2923,7 +2795,7 @@ class Shokupan extends ShokupanRouter {
2923
2795
  return response;
2924
2796
  } catch (err) {
2925
2797
  console.error(err);
2926
- const span = asyncContext.getStore()?.get("span");
2798
+ const span = asyncContext.getStore()?.span;
2927
2799
  if (span) span.setStatus({ code: 2 });
2928
2800
  const status = err.status || err.statusCode || 500;
2929
2801
  const body = { error: err.message || "Internal Server Error" };
@@ -2931,31 +2803,195 @@ class Shokupan extends ShokupanRouter {
2931
2803
  await this.runHooks("onError", ctx, err);
2932
2804
  return ctx.json(body, status);
2933
2805
  }
2934
- };
2935
- let executionPromise = handle();
2936
- const timeoutMs = this.applicationConfig.requestTimeout;
2937
- if (timeoutMs && timeoutMs > 0) {
2938
- let timeoutId;
2939
- const timeoutPromise = new Promise((_, reject) => {
2940
- timeoutId = setTimeout(async () => {
2941
- controller.abort();
2942
- await this.runHooks("onRequestTimeout", ctx);
2943
- reject(new Error("Request Timeout"));
2944
- }, timeoutMs);
2806
+ };
2807
+ let executionPromise = handle();
2808
+ const timeoutMs = this.applicationConfig.requestTimeout;
2809
+ if (timeoutMs && timeoutMs > 0) {
2810
+ let timeoutId;
2811
+ const timeoutPromise = new Promise((_, reject) => {
2812
+ timeoutId = setTimeout(async () => {
2813
+ controller.abort();
2814
+ await this.runHooks("onRequestTimeout", ctx);
2815
+ reject(new Error("Request Timeout"));
2816
+ }, timeoutMs);
2817
+ });
2818
+ executionPromise = Promise.race([executionPromise, timeoutPromise]).finally(() => clearTimeout(timeoutId));
2819
+ }
2820
+ return executionPromise.catch((err) => {
2821
+ if (err.message === "Request Timeout") {
2822
+ return ctx.text("Request Timeout", 408);
2823
+ }
2824
+ console.error("Unexpected error in request execution:", err);
2825
+ return ctx.text("Internal Server Error", 500);
2826
+ }).then(async (res) => {
2827
+ await this.runHooks("onResponseEnd", ctx, res);
2828
+ return res;
2829
+ });
2830
+ }
2831
+ }
2832
+ function RateLimitMiddleware(options = {}) {
2833
+ const windowMs = options.windowMs || 60 * 1e3;
2834
+ const max = options.limit || options.max || 5;
2835
+ const message = options.message || "Too many requests, please try again later.";
2836
+ const statusCode = options.statusCode || 429;
2837
+ const headers = options.headers !== false;
2838
+ const mode = options.mode || "user";
2839
+ const trustedProxies = options.trustedProxies || [];
2840
+ const keyGenerator = options.keyGenerator || ((ctx) => {
2841
+ if (mode === "absolute") {
2842
+ return "global";
2843
+ }
2844
+ const xForwardedFor = ctx.headers.get("x-forwarded-for");
2845
+ if (xForwardedFor && trustedProxies.length > 0) {
2846
+ const ips = xForwardedFor.split(",").map((ip) => ip.trim());
2847
+ for (let i = ips.length - 1; i >= 0; i--) {
2848
+ const ip = ips[i];
2849
+ if (!trustedProxies.includes(ip)) {
2850
+ if (/^[\d.:a-fA-F]+$/.test(ip)) {
2851
+ return ip;
2852
+ }
2853
+ }
2854
+ }
2855
+ }
2856
+ return ctx.server?.requestIP?.(ctx.request)?.address || "unknown";
2857
+ });
2858
+ const skip = options.skip || (() => false);
2859
+ const hits = /* @__PURE__ */ new Map();
2860
+ const interval = setInterval(() => {
2861
+ const now = Date.now();
2862
+ const entries = Array.from(hits.entries());
2863
+ for (let i = 0; i < entries.length; i++) {
2864
+ const [key, record] = entries[i];
2865
+ if (record.resetTime <= now) {
2866
+ hits.delete(key);
2867
+ }
2868
+ }
2869
+ }, windowMs);
2870
+ if (interval.unref) interval.unref();
2871
+ const rateLimitMiddleware = async function RateLimitMiddleware2(ctx, next) {
2872
+ if (skip(ctx)) return next();
2873
+ const key = keyGenerator(ctx);
2874
+ const now = Date.now();
2875
+ let record = hits.get(key);
2876
+ if (!record || record.resetTime <= now) {
2877
+ record = {
2878
+ hits: 0,
2879
+ resetTime: now + windowMs
2880
+ };
2881
+ hits.set(key, record);
2882
+ }
2883
+ record.hits++;
2884
+ const remaining = Math.max(0, max - record.hits);
2885
+ const resetTime = Math.ceil(record.resetTime / 1e3);
2886
+ const retryAfter = Math.ceil((record.resetTime - now) / 1e3);
2887
+ const setHeaders = (res) => {
2888
+ if (!headers || !res || !res.headers) return;
2889
+ try {
2890
+ res.headers.set("X-RateLimit-Limit", String(max));
2891
+ res.headers.set("X-RateLimit-Remaining", String(remaining));
2892
+ res.headers.set("X-RateLimit-Reset", String(resetTime));
2893
+ } catch (e) {
2894
+ }
2895
+ };
2896
+ if (record.hits > max) {
2897
+ if (options.onRateLimited) {
2898
+ const result = await options.onRateLimited(ctx, key);
2899
+ if (result instanceof Response) {
2900
+ return result;
2901
+ }
2902
+ }
2903
+ const msg = typeof message === "function" ? message(ctx, key) : message;
2904
+ typeof msg === "object" ? JSON.stringify(msg) : String(msg);
2905
+ const res = typeof msg === "object" ? ctx.json(msg, statusCode) : ctx.text(String(msg), statusCode);
2906
+ if (headers) {
2907
+ setHeaders(res);
2908
+ res.headers.set("Retry-After", String(retryAfter));
2909
+ }
2910
+ return res;
2911
+ }
2912
+ const response = await next();
2913
+ if (response instanceof Response && headers) {
2914
+ setHeaders(response);
2915
+ }
2916
+ return response;
2917
+ };
2918
+ rateLimitMiddleware.isBuiltin = true;
2919
+ rateLimitMiddleware.pluginName = "RateLimit";
2920
+ return rateLimitMiddleware;
2921
+ }
2922
+ function Controller(path2 = "/") {
2923
+ return (target) => {
2924
+ target[$controllerPath] = path2;
2925
+ };
2926
+ }
2927
+ function Use(...middleware) {
2928
+ return (target, propertyKey, descriptor) => {
2929
+ if (!propertyKey) {
2930
+ const existing = target[$middleware] || [];
2931
+ target[$middleware] = [...existing, ...middleware];
2932
+ } else {
2933
+ if (!target[$middleware]) {
2934
+ target[$middleware] = /* @__PURE__ */ new Map();
2935
+ }
2936
+ const existing = target[$middleware].get(propertyKey) || [];
2937
+ target[$middleware].set(propertyKey, [...existing, ...middleware]);
2938
+ }
2939
+ };
2940
+ }
2941
+ function createParamDecorator(type) {
2942
+ return (name) => {
2943
+ return (target, propertyKey, parameterIndex) => {
2944
+ if (!target[$routeArgs]) {
2945
+ target[$routeArgs] = /* @__PURE__ */ new Map();
2946
+ }
2947
+ if (!target[$routeArgs].has(propertyKey)) {
2948
+ target[$routeArgs].set(propertyKey, []);
2949
+ }
2950
+ target[$routeArgs].get(propertyKey).push({
2951
+ index: parameterIndex,
2952
+ type,
2953
+ name
2945
2954
  });
2946
- executionPromise = Promise.race([executionPromise, timeoutPromise]).finally(() => clearTimeout(timeoutId));
2955
+ };
2956
+ };
2957
+ }
2958
+ const Body = createParamDecorator(RouteParamType.BODY);
2959
+ const Param = createParamDecorator(RouteParamType.PARAM);
2960
+ const Query = createParamDecorator(RouteParamType.QUERY);
2961
+ const Headers$1 = createParamDecorator(RouteParamType.HEADER);
2962
+ const Req = createParamDecorator(RouteParamType.REQUEST);
2963
+ const Ctx = createParamDecorator(RouteParamType.CONTEXT);
2964
+ function Spec(spec) {
2965
+ return (target, propertyKey, descriptor) => {
2966
+ if (!target[$routeSpec]) {
2967
+ target[$routeSpec] = /* @__PURE__ */ new Map();
2947
2968
  }
2948
- return executionPromise.catch((err) => {
2949
- if (err.message === "Request Timeout") {
2950
- return ctx.text("Request Timeout", 408);
2969
+ target[$routeSpec].set(propertyKey, spec);
2970
+ };
2971
+ }
2972
+ function createMethodDecorator(method) {
2973
+ return (path2 = "/") => {
2974
+ return (target, propertyKey, descriptor) => {
2975
+ if (!target[$routeMethods]) {
2976
+ target[$routeMethods] = /* @__PURE__ */ new Map();
2951
2977
  }
2952
- console.error("Unexpected error in request execution:", err);
2953
- return ctx.text("Internal Server Error", 500);
2954
- }).then(async (res) => {
2955
- await this.runHooks("onResponseEnd", ctx, res);
2956
- return res;
2957
- });
2958
- }
2978
+ target[$routeMethods].set(propertyKey, {
2979
+ method,
2980
+ path: path2
2981
+ });
2982
+ };
2983
+ };
2984
+ }
2985
+ const Get = createMethodDecorator("GET");
2986
+ const Post = createMethodDecorator("POST");
2987
+ const Put = createMethodDecorator("PUT");
2988
+ const Delete = createMethodDecorator("DELETE");
2989
+ const Patch = createMethodDecorator("PATCH");
2990
+ const Options = createMethodDecorator("OPTIONS");
2991
+ const Head = createMethodDecorator("HEAD");
2992
+ const All = createMethodDecorator("ALL");
2993
+ function RateLimit(options) {
2994
+ return Use(RateLimitMiddleware(options));
2959
2995
  }
2960
2996
  class AuthPlugin extends ShokupanRouter {
2961
2997
  constructor(authConfig) {
@@ -2965,6 +3001,13 @@ class AuthPlugin extends ShokupanRouter {
2965
3001
  this.init();
2966
3002
  }
2967
3003
  secret;
3004
+ onInit(app, options) {
3005
+ if (options?.path) {
3006
+ app.mount(options.path, this);
3007
+ } else {
3008
+ app.mount(options.path ?? "/", this);
3009
+ }
3010
+ }
2968
3011
  getProviderInstance(name, p) {
2969
3012
  switch (name) {
2970
3013
  case "github":
@@ -3182,8 +3225,196 @@ class AuthPlugin extends ShokupanRouter {
3182
3225
  };
3183
3226
  }
3184
3227
  }
3228
+ class ClusterPlugin {
3229
+ constructor(options = {}) {
3230
+ this.options = options;
3231
+ }
3232
+ onInit(app) {
3233
+ const originalListen = app.listen.bind(app);
3234
+ const { workers = "auto", silent = false, sticky = false } = this.options;
3235
+ const isBun = typeof Bun !== "undefined";
3236
+ const numCPUs = os.cpus().length;
3237
+ const numWorkers = workers === "auto" || workers === -1 ? numCPUs : workers;
3238
+ if (numWorkers <= 1) {
3239
+ return;
3240
+ }
3241
+ app.listen = async (port) => {
3242
+ const finalPort = port ?? app.applicationConfig.port ?? 3e3;
3243
+ if (isBun) {
3244
+ return this.handleBun(app, finalPort, numWorkers, originalListen);
3245
+ } else {
3246
+ return this.handleNode(app, finalPort, numWorkers, originalListen, silent, sticky);
3247
+ }
3248
+ };
3249
+ }
3250
+ async handleBun(app, port, workers, originalListen) {
3251
+ const workerId = process.env["SHOKUPAN_WORKER_ID"];
3252
+ if (workerId) {
3253
+ app.applicationConfig.reusePort = true;
3254
+ return originalListen(port);
3255
+ }
3256
+ console.log(`[Cluster] Starting ${workers} Bun workers on port ${port}...`);
3257
+ const spawnWorker = (id) => {
3258
+ Bun.spawn([process.argv0, ...process.argv.slice(1)], {
3259
+ env: { ...process.env, SHOKUPAN_WORKER_ID: id },
3260
+ stdio: ["inherit", "inherit", "inherit"],
3261
+ onExit(proc, exitCode, signalCode, error) {
3262
+ console.log(`[Cluster] Worker ${id} died (code: ${exitCode}). Restarting...`);
3263
+ spawnWorker(id);
3264
+ }
3265
+ });
3266
+ };
3267
+ for (let i = 0; i < workers; i++) {
3268
+ spawnWorker(process.pid + "_" + i + 1);
3269
+ }
3270
+ setInterval(() => {
3271
+ }, 1e3 * 60 * 60);
3272
+ return {
3273
+ stop: () => {
3274
+ },
3275
+ port
3276
+ };
3277
+ }
3278
+ async handleNode(app, port, workers, originalListen, silent, sticky) {
3279
+ if (cluster.isPrimary) {
3280
+ console.log(`[Cluster] Master ${process.pid} is running`);
3281
+ const fork = () => cluster.fork(process.env);
3282
+ for (let i = 0; i < workers; i++) {
3283
+ fork();
3284
+ }
3285
+ cluster.on("exit", (worker, code, signal) => {
3286
+ console.log(`[Cluster] Worker ${worker.process.pid} died. Restarting...`);
3287
+ fork();
3288
+ });
3289
+ if (sticky) {
3290
+ const server = net.createServer({ pauseOnConnect: true }, (connection) => {
3291
+ const remote = connection.remoteAddress || "";
3292
+ let hash = 0;
3293
+ for (let i = 0; i < remote.length; i++) {
3294
+ hash = (hash << 5) - hash + remote.charCodeAt(i);
3295
+ hash |= 0;
3296
+ }
3297
+ const index = Math.abs(hash) % workers;
3298
+ const worker = Object.values(cluster.workers)[index];
3299
+ if (worker) {
3300
+ worker.send("sticky-session:connection", connection);
3301
+ } else {
3302
+ connection.end();
3303
+ }
3304
+ });
3305
+ server.listen(port, () => {
3306
+ console.log(`[Cluster] Sticky Load Balancer listening on port ${port}`);
3307
+ });
3308
+ return {
3309
+ close: () => server.close(),
3310
+ port
3311
+ };
3312
+ } else {
3313
+ return {
3314
+ close: () => {
3315
+ },
3316
+ // Master controls
3317
+ port
3318
+ };
3319
+ }
3320
+ } else {
3321
+ if (sticky) {
3322
+ const server = await originalListen(0);
3323
+ process.on("message", (message, handle) => {
3324
+ if (message !== "sticky-session:connection") return;
3325
+ if (!handle) return;
3326
+ server.emit("connection", handle);
3327
+ handle.resume();
3328
+ });
3329
+ return server;
3330
+ } else {
3331
+ return originalListen(port);
3332
+ }
3333
+ }
3334
+ }
3335
+ }
3336
+ const eta = new eta$2.Eta();
3337
+ class ScalarPlugin extends ShokupanRouter {
3338
+ constructor(pluginOptions = {}) {
3339
+ pluginOptions.config ??= {};
3340
+ super();
3341
+ this.pluginOptions = pluginOptions;
3342
+ this.init();
3343
+ }
3344
+ onInit(app, options) {
3345
+ if (options?.path) {
3346
+ app.mount(options.path, this);
3347
+ } else {
3348
+ app.mount(options.path ?? "/", this);
3349
+ }
3350
+ this.onMount(app);
3351
+ }
3352
+ init() {
3353
+ this.get("/", (ctx) => {
3354
+ let path2 = ctx.url.toString();
3355
+ if (!path2.endsWith("/")) path2 += "/";
3356
+ return ctx.html(eta.renderString(`<!doctype html>
3357
+ <html>
3358
+ <head>
3359
+ <title>API Reference</title>
3360
+ <meta charset = "utf-8" />
3361
+ <meta name="viewport" content = "width=device-width, initial-scale=1" />
3362
+ </head>
3363
+
3364
+ <body>
3365
+ <div id="app"></div>
3366
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
3367
+ <script>
3368
+ Scalar.createApiReference('#app', [{ ...<%~ JSON.stringify(it.config.baseDocument) %>,
3369
+ url: "<%= it.path %>openapi.json",
3370
+ }
3371
+ ])
3372
+ <\/script>
3373
+ </body>
3374
+
3375
+ </html>`, { path: path2, config: this.pluginOptions }));
3376
+ });
3377
+ this.get("/openapi.json", async (ctx) => {
3378
+ let spec;
3379
+ if (this.root.openApiSpec) {
3380
+ try {
3381
+ spec = structuredClone(this.root.openApiSpec);
3382
+ } catch (e) {
3383
+ spec = Object.assign({}, this.root.openApiSpec);
3384
+ }
3385
+ } else {
3386
+ spec = await (this.root || this).generateApiSpec();
3387
+ }
3388
+ if (this.pluginOptions.baseDocument) {
3389
+ deepMerge(spec, this.pluginOptions.baseDocument);
3390
+ }
3391
+ return ctx.json(spec);
3392
+ });
3393
+ }
3394
+ // New lifecycle method to be called by router.mount
3395
+ onMount(parent) {
3396
+ if (parent.onStart) {
3397
+ parent.onStart(async () => {
3398
+ if (this.pluginOptions.enableStaticAnalysis) {
3399
+ try {
3400
+ const entrypoint = process.argv[1];
3401
+ console.log(`[ScalarPlugin] Running eager static analysis on entrypoint: ${entrypoint}`);
3402
+ const analyzer$1 = new analyzer.OpenAPIAnalyzer(process.cwd(), entrypoint);
3403
+ let staticSpec = await analyzer$1.analyze();
3404
+ if (!this.pluginOptions.baseDocument) this.pluginOptions.baseDocument = {};
3405
+ deepMerge(this.pluginOptions.baseDocument, staticSpec);
3406
+ console.log("[ScalarPlugin] Static analysis completed successfully.");
3407
+ } catch (err) {
3408
+ console.error("[ScalarPlugin] Failed to run static analysis:", err);
3409
+ }
3410
+ }
3411
+ });
3412
+ }
3413
+ }
3414
+ }
3185
3415
  function Compression(options = {}) {
3186
3416
  const threshold = options.threshold ?? 512;
3417
+ const allowedAlgorithms = new Set(options.allowedAlgorithms ?? ["br", "gzip", "zstd", "deflate"]);
3187
3418
  const compressionMiddleware = async function CompressionMiddleware(ctx, next) {
3188
3419
  const acceptEncoding = ctx.headers.get("accept-encoding") || "";
3189
3420
  let method = null;
@@ -3196,6 +3427,9 @@ function Compression(options = {}) {
3196
3427
  } else if (acceptEncoding.includes("gzip")) method = "gzip";
3197
3428
  else if (acceptEncoding.includes("deflate")) method = "deflate";
3198
3429
  if (!method) return next();
3430
+ if (!allowedAlgorithms.has(method)) {
3431
+ return next();
3432
+ }
3199
3433
  let response = await next();
3200
3434
  if (!(response instanceof Response) && ctx._finalResponse instanceof Response) {
3201
3435
  response = ctx._finalResponse;
@@ -3750,77 +3984,6 @@ function enableOpenApiValidation(app) {
3750
3984
  precompileValidators(app, spec);
3751
3985
  });
3752
3986
  }
3753
- const eta = new eta$2.Eta();
3754
- class ScalarPlugin extends ShokupanRouter {
3755
- constructor(pluginOptions = {}) {
3756
- pluginOptions.config ??= {};
3757
- super();
3758
- this.pluginOptions = pluginOptions;
3759
- this.init();
3760
- }
3761
- init() {
3762
- this.get("/", (ctx) => {
3763
- let path2 = ctx.url.toString();
3764
- if (!path2.endsWith("/")) path2 += "/";
3765
- return ctx.html(eta.renderString(`<!doctype html>
3766
- <html>
3767
- <head>
3768
- <title>API Reference</title>
3769
- <meta charset = "utf-8" />
3770
- <meta name="viewport" content = "width=device-width, initial-scale=1" />
3771
- </head>
3772
-
3773
- <body>
3774
- <div id="app"></div>
3775
- <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
3776
- <script>
3777
- Scalar.createApiReference('#app', [{ ...<%~ JSON.stringify(it.config.baseDocument) %>,
3778
- url: "<%= it.path %>openapi.json",
3779
- }
3780
- ])
3781
- <\/script>
3782
- </body>
3783
-
3784
- </html>`, { path: path2, config: this.pluginOptions }));
3785
- });
3786
- this.get("/openapi.json", async (ctx) => {
3787
- let spec;
3788
- if (this.root.openApiSpec) {
3789
- try {
3790
- spec = structuredClone(this.root.openApiSpec);
3791
- } catch (e) {
3792
- spec = Object.assign({}, this.root.openApiSpec);
3793
- }
3794
- } else {
3795
- spec = await (this.root || this).generateApiSpec();
3796
- }
3797
- if (this.pluginOptions.baseDocument) {
3798
- deepMerge(spec, this.pluginOptions.baseDocument);
3799
- }
3800
- return ctx.json(spec);
3801
- });
3802
- }
3803
- // New lifecycle method to be called by router.mount
3804
- onMount(parent) {
3805
- if (parent.onStart) {
3806
- parent.onStart(async () => {
3807
- if (this.pluginOptions.enableStaticAnalysis) {
3808
- try {
3809
- const entrypoint = process.argv[1];
3810
- console.log(`[ScalarPlugin] Running eager static analysis on entrypoint: ${entrypoint}`);
3811
- const analyzer = new openapiAnalyzer.OpenAPIAnalyzer(process.cwd(), entrypoint);
3812
- let staticSpec = await analyzer.analyze();
3813
- if (!this.pluginOptions.baseDocument) this.pluginOptions.baseDocument = {};
3814
- deepMerge(this.pluginOptions.baseDocument, staticSpec);
3815
- console.log("[ScalarPlugin] Static analysis completed successfully.");
3816
- } catch (err) {
3817
- console.error("[ScalarPlugin] Failed to run static analysis:", err);
3818
- }
3819
- }
3820
- });
3821
- }
3822
- }
3823
- }
3824
3987
  function SecurityHeaders(options = {}) {
3825
3988
  const securityHeadersMiddleware = async function SecurityHeadersMiddleware(ctx, next) {
3826
3989
  const headers = {};
@@ -3944,18 +4107,18 @@ class MemoryStore extends events.EventEmitter {
3944
4107
  }
3945
4108
  set(sid, sess, cb) {
3946
4109
  this.sessions[sid] = JSON.stringify(sess);
3947
- cb && cb();
4110
+ cb?.();
3948
4111
  }
3949
4112
  destroy(sid, cb) {
3950
4113
  delete this.sessions[sid];
3951
- cb && cb();
4114
+ cb?.();
3952
4115
  }
3953
4116
  touch(sid, sess, cb) {
3954
4117
  const current = this.sessions[sid];
3955
4118
  if (current) {
3956
4119
  this.sessions[sid] = JSON.stringify(sess);
3957
4120
  }
3958
- cb && cb();
4121
+ cb?.();
3959
4122
  }
3960
4123
  all(cb) {
3961
4124
  const result = {};
@@ -3971,7 +4134,7 @@ class MemoryStore extends events.EventEmitter {
3971
4134
  }
3972
4135
  clear(cb) {
3973
4136
  this.sessions = {};
3974
- cb && cb();
4137
+ cb?.();
3975
4138
  }
3976
4139
  }
3977
4140
  function sign(val, secret) {
@@ -4166,6 +4329,7 @@ exports.$routes = $routes;
4166
4329
  exports.All = All;
4167
4330
  exports.AuthPlugin = AuthPlugin;
4168
4331
  exports.Body = Body;
4332
+ exports.ClusterPlugin = ClusterPlugin;
4169
4333
  exports.Compression = Compression;
4170
4334
  exports.Container = Container;
4171
4335
  exports.Controller = Controller;
@@ -4189,16 +4353,13 @@ exports.RateLimit = RateLimit;
4189
4353
  exports.RateLimitMiddleware = RateLimitMiddleware;
4190
4354
  exports.Req = Req;
4191
4355
  exports.RouteParamType = RouteParamType;
4192
- exports.RouterRegistry = RouterRegistry;
4193
4356
  exports.ScalarPlugin = ScalarPlugin;
4194
4357
  exports.SecurityHeaders = SecurityHeaders;
4195
4358
  exports.Session = Session;
4196
4359
  exports.Shokupan = Shokupan;
4197
- exports.ShokupanApplicationTree = ShokupanApplicationTree;
4198
4360
  exports.ShokupanContext = ShokupanContext;
4199
4361
  exports.ShokupanRequest = ShokupanRequest;
4200
4362
  exports.ShokupanResponse = ShokupanResponse;
4201
- exports.ShokupanRouter = ShokupanRouter;
4202
4363
  exports.Spec = Spec;
4203
4364
  exports.Use = Use;
4204
4365
  exports.ValidationError = ValidationError;