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.
- package/README.md +2 -2
- package/dist/{openapi-analyzer-Bei1sVWp.cjs → analyzer-Bei1sVWp.cjs} +1 -1
- package/dist/analyzer-Bei1sVWp.cjs.map +1 -0
- package/dist/{openapi-analyzer-Ce_7JxZh.js → analyzer-Ce_7JxZh.js} +1 -1
- package/dist/analyzer-Ce_7JxZh.js.map +1 -0
- package/dist/cli.cjs +2 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/context.d.ts +9 -9
- package/dist/{server-adapter-0xH174zz.js → http-server-0xH174zz.js} +1 -1
- package/dist/http-server-0xH174zz.js.map +1 -0
- package/dist/{server-adapter-DFhwlK8e.cjs → http-server-DFhwlK8e.cjs} +1 -1
- package/dist/http-server-DFhwlK8e.cjs.map +1 -0
- package/dist/index.cjs +928 -767
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +17 -17
- package/dist/index.js +953 -791
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts +1 -1
- package/dist/plugins/{auth.d.ts → application/auth.d.ts} +72 -3
- package/dist/plugins/application/cluster.d.ts +33 -0
- package/dist/plugins/{failed-request-recorder.d.ts → application/dashboard/failed-request-recorder.d.ts} +1 -1
- package/dist/plugins/{debugview → application/dashboard}/plugin.d.ts +13 -6
- package/dist/plugins/{server-adapter.d.ts → application/http-server.d.ts} +1 -1
- package/dist/plugins/{idempotency → application/idempotency}/plugin.d.ts +7 -1
- package/dist/plugins/{openapi.d.ts → application/openapi/openapi.d.ts} +2 -2
- package/dist/plugins/application/scalar.d.ts +36 -0
- package/dist/plugins/middleware/compression.d.ts +17 -0
- package/dist/plugins/middleware/cors.d.ts +34 -0
- package/dist/plugins/{express.d.ts → middleware/express.d.ts} +1 -1
- package/dist/plugins/{openapi-validator.d.ts → middleware/openapi-validator.d.ts} +2 -2
- package/dist/plugins/middleware/proxy.d.ts +37 -0
- package/dist/plugins/middleware/rate-limit.d.ts +58 -0
- package/dist/plugins/{security-headers.d.ts → middleware/security-headers.d.ts} +51 -1
- package/dist/plugins/{serve-static.d.ts → middleware/serve-static.d.ts} +1 -1
- package/dist/plugins/{session.d.ts → middleware/session.d.ts} +89 -3
- package/dist/plugins/{validation.d.ts → middleware/validation.d.ts} +6 -1
- package/dist/router.d.ts +5 -5
- package/dist/shokupan.d.ts +10 -4
- package/dist/util/async-hooks.d.ts +8 -2
- package/dist/{decorators.d.ts → util/decorators.d.ts} +1 -1
- package/dist/util/http-status.d.ts +2 -0
- package/dist/util/instrumentation.d.ts +1 -1
- package/dist/{router → util}/trie.d.ts +1 -1
- package/dist/{types.d.ts → util/types.d.ts} +8 -1
- package/package.json +4 -4
- package/dist/openapi-analyzer-Bei1sVWp.cjs.map +0 -1
- package/dist/openapi-analyzer-Ce_7JxZh.js.map +0 -1
- package/dist/plugins/compression.d.ts +0 -5
- package/dist/plugins/cors.d.ts +0 -11
- package/dist/plugins/proxy.d.ts +0 -11
- package/dist/plugins/rate-limit.d.ts +0 -15
- package/dist/plugins/scalar.d.ts +0 -15
- package/dist/server-adapter-0xH174zz.js.map +0 -1
- package/dist/server-adapter-DFhwlK8e.cjs.map +0 -1
- /package/dist/{analysis/openapi-analyzer.d.ts → plugins/application/openapi/analyzer.d.ts} +0 -0
- /package/dist/{di.d.ts → util/di.d.ts} +0 -0
- /package/dist/{request.d.ts → util/request.d.ts} +0 -0
- /package/dist/{response.d.ts → util/response.d.ts} +0 -0
- /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
|
-
|
|
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.
|
|
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
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
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("./
|
|
1176
|
-
const
|
|
1177
|
-
const { applications } = await
|
|
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
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
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
|
|
1733
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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-
|
|
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?.
|
|
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
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
return asyncContext.run(
|
|
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
|
|
2874
|
-
|
|
2875
|
-
return asyncContext.run(
|
|
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.
|
|
2779
|
+
} else if (ctx._routeMatched) {
|
|
2912
2780
|
response = ctx.send(null, { status: ctx.response.status, headers: ctx.response.headers });
|
|
2913
2781
|
} else {
|
|
2914
|
-
|
|
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()?.
|
|
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
|
-
|
|
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
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
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
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
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
|
|
4110
|
+
cb?.();
|
|
3948
4111
|
}
|
|
3949
4112
|
destroy(sid, cb) {
|
|
3950
4113
|
delete this.sessions[sid];
|
|
3951
|
-
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
|
|
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
|
|
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;
|