shokupan 0.6.0 → 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 +4 -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 +72 -11
- 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 +1022 -801
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +17 -17
- package/dist/index.js +1022 -800
- 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 +99 -40
- package/dist/shokupan.d.ts +74 -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} +41 -2
- package/package.json +5 -5
- 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 -9
- package/dist/plugins/rate-limit.d.ts +0 -14
- 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,69 +58,6 @@ 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);
|
|
59
|
-
class ShokupanResponse {
|
|
60
|
-
_headers = null;
|
|
61
|
-
_status = 200;
|
|
62
|
-
/**
|
|
63
|
-
* Get the current headers
|
|
64
|
-
*/
|
|
65
|
-
get headers() {
|
|
66
|
-
if (!this._headers) this._headers = new Headers();
|
|
67
|
-
return this._headers;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Get the current status code
|
|
71
|
-
*/
|
|
72
|
-
get status() {
|
|
73
|
-
return this._status;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Set the status code
|
|
77
|
-
*/
|
|
78
|
-
set status(code) {
|
|
79
|
-
this._status = code;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Set a response header
|
|
83
|
-
* @param key Header name
|
|
84
|
-
* @param value Header value
|
|
85
|
-
*/
|
|
86
|
-
set(key, value) {
|
|
87
|
-
if (!this._headers) this._headers = new Headers();
|
|
88
|
-
this._headers.set(key, value);
|
|
89
|
-
return this;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Append to a response header
|
|
93
|
-
* @param key Header name
|
|
94
|
-
* @param value Header value
|
|
95
|
-
*/
|
|
96
|
-
append(key, value) {
|
|
97
|
-
if (!this._headers) this._headers = new Headers();
|
|
98
|
-
this._headers.append(key, value);
|
|
99
|
-
return this;
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Get a response header value
|
|
103
|
-
* @param key Header name
|
|
104
|
-
*/
|
|
105
|
-
get(key) {
|
|
106
|
-
return this._headers?.get(key) || null;
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Check if a header exists
|
|
110
|
-
* @param key Header name
|
|
111
|
-
*/
|
|
112
|
-
has(key) {
|
|
113
|
-
return this._headers?.has(key) || false;
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Internal: check if headers have been initialized/modified
|
|
117
|
-
*/
|
|
118
|
-
get hasPopulatedHeaders() {
|
|
119
|
-
return this._headers !== null;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
61
|
const VALID_HTTP_STATUSES = /* @__PURE__ */ new Set([
|
|
123
62
|
100,
|
|
124
63
|
101,
|
|
@@ -185,6 +124,78 @@ const VALID_HTTP_STATUSES = /* @__PURE__ */ new Set([
|
|
|
185
124
|
511
|
|
186
125
|
]);
|
|
187
126
|
const VALID_REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
127
|
+
class ShokupanResponse {
|
|
128
|
+
_headers = null;
|
|
129
|
+
_status = 200;
|
|
130
|
+
/**
|
|
131
|
+
* Get the current headers
|
|
132
|
+
*/
|
|
133
|
+
get headers() {
|
|
134
|
+
if (!this._headers) this._headers = new Headers();
|
|
135
|
+
return this._headers;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get the current status code
|
|
139
|
+
*/
|
|
140
|
+
get status() {
|
|
141
|
+
return this._status;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Set the status code
|
|
145
|
+
*/
|
|
146
|
+
set status(code) {
|
|
147
|
+
this._status = code;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Set a response header
|
|
151
|
+
* @param key Header name
|
|
152
|
+
* @param value Header value
|
|
153
|
+
*/
|
|
154
|
+
set(key, value) {
|
|
155
|
+
if (!this._headers) this._headers = new Headers();
|
|
156
|
+
this._headers.set(key, value);
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Append to a response header
|
|
161
|
+
* @param key Header name
|
|
162
|
+
* @param value Header value
|
|
163
|
+
*/
|
|
164
|
+
append(key, value) {
|
|
165
|
+
if (!this._headers) this._headers = new Headers();
|
|
166
|
+
this._headers.append(key, value);
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get a response header value
|
|
171
|
+
* @param key Header name
|
|
172
|
+
*/
|
|
173
|
+
get(key) {
|
|
174
|
+
return this._headers?.get(key) || null;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Check if a header exists
|
|
178
|
+
* @param key Header name
|
|
179
|
+
*/
|
|
180
|
+
has(key) {
|
|
181
|
+
return this._headers?.has(key) || false;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Internal: check if headers have been initialized/modified
|
|
185
|
+
*/
|
|
186
|
+
get hasPopulatedHeaders() {
|
|
187
|
+
return this._headers !== null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function isValidCookieDomain(domain, currentHost) {
|
|
191
|
+
const hostWithoutPort = currentHost.split(":")[0];
|
|
192
|
+
if (domain === hostWithoutPort) return true;
|
|
193
|
+
if (domain.startsWith(".")) {
|
|
194
|
+
const domainWithoutDot = domain.slice(1);
|
|
195
|
+
return hostWithoutPort.endsWith(domainWithoutDot);
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
188
199
|
class ShokupanContext {
|
|
189
200
|
constructor(request, server, state, app, signal, enableMiddlewareTracking = false) {
|
|
190
201
|
this.request = request;
|
|
@@ -223,12 +234,20 @@ class ShokupanContext {
|
|
|
223
234
|
_bodyType;
|
|
224
235
|
_bodyParsed = false;
|
|
225
236
|
_bodyParseError;
|
|
237
|
+
_routeMatched = false;
|
|
226
238
|
// Cached URL properties to avoid repeated parsing
|
|
227
239
|
_cachedHostname;
|
|
228
240
|
_cachedProtocol;
|
|
229
241
|
_cachedHost;
|
|
230
242
|
_cachedOrigin;
|
|
231
243
|
_cachedQuery;
|
|
244
|
+
/**
|
|
245
|
+
* JSX Rendering Function
|
|
246
|
+
*/
|
|
247
|
+
renderer;
|
|
248
|
+
setRenderer(renderer) {
|
|
249
|
+
this.renderer = renderer;
|
|
250
|
+
}
|
|
232
251
|
get url() {
|
|
233
252
|
if (!this._url) {
|
|
234
253
|
const urlString = this.request.url || "http://localhost/";
|
|
@@ -278,16 +297,20 @@ class ShokupanContext {
|
|
|
278
297
|
*/
|
|
279
298
|
get query() {
|
|
280
299
|
if (this._cachedQuery) return this._cachedQuery;
|
|
281
|
-
const q =
|
|
300
|
+
const q = /* @__PURE__ */ Object.create(null);
|
|
301
|
+
const blocklist = ["__proto__", "constructor", "prototype"];
|
|
282
302
|
const entries = Object.entries(this.url.searchParams);
|
|
283
303
|
for (let i = 0; i < entries.length; i++) {
|
|
284
304
|
const [key, value] = entries[i];
|
|
285
|
-
if (
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
305
|
+
if (blocklist.includes(key)) continue;
|
|
306
|
+
if (Object.prototype.hasOwnProperty.call(q, key)) {
|
|
307
|
+
if (Array.isArray(q[key])) {
|
|
308
|
+
q[key].push(value);
|
|
309
|
+
} else {
|
|
310
|
+
q[key] = [q[key], value];
|
|
311
|
+
}
|
|
289
312
|
} else {
|
|
290
|
-
q[key] =
|
|
313
|
+
q[key] = value;
|
|
291
314
|
}
|
|
292
315
|
}
|
|
293
316
|
this._cachedQuery = q;
|
|
@@ -364,6 +387,12 @@ class ShokupanContext {
|
|
|
364
387
|
* @param options Cookie options
|
|
365
388
|
*/
|
|
366
389
|
setCookie(name, value, options = {}) {
|
|
390
|
+
if (options.domain) {
|
|
391
|
+
const currentHost = this.hostname;
|
|
392
|
+
if (!isValidCookieDomain(options.domain, currentHost)) {
|
|
393
|
+
throw new Error(`Invalid cookie domain: ${options.domain} for host ${currentHost}`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
367
396
|
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
|
368
397
|
if (options.maxAge) cookie += `; Max-Age=${Math.floor(options.maxAge)}`;
|
|
369
398
|
if (options.domain) cookie += `; Domain=${options.domain}`;
|
|
@@ -436,11 +465,15 @@ class ShokupanContext {
|
|
|
436
465
|
}
|
|
437
466
|
const contentType = this.request.headers.get("content-type") || "";
|
|
438
467
|
if (contentType.includes("application/json") || contentType.includes("+json")) {
|
|
439
|
-
const rawText = await this.readRawBody();
|
|
440
468
|
const parserType = this.app?.applicationConfig?.jsonParser || "native";
|
|
441
469
|
if (parserType === "native") {
|
|
442
|
-
|
|
470
|
+
try {
|
|
471
|
+
this._cachedBody = await this.request.json();
|
|
472
|
+
} catch (e) {
|
|
473
|
+
throw e;
|
|
474
|
+
}
|
|
443
475
|
} else {
|
|
476
|
+
const rawText = await this.request.text();
|
|
444
477
|
const { getJSONParser } = await Promise.resolve().then(() => require("./json-parser-COdZ0fqY.cjs"));
|
|
445
478
|
const parser = getJSONParser(parserType);
|
|
446
479
|
this._cachedBody = parser(rawText);
|
|
@@ -450,7 +483,7 @@ class ShokupanContext {
|
|
|
450
483
|
this._cachedBody = await this.request.formData();
|
|
451
484
|
this._bodyType = "formData";
|
|
452
485
|
} else {
|
|
453
|
-
this._cachedBody = await this.
|
|
486
|
+
this._cachedBody = await this.request.text();
|
|
454
487
|
this._bodyType = "text";
|
|
455
488
|
}
|
|
456
489
|
this._bodyParsed = true;
|
|
@@ -628,10 +661,6 @@ class ShokupanContext {
|
|
|
628
661
|
return this._finalResponse;
|
|
629
662
|
}
|
|
630
663
|
}
|
|
631
|
-
/**
|
|
632
|
-
* JSX Rendering Function
|
|
633
|
-
*/
|
|
634
|
-
renderer;
|
|
635
664
|
/**
|
|
636
665
|
* Render a JSX element
|
|
637
666
|
* @param element JSX Element
|
|
@@ -650,271 +679,67 @@ class ShokupanContext {
|
|
|
650
679
|
return this.html(html, status, headers);
|
|
651
680
|
}
|
|
652
681
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
return "
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
const skip = options.skip || (() => false);
|
|
667
|
-
const hits = /* @__PURE__ */ new Map();
|
|
668
|
-
const interval = setInterval(() => {
|
|
669
|
-
const now = Date.now();
|
|
670
|
-
const entries = Array.from(hits.entries());
|
|
671
|
-
for (let i = 0; i < entries.length; i++) {
|
|
672
|
-
const [key, record] = entries[i];
|
|
673
|
-
if (record.resetTime <= now) {
|
|
674
|
-
hits.delete(key);
|
|
682
|
+
const compose = (middleware) => {
|
|
683
|
+
if (!middleware.length) {
|
|
684
|
+
return (context, next) => {
|
|
685
|
+
return next ? next() : Promise.resolve();
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
return function dispatch(context, next) {
|
|
689
|
+
let index = -1;
|
|
690
|
+
async function runner(i) {
|
|
691
|
+
if (i <= index) return Promise.reject(new Error("next() called multiple times"));
|
|
692
|
+
index = i;
|
|
693
|
+
if (i >= middleware.length) {
|
|
694
|
+
return next ? next() : Promise.resolve();
|
|
675
695
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
const rateLimitMiddleware = async function RateLimitMiddleware2(ctx, next) {
|
|
680
|
-
if (skip(ctx)) return next();
|
|
681
|
-
const key = keyGenerator(ctx);
|
|
682
|
-
const now = Date.now();
|
|
683
|
-
let record = hits.get(key);
|
|
684
|
-
if (!record || record.resetTime <= now) {
|
|
685
|
-
record = {
|
|
686
|
-
hits: 0,
|
|
687
|
-
resetTime: now + windowMs
|
|
688
|
-
};
|
|
689
|
-
hits.set(key, record);
|
|
690
|
-
}
|
|
691
|
-
record.hits++;
|
|
692
|
-
const remaining = Math.max(0, max - record.hits);
|
|
693
|
-
const resetTime = Math.ceil(record.resetTime / 1e3);
|
|
694
|
-
const retryAfter = Math.ceil((record.resetTime - now) / 1e3);
|
|
695
|
-
const setHeaders = (res) => {
|
|
696
|
-
if (!headers || !res || !res.headers) return;
|
|
697
|
-
try {
|
|
698
|
-
res.headers.set("X-RateLimit-Limit", String(max));
|
|
699
|
-
res.headers.set("X-RateLimit-Remaining", String(remaining));
|
|
700
|
-
res.headers.set("X-RateLimit-Reset", String(resetTime));
|
|
701
|
-
} catch (e) {
|
|
696
|
+
const fn = middleware[i];
|
|
697
|
+
if (!context._debug) {
|
|
698
|
+
return fn(context, () => runner(i + 1));
|
|
702
699
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
700
|
+
const debug = context._debug;
|
|
701
|
+
const debugId = fn._debugId || fn.name || "anonymous";
|
|
702
|
+
const previousNode = debug.getCurrentNode();
|
|
703
|
+
debug.trackEdge(previousNode, debugId);
|
|
704
|
+
debug.setNode(debugId);
|
|
705
|
+
const start = performance.now();
|
|
706
|
+
try {
|
|
707
|
+
const res = await Promise.resolve(fn(context, () => runner(i + 1)));
|
|
708
|
+
debug.trackStep(debugId, "middleware", performance.now() - start, "success");
|
|
709
|
+
return res;
|
|
710
|
+
} catch (err) {
|
|
711
|
+
debug.trackStep(debugId, "middleware", performance.now() - start, "error", err);
|
|
712
|
+
return Promise.reject(err);
|
|
713
|
+
} finally {
|
|
714
|
+
if (previousNode) debug.setNode(previousNode);
|
|
710
715
|
}
|
|
711
|
-
return res;
|
|
712
|
-
}
|
|
713
|
-
const response = await next();
|
|
714
|
-
if (response instanceof Response && headers) {
|
|
715
|
-
setHeaders(response);
|
|
716
716
|
}
|
|
717
|
-
return
|
|
717
|
+
return runner(0);
|
|
718
718
|
};
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
const $controllerPath = /* @__PURE__ */ Symbol("Shokupan.controllerPath");
|
|
729
|
-
const $middleware = /* @__PURE__ */ Symbol("Shokupan.middleware");
|
|
730
|
-
const $isRouter = /* @__PURE__ */ Symbol.for("Shokupan.router");
|
|
731
|
-
const $parent = /* @__PURE__ */ Symbol.for("Shokupan.parent");
|
|
732
|
-
const $childRouters = /* @__PURE__ */ Symbol.for("Shokupan.child-routers");
|
|
733
|
-
const $childControllers = /* @__PURE__ */ Symbol.for("Shokupan.child-controllers");
|
|
734
|
-
const $mountPath = /* @__PURE__ */ Symbol.for("Shokupan.mount-path");
|
|
735
|
-
const $dispatch = /* @__PURE__ */ Symbol.for("Shokupan.dispatch");
|
|
736
|
-
const $routes = /* @__PURE__ */ Symbol.for("Shokupan.routes");
|
|
737
|
-
const $routeSpec = /* @__PURE__ */ Symbol.for("Shokupan.routeSpec");
|
|
738
|
-
const HTTPMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "ALL"];
|
|
739
|
-
var RouteParamType = /* @__PURE__ */ ((RouteParamType2) => {
|
|
740
|
-
RouteParamType2["BODY"] = "BODY";
|
|
741
|
-
RouteParamType2["PARAM"] = "PARAM";
|
|
742
|
-
RouteParamType2["QUERY"] = "QUERY";
|
|
743
|
-
RouteParamType2["HEADER"] = "HEADER";
|
|
744
|
-
RouteParamType2["REQUEST"] = "REQUEST";
|
|
745
|
-
RouteParamType2["CONTEXT"] = "CONTEXT";
|
|
746
|
-
return RouteParamType2;
|
|
747
|
-
})(RouteParamType || {});
|
|
748
|
-
function Controller(path2 = "/") {
|
|
749
|
-
return (target) => {
|
|
750
|
-
target[$controllerPath] = path2;
|
|
751
|
-
};
|
|
752
|
-
}
|
|
753
|
-
function Use(...middleware) {
|
|
754
|
-
return (target, propertyKey, descriptor) => {
|
|
755
|
-
if (!propertyKey) {
|
|
756
|
-
const existing = target[$middleware] || [];
|
|
757
|
-
target[$middleware] = [...existing, ...middleware];
|
|
758
|
-
} else {
|
|
759
|
-
if (!target[$middleware]) {
|
|
760
|
-
target[$middleware] = /* @__PURE__ */ new Map();
|
|
761
|
-
}
|
|
762
|
-
const existing = target[$middleware].get(propertyKey) || [];
|
|
763
|
-
target[$middleware].set(propertyKey, [...existing, ...middleware]);
|
|
764
|
-
}
|
|
765
|
-
};
|
|
766
|
-
}
|
|
767
|
-
function createParamDecorator(type) {
|
|
768
|
-
return (name) => {
|
|
769
|
-
return (target, propertyKey, parameterIndex) => {
|
|
770
|
-
if (!target[$routeArgs]) {
|
|
771
|
-
target[$routeArgs] = /* @__PURE__ */ new Map();
|
|
772
|
-
}
|
|
773
|
-
if (!target[$routeArgs].has(propertyKey)) {
|
|
774
|
-
target[$routeArgs].set(propertyKey, []);
|
|
775
|
-
}
|
|
776
|
-
target[$routeArgs].get(propertyKey).push({
|
|
777
|
-
index: parameterIndex,
|
|
778
|
-
type,
|
|
779
|
-
name
|
|
780
|
-
});
|
|
781
|
-
};
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
const Body = createParamDecorator(RouteParamType.BODY);
|
|
785
|
-
const Param = createParamDecorator(RouteParamType.PARAM);
|
|
786
|
-
const Query = createParamDecorator(RouteParamType.QUERY);
|
|
787
|
-
const Headers$1 = createParamDecorator(RouteParamType.HEADER);
|
|
788
|
-
const Req = createParamDecorator(RouteParamType.REQUEST);
|
|
789
|
-
const Ctx = createParamDecorator(RouteParamType.CONTEXT);
|
|
790
|
-
function Spec(spec) {
|
|
791
|
-
return (target, propertyKey, descriptor) => {
|
|
792
|
-
if (!target[$routeSpec]) {
|
|
793
|
-
target[$routeSpec] = /* @__PURE__ */ new Map();
|
|
794
|
-
}
|
|
795
|
-
target[$routeSpec].set(propertyKey, spec);
|
|
796
|
-
};
|
|
797
|
-
}
|
|
798
|
-
function createMethodDecorator(method) {
|
|
799
|
-
return (path2 = "/") => {
|
|
800
|
-
return (target, propertyKey, descriptor) => {
|
|
801
|
-
if (!target[$routeMethods]) {
|
|
802
|
-
target[$routeMethods] = /* @__PURE__ */ new Map();
|
|
803
|
-
}
|
|
804
|
-
target[$routeMethods].set(propertyKey, {
|
|
805
|
-
method,
|
|
806
|
-
path: path2
|
|
807
|
-
});
|
|
808
|
-
};
|
|
809
|
-
};
|
|
810
|
-
}
|
|
811
|
-
const Get = createMethodDecorator("GET");
|
|
812
|
-
const Post = createMethodDecorator("POST");
|
|
813
|
-
const Put = createMethodDecorator("PUT");
|
|
814
|
-
const Delete = createMethodDecorator("DELETE");
|
|
815
|
-
const Patch = createMethodDecorator("PATCH");
|
|
816
|
-
const Options = createMethodDecorator("OPTIONS");
|
|
817
|
-
const Head = createMethodDecorator("HEAD");
|
|
818
|
-
const All = createMethodDecorator("ALL");
|
|
819
|
-
function RateLimit(options) {
|
|
820
|
-
return Use(RateLimitMiddleware(options));
|
|
821
|
-
}
|
|
822
|
-
class Container {
|
|
823
|
-
static services = /* @__PURE__ */ new Map();
|
|
824
|
-
static register(target, instance) {
|
|
825
|
-
this.services.set(target, instance);
|
|
826
|
-
}
|
|
827
|
-
static get(target) {
|
|
828
|
-
return this.services.get(target);
|
|
829
|
-
}
|
|
830
|
-
static has(target) {
|
|
831
|
-
return this.services.has(target);
|
|
832
|
-
}
|
|
833
|
-
static resolve(target) {
|
|
834
|
-
if (this.services.has(target)) {
|
|
835
|
-
return this.services.get(target);
|
|
836
|
-
}
|
|
837
|
-
const instance = new target();
|
|
838
|
-
this.services.set(target, instance);
|
|
839
|
-
return instance;
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
function Injectable() {
|
|
843
|
-
return (target) => {
|
|
844
|
-
};
|
|
845
|
-
}
|
|
846
|
-
function Inject(token) {
|
|
847
|
-
return (target, key) => {
|
|
848
|
-
Object.defineProperty(target, key, {
|
|
849
|
-
get: () => Container.resolve(token),
|
|
850
|
-
enumerable: true,
|
|
851
|
-
configurable: true
|
|
852
|
-
});
|
|
853
|
-
};
|
|
854
|
-
}
|
|
855
|
-
const compose = (middleware) => {
|
|
856
|
-
if (!middleware.length) {
|
|
857
|
-
return (context, next) => {
|
|
858
|
-
return next ? next() : Promise.resolve();
|
|
859
|
-
};
|
|
860
|
-
}
|
|
861
|
-
return function dispatch(context, next) {
|
|
862
|
-
let index = -1;
|
|
863
|
-
async function runner(i) {
|
|
864
|
-
if (i <= index) return Promise.reject(new Error("next() called multiple times"));
|
|
865
|
-
index = i;
|
|
866
|
-
if (i >= middleware.length) {
|
|
867
|
-
return next ? next() : Promise.resolve();
|
|
868
|
-
}
|
|
869
|
-
const fn = middleware[i];
|
|
870
|
-
if (!context._debug) {
|
|
871
|
-
return fn(context, () => runner(i + 1));
|
|
719
|
+
};
|
|
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"
|
|
872
728
|
}
|
|
873
|
-
|
|
874
|
-
const debugId = fn._debugId || fn.name || "anonymous";
|
|
875
|
-
const previousNode = debug.getCurrentNode();
|
|
876
|
-
debug.trackEdge(previousNode, debugId);
|
|
877
|
-
debug.setNode(debugId);
|
|
878
|
-
const start = performance.now();
|
|
729
|
+
}, async (span) => {
|
|
879
730
|
try {
|
|
880
|
-
const
|
|
881
|
-
|
|
882
|
-
return res;
|
|
731
|
+
const result = await fn.apply(this, args);
|
|
732
|
+
return result;
|
|
883
733
|
} catch (err) {
|
|
884
|
-
|
|
885
|
-
|
|
734
|
+
span.recordException(err);
|
|
735
|
+
span.setStatus({ code: api.SpanStatusCode.ERROR, message: err.message });
|
|
736
|
+
throw err;
|
|
886
737
|
} finally {
|
|
887
|
-
|
|
738
|
+
span.end();
|
|
888
739
|
}
|
|
889
|
-
}
|
|
890
|
-
return runner(0);
|
|
740
|
+
});
|
|
891
741
|
};
|
|
892
|
-
};
|
|
893
|
-
class ShokupanRequestBase {
|
|
894
|
-
method;
|
|
895
|
-
url;
|
|
896
|
-
headers;
|
|
897
|
-
body;
|
|
898
|
-
async json() {
|
|
899
|
-
return JSON.parse(this.body);
|
|
900
|
-
}
|
|
901
|
-
async text() {
|
|
902
|
-
return this.body;
|
|
903
|
-
}
|
|
904
|
-
async formData() {
|
|
905
|
-
if (this.body instanceof FormData) {
|
|
906
|
-
return this.body;
|
|
907
|
-
}
|
|
908
|
-
return new Response(this.body, { headers: this.headers }).formData();
|
|
909
|
-
}
|
|
910
|
-
constructor(props) {
|
|
911
|
-
Object.assign(this, props);
|
|
912
|
-
if (!(this.headers instanceof Headers)) {
|
|
913
|
-
this.headers = new Headers(this.headers);
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
742
|
}
|
|
917
|
-
const ShokupanRequest = ShokupanRequestBase;
|
|
918
743
|
function isObject(item) {
|
|
919
744
|
return item && typeof item === "object" && !Array.isArray(item);
|
|
920
745
|
}
|
|
@@ -948,6 +773,21 @@ function deepMerge(target, ...sources) {
|
|
|
948
773
|
}
|
|
949
774
|
return deepMerge(target, ...sources);
|
|
950
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");
|
|
951
791
|
const REGEX_PATTERNS = {
|
|
952
792
|
QUERY_INT: /parseInt\(ctx\.query\.(\w+)\)/g,
|
|
953
793
|
QUERY_FLOAT: /parseFloat\(ctx\.query\.(\w+)\)/g,
|
|
@@ -1140,9 +980,9 @@ async function generateOpenApi(rootRouter, options = {}) {
|
|
|
1140
980
|
const defaultTagName = options.defaultTag || "Application";
|
|
1141
981
|
let astRoutes = [];
|
|
1142
982
|
try {
|
|
1143
|
-
const { OpenAPIAnalyzer } = await Promise.resolve().then(() => require("./
|
|
1144
|
-
const
|
|
1145
|
-
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();
|
|
1146
986
|
astRoutes = await getAstRoutes(applications);
|
|
1147
987
|
} catch (e) {
|
|
1148
988
|
}
|
|
@@ -1329,6 +1169,11 @@ async function generateOpenApi(rootRouter, options = {}) {
|
|
|
1329
1169
|
"x-tagGroups": xTagGroups
|
|
1330
1170
|
};
|
|
1331
1171
|
}
|
|
1172
|
+
class RequestContextStore {
|
|
1173
|
+
request;
|
|
1174
|
+
span;
|
|
1175
|
+
}
|
|
1176
|
+
const asyncContext = new node_async_hooks.AsyncLocalStorage();
|
|
1332
1177
|
const eta$1 = new eta$2.Eta();
|
|
1333
1178
|
function serveStatic(config, prefix) {
|
|
1334
1179
|
const rootPath = path.resolve(config.root || ".");
|
|
@@ -1337,12 +1182,23 @@ function serveStatic(config, prefix) {
|
|
|
1337
1182
|
let relative = ctx.path.slice(normalizedPrefix.length);
|
|
1338
1183
|
if (!relative.startsWith("/") && relative.length > 0) relative = "/" + relative;
|
|
1339
1184
|
if (relative.length === 0) relative = "/";
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1185
|
+
if (relative.includes("\0")) {
|
|
1186
|
+
return ctx.json({ error: "Forbidden" }, 403);
|
|
1187
|
+
}
|
|
1188
|
+
try {
|
|
1189
|
+
relative = decodeURIComponent(relative);
|
|
1190
|
+
} catch (e) {
|
|
1191
|
+
return ctx.json({ error: "Bad Request" }, 400);
|
|
1192
|
+
}
|
|
1193
|
+
if (relative.includes("\0")) {
|
|
1343
1194
|
return ctx.json({ error: "Forbidden" }, 403);
|
|
1344
1195
|
}
|
|
1345
|
-
if (
|
|
1196
|
+
if (relative.includes("../") || relative.includes("..\\")) {
|
|
1197
|
+
return ctx.json({ error: "Forbidden" }, 403);
|
|
1198
|
+
}
|
|
1199
|
+
const requestPath = path.resolve(path.join(rootPath, relative));
|
|
1200
|
+
const normalizedRoot = path.resolve(rootPath);
|
|
1201
|
+
if (!requestPath.startsWith(normalizedRoot + path.sep) && requestPath !== normalizedRoot) {
|
|
1346
1202
|
return ctx.json({ error: "Forbidden" }, 403);
|
|
1347
1203
|
}
|
|
1348
1204
|
if (config.hooks?.onRequest) {
|
|
@@ -1470,107 +1326,6 @@ function serveStatic(config, prefix) {
|
|
|
1470
1326
|
serveStaticMiddleware.pluginName = "ServeStatic";
|
|
1471
1327
|
return serveStaticMiddleware;
|
|
1472
1328
|
}
|
|
1473
|
-
class RouterTrie {
|
|
1474
|
-
root;
|
|
1475
|
-
constructor() {
|
|
1476
|
-
this.root = this.createNode();
|
|
1477
|
-
}
|
|
1478
|
-
createNode() {
|
|
1479
|
-
return {
|
|
1480
|
-
children: {}
|
|
1481
|
-
};
|
|
1482
|
-
}
|
|
1483
|
-
insert(method, path2, handler) {
|
|
1484
|
-
let node = this.root;
|
|
1485
|
-
const segments = this.splitPath(path2);
|
|
1486
|
-
for (let i = 0; i < segments.length; i++) {
|
|
1487
|
-
const segment = segments[i];
|
|
1488
|
-
if (segment === "**") {
|
|
1489
|
-
if (!node.recursiveChild) {
|
|
1490
|
-
node.recursiveChild = this.createNode();
|
|
1491
|
-
}
|
|
1492
|
-
node = node.recursiveChild;
|
|
1493
|
-
} else if (segment === "*") {
|
|
1494
|
-
if (!node.wildcardChild) {
|
|
1495
|
-
node.wildcardChild = this.createNode();
|
|
1496
|
-
}
|
|
1497
|
-
node = node.wildcardChild;
|
|
1498
|
-
} else if (segment.startsWith(":")) {
|
|
1499
|
-
const paramName = segment.slice(1);
|
|
1500
|
-
if (!node.paramChild) {
|
|
1501
|
-
node.paramChild = this.createNode();
|
|
1502
|
-
node.paramChild.paramName = paramName;
|
|
1503
|
-
}
|
|
1504
|
-
node = node.paramChild;
|
|
1505
|
-
node.paramName = paramName;
|
|
1506
|
-
} else {
|
|
1507
|
-
if (!node.children[segment]) {
|
|
1508
|
-
node.children[segment] = this.createNode();
|
|
1509
|
-
}
|
|
1510
|
-
node = node.children[segment];
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
if (!node.handlers) {
|
|
1514
|
-
node.handlers = {};
|
|
1515
|
-
}
|
|
1516
|
-
node.handlers[method] = handler;
|
|
1517
|
-
}
|
|
1518
|
-
search(method, path2) {
|
|
1519
|
-
const segments = this.splitPath(path2);
|
|
1520
|
-
const params = {};
|
|
1521
|
-
const match = this.findNode(this.root, segments, 0, params);
|
|
1522
|
-
if (match && match.handlers) {
|
|
1523
|
-
const handler = match.handlers[method] || match.handlers["ALL"];
|
|
1524
|
-
if (handler) {
|
|
1525
|
-
return { handler, params };
|
|
1526
|
-
}
|
|
1527
|
-
if (method === "HEAD" && match.handlers["GET"]) {
|
|
1528
|
-
return { handler: match.handlers["GET"], params };
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
return null;
|
|
1532
|
-
}
|
|
1533
|
-
findNode(node, segments, index, params) {
|
|
1534
|
-
if (index === segments.length) {
|
|
1535
|
-
if (node.handlers) return node;
|
|
1536
|
-
if (node.recursiveChild && node.recursiveChild.handlers) {
|
|
1537
|
-
return node.recursiveChild;
|
|
1538
|
-
}
|
|
1539
|
-
return null;
|
|
1540
|
-
}
|
|
1541
|
-
const segment = segments[index];
|
|
1542
|
-
const child = node.children[segment];
|
|
1543
|
-
if (child) {
|
|
1544
|
-
const result = this.findNode(child, segments, index + 1, params);
|
|
1545
|
-
if (result) return result;
|
|
1546
|
-
}
|
|
1547
|
-
if (node.paramChild) {
|
|
1548
|
-
params[node.paramChild.paramName] = segment;
|
|
1549
|
-
const result = this.findNode(node.paramChild, segments, index + 1, params);
|
|
1550
|
-
if (result) return result;
|
|
1551
|
-
delete params[node.paramChild.paramName];
|
|
1552
|
-
}
|
|
1553
|
-
if (node.wildcardChild) {
|
|
1554
|
-
const result = this.findNode(node.wildcardChild, segments, index + 1, params);
|
|
1555
|
-
if (result) return result;
|
|
1556
|
-
}
|
|
1557
|
-
if (node.recursiveChild) {
|
|
1558
|
-
const remaining = segments.length - index;
|
|
1559
|
-
for (let k = 0; k <= remaining; k++) {
|
|
1560
|
-
const result = this.findNode(node.recursiveChild, segments, index + k, params);
|
|
1561
|
-
if (result) return result;
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
return null;
|
|
1565
|
-
}
|
|
1566
|
-
splitPath(path2) {
|
|
1567
|
-
if (path2 === "/" || path2 === "") return [];
|
|
1568
|
-
const s = path2.startsWith("/") ? path2.slice(1) : path2;
|
|
1569
|
-
if (s === "") return [];
|
|
1570
|
-
return s.split("/");
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
const asyncContext = new node_async_hooks.AsyncLocalStorage();
|
|
1574
1329
|
let db;
|
|
1575
1330
|
let dbPromise = null;
|
|
1576
1331
|
let RecordId;
|
|
@@ -1634,29 +1389,64 @@ const datastore = {
|
|
|
1634
1389
|
process.on("exit", async () => {
|
|
1635
1390
|
if (db) await db.close();
|
|
1636
1391
|
});
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
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
|
|
1657
1422
|
});
|
|
1658
1423
|
};
|
|
1659
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;
|
|
1660
1450
|
function getCallerInfo(skipFrames = 1) {
|
|
1661
1451
|
let file = "unknown";
|
|
1662
1452
|
let line = 0;
|
|
@@ -1682,12 +1472,120 @@ function getCallerInfo(skipFrames = 1) {
|
|
|
1682
1472
|
}
|
|
1683
1473
|
}
|
|
1684
1474
|
}
|
|
1685
|
-
} 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("/");
|
|
1686
1577
|
}
|
|
1687
|
-
return { file, line };
|
|
1688
1578
|
}
|
|
1689
|
-
const
|
|
1690
|
-
|
|
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 || {});
|
|
1691
1589
|
class ShokupanRouter {
|
|
1692
1590
|
constructor(config) {
|
|
1693
1591
|
this.config = config;
|
|
@@ -1798,216 +1696,9 @@ class ShokupanRouter {
|
|
|
1798
1696
|
throw new Error(`[Shokupan] strict controller check failed: ${controller.constructor.name || typeof controller} is not a class constructor.`);
|
|
1799
1697
|
}
|
|
1800
1698
|
if (this.isRouterInstance(controller)) {
|
|
1801
|
-
|
|
1802
|
-
throw new Error("Router is already mounted");
|
|
1803
|
-
}
|
|
1804
|
-
controller[$mountPath] = prefix;
|
|
1805
|
-
if (!controller.metadata) {
|
|
1806
|
-
const info = getCallerInfo();
|
|
1807
|
-
controller.metadata = {
|
|
1808
|
-
file: info.file,
|
|
1809
|
-
line: info.line,
|
|
1810
|
-
name: "MountedRouter"
|
|
1811
|
-
};
|
|
1812
|
-
}
|
|
1813
|
-
this[$childRouters].push(controller);
|
|
1814
|
-
controller[$parent] = this;
|
|
1815
|
-
const setRouterContext = (router) => {
|
|
1816
|
-
router[$appRoot] = this.root;
|
|
1817
|
-
router[$childRouters].forEach((child) => setRouterContext(child));
|
|
1818
|
-
};
|
|
1819
|
-
setRouterContext(controller);
|
|
1820
|
-
if (this[$appRoot]) ;
|
|
1821
|
-
controller[$appRoot] = this.root;
|
|
1822
|
-
controller[$isMounted] = true;
|
|
1699
|
+
this.mountRouter(prefix, controller);
|
|
1823
1700
|
} else {
|
|
1824
|
-
|
|
1825
|
-
if (typeof controller === "function") {
|
|
1826
|
-
instance = Container.resolve(controller);
|
|
1827
|
-
const controllerPath = controller[$controllerPath];
|
|
1828
|
-
if (controllerPath) {
|
|
1829
|
-
const p1 = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
1830
|
-
const p2 = controllerPath.startsWith("/") ? controllerPath : "/" + controllerPath;
|
|
1831
|
-
prefix = p1 + p2;
|
|
1832
|
-
if (!prefix) prefix = "/";
|
|
1833
|
-
}
|
|
1834
|
-
} else {
|
|
1835
|
-
const ctor = instance.constructor;
|
|
1836
|
-
const controllerPath = ctor[$controllerPath];
|
|
1837
|
-
if (controllerPath) {
|
|
1838
|
-
const p1 = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
1839
|
-
const p2 = controllerPath.startsWith("/") ? controllerPath : "/" + controllerPath;
|
|
1840
|
-
prefix = p1 + p2;
|
|
1841
|
-
if (!prefix) prefix = "/";
|
|
1842
|
-
}
|
|
1843
|
-
}
|
|
1844
|
-
instance[$mountPath] = prefix;
|
|
1845
|
-
const info = getCallerInfo();
|
|
1846
|
-
instance.metadata = {
|
|
1847
|
-
file: info.file,
|
|
1848
|
-
line: info.line,
|
|
1849
|
-
name: instance.constructor.name
|
|
1850
|
-
};
|
|
1851
|
-
this[$childControllers].push(instance);
|
|
1852
|
-
const controllerMiddleware = (typeof controller === "function" ? controller[$middleware] : instance[$middleware]) || [];
|
|
1853
|
-
const proto = Object.getPrototypeOf(instance);
|
|
1854
|
-
const methods = /* @__PURE__ */ new Set();
|
|
1855
|
-
let current = proto;
|
|
1856
|
-
while (current && current !== Object.prototype) {
|
|
1857
|
-
Object.getOwnPropertyNames(current).forEach((name) => methods.add(name));
|
|
1858
|
-
current = Object.getPrototypeOf(current);
|
|
1859
|
-
}
|
|
1860
|
-
Object.getOwnPropertyNames(instance).forEach((name) => methods.add(name));
|
|
1861
|
-
const decoratedRoutes = instance[$routeMethods] || proto && proto[$routeMethods];
|
|
1862
|
-
const decoratedArgs = instance[$routeArgs] || proto && proto[$routeArgs];
|
|
1863
|
-
const methodMiddlewareMap = instance[$middleware] || proto && proto[$middleware];
|
|
1864
|
-
let routesAttached = 0;
|
|
1865
|
-
for (let i = 0; i < Array.from(methods).length; i++) {
|
|
1866
|
-
const name = Array.from(methods)[i];
|
|
1867
|
-
if (name === "constructor") continue;
|
|
1868
|
-
if (["arguments", "caller", "callee"].includes(name)) continue;
|
|
1869
|
-
const originalHandler = instance[name];
|
|
1870
|
-
if (typeof originalHandler !== "function") continue;
|
|
1871
|
-
let method;
|
|
1872
|
-
let subPath = "";
|
|
1873
|
-
if (decoratedRoutes && decoratedRoutes.has(name)) {
|
|
1874
|
-
const config = decoratedRoutes.get(name);
|
|
1875
|
-
method = config.method;
|
|
1876
|
-
subPath = config.path;
|
|
1877
|
-
} else {
|
|
1878
|
-
for (let j = 0; j < HTTPMethods.length; j++) {
|
|
1879
|
-
const m = HTTPMethods[j];
|
|
1880
|
-
if (name.toUpperCase().startsWith(m)) {
|
|
1881
|
-
method = m;
|
|
1882
|
-
const rest = name.slice(m.length);
|
|
1883
|
-
if (rest.length === 0) {
|
|
1884
|
-
subPath = "/";
|
|
1885
|
-
} else {
|
|
1886
|
-
subPath = "";
|
|
1887
|
-
let buffer = "";
|
|
1888
|
-
const flush = () => {
|
|
1889
|
-
if (buffer.length > 0) {
|
|
1890
|
-
subPath += "/" + buffer.toLowerCase();
|
|
1891
|
-
buffer = "";
|
|
1892
|
-
}
|
|
1893
|
-
};
|
|
1894
|
-
for (let i2 = 0; i2 < rest.length; i2++) {
|
|
1895
|
-
const char = rest[i2];
|
|
1896
|
-
if (char === "$") {
|
|
1897
|
-
flush();
|
|
1898
|
-
subPath += "/:";
|
|
1899
|
-
continue;
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
|
-
subPath = rest.replace(/\$/g, "/:").replace(/([a-z0-9])([A-Z])/g, "$1/$2").toLowerCase();
|
|
1903
|
-
if (!subPath.startsWith("/")) {
|
|
1904
|
-
subPath = "/" + subPath;
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
break;
|
|
1908
|
-
}
|
|
1909
|
-
}
|
|
1910
|
-
}
|
|
1911
|
-
if (method) {
|
|
1912
|
-
routesAttached++;
|
|
1913
|
-
const cleanPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
1914
|
-
const cleanSubPath = subPath === "/" ? "" : subPath;
|
|
1915
|
-
let joined;
|
|
1916
|
-
if (cleanSubPath.length === 0) {
|
|
1917
|
-
joined = cleanPrefix;
|
|
1918
|
-
} else if (cleanSubPath.startsWith("/")) {
|
|
1919
|
-
joined = cleanPrefix + cleanSubPath;
|
|
1920
|
-
} else {
|
|
1921
|
-
joined = cleanPrefix + "/" + cleanSubPath;
|
|
1922
|
-
}
|
|
1923
|
-
const fullPath = joined || "/";
|
|
1924
|
-
const normalizedPath = fullPath.replace(/\/+/g, "/");
|
|
1925
|
-
const methodMw = methodMiddlewareMap instanceof Map ? methodMiddlewareMap.get(name) || [] : [];
|
|
1926
|
-
const allMiddleware = [...controllerMiddleware, ...methodMw];
|
|
1927
|
-
const routeArgs = decoratedArgs && decoratedArgs.get(name);
|
|
1928
|
-
const wrappedHandler = async (ctx) => {
|
|
1929
|
-
let args = [ctx];
|
|
1930
|
-
if (routeArgs?.length > 0) {
|
|
1931
|
-
args = [];
|
|
1932
|
-
const sortedArgs = [...routeArgs].sort((a, b) => a.index - b.index);
|
|
1933
|
-
for (let k = 0; k < sortedArgs.length; k++) {
|
|
1934
|
-
const arg = sortedArgs[k];
|
|
1935
|
-
switch (arg.type) {
|
|
1936
|
-
case RouteParamType.BODY:
|
|
1937
|
-
try {
|
|
1938
|
-
if (ctx.req.headers.get("content-type")?.includes("application/json")) {
|
|
1939
|
-
args[arg.index] = await ctx.req.json();
|
|
1940
|
-
} else {
|
|
1941
|
-
const text = await ctx.req.text();
|
|
1942
|
-
if (!text) {
|
|
1943
|
-
args[arg.index] = {};
|
|
1944
|
-
} else {
|
|
1945
|
-
args[arg.index] = JSON.parse(text);
|
|
1946
|
-
}
|
|
1947
|
-
}
|
|
1948
|
-
} catch (e) {
|
|
1949
|
-
const err = new Error("Invalid JSON body");
|
|
1950
|
-
err.status = 400;
|
|
1951
|
-
throw err;
|
|
1952
|
-
}
|
|
1953
|
-
break;
|
|
1954
|
-
case RouteParamType.PARAM:
|
|
1955
|
-
args[arg.index] = arg.name ? ctx.params[arg.name] : ctx.params;
|
|
1956
|
-
break;
|
|
1957
|
-
case RouteParamType.QUERY: {
|
|
1958
|
-
const url = new URL(ctx.req.url);
|
|
1959
|
-
if (arg.name) {
|
|
1960
|
-
const vals = url.searchParams.getAll(arg.name);
|
|
1961
|
-
args[arg.index] = vals.length > 1 ? vals : vals[0];
|
|
1962
|
-
} else {
|
|
1963
|
-
const query = {};
|
|
1964
|
-
const keys = Object.keys(url.searchParams);
|
|
1965
|
-
for (let k2 = 0; k2 < keys.length; k2++) {
|
|
1966
|
-
const key = keys[k2];
|
|
1967
|
-
const vals = url.searchParams.getAll(key);
|
|
1968
|
-
query[key] = vals.length > 1 ? vals : vals[0];
|
|
1969
|
-
}
|
|
1970
|
-
args[arg.index] = query;
|
|
1971
|
-
}
|
|
1972
|
-
break;
|
|
1973
|
-
}
|
|
1974
|
-
case RouteParamType.HEADER:
|
|
1975
|
-
args[arg.index] = arg.name ? ctx.req.headers.get(arg.name) : ctx.req.headers;
|
|
1976
|
-
break;
|
|
1977
|
-
case RouteParamType.REQUEST:
|
|
1978
|
-
args[arg.index] = ctx.req;
|
|
1979
|
-
break;
|
|
1980
|
-
case RouteParamType.CONTEXT:
|
|
1981
|
-
args[arg.index] = ctx;
|
|
1982
|
-
break;
|
|
1983
|
-
}
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1986
|
-
const tracedOriginalHandler = ctx.app?.applicationConfig.enableTracing ? traceHandler(originalHandler, normalizedPath) : originalHandler;
|
|
1987
|
-
return tracedOriginalHandler.apply(instance, args);
|
|
1988
|
-
};
|
|
1989
|
-
let finalHandler = wrappedHandler;
|
|
1990
|
-
if (allMiddleware.length > 0) {
|
|
1991
|
-
const composed = compose(allMiddleware);
|
|
1992
|
-
finalHandler = async (ctx) => {
|
|
1993
|
-
return composed(ctx, () => wrappedHandler(ctx));
|
|
1994
|
-
};
|
|
1995
|
-
}
|
|
1996
|
-
finalHandler.originalHandler = originalHandler;
|
|
1997
|
-
if (finalHandler !== wrappedHandler) {
|
|
1998
|
-
wrappedHandler.originalHandler = originalHandler;
|
|
1999
|
-
}
|
|
2000
|
-
const tagName = instance.constructor.name;
|
|
2001
|
-
const decoratedSpecs = instance[$routeSpec] || proto && proto[$routeSpec];
|
|
2002
|
-
const userSpec = decoratedSpecs && decoratedSpecs.get(name);
|
|
2003
|
-
const spec = { tags: [tagName], ...userSpec };
|
|
2004
|
-
this.add({ method, path: normalizedPath, handler: finalHandler, spec, controller: instance });
|
|
2005
|
-
}
|
|
2006
|
-
}
|
|
2007
|
-
if (routesAttached === 0) {
|
|
2008
|
-
console.warn(`No routes attached to controller ${instance.constructor.name}`);
|
|
2009
|
-
}
|
|
2010
|
-
instance[$isMounted] = true;
|
|
1701
|
+
this.scanControllerRoutes(prefix, controller);
|
|
2011
1702
|
}
|
|
2012
1703
|
return this;
|
|
2013
1704
|
}
|
|
@@ -2045,8 +1736,6 @@ class ShokupanRouter {
|
|
|
2045
1736
|
*/
|
|
2046
1737
|
async internalRequest(arg) {
|
|
2047
1738
|
const options = typeof arg === "string" ? { path: arg } : arg;
|
|
2048
|
-
const store = asyncContext.getStore();
|
|
2049
|
-
store?.get("req");
|
|
2050
1739
|
let url = options.path;
|
|
2051
1740
|
if (!url.startsWith("http")) {
|
|
2052
1741
|
const base = `http://${this.rootConfig?.hostname || "localhost"}:${this.rootConfig.port || 3e3}`;
|
|
@@ -2158,6 +1847,220 @@ class ShokupanRouter {
|
|
|
2158
1847
|
wrapped.originalHandler = originalHandler.originalHandler ?? originalHandler;
|
|
2159
1848
|
return wrapped;
|
|
2160
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
|
+
}
|
|
2161
2064
|
/**
|
|
2162
2065
|
* Find a route matching the given method and path.
|
|
2163
2066
|
* @param method HTTP method
|
|
@@ -2191,10 +2094,13 @@ class ShokupanRouter {
|
|
|
2191
2094
|
}
|
|
2192
2095
|
parsePath(path2) {
|
|
2193
2096
|
const keys = [];
|
|
2097
|
+
if (path2.length > 2048) {
|
|
2098
|
+
throw new Error("Path too long");
|
|
2099
|
+
}
|
|
2194
2100
|
const pattern = path2.replace(/:([a-zA-Z0-9_]+)/g, (_, key) => {
|
|
2195
2101
|
keys.push(key);
|
|
2196
|
-
return "([^/]
|
|
2197
|
-
}).replace(/\*\*/g, "
|
|
2102
|
+
return "([^/]{1,255})";
|
|
2103
|
+
}).replace(/\*\*/g, ".{0,1000}").replace(/\*/g, "[^/]{1,255}");
|
|
2198
2104
|
return {
|
|
2199
2105
|
regex: new RegExp(`^${pattern}$`),
|
|
2200
2106
|
keys
|
|
@@ -2281,7 +2187,7 @@ class ShokupanRouter {
|
|
|
2281
2187
|
if (effectiveRenderer) {
|
|
2282
2188
|
const innerHandler = wrappedHandler;
|
|
2283
2189
|
wrappedHandler = async (ctx) => {
|
|
2284
|
-
ctx.
|
|
2190
|
+
ctx.setRenderer(effectiveRenderer);
|
|
2285
2191
|
return innerHandler(ctx);
|
|
2286
2192
|
};
|
|
2287
2193
|
}
|
|
@@ -2683,6 +2589,13 @@ class Shokupan extends ShokupanRouter {
|
|
|
2683
2589
|
}
|
|
2684
2590
|
return this;
|
|
2685
2591
|
}
|
|
2592
|
+
/**
|
|
2593
|
+
* Registers a plugin.
|
|
2594
|
+
*/
|
|
2595
|
+
register(plugin, options) {
|
|
2596
|
+
plugin.onInit(this, options);
|
|
2597
|
+
return this;
|
|
2598
|
+
}
|
|
2686
2599
|
startupHooks = [];
|
|
2687
2600
|
/**
|
|
2688
2601
|
* Registers a callback to be executed before the server starts listening.
|
|
@@ -2745,7 +2658,7 @@ class Shokupan extends ShokupanRouter {
|
|
|
2745
2658
|
};
|
|
2746
2659
|
let factory = this.applicationConfig.serverFactory;
|
|
2747
2660
|
if (!factory && typeof Bun === "undefined") {
|
|
2748
|
-
const { createHttpServer } = await Promise.resolve().then(() => require("./server-
|
|
2661
|
+
const { createHttpServer } = await Promise.resolve().then(() => require("./http-server-DFhwlK8e.cjs"));
|
|
2749
2662
|
factory = createHttpServer();
|
|
2750
2663
|
}
|
|
2751
2664
|
const server = factory ? await factory(serveOptions) : Bun.serve(serveOptions);
|
|
@@ -2814,19 +2727,19 @@ class Shokupan extends ShokupanRouter {
|
|
|
2814
2727
|
"http.method": req.method
|
|
2815
2728
|
}
|
|
2816
2729
|
};
|
|
2817
|
-
const parent = store?.
|
|
2730
|
+
const parent = store?.span;
|
|
2818
2731
|
const ctx = parent ? api.trace.setSpan(api.context.active(), parent) : void 0;
|
|
2819
2732
|
return tracer2.startActiveSpan(`${req.method} ${new URL(req.url).pathname}`, attrs, ctx, (span) => {
|
|
2820
|
-
const
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
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()));
|
|
2824
2737
|
});
|
|
2825
2738
|
}
|
|
2826
2739
|
if (this.applicationConfig.enableAsyncLocalStorage) {
|
|
2827
|
-
const
|
|
2828
|
-
|
|
2829
|
-
return asyncContext.run(
|
|
2740
|
+
const ctxStore = new RequestContextStore();
|
|
2741
|
+
ctxStore.request = req;
|
|
2742
|
+
return asyncContext.run(ctxStore, () => this.handleRequest(req, server));
|
|
2830
2743
|
}
|
|
2831
2744
|
return this.handleRequest(req, server);
|
|
2832
2745
|
}
|
|
@@ -2848,6 +2761,7 @@ class Shokupan extends ShokupanRouter {
|
|
|
2848
2761
|
const bodyParsing = ["POST", "PUT", "PATCH", "DELETE"].includes(req.method) ? ctx.parseBody() : Promise.resolve();
|
|
2849
2762
|
const match = this.find(req.method, ctx.path);
|
|
2850
2763
|
if (match) {
|
|
2764
|
+
ctx._routeMatched = true;
|
|
2851
2765
|
ctx.params = match.params;
|
|
2852
2766
|
await bodyParsing;
|
|
2853
2767
|
return match.handler(ctx);
|
|
@@ -2862,10 +2776,14 @@ class Shokupan extends ShokupanRouter {
|
|
|
2862
2776
|
} else if (result === null || result === void 0) {
|
|
2863
2777
|
if (ctx._finalResponse instanceof Response) {
|
|
2864
2778
|
response = ctx._finalResponse;
|
|
2865
|
-
} else if (ctx.
|
|
2779
|
+
} else if (ctx._routeMatched) {
|
|
2866
2780
|
response = ctx.send(null, { status: ctx.response.status, headers: ctx.response.headers });
|
|
2867
2781
|
} else {
|
|
2868
|
-
|
|
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
|
+
}
|
|
2869
2787
|
}
|
|
2870
2788
|
} else if (typeof result === "object") {
|
|
2871
2789
|
response = ctx.json(result);
|
|
@@ -2877,7 +2795,7 @@ class Shokupan extends ShokupanRouter {
|
|
|
2877
2795
|
return response;
|
|
2878
2796
|
} catch (err) {
|
|
2879
2797
|
console.error(err);
|
|
2880
|
-
const span = asyncContext.getStore()?.
|
|
2798
|
+
const span = asyncContext.getStore()?.span;
|
|
2881
2799
|
if (span) span.setStatus({ code: 2 });
|
|
2882
2800
|
const status = err.status || err.statusCode || 500;
|
|
2883
2801
|
const body = { error: err.message || "Internal Server Error" };
|
|
@@ -2885,31 +2803,195 @@ class Shokupan extends ShokupanRouter {
|
|
|
2885
2803
|
await this.runHooks("onError", ctx, err);
|
|
2886
2804
|
return ctx.json(body, status);
|
|
2887
2805
|
}
|
|
2888
|
-
};
|
|
2889
|
-
let executionPromise = handle();
|
|
2890
|
-
const timeoutMs = this.applicationConfig.requestTimeout;
|
|
2891
|
-
if (timeoutMs && timeoutMs > 0) {
|
|
2892
|
-
let timeoutId;
|
|
2893
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
2894
|
-
timeoutId = setTimeout(async () => {
|
|
2895
|
-
controller.abort();
|
|
2896
|
-
await this.runHooks("onRequestTimeout", ctx);
|
|
2897
|
-
reject(new Error("Request Timeout"));
|
|
2898
|
-
}, 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
|
|
2899
2954
|
});
|
|
2900
|
-
|
|
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();
|
|
2901
2968
|
}
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
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();
|
|
2905
2977
|
}
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
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));
|
|
2913
2995
|
}
|
|
2914
2996
|
class AuthPlugin extends ShokupanRouter {
|
|
2915
2997
|
constructor(authConfig) {
|
|
@@ -2919,6 +3001,13 @@ class AuthPlugin extends ShokupanRouter {
|
|
|
2919
3001
|
this.init();
|
|
2920
3002
|
}
|
|
2921
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
|
+
}
|
|
2922
3011
|
getProviderInstance(name, p) {
|
|
2923
3012
|
switch (name) {
|
|
2924
3013
|
case "github":
|
|
@@ -2982,9 +3071,10 @@ class AuthPlugin extends ShokupanRouter {
|
|
|
2982
3071
|
} else {
|
|
2983
3072
|
return ctx.text("Provider config error", 500);
|
|
2984
3073
|
}
|
|
2985
|
-
|
|
3074
|
+
const isSecure = ctx.secure;
|
|
3075
|
+
ctx.res.headers.set("Set-Cookie", `oauth_state=${state}; Path=/; HttpOnly; SameSite=Lax${isSecure ? "; Secure" : ""}; Max-Age=600`);
|
|
2986
3076
|
if (codeVerifier) {
|
|
2987
|
-
ctx.res.headers.append("Set-Cookie", `oauth_verifier=${codeVerifier}; Path=/; HttpOnly; Max-Age=600`);
|
|
3077
|
+
ctx.res.headers.append("Set-Cookie", `oauth_verifier=${codeVerifier}; Path=/; HttpOnly; SameSite=Lax${isSecure ? "; Secure" : ""}; Max-Age=600`);
|
|
2988
3078
|
}
|
|
2989
3079
|
return ctx.redirect(url.toString());
|
|
2990
3080
|
});
|
|
@@ -3025,7 +3115,7 @@ class AuthPlugin extends ShokupanRouter {
|
|
|
3025
3115
|
return ctx.json({ token: jwt, user });
|
|
3026
3116
|
} catch (e) {
|
|
3027
3117
|
console.error("Auth Error", e);
|
|
3028
|
-
return ctx.text("Authentication failed
|
|
3118
|
+
return ctx.text("Authentication failed. Please try again.", 500);
|
|
3029
3119
|
}
|
|
3030
3120
|
});
|
|
3031
3121
|
}
|
|
@@ -3135,8 +3225,196 @@ class AuthPlugin extends ShokupanRouter {
|
|
|
3135
3225
|
};
|
|
3136
3226
|
}
|
|
3137
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
|
+
}
|
|
3138
3415
|
function Compression(options = {}) {
|
|
3139
3416
|
const threshold = options.threshold ?? 512;
|
|
3417
|
+
const allowedAlgorithms = new Set(options.allowedAlgorithms ?? ["br", "gzip", "zstd", "deflate"]);
|
|
3140
3418
|
const compressionMiddleware = async function CompressionMiddleware(ctx, next) {
|
|
3141
3419
|
const acceptEncoding = ctx.headers.get("accept-encoding") || "";
|
|
3142
3420
|
let method = null;
|
|
@@ -3149,6 +3427,9 @@ function Compression(options = {}) {
|
|
|
3149
3427
|
} else if (acceptEncoding.includes("gzip")) method = "gzip";
|
|
3150
3428
|
else if (acceptEncoding.includes("deflate")) method = "deflate";
|
|
3151
3429
|
if (!method) return next();
|
|
3430
|
+
if (!allowedAlgorithms.has(method)) {
|
|
3431
|
+
return next();
|
|
3432
|
+
}
|
|
3152
3433
|
let response = await next();
|
|
3153
3434
|
if (!(response instanceof Response) && ctx._finalResponse instanceof Response) {
|
|
3154
3435
|
response = ctx._finalResponse;
|
|
@@ -3236,14 +3517,21 @@ function Cors(options = {}) {
|
|
|
3236
3517
|
const origin = ctx.headers.get("origin");
|
|
3237
3518
|
const set = (k, v) => headers.set(k, v);
|
|
3238
3519
|
const append = (k, v) => headers.append(k, v);
|
|
3520
|
+
if (origin === "null" && opts.origin !== "null") {
|
|
3521
|
+
return next();
|
|
3522
|
+
}
|
|
3239
3523
|
if (opts.origin === "*") {
|
|
3240
3524
|
set("Access-Control-Allow-Origin", "*");
|
|
3241
3525
|
} else if (typeof opts.origin === "string") {
|
|
3242
3526
|
set("Access-Control-Allow-Origin", opts.origin);
|
|
3243
3527
|
} else if (Array.isArray(opts.origin)) {
|
|
3244
|
-
if (origin
|
|
3245
|
-
|
|
3246
|
-
|
|
3528
|
+
if (origin) {
|
|
3529
|
+
const normalizedOrigin = origin.toLowerCase();
|
|
3530
|
+
const normalizedAllowed = opts.origin.map((o) => o.toLowerCase());
|
|
3531
|
+
if (normalizedAllowed.includes(normalizedOrigin)) {
|
|
3532
|
+
set("Access-Control-Allow-Origin", origin);
|
|
3533
|
+
append("Vary", "Origin");
|
|
3534
|
+
}
|
|
3247
3535
|
}
|
|
3248
3536
|
} else if (typeof opts.origin === "function") {
|
|
3249
3537
|
const allowed = opts.origin(ctx);
|
|
@@ -3696,77 +3984,6 @@ function enableOpenApiValidation(app) {
|
|
|
3696
3984
|
precompileValidators(app, spec);
|
|
3697
3985
|
});
|
|
3698
3986
|
}
|
|
3699
|
-
const eta = new eta$2.Eta();
|
|
3700
|
-
class ScalarPlugin extends ShokupanRouter {
|
|
3701
|
-
constructor(pluginOptions = {}) {
|
|
3702
|
-
pluginOptions.config ??= {};
|
|
3703
|
-
super();
|
|
3704
|
-
this.pluginOptions = pluginOptions;
|
|
3705
|
-
this.init();
|
|
3706
|
-
}
|
|
3707
|
-
init() {
|
|
3708
|
-
this.get("/", (ctx) => {
|
|
3709
|
-
let path2 = ctx.url.toString();
|
|
3710
|
-
if (!path2.endsWith("/")) path2 += "/";
|
|
3711
|
-
return ctx.html(eta.renderString(`<!doctype html>
|
|
3712
|
-
<html>
|
|
3713
|
-
<head>
|
|
3714
|
-
<title>API Reference</title>
|
|
3715
|
-
<meta charset = "utf-8" />
|
|
3716
|
-
<meta name="viewport" content = "width=device-width, initial-scale=1" />
|
|
3717
|
-
</head>
|
|
3718
|
-
|
|
3719
|
-
<body>
|
|
3720
|
-
<div id="app"></div>
|
|
3721
|
-
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
|
|
3722
|
-
<script>
|
|
3723
|
-
Scalar.createApiReference('#app', [{ ...<%~ JSON.stringify(it.config.baseDocument) %>,
|
|
3724
|
-
url: "<%= it.path %>openapi.json",
|
|
3725
|
-
}
|
|
3726
|
-
])
|
|
3727
|
-
<\/script>
|
|
3728
|
-
</body>
|
|
3729
|
-
|
|
3730
|
-
</html>`, { path: path2, config: this.pluginOptions }));
|
|
3731
|
-
});
|
|
3732
|
-
this.get("/openapi.json", async (ctx) => {
|
|
3733
|
-
let spec;
|
|
3734
|
-
if (this.root.openApiSpec) {
|
|
3735
|
-
try {
|
|
3736
|
-
spec = structuredClone(this.root.openApiSpec);
|
|
3737
|
-
} catch (e) {
|
|
3738
|
-
spec = Object.assign({}, this.root.openApiSpec);
|
|
3739
|
-
}
|
|
3740
|
-
} else {
|
|
3741
|
-
spec = await (this.root || this).generateApiSpec();
|
|
3742
|
-
}
|
|
3743
|
-
if (this.pluginOptions.baseDocument) {
|
|
3744
|
-
deepMerge(spec, this.pluginOptions.baseDocument);
|
|
3745
|
-
}
|
|
3746
|
-
return ctx.json(spec);
|
|
3747
|
-
});
|
|
3748
|
-
}
|
|
3749
|
-
// New lifecycle method to be called by router.mount
|
|
3750
|
-
onMount(parent) {
|
|
3751
|
-
if (parent.onStart) {
|
|
3752
|
-
parent.onStart(async () => {
|
|
3753
|
-
if (this.pluginOptions.enableStaticAnalysis) {
|
|
3754
|
-
try {
|
|
3755
|
-
const entrypoint = process.argv[1];
|
|
3756
|
-
console.log(`[ScalarPlugin] Running eager static analysis on entrypoint: ${entrypoint}`);
|
|
3757
|
-
const analyzer = new openapiAnalyzer.OpenAPIAnalyzer(process.cwd(), entrypoint);
|
|
3758
|
-
let staticSpec = await analyzer.analyze();
|
|
3759
|
-
if (!this.pluginOptions.baseDocument) this.pluginOptions.baseDocument = {};
|
|
3760
|
-
deepMerge(this.pluginOptions.baseDocument, staticSpec);
|
|
3761
|
-
console.log("[ScalarPlugin] Static analysis completed successfully.");
|
|
3762
|
-
} catch (err) {
|
|
3763
|
-
console.error("[ScalarPlugin] Failed to run static analysis:", err);
|
|
3764
|
-
}
|
|
3765
|
-
}
|
|
3766
|
-
});
|
|
3767
|
-
}
|
|
3768
|
-
}
|
|
3769
|
-
}
|
|
3770
3987
|
function SecurityHeaders(options = {}) {
|
|
3771
3988
|
const securityHeadersMiddleware = async function SecurityHeadersMiddleware(ctx, next) {
|
|
3772
3989
|
const headers = {};
|
|
@@ -3890,18 +4107,18 @@ class MemoryStore extends events.EventEmitter {
|
|
|
3890
4107
|
}
|
|
3891
4108
|
set(sid, sess, cb) {
|
|
3892
4109
|
this.sessions[sid] = JSON.stringify(sess);
|
|
3893
|
-
cb
|
|
4110
|
+
cb?.();
|
|
3894
4111
|
}
|
|
3895
4112
|
destroy(sid, cb) {
|
|
3896
4113
|
delete this.sessions[sid];
|
|
3897
|
-
cb
|
|
4114
|
+
cb?.();
|
|
3898
4115
|
}
|
|
3899
4116
|
touch(sid, sess, cb) {
|
|
3900
4117
|
const current = this.sessions[sid];
|
|
3901
4118
|
if (current) {
|
|
3902
4119
|
this.sessions[sid] = JSON.stringify(sess);
|
|
3903
4120
|
}
|
|
3904
|
-
cb
|
|
4121
|
+
cb?.();
|
|
3905
4122
|
}
|
|
3906
4123
|
all(cb) {
|
|
3907
4124
|
const result = {};
|
|
@@ -3917,7 +4134,7 @@ class MemoryStore extends events.EventEmitter {
|
|
|
3917
4134
|
}
|
|
3918
4135
|
clear(cb) {
|
|
3919
4136
|
this.sessions = {};
|
|
3920
|
-
cb
|
|
4137
|
+
cb?.();
|
|
3921
4138
|
}
|
|
3922
4139
|
}
|
|
3923
4140
|
function sign(val, secret) {
|
|
@@ -3930,11 +4147,17 @@ function unsign(input, secret) {
|
|
|
3930
4147
|
if (typeof secret !== "string") throw new TypeError("Secret string must be provided.");
|
|
3931
4148
|
const tentValue = input.slice(0, input.lastIndexOf("."));
|
|
3932
4149
|
const expectedInput = sign(tentValue, secret);
|
|
3933
|
-
const
|
|
3934
|
-
const
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
4150
|
+
const maxLength = Math.max(expectedInput.length, input.length);
|
|
4151
|
+
const paddedExpected = Buffer.alloc(maxLength);
|
|
4152
|
+
const paddedInput = Buffer.alloc(maxLength);
|
|
4153
|
+
Buffer.from(expectedInput).copy(paddedExpected);
|
|
4154
|
+
Buffer.from(input).copy(paddedInput);
|
|
4155
|
+
try {
|
|
4156
|
+
const valid = require("crypto").timingSafeEqual(paddedExpected, paddedInput);
|
|
4157
|
+
return valid ? tentValue : false;
|
|
4158
|
+
} catch {
|
|
4159
|
+
return false;
|
|
4160
|
+
}
|
|
3938
4161
|
}
|
|
3939
4162
|
function Session(options) {
|
|
3940
4163
|
const store = options.store || new MemoryStore();
|
|
@@ -4106,6 +4329,7 @@ exports.$routes = $routes;
|
|
|
4106
4329
|
exports.All = All;
|
|
4107
4330
|
exports.AuthPlugin = AuthPlugin;
|
|
4108
4331
|
exports.Body = Body;
|
|
4332
|
+
exports.ClusterPlugin = ClusterPlugin;
|
|
4109
4333
|
exports.Compression = Compression;
|
|
4110
4334
|
exports.Container = Container;
|
|
4111
4335
|
exports.Controller = Controller;
|
|
@@ -4129,16 +4353,13 @@ exports.RateLimit = RateLimit;
|
|
|
4129
4353
|
exports.RateLimitMiddleware = RateLimitMiddleware;
|
|
4130
4354
|
exports.Req = Req;
|
|
4131
4355
|
exports.RouteParamType = RouteParamType;
|
|
4132
|
-
exports.RouterRegistry = RouterRegistry;
|
|
4133
4356
|
exports.ScalarPlugin = ScalarPlugin;
|
|
4134
4357
|
exports.SecurityHeaders = SecurityHeaders;
|
|
4135
4358
|
exports.Session = Session;
|
|
4136
4359
|
exports.Shokupan = Shokupan;
|
|
4137
|
-
exports.ShokupanApplicationTree = ShokupanApplicationTree;
|
|
4138
4360
|
exports.ShokupanContext = ShokupanContext;
|
|
4139
4361
|
exports.ShokupanRequest = ShokupanRequest;
|
|
4140
4362
|
exports.ShokupanResponse = ShokupanResponse;
|
|
4141
|
-
exports.ShokupanRouter = ShokupanRouter;
|
|
4142
4363
|
exports.Spec = Spec;
|
|
4143
4364
|
exports.Use = Use;
|
|
4144
4365
|
exports.ValidationError = ValidationError;
|