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.js
CHANGED
|
@@ -1,81 +1,21 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { trace, SpanKind, SpanStatusCode, context } from "@opentelemetry/api";
|
|
3
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
4
|
import { Eta } from "eta";
|
|
3
5
|
import { stat, readdir, readFile as readFile$1 } from "fs/promises";
|
|
4
|
-
import { resolve, join, basename } from "path";
|
|
5
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
|
-
import { trace, SpanKind, SpanStatusCode, context } from "@opentelemetry/api";
|
|
6
|
+
import { resolve, join, sep, basename } from "path";
|
|
7
7
|
import * as os from "node:os";
|
|
8
|
+
import os__default from "node:os";
|
|
8
9
|
import { OAuth2Client, Okta, Auth0, Apple, MicrosoftEntraId, Google, GitHub, generateState, generateCodeVerifier } from "arctic";
|
|
9
10
|
import * as jose from "jose";
|
|
11
|
+
import cluster from "node:cluster";
|
|
12
|
+
import net from "node:net";
|
|
13
|
+
import { OpenAPIAnalyzer } from "./analyzer-Ce_7JxZh.js";
|
|
10
14
|
import * as zlib from "node:zlib";
|
|
11
15
|
import Ajv from "ajv";
|
|
12
16
|
import addFormats from "ajv-formats";
|
|
13
|
-
import { OpenAPIAnalyzer } from "./openapi-analyzer-Ce_7JxZh.js";
|
|
14
17
|
import { randomUUID, createHmac } from "crypto";
|
|
15
18
|
import { EventEmitter } from "events";
|
|
16
|
-
class ShokupanResponse {
|
|
17
|
-
_headers = null;
|
|
18
|
-
_status = 200;
|
|
19
|
-
/**
|
|
20
|
-
* Get the current headers
|
|
21
|
-
*/
|
|
22
|
-
get headers() {
|
|
23
|
-
if (!this._headers) this._headers = new Headers();
|
|
24
|
-
return this._headers;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Get the current status code
|
|
28
|
-
*/
|
|
29
|
-
get status() {
|
|
30
|
-
return this._status;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Set the status code
|
|
34
|
-
*/
|
|
35
|
-
set status(code) {
|
|
36
|
-
this._status = code;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Set a response header
|
|
40
|
-
* @param key Header name
|
|
41
|
-
* @param value Header value
|
|
42
|
-
*/
|
|
43
|
-
set(key, value) {
|
|
44
|
-
if (!this._headers) this._headers = new Headers();
|
|
45
|
-
this._headers.set(key, value);
|
|
46
|
-
return this;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Append to a response header
|
|
50
|
-
* @param key Header name
|
|
51
|
-
* @param value Header value
|
|
52
|
-
*/
|
|
53
|
-
append(key, value) {
|
|
54
|
-
if (!this._headers) this._headers = new Headers();
|
|
55
|
-
this._headers.append(key, value);
|
|
56
|
-
return this;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Get a response header value
|
|
60
|
-
* @param key Header name
|
|
61
|
-
*/
|
|
62
|
-
get(key) {
|
|
63
|
-
return this._headers?.get(key) || null;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Check if a header exists
|
|
67
|
-
* @param key Header name
|
|
68
|
-
*/
|
|
69
|
-
has(key) {
|
|
70
|
-
return this._headers?.has(key) || false;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Internal: check if headers have been initialized/modified
|
|
74
|
-
*/
|
|
75
|
-
get hasPopulatedHeaders() {
|
|
76
|
-
return this._headers !== null;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
19
|
const VALID_HTTP_STATUSES = /* @__PURE__ */ new Set([
|
|
80
20
|
100,
|
|
81
21
|
101,
|
|
@@ -142,6 +82,78 @@ const VALID_HTTP_STATUSES = /* @__PURE__ */ new Set([
|
|
|
142
82
|
511
|
|
143
83
|
]);
|
|
144
84
|
const VALID_REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
85
|
+
class ShokupanResponse {
|
|
86
|
+
_headers = null;
|
|
87
|
+
_status = 200;
|
|
88
|
+
/**
|
|
89
|
+
* Get the current headers
|
|
90
|
+
*/
|
|
91
|
+
get headers() {
|
|
92
|
+
if (!this._headers) this._headers = new Headers();
|
|
93
|
+
return this._headers;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get the current status code
|
|
97
|
+
*/
|
|
98
|
+
get status() {
|
|
99
|
+
return this._status;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Set the status code
|
|
103
|
+
*/
|
|
104
|
+
set status(code) {
|
|
105
|
+
this._status = code;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Set a response header
|
|
109
|
+
* @param key Header name
|
|
110
|
+
* @param value Header value
|
|
111
|
+
*/
|
|
112
|
+
set(key, value) {
|
|
113
|
+
if (!this._headers) this._headers = new Headers();
|
|
114
|
+
this._headers.set(key, value);
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Append to a response header
|
|
119
|
+
* @param key Header name
|
|
120
|
+
* @param value Header value
|
|
121
|
+
*/
|
|
122
|
+
append(key, value) {
|
|
123
|
+
if (!this._headers) this._headers = new Headers();
|
|
124
|
+
this._headers.append(key, value);
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get a response header value
|
|
129
|
+
* @param key Header name
|
|
130
|
+
*/
|
|
131
|
+
get(key) {
|
|
132
|
+
return this._headers?.get(key) || null;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Check if a header exists
|
|
136
|
+
* @param key Header name
|
|
137
|
+
*/
|
|
138
|
+
has(key) {
|
|
139
|
+
return this._headers?.has(key) || false;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Internal: check if headers have been initialized/modified
|
|
143
|
+
*/
|
|
144
|
+
get hasPopulatedHeaders() {
|
|
145
|
+
return this._headers !== null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function isValidCookieDomain(domain, currentHost) {
|
|
149
|
+
const hostWithoutPort = currentHost.split(":")[0];
|
|
150
|
+
if (domain === hostWithoutPort) return true;
|
|
151
|
+
if (domain.startsWith(".")) {
|
|
152
|
+
const domainWithoutDot = domain.slice(1);
|
|
153
|
+
return hostWithoutPort.endsWith(domainWithoutDot);
|
|
154
|
+
}
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
145
157
|
class ShokupanContext {
|
|
146
158
|
constructor(request, server, state, app, signal, enableMiddlewareTracking = false) {
|
|
147
159
|
this.request = request;
|
|
@@ -180,12 +192,20 @@ class ShokupanContext {
|
|
|
180
192
|
_bodyType;
|
|
181
193
|
_bodyParsed = false;
|
|
182
194
|
_bodyParseError;
|
|
195
|
+
_routeMatched = false;
|
|
183
196
|
// Cached URL properties to avoid repeated parsing
|
|
184
197
|
_cachedHostname;
|
|
185
198
|
_cachedProtocol;
|
|
186
199
|
_cachedHost;
|
|
187
200
|
_cachedOrigin;
|
|
188
201
|
_cachedQuery;
|
|
202
|
+
/**
|
|
203
|
+
* JSX Rendering Function
|
|
204
|
+
*/
|
|
205
|
+
renderer;
|
|
206
|
+
setRenderer(renderer) {
|
|
207
|
+
this.renderer = renderer;
|
|
208
|
+
}
|
|
189
209
|
get url() {
|
|
190
210
|
if (!this._url) {
|
|
191
211
|
const urlString = this.request.url || "http://localhost/";
|
|
@@ -235,16 +255,20 @@ class ShokupanContext {
|
|
|
235
255
|
*/
|
|
236
256
|
get query() {
|
|
237
257
|
if (this._cachedQuery) return this._cachedQuery;
|
|
238
|
-
const q =
|
|
258
|
+
const q = /* @__PURE__ */ Object.create(null);
|
|
259
|
+
const blocklist = ["__proto__", "constructor", "prototype"];
|
|
239
260
|
const entries = Object.entries(this.url.searchParams);
|
|
240
261
|
for (let i = 0; i < entries.length; i++) {
|
|
241
262
|
const [key, value] = entries[i];
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
263
|
+
if (blocklist.includes(key)) continue;
|
|
264
|
+
if (Object.prototype.hasOwnProperty.call(q, key)) {
|
|
265
|
+
if (Array.isArray(q[key])) {
|
|
266
|
+
q[key].push(value);
|
|
267
|
+
} else {
|
|
268
|
+
q[key] = [q[key], value];
|
|
269
|
+
}
|
|
246
270
|
} else {
|
|
247
|
-
q[key] =
|
|
271
|
+
q[key] = value;
|
|
248
272
|
}
|
|
249
273
|
}
|
|
250
274
|
this._cachedQuery = q;
|
|
@@ -321,6 +345,12 @@ class ShokupanContext {
|
|
|
321
345
|
* @param options Cookie options
|
|
322
346
|
*/
|
|
323
347
|
setCookie(name, value, options = {}) {
|
|
348
|
+
if (options.domain) {
|
|
349
|
+
const currentHost = this.hostname;
|
|
350
|
+
if (!isValidCookieDomain(options.domain, currentHost)) {
|
|
351
|
+
throw new Error(`Invalid cookie domain: ${options.domain} for host ${currentHost}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
324
354
|
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
|
325
355
|
if (options.maxAge) cookie += `; Max-Age=${Math.floor(options.maxAge)}`;
|
|
326
356
|
if (options.domain) cookie += `; Domain=${options.domain}`;
|
|
@@ -393,11 +423,15 @@ class ShokupanContext {
|
|
|
393
423
|
}
|
|
394
424
|
const contentType = this.request.headers.get("content-type") || "";
|
|
395
425
|
if (contentType.includes("application/json") || contentType.includes("+json")) {
|
|
396
|
-
const rawText = await this.readRawBody();
|
|
397
426
|
const parserType = this.app?.applicationConfig?.jsonParser || "native";
|
|
398
427
|
if (parserType === "native") {
|
|
399
|
-
|
|
428
|
+
try {
|
|
429
|
+
this._cachedBody = await this.request.json();
|
|
430
|
+
} catch (e) {
|
|
431
|
+
throw e;
|
|
432
|
+
}
|
|
400
433
|
} else {
|
|
434
|
+
const rawText = await this.request.text();
|
|
401
435
|
const { getJSONParser } = await import("./json-parser-B3dnQmCC.js");
|
|
402
436
|
const parser = getJSONParser(parserType);
|
|
403
437
|
this._cachedBody = parser(rawText);
|
|
@@ -407,7 +441,7 @@ class ShokupanContext {
|
|
|
407
441
|
this._cachedBody = await this.request.formData();
|
|
408
442
|
this._bodyType = "formData";
|
|
409
443
|
} else {
|
|
410
|
-
this._cachedBody = await this.
|
|
444
|
+
this._cachedBody = await this.request.text();
|
|
411
445
|
this._bodyType = "text";
|
|
412
446
|
}
|
|
413
447
|
this._bodyParsed = true;
|
|
@@ -585,10 +619,6 @@ class ShokupanContext {
|
|
|
585
619
|
return this._finalResponse;
|
|
586
620
|
}
|
|
587
621
|
}
|
|
588
|
-
/**
|
|
589
|
-
* JSX Rendering Function
|
|
590
|
-
*/
|
|
591
|
-
renderer;
|
|
592
622
|
/**
|
|
593
623
|
* Render a JSX element
|
|
594
624
|
* @param element JSX Element
|
|
@@ -607,271 +637,67 @@ class ShokupanContext {
|
|
|
607
637
|
return this.html(html, status, headers);
|
|
608
638
|
}
|
|
609
639
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
return "
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
const skip = options.skip || (() => false);
|
|
624
|
-
const hits = /* @__PURE__ */ new Map();
|
|
625
|
-
const interval = setInterval(() => {
|
|
626
|
-
const now = Date.now();
|
|
627
|
-
const entries = Array.from(hits.entries());
|
|
628
|
-
for (let i = 0; i < entries.length; i++) {
|
|
629
|
-
const [key, record] = entries[i];
|
|
630
|
-
if (record.resetTime <= now) {
|
|
631
|
-
hits.delete(key);
|
|
640
|
+
const compose = (middleware) => {
|
|
641
|
+
if (!middleware.length) {
|
|
642
|
+
return (context2, next) => {
|
|
643
|
+
return next ? next() : Promise.resolve();
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
return function dispatch(context2, next) {
|
|
647
|
+
let index = -1;
|
|
648
|
+
async function runner(i) {
|
|
649
|
+
if (i <= index) return Promise.reject(new Error("next() called multiple times"));
|
|
650
|
+
index = i;
|
|
651
|
+
if (i >= middleware.length) {
|
|
652
|
+
return next ? next() : Promise.resolve();
|
|
632
653
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
const rateLimitMiddleware = async function RateLimitMiddleware2(ctx, next) {
|
|
637
|
-
if (skip(ctx)) return next();
|
|
638
|
-
const key = keyGenerator(ctx);
|
|
639
|
-
const now = Date.now();
|
|
640
|
-
let record = hits.get(key);
|
|
641
|
-
if (!record || record.resetTime <= now) {
|
|
642
|
-
record = {
|
|
643
|
-
hits: 0,
|
|
644
|
-
resetTime: now + windowMs
|
|
645
|
-
};
|
|
646
|
-
hits.set(key, record);
|
|
647
|
-
}
|
|
648
|
-
record.hits++;
|
|
649
|
-
const remaining = Math.max(0, max - record.hits);
|
|
650
|
-
const resetTime = Math.ceil(record.resetTime / 1e3);
|
|
651
|
-
const retryAfter = Math.ceil((record.resetTime - now) / 1e3);
|
|
652
|
-
const setHeaders = (res) => {
|
|
653
|
-
if (!headers || !res || !res.headers) return;
|
|
654
|
-
try {
|
|
655
|
-
res.headers.set("X-RateLimit-Limit", String(max));
|
|
656
|
-
res.headers.set("X-RateLimit-Remaining", String(remaining));
|
|
657
|
-
res.headers.set("X-RateLimit-Reset", String(resetTime));
|
|
658
|
-
} catch (e) {
|
|
654
|
+
const fn = middleware[i];
|
|
655
|
+
if (!context2._debug) {
|
|
656
|
+
return fn(context2, () => runner(i + 1));
|
|
659
657
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
658
|
+
const debug = context2._debug;
|
|
659
|
+
const debugId = fn._debugId || fn.name || "anonymous";
|
|
660
|
+
const previousNode = debug.getCurrentNode();
|
|
661
|
+
debug.trackEdge(previousNode, debugId);
|
|
662
|
+
debug.setNode(debugId);
|
|
663
|
+
const start = performance.now();
|
|
664
|
+
try {
|
|
665
|
+
const res = await Promise.resolve(fn(context2, () => runner(i + 1)));
|
|
666
|
+
debug.trackStep(debugId, "middleware", performance.now() - start, "success");
|
|
667
|
+
return res;
|
|
668
|
+
} catch (err) {
|
|
669
|
+
debug.trackStep(debugId, "middleware", performance.now() - start, "error", err);
|
|
670
|
+
return Promise.reject(err);
|
|
671
|
+
} finally {
|
|
672
|
+
if (previousNode) debug.setNode(previousNode);
|
|
667
673
|
}
|
|
668
|
-
return res;
|
|
669
|
-
}
|
|
670
|
-
const response = await next();
|
|
671
|
-
if (response instanceof Response && headers) {
|
|
672
|
-
setHeaders(response);
|
|
673
674
|
}
|
|
674
|
-
return
|
|
675
|
+
return runner(0);
|
|
675
676
|
};
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
const $controllerPath = /* @__PURE__ */ Symbol("Shokupan.controllerPath");
|
|
686
|
-
const $middleware = /* @__PURE__ */ Symbol("Shokupan.middleware");
|
|
687
|
-
const $isRouter = /* @__PURE__ */ Symbol.for("Shokupan.router");
|
|
688
|
-
const $parent = /* @__PURE__ */ Symbol.for("Shokupan.parent");
|
|
689
|
-
const $childRouters = /* @__PURE__ */ Symbol.for("Shokupan.child-routers");
|
|
690
|
-
const $childControllers = /* @__PURE__ */ Symbol.for("Shokupan.child-controllers");
|
|
691
|
-
const $mountPath = /* @__PURE__ */ Symbol.for("Shokupan.mount-path");
|
|
692
|
-
const $dispatch = /* @__PURE__ */ Symbol.for("Shokupan.dispatch");
|
|
693
|
-
const $routes = /* @__PURE__ */ Symbol.for("Shokupan.routes");
|
|
694
|
-
const $routeSpec = /* @__PURE__ */ Symbol.for("Shokupan.routeSpec");
|
|
695
|
-
const HTTPMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "ALL"];
|
|
696
|
-
var RouteParamType = /* @__PURE__ */ ((RouteParamType2) => {
|
|
697
|
-
RouteParamType2["BODY"] = "BODY";
|
|
698
|
-
RouteParamType2["PARAM"] = "PARAM";
|
|
699
|
-
RouteParamType2["QUERY"] = "QUERY";
|
|
700
|
-
RouteParamType2["HEADER"] = "HEADER";
|
|
701
|
-
RouteParamType2["REQUEST"] = "REQUEST";
|
|
702
|
-
RouteParamType2["CONTEXT"] = "CONTEXT";
|
|
703
|
-
return RouteParamType2;
|
|
704
|
-
})(RouteParamType || {});
|
|
705
|
-
function Controller(path = "/") {
|
|
706
|
-
return (target) => {
|
|
707
|
-
target[$controllerPath] = path;
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
function Use(...middleware) {
|
|
711
|
-
return (target, propertyKey, descriptor) => {
|
|
712
|
-
if (!propertyKey) {
|
|
713
|
-
const existing = target[$middleware] || [];
|
|
714
|
-
target[$middleware] = [...existing, ...middleware];
|
|
715
|
-
} else {
|
|
716
|
-
if (!target[$middleware]) {
|
|
717
|
-
target[$middleware] = /* @__PURE__ */ new Map();
|
|
718
|
-
}
|
|
719
|
-
const existing = target[$middleware].get(propertyKey) || [];
|
|
720
|
-
target[$middleware].set(propertyKey, [...existing, ...middleware]);
|
|
721
|
-
}
|
|
722
|
-
};
|
|
723
|
-
}
|
|
724
|
-
function createParamDecorator(type) {
|
|
725
|
-
return (name) => {
|
|
726
|
-
return (target, propertyKey, parameterIndex) => {
|
|
727
|
-
if (!target[$routeArgs]) {
|
|
728
|
-
target[$routeArgs] = /* @__PURE__ */ new Map();
|
|
729
|
-
}
|
|
730
|
-
if (!target[$routeArgs].has(propertyKey)) {
|
|
731
|
-
target[$routeArgs].set(propertyKey, []);
|
|
732
|
-
}
|
|
733
|
-
target[$routeArgs].get(propertyKey).push({
|
|
734
|
-
index: parameterIndex,
|
|
735
|
-
type,
|
|
736
|
-
name
|
|
737
|
-
});
|
|
738
|
-
};
|
|
739
|
-
};
|
|
740
|
-
}
|
|
741
|
-
const Body = createParamDecorator(RouteParamType.BODY);
|
|
742
|
-
const Param = createParamDecorator(RouteParamType.PARAM);
|
|
743
|
-
const Query = createParamDecorator(RouteParamType.QUERY);
|
|
744
|
-
const Headers$1 = createParamDecorator(RouteParamType.HEADER);
|
|
745
|
-
const Req = createParamDecorator(RouteParamType.REQUEST);
|
|
746
|
-
const Ctx = createParamDecorator(RouteParamType.CONTEXT);
|
|
747
|
-
function Spec(spec) {
|
|
748
|
-
return (target, propertyKey, descriptor) => {
|
|
749
|
-
if (!target[$routeSpec]) {
|
|
750
|
-
target[$routeSpec] = /* @__PURE__ */ new Map();
|
|
751
|
-
}
|
|
752
|
-
target[$routeSpec].set(propertyKey, spec);
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
function createMethodDecorator(method) {
|
|
756
|
-
return (path = "/") => {
|
|
757
|
-
return (target, propertyKey, descriptor) => {
|
|
758
|
-
if (!target[$routeMethods]) {
|
|
759
|
-
target[$routeMethods] = /* @__PURE__ */ new Map();
|
|
760
|
-
}
|
|
761
|
-
target[$routeMethods].set(propertyKey, {
|
|
762
|
-
method,
|
|
763
|
-
path
|
|
764
|
-
});
|
|
765
|
-
};
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
const Get = createMethodDecorator("GET");
|
|
769
|
-
const Post = createMethodDecorator("POST");
|
|
770
|
-
const Put = createMethodDecorator("PUT");
|
|
771
|
-
const Delete = createMethodDecorator("DELETE");
|
|
772
|
-
const Patch = createMethodDecorator("PATCH");
|
|
773
|
-
const Options = createMethodDecorator("OPTIONS");
|
|
774
|
-
const Head = createMethodDecorator("HEAD");
|
|
775
|
-
const All = createMethodDecorator("ALL");
|
|
776
|
-
function RateLimit(options) {
|
|
777
|
-
return Use(RateLimitMiddleware(options));
|
|
778
|
-
}
|
|
779
|
-
class Container {
|
|
780
|
-
static services = /* @__PURE__ */ new Map();
|
|
781
|
-
static register(target, instance) {
|
|
782
|
-
this.services.set(target, instance);
|
|
783
|
-
}
|
|
784
|
-
static get(target) {
|
|
785
|
-
return this.services.get(target);
|
|
786
|
-
}
|
|
787
|
-
static has(target) {
|
|
788
|
-
return this.services.has(target);
|
|
789
|
-
}
|
|
790
|
-
static resolve(target) {
|
|
791
|
-
if (this.services.has(target)) {
|
|
792
|
-
return this.services.get(target);
|
|
793
|
-
}
|
|
794
|
-
const instance = new target();
|
|
795
|
-
this.services.set(target, instance);
|
|
796
|
-
return instance;
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
function Injectable() {
|
|
800
|
-
return (target) => {
|
|
801
|
-
};
|
|
802
|
-
}
|
|
803
|
-
function Inject(token) {
|
|
804
|
-
return (target, key) => {
|
|
805
|
-
Object.defineProperty(target, key, {
|
|
806
|
-
get: () => Container.resolve(token),
|
|
807
|
-
enumerable: true,
|
|
808
|
-
configurable: true
|
|
809
|
-
});
|
|
810
|
-
};
|
|
811
|
-
}
|
|
812
|
-
const compose = (middleware) => {
|
|
813
|
-
if (!middleware.length) {
|
|
814
|
-
return (context2, next) => {
|
|
815
|
-
return next ? next() : Promise.resolve();
|
|
816
|
-
};
|
|
817
|
-
}
|
|
818
|
-
return function dispatch(context2, next) {
|
|
819
|
-
let index = -1;
|
|
820
|
-
async function runner(i) {
|
|
821
|
-
if (i <= index) return Promise.reject(new Error("next() called multiple times"));
|
|
822
|
-
index = i;
|
|
823
|
-
if (i >= middleware.length) {
|
|
824
|
-
return next ? next() : Promise.resolve();
|
|
825
|
-
}
|
|
826
|
-
const fn = middleware[i];
|
|
827
|
-
if (!context2._debug) {
|
|
828
|
-
return fn(context2, () => runner(i + 1));
|
|
677
|
+
};
|
|
678
|
+
const tracer = trace.getTracer("shokupan.middleware");
|
|
679
|
+
function traceHandler(fn, name) {
|
|
680
|
+
return async function(...args) {
|
|
681
|
+
return tracer.startActiveSpan(`route handler - ${name}`, {
|
|
682
|
+
kind: SpanKind.INTERNAL,
|
|
683
|
+
attributes: {
|
|
684
|
+
"http.route": name,
|
|
685
|
+
"component": "shokupan.route"
|
|
829
686
|
}
|
|
830
|
-
|
|
831
|
-
const debugId = fn._debugId || fn.name || "anonymous";
|
|
832
|
-
const previousNode = debug.getCurrentNode();
|
|
833
|
-
debug.trackEdge(previousNode, debugId);
|
|
834
|
-
debug.setNode(debugId);
|
|
835
|
-
const start = performance.now();
|
|
687
|
+
}, async (span) => {
|
|
836
688
|
try {
|
|
837
|
-
const
|
|
838
|
-
|
|
839
|
-
return res;
|
|
689
|
+
const result = await fn.apply(this, args);
|
|
690
|
+
return result;
|
|
840
691
|
} catch (err) {
|
|
841
|
-
|
|
842
|
-
|
|
692
|
+
span.recordException(err);
|
|
693
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
|
|
694
|
+
throw err;
|
|
843
695
|
} finally {
|
|
844
|
-
|
|
696
|
+
span.end();
|
|
845
697
|
}
|
|
846
|
-
}
|
|
847
|
-
return runner(0);
|
|
698
|
+
});
|
|
848
699
|
};
|
|
849
|
-
};
|
|
850
|
-
class ShokupanRequestBase {
|
|
851
|
-
method;
|
|
852
|
-
url;
|
|
853
|
-
headers;
|
|
854
|
-
body;
|
|
855
|
-
async json() {
|
|
856
|
-
return JSON.parse(this.body);
|
|
857
|
-
}
|
|
858
|
-
async text() {
|
|
859
|
-
return this.body;
|
|
860
|
-
}
|
|
861
|
-
async formData() {
|
|
862
|
-
if (this.body instanceof FormData) {
|
|
863
|
-
return this.body;
|
|
864
|
-
}
|
|
865
|
-
return new Response(this.body, { headers: this.headers }).formData();
|
|
866
|
-
}
|
|
867
|
-
constructor(props) {
|
|
868
|
-
Object.assign(this, props);
|
|
869
|
-
if (!(this.headers instanceof Headers)) {
|
|
870
|
-
this.headers = new Headers(this.headers);
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
700
|
}
|
|
874
|
-
const ShokupanRequest = ShokupanRequestBase;
|
|
875
701
|
function isObject(item) {
|
|
876
702
|
return item && typeof item === "object" && !Array.isArray(item);
|
|
877
703
|
}
|
|
@@ -905,6 +731,21 @@ function deepMerge(target, ...sources) {
|
|
|
905
731
|
}
|
|
906
732
|
return deepMerge(target, ...sources);
|
|
907
733
|
}
|
|
734
|
+
const $isApplication = /* @__PURE__ */ Symbol.for("Shokupan.app");
|
|
735
|
+
const $appRoot = /* @__PURE__ */ Symbol.for("Shokupan.app-root");
|
|
736
|
+
const $isMounted = /* @__PURE__ */ Symbol("Shokupan.isMounted");
|
|
737
|
+
const $routeMethods = /* @__PURE__ */ Symbol("Shokupan.routeMethods");
|
|
738
|
+
const $routeArgs = /* @__PURE__ */ Symbol("Shokupan.routeArgs");
|
|
739
|
+
const $controllerPath = /* @__PURE__ */ Symbol("Shokupan.controllerPath");
|
|
740
|
+
const $middleware = /* @__PURE__ */ Symbol("Shokupan.middleware");
|
|
741
|
+
const $isRouter = /* @__PURE__ */ Symbol.for("Shokupan.router");
|
|
742
|
+
const $parent = /* @__PURE__ */ Symbol.for("Shokupan.parent");
|
|
743
|
+
const $childRouters = /* @__PURE__ */ Symbol.for("Shokupan.child-routers");
|
|
744
|
+
const $childControllers = /* @__PURE__ */ Symbol.for("Shokupan.child-controllers");
|
|
745
|
+
const $mountPath = /* @__PURE__ */ Symbol.for("Shokupan.mount-path");
|
|
746
|
+
const $dispatch = /* @__PURE__ */ Symbol.for("Shokupan.dispatch");
|
|
747
|
+
const $routes = /* @__PURE__ */ Symbol.for("Shokupan.routes");
|
|
748
|
+
const $routeSpec = /* @__PURE__ */ Symbol.for("Shokupan.routeSpec");
|
|
908
749
|
const REGEX_PATTERNS = {
|
|
909
750
|
QUERY_INT: /parseInt\(ctx\.query\.(\w+)\)/g,
|
|
910
751
|
QUERY_FLOAT: /parseFloat\(ctx\.query\.(\w+)\)/g,
|
|
@@ -1097,7 +938,7 @@ async function generateOpenApi(rootRouter, options = {}) {
|
|
|
1097
938
|
const defaultTagName = options.defaultTag || "Application";
|
|
1098
939
|
let astRoutes = [];
|
|
1099
940
|
try {
|
|
1100
|
-
const { OpenAPIAnalyzer: OpenAPIAnalyzer2 } = await import("./
|
|
941
|
+
const { OpenAPIAnalyzer: OpenAPIAnalyzer2 } = await import("./analyzer-Ce_7JxZh.js");
|
|
1101
942
|
const analyzer = new OpenAPIAnalyzer2(process.cwd());
|
|
1102
943
|
const { applications } = await analyzer.analyze();
|
|
1103
944
|
astRoutes = await getAstRoutes(applications);
|
|
@@ -1286,6 +1127,11 @@ async function generateOpenApi(rootRouter, options = {}) {
|
|
|
1286
1127
|
"x-tagGroups": xTagGroups
|
|
1287
1128
|
};
|
|
1288
1129
|
}
|
|
1130
|
+
class RequestContextStore {
|
|
1131
|
+
request;
|
|
1132
|
+
span;
|
|
1133
|
+
}
|
|
1134
|
+
const asyncContext = new AsyncLocalStorage();
|
|
1289
1135
|
const eta$1 = new Eta();
|
|
1290
1136
|
function serveStatic(config, prefix) {
|
|
1291
1137
|
const rootPath = resolve(config.root || ".");
|
|
@@ -1294,12 +1140,23 @@ function serveStatic(config, prefix) {
|
|
|
1294
1140
|
let relative = ctx.path.slice(normalizedPrefix.length);
|
|
1295
1141
|
if (!relative.startsWith("/") && relative.length > 0) relative = "/" + relative;
|
|
1296
1142
|
if (relative.length === 0) relative = "/";
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1143
|
+
if (relative.includes("\0")) {
|
|
1144
|
+
return ctx.json({ error: "Forbidden" }, 403);
|
|
1145
|
+
}
|
|
1146
|
+
try {
|
|
1147
|
+
relative = decodeURIComponent(relative);
|
|
1148
|
+
} catch (e) {
|
|
1149
|
+
return ctx.json({ error: "Bad Request" }, 400);
|
|
1150
|
+
}
|
|
1151
|
+
if (relative.includes("\0")) {
|
|
1152
|
+
return ctx.json({ error: "Forbidden" }, 403);
|
|
1153
|
+
}
|
|
1154
|
+
if (relative.includes("../") || relative.includes("..\\")) {
|
|
1300
1155
|
return ctx.json({ error: "Forbidden" }, 403);
|
|
1301
1156
|
}
|
|
1302
|
-
|
|
1157
|
+
const requestPath = resolve(join(rootPath, relative));
|
|
1158
|
+
const normalizedRoot = resolve(rootPath);
|
|
1159
|
+
if (!requestPath.startsWith(normalizedRoot + sep) && requestPath !== normalizedRoot) {
|
|
1303
1160
|
return ctx.json({ error: "Forbidden" }, 403);
|
|
1304
1161
|
}
|
|
1305
1162
|
if (config.hooks?.onRequest) {
|
|
@@ -1427,107 +1284,6 @@ function serveStatic(config, prefix) {
|
|
|
1427
1284
|
serveStaticMiddleware.pluginName = "ServeStatic";
|
|
1428
1285
|
return serveStaticMiddleware;
|
|
1429
1286
|
}
|
|
1430
|
-
class RouterTrie {
|
|
1431
|
-
root;
|
|
1432
|
-
constructor() {
|
|
1433
|
-
this.root = this.createNode();
|
|
1434
|
-
}
|
|
1435
|
-
createNode() {
|
|
1436
|
-
return {
|
|
1437
|
-
children: {}
|
|
1438
|
-
};
|
|
1439
|
-
}
|
|
1440
|
-
insert(method, path, handler) {
|
|
1441
|
-
let node = this.root;
|
|
1442
|
-
const segments = this.splitPath(path);
|
|
1443
|
-
for (let i = 0; i < segments.length; i++) {
|
|
1444
|
-
const segment = segments[i];
|
|
1445
|
-
if (segment === "**") {
|
|
1446
|
-
if (!node.recursiveChild) {
|
|
1447
|
-
node.recursiveChild = this.createNode();
|
|
1448
|
-
}
|
|
1449
|
-
node = node.recursiveChild;
|
|
1450
|
-
} else if (segment === "*") {
|
|
1451
|
-
if (!node.wildcardChild) {
|
|
1452
|
-
node.wildcardChild = this.createNode();
|
|
1453
|
-
}
|
|
1454
|
-
node = node.wildcardChild;
|
|
1455
|
-
} else if (segment.startsWith(":")) {
|
|
1456
|
-
const paramName = segment.slice(1);
|
|
1457
|
-
if (!node.paramChild) {
|
|
1458
|
-
node.paramChild = this.createNode();
|
|
1459
|
-
node.paramChild.paramName = paramName;
|
|
1460
|
-
}
|
|
1461
|
-
node = node.paramChild;
|
|
1462
|
-
node.paramName = paramName;
|
|
1463
|
-
} else {
|
|
1464
|
-
if (!node.children[segment]) {
|
|
1465
|
-
node.children[segment] = this.createNode();
|
|
1466
|
-
}
|
|
1467
|
-
node = node.children[segment];
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
if (!node.handlers) {
|
|
1471
|
-
node.handlers = {};
|
|
1472
|
-
}
|
|
1473
|
-
node.handlers[method] = handler;
|
|
1474
|
-
}
|
|
1475
|
-
search(method, path) {
|
|
1476
|
-
const segments = this.splitPath(path);
|
|
1477
|
-
const params = {};
|
|
1478
|
-
const match = this.findNode(this.root, segments, 0, params);
|
|
1479
|
-
if (match && match.handlers) {
|
|
1480
|
-
const handler = match.handlers[method] || match.handlers["ALL"];
|
|
1481
|
-
if (handler) {
|
|
1482
|
-
return { handler, params };
|
|
1483
|
-
}
|
|
1484
|
-
if (method === "HEAD" && match.handlers["GET"]) {
|
|
1485
|
-
return { handler: match.handlers["GET"], params };
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
return null;
|
|
1489
|
-
}
|
|
1490
|
-
findNode(node, segments, index, params) {
|
|
1491
|
-
if (index === segments.length) {
|
|
1492
|
-
if (node.handlers) return node;
|
|
1493
|
-
if (node.recursiveChild && node.recursiveChild.handlers) {
|
|
1494
|
-
return node.recursiveChild;
|
|
1495
|
-
}
|
|
1496
|
-
return null;
|
|
1497
|
-
}
|
|
1498
|
-
const segment = segments[index];
|
|
1499
|
-
const child = node.children[segment];
|
|
1500
|
-
if (child) {
|
|
1501
|
-
const result = this.findNode(child, segments, index + 1, params);
|
|
1502
|
-
if (result) return result;
|
|
1503
|
-
}
|
|
1504
|
-
if (node.paramChild) {
|
|
1505
|
-
params[node.paramChild.paramName] = segment;
|
|
1506
|
-
const result = this.findNode(node.paramChild, segments, index + 1, params);
|
|
1507
|
-
if (result) return result;
|
|
1508
|
-
delete params[node.paramChild.paramName];
|
|
1509
|
-
}
|
|
1510
|
-
if (node.wildcardChild) {
|
|
1511
|
-
const result = this.findNode(node.wildcardChild, segments, index + 1, params);
|
|
1512
|
-
if (result) return result;
|
|
1513
|
-
}
|
|
1514
|
-
if (node.recursiveChild) {
|
|
1515
|
-
const remaining = segments.length - index;
|
|
1516
|
-
for (let k = 0; k <= remaining; k++) {
|
|
1517
|
-
const result = this.findNode(node.recursiveChild, segments, index + k, params);
|
|
1518
|
-
if (result) return result;
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
return null;
|
|
1522
|
-
}
|
|
1523
|
-
splitPath(path) {
|
|
1524
|
-
if (path === "/" || path === "") return [];
|
|
1525
|
-
const s = path.startsWith("/") ? path.slice(1) : path;
|
|
1526
|
-
if (s === "") return [];
|
|
1527
|
-
return s.split("/");
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
const asyncContext = new AsyncLocalStorage();
|
|
1531
1287
|
let db;
|
|
1532
1288
|
let dbPromise = null;
|
|
1533
1289
|
let RecordId;
|
|
@@ -1591,29 +1347,64 @@ const datastore = {
|
|
|
1591
1347
|
process.on("exit", async () => {
|
|
1592
1348
|
if (db) await db.close();
|
|
1593
1349
|
});
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1350
|
+
class Container {
|
|
1351
|
+
static services = /* @__PURE__ */ new Map();
|
|
1352
|
+
static register(target, instance) {
|
|
1353
|
+
this.services.set(target, instance);
|
|
1354
|
+
}
|
|
1355
|
+
static get(target) {
|
|
1356
|
+
return this.services.get(target);
|
|
1357
|
+
}
|
|
1358
|
+
static has(target) {
|
|
1359
|
+
return this.services.has(target);
|
|
1360
|
+
}
|
|
1361
|
+
static resolve(target) {
|
|
1362
|
+
if (this.services.has(target)) {
|
|
1363
|
+
return this.services.get(target);
|
|
1364
|
+
}
|
|
1365
|
+
const instance = new target();
|
|
1366
|
+
this.services.set(target, instance);
|
|
1367
|
+
return instance;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
function Injectable() {
|
|
1371
|
+
return (target) => {
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
function Inject(token) {
|
|
1375
|
+
return (target, key) => {
|
|
1376
|
+
Object.defineProperty(target, key, {
|
|
1377
|
+
get: () => Container.resolve(token),
|
|
1378
|
+
enumerable: true,
|
|
1379
|
+
configurable: true
|
|
1614
1380
|
});
|
|
1615
1381
|
};
|
|
1616
1382
|
}
|
|
1383
|
+
class ShokupanRequestBase {
|
|
1384
|
+
method;
|
|
1385
|
+
url;
|
|
1386
|
+
headers;
|
|
1387
|
+
body;
|
|
1388
|
+
async json() {
|
|
1389
|
+
return JSON.parse(this.body);
|
|
1390
|
+
}
|
|
1391
|
+
async text() {
|
|
1392
|
+
return this.body;
|
|
1393
|
+
}
|
|
1394
|
+
async formData() {
|
|
1395
|
+
if (this.body instanceof FormData) {
|
|
1396
|
+
return this.body;
|
|
1397
|
+
}
|
|
1398
|
+
return new Response(this.body, { headers: this.headers }).formData();
|
|
1399
|
+
}
|
|
1400
|
+
constructor(props) {
|
|
1401
|
+
Object.assign(this, props);
|
|
1402
|
+
if (!(this.headers instanceof Headers)) {
|
|
1403
|
+
this.headers = new Headers(this.headers);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
const ShokupanRequest = ShokupanRequestBase;
|
|
1617
1408
|
function getCallerInfo(skipFrames = 1) {
|
|
1618
1409
|
let file = "unknown";
|
|
1619
1410
|
let line = 0;
|
|
@@ -1639,12 +1430,120 @@ function getCallerInfo(skipFrames = 1) {
|
|
|
1639
1430
|
}
|
|
1640
1431
|
}
|
|
1641
1432
|
}
|
|
1642
|
-
} catch (e) {
|
|
1433
|
+
} catch (e) {
|
|
1434
|
+
}
|
|
1435
|
+
return { file, line };
|
|
1436
|
+
}
|
|
1437
|
+
class RouterTrie {
|
|
1438
|
+
root;
|
|
1439
|
+
constructor() {
|
|
1440
|
+
this.root = this.createNode();
|
|
1441
|
+
}
|
|
1442
|
+
createNode() {
|
|
1443
|
+
return {
|
|
1444
|
+
children: {}
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
insert(method, path, handler) {
|
|
1448
|
+
let node = this.root;
|
|
1449
|
+
const segments = this.splitPath(path);
|
|
1450
|
+
for (let i = 0; i < segments.length; i++) {
|
|
1451
|
+
const segment = segments[i];
|
|
1452
|
+
if (segment === "**") {
|
|
1453
|
+
if (!node.recursiveChild) {
|
|
1454
|
+
node.recursiveChild = this.createNode();
|
|
1455
|
+
}
|
|
1456
|
+
node = node.recursiveChild;
|
|
1457
|
+
} else if (segment === "*") {
|
|
1458
|
+
if (!node.wildcardChild) {
|
|
1459
|
+
node.wildcardChild = this.createNode();
|
|
1460
|
+
}
|
|
1461
|
+
node = node.wildcardChild;
|
|
1462
|
+
} else if (segment.startsWith(":")) {
|
|
1463
|
+
const paramName = segment.slice(1);
|
|
1464
|
+
if (!node.paramChild) {
|
|
1465
|
+
node.paramChild = this.createNode();
|
|
1466
|
+
node.paramChild.paramName = paramName;
|
|
1467
|
+
}
|
|
1468
|
+
node = node.paramChild;
|
|
1469
|
+
node.paramName = paramName;
|
|
1470
|
+
} else {
|
|
1471
|
+
if (!node.children[segment]) {
|
|
1472
|
+
node.children[segment] = this.createNode();
|
|
1473
|
+
}
|
|
1474
|
+
node = node.children[segment];
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
if (!node.handlers) {
|
|
1478
|
+
node.handlers = {};
|
|
1479
|
+
}
|
|
1480
|
+
node.handlers[method] = handler;
|
|
1481
|
+
}
|
|
1482
|
+
search(method, path) {
|
|
1483
|
+
const segments = this.splitPath(path);
|
|
1484
|
+
const params = {};
|
|
1485
|
+
const match = this.findNode(this.root, segments, 0, params);
|
|
1486
|
+
if (match && match.handlers) {
|
|
1487
|
+
const handler = match.handlers[method] || match.handlers["ALL"];
|
|
1488
|
+
if (handler) {
|
|
1489
|
+
return { handler, params };
|
|
1490
|
+
}
|
|
1491
|
+
if (method === "HEAD" && match.handlers["GET"]) {
|
|
1492
|
+
return { handler: match.handlers["GET"], params };
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
return null;
|
|
1496
|
+
}
|
|
1497
|
+
findNode(node, segments, index, params) {
|
|
1498
|
+
if (index === segments.length) {
|
|
1499
|
+
if (node.handlers) return node;
|
|
1500
|
+
if (node.recursiveChild && node.recursiveChild.handlers) {
|
|
1501
|
+
return node.recursiveChild;
|
|
1502
|
+
}
|
|
1503
|
+
return null;
|
|
1504
|
+
}
|
|
1505
|
+
const segment = segments[index];
|
|
1506
|
+
const child = node.children[segment];
|
|
1507
|
+
if (child) {
|
|
1508
|
+
const result = this.findNode(child, segments, index + 1, params);
|
|
1509
|
+
if (result) return result;
|
|
1510
|
+
}
|
|
1511
|
+
if (node.paramChild) {
|
|
1512
|
+
params[node.paramChild.paramName] = segment;
|
|
1513
|
+
const result = this.findNode(node.paramChild, segments, index + 1, params);
|
|
1514
|
+
if (result) return result;
|
|
1515
|
+
delete params[node.paramChild.paramName];
|
|
1516
|
+
}
|
|
1517
|
+
if (node.wildcardChild) {
|
|
1518
|
+
const result = this.findNode(node.wildcardChild, segments, index + 1, params);
|
|
1519
|
+
if (result) return result;
|
|
1520
|
+
}
|
|
1521
|
+
if (node.recursiveChild) {
|
|
1522
|
+
const remaining = segments.length - index;
|
|
1523
|
+
for (let k = 0; k <= remaining; k++) {
|
|
1524
|
+
const result = this.findNode(node.recursiveChild, segments, index + k, params);
|
|
1525
|
+
if (result) return result;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
return null;
|
|
1529
|
+
}
|
|
1530
|
+
splitPath(path) {
|
|
1531
|
+
if (path === "/" || path === "") return [];
|
|
1532
|
+
const s = path.startsWith("/") ? path.slice(1) : path;
|
|
1533
|
+
if (s === "") return [];
|
|
1534
|
+
return s.split("/");
|
|
1643
1535
|
}
|
|
1644
|
-
return { file, line };
|
|
1645
1536
|
}
|
|
1646
|
-
const
|
|
1647
|
-
|
|
1537
|
+
const HTTPMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "ALL"];
|
|
1538
|
+
var RouteParamType = /* @__PURE__ */ ((RouteParamType2) => {
|
|
1539
|
+
RouteParamType2["BODY"] = "BODY";
|
|
1540
|
+
RouteParamType2["PARAM"] = "PARAM";
|
|
1541
|
+
RouteParamType2["QUERY"] = "QUERY";
|
|
1542
|
+
RouteParamType2["HEADER"] = "HEADER";
|
|
1543
|
+
RouteParamType2["REQUEST"] = "REQUEST";
|
|
1544
|
+
RouteParamType2["CONTEXT"] = "CONTEXT";
|
|
1545
|
+
return RouteParamType2;
|
|
1546
|
+
})(RouteParamType || {});
|
|
1648
1547
|
class ShokupanRouter {
|
|
1649
1548
|
constructor(config) {
|
|
1650
1549
|
this.config = config;
|
|
@@ -1755,216 +1654,9 @@ class ShokupanRouter {
|
|
|
1755
1654
|
throw new Error(`[Shokupan] strict controller check failed: ${controller.constructor.name || typeof controller} is not a class constructor.`);
|
|
1756
1655
|
}
|
|
1757
1656
|
if (this.isRouterInstance(controller)) {
|
|
1758
|
-
|
|
1759
|
-
throw new Error("Router is already mounted");
|
|
1760
|
-
}
|
|
1761
|
-
controller[$mountPath] = prefix;
|
|
1762
|
-
if (!controller.metadata) {
|
|
1763
|
-
const info = getCallerInfo();
|
|
1764
|
-
controller.metadata = {
|
|
1765
|
-
file: info.file,
|
|
1766
|
-
line: info.line,
|
|
1767
|
-
name: "MountedRouter"
|
|
1768
|
-
};
|
|
1769
|
-
}
|
|
1770
|
-
this[$childRouters].push(controller);
|
|
1771
|
-
controller[$parent] = this;
|
|
1772
|
-
const setRouterContext = (router) => {
|
|
1773
|
-
router[$appRoot] = this.root;
|
|
1774
|
-
router[$childRouters].forEach((child) => setRouterContext(child));
|
|
1775
|
-
};
|
|
1776
|
-
setRouterContext(controller);
|
|
1777
|
-
if (this[$appRoot]) ;
|
|
1778
|
-
controller[$appRoot] = this.root;
|
|
1779
|
-
controller[$isMounted] = true;
|
|
1657
|
+
this.mountRouter(prefix, controller);
|
|
1780
1658
|
} else {
|
|
1781
|
-
|
|
1782
|
-
if (typeof controller === "function") {
|
|
1783
|
-
instance = Container.resolve(controller);
|
|
1784
|
-
const controllerPath = controller[$controllerPath];
|
|
1785
|
-
if (controllerPath) {
|
|
1786
|
-
const p1 = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
1787
|
-
const p2 = controllerPath.startsWith("/") ? controllerPath : "/" + controllerPath;
|
|
1788
|
-
prefix = p1 + p2;
|
|
1789
|
-
if (!prefix) prefix = "/";
|
|
1790
|
-
}
|
|
1791
|
-
} else {
|
|
1792
|
-
const ctor = instance.constructor;
|
|
1793
|
-
const controllerPath = ctor[$controllerPath];
|
|
1794
|
-
if (controllerPath) {
|
|
1795
|
-
const p1 = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
1796
|
-
const p2 = controllerPath.startsWith("/") ? controllerPath : "/" + controllerPath;
|
|
1797
|
-
prefix = p1 + p2;
|
|
1798
|
-
if (!prefix) prefix = "/";
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1801
|
-
instance[$mountPath] = prefix;
|
|
1802
|
-
const info = getCallerInfo();
|
|
1803
|
-
instance.metadata = {
|
|
1804
|
-
file: info.file,
|
|
1805
|
-
line: info.line,
|
|
1806
|
-
name: instance.constructor.name
|
|
1807
|
-
};
|
|
1808
|
-
this[$childControllers].push(instance);
|
|
1809
|
-
const controllerMiddleware = (typeof controller === "function" ? controller[$middleware] : instance[$middleware]) || [];
|
|
1810
|
-
const proto = Object.getPrototypeOf(instance);
|
|
1811
|
-
const methods = /* @__PURE__ */ new Set();
|
|
1812
|
-
let current = proto;
|
|
1813
|
-
while (current && current !== Object.prototype) {
|
|
1814
|
-
Object.getOwnPropertyNames(current).forEach((name) => methods.add(name));
|
|
1815
|
-
current = Object.getPrototypeOf(current);
|
|
1816
|
-
}
|
|
1817
|
-
Object.getOwnPropertyNames(instance).forEach((name) => methods.add(name));
|
|
1818
|
-
const decoratedRoutes = instance[$routeMethods] || proto && proto[$routeMethods];
|
|
1819
|
-
const decoratedArgs = instance[$routeArgs] || proto && proto[$routeArgs];
|
|
1820
|
-
const methodMiddlewareMap = instance[$middleware] || proto && proto[$middleware];
|
|
1821
|
-
let routesAttached = 0;
|
|
1822
|
-
for (let i = 0; i < Array.from(methods).length; i++) {
|
|
1823
|
-
const name = Array.from(methods)[i];
|
|
1824
|
-
if (name === "constructor") continue;
|
|
1825
|
-
if (["arguments", "caller", "callee"].includes(name)) continue;
|
|
1826
|
-
const originalHandler = instance[name];
|
|
1827
|
-
if (typeof originalHandler !== "function") continue;
|
|
1828
|
-
let method;
|
|
1829
|
-
let subPath = "";
|
|
1830
|
-
if (decoratedRoutes && decoratedRoutes.has(name)) {
|
|
1831
|
-
const config = decoratedRoutes.get(name);
|
|
1832
|
-
method = config.method;
|
|
1833
|
-
subPath = config.path;
|
|
1834
|
-
} else {
|
|
1835
|
-
for (let j = 0; j < HTTPMethods.length; j++) {
|
|
1836
|
-
const m = HTTPMethods[j];
|
|
1837
|
-
if (name.toUpperCase().startsWith(m)) {
|
|
1838
|
-
method = m;
|
|
1839
|
-
const rest = name.slice(m.length);
|
|
1840
|
-
if (rest.length === 0) {
|
|
1841
|
-
subPath = "/";
|
|
1842
|
-
} else {
|
|
1843
|
-
subPath = "";
|
|
1844
|
-
let buffer = "";
|
|
1845
|
-
const flush = () => {
|
|
1846
|
-
if (buffer.length > 0) {
|
|
1847
|
-
subPath += "/" + buffer.toLowerCase();
|
|
1848
|
-
buffer = "";
|
|
1849
|
-
}
|
|
1850
|
-
};
|
|
1851
|
-
for (let i2 = 0; i2 < rest.length; i2++) {
|
|
1852
|
-
const char = rest[i2];
|
|
1853
|
-
if (char === "$") {
|
|
1854
|
-
flush();
|
|
1855
|
-
subPath += "/:";
|
|
1856
|
-
continue;
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
|
-
subPath = rest.replace(/\$/g, "/:").replace(/([a-z0-9])([A-Z])/g, "$1/$2").toLowerCase();
|
|
1860
|
-
if (!subPath.startsWith("/")) {
|
|
1861
|
-
subPath = "/" + subPath;
|
|
1862
|
-
}
|
|
1863
|
-
}
|
|
1864
|
-
break;
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
if (method) {
|
|
1869
|
-
routesAttached++;
|
|
1870
|
-
const cleanPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
1871
|
-
const cleanSubPath = subPath === "/" ? "" : subPath;
|
|
1872
|
-
let joined;
|
|
1873
|
-
if (cleanSubPath.length === 0) {
|
|
1874
|
-
joined = cleanPrefix;
|
|
1875
|
-
} else if (cleanSubPath.startsWith("/")) {
|
|
1876
|
-
joined = cleanPrefix + cleanSubPath;
|
|
1877
|
-
} else {
|
|
1878
|
-
joined = cleanPrefix + "/" + cleanSubPath;
|
|
1879
|
-
}
|
|
1880
|
-
const fullPath = joined || "/";
|
|
1881
|
-
const normalizedPath = fullPath.replace(/\/+/g, "/");
|
|
1882
|
-
const methodMw = methodMiddlewareMap instanceof Map ? methodMiddlewareMap.get(name) || [] : [];
|
|
1883
|
-
const allMiddleware = [...controllerMiddleware, ...methodMw];
|
|
1884
|
-
const routeArgs = decoratedArgs && decoratedArgs.get(name);
|
|
1885
|
-
const wrappedHandler = async (ctx) => {
|
|
1886
|
-
let args = [ctx];
|
|
1887
|
-
if (routeArgs?.length > 0) {
|
|
1888
|
-
args = [];
|
|
1889
|
-
const sortedArgs = [...routeArgs].sort((a, b) => a.index - b.index);
|
|
1890
|
-
for (let k = 0; k < sortedArgs.length; k++) {
|
|
1891
|
-
const arg = sortedArgs[k];
|
|
1892
|
-
switch (arg.type) {
|
|
1893
|
-
case RouteParamType.BODY:
|
|
1894
|
-
try {
|
|
1895
|
-
if (ctx.req.headers.get("content-type")?.includes("application/json")) {
|
|
1896
|
-
args[arg.index] = await ctx.req.json();
|
|
1897
|
-
} else {
|
|
1898
|
-
const text = await ctx.req.text();
|
|
1899
|
-
if (!text) {
|
|
1900
|
-
args[arg.index] = {};
|
|
1901
|
-
} else {
|
|
1902
|
-
args[arg.index] = JSON.parse(text);
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
} catch (e) {
|
|
1906
|
-
const err = new Error("Invalid JSON body");
|
|
1907
|
-
err.status = 400;
|
|
1908
|
-
throw err;
|
|
1909
|
-
}
|
|
1910
|
-
break;
|
|
1911
|
-
case RouteParamType.PARAM:
|
|
1912
|
-
args[arg.index] = arg.name ? ctx.params[arg.name] : ctx.params;
|
|
1913
|
-
break;
|
|
1914
|
-
case RouteParamType.QUERY: {
|
|
1915
|
-
const url = new URL(ctx.req.url);
|
|
1916
|
-
if (arg.name) {
|
|
1917
|
-
const vals = url.searchParams.getAll(arg.name);
|
|
1918
|
-
args[arg.index] = vals.length > 1 ? vals : vals[0];
|
|
1919
|
-
} else {
|
|
1920
|
-
const query = {};
|
|
1921
|
-
const keys = Object.keys(url.searchParams);
|
|
1922
|
-
for (let k2 = 0; k2 < keys.length; k2++) {
|
|
1923
|
-
const key = keys[k2];
|
|
1924
|
-
const vals = url.searchParams.getAll(key);
|
|
1925
|
-
query[key] = vals.length > 1 ? vals : vals[0];
|
|
1926
|
-
}
|
|
1927
|
-
args[arg.index] = query;
|
|
1928
|
-
}
|
|
1929
|
-
break;
|
|
1930
|
-
}
|
|
1931
|
-
case RouteParamType.HEADER:
|
|
1932
|
-
args[arg.index] = arg.name ? ctx.req.headers.get(arg.name) : ctx.req.headers;
|
|
1933
|
-
break;
|
|
1934
|
-
case RouteParamType.REQUEST:
|
|
1935
|
-
args[arg.index] = ctx.req;
|
|
1936
|
-
break;
|
|
1937
|
-
case RouteParamType.CONTEXT:
|
|
1938
|
-
args[arg.index] = ctx;
|
|
1939
|
-
break;
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
const tracedOriginalHandler = ctx.app?.applicationConfig.enableTracing ? traceHandler(originalHandler, normalizedPath) : originalHandler;
|
|
1944
|
-
return tracedOriginalHandler.apply(instance, args);
|
|
1945
|
-
};
|
|
1946
|
-
let finalHandler = wrappedHandler;
|
|
1947
|
-
if (allMiddleware.length > 0) {
|
|
1948
|
-
const composed = compose(allMiddleware);
|
|
1949
|
-
finalHandler = async (ctx) => {
|
|
1950
|
-
return composed(ctx, () => wrappedHandler(ctx));
|
|
1951
|
-
};
|
|
1952
|
-
}
|
|
1953
|
-
finalHandler.originalHandler = originalHandler;
|
|
1954
|
-
if (finalHandler !== wrappedHandler) {
|
|
1955
|
-
wrappedHandler.originalHandler = originalHandler;
|
|
1956
|
-
}
|
|
1957
|
-
const tagName = instance.constructor.name;
|
|
1958
|
-
const decoratedSpecs = instance[$routeSpec] || proto && proto[$routeSpec];
|
|
1959
|
-
const userSpec = decoratedSpecs && decoratedSpecs.get(name);
|
|
1960
|
-
const spec = { tags: [tagName], ...userSpec };
|
|
1961
|
-
this.add({ method, path: normalizedPath, handler: finalHandler, spec, controller: instance });
|
|
1962
|
-
}
|
|
1963
|
-
}
|
|
1964
|
-
if (routesAttached === 0) {
|
|
1965
|
-
console.warn(`No routes attached to controller ${instance.constructor.name}`);
|
|
1966
|
-
}
|
|
1967
|
-
instance[$isMounted] = true;
|
|
1659
|
+
this.scanControllerRoutes(prefix, controller);
|
|
1968
1660
|
}
|
|
1969
1661
|
return this;
|
|
1970
1662
|
}
|
|
@@ -2002,8 +1694,6 @@ class ShokupanRouter {
|
|
|
2002
1694
|
*/
|
|
2003
1695
|
async internalRequest(arg) {
|
|
2004
1696
|
const options = typeof arg === "string" ? { path: arg } : arg;
|
|
2005
|
-
const store = asyncContext.getStore();
|
|
2006
|
-
store?.get("req");
|
|
2007
1697
|
let url = options.path;
|
|
2008
1698
|
if (!url.startsWith("http")) {
|
|
2009
1699
|
const base = `http://${this.rootConfig?.hostname || "localhost"}:${this.rootConfig.port || 3e3}`;
|
|
@@ -2115,6 +1805,220 @@ class ShokupanRouter {
|
|
|
2115
1805
|
wrapped.originalHandler = originalHandler.originalHandler ?? originalHandler;
|
|
2116
1806
|
return wrapped;
|
|
2117
1807
|
}
|
|
1808
|
+
mountRouter(prefix, router) {
|
|
1809
|
+
if (router[$isMounted]) {
|
|
1810
|
+
throw new Error("Router is already mounted");
|
|
1811
|
+
}
|
|
1812
|
+
router[$mountPath] = prefix;
|
|
1813
|
+
if (!router.metadata) {
|
|
1814
|
+
const info = getCallerInfo();
|
|
1815
|
+
router.metadata = {
|
|
1816
|
+
file: info.file,
|
|
1817
|
+
line: info.line,
|
|
1818
|
+
name: "MountedRouter"
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
this[$childRouters].push(router);
|
|
1822
|
+
router[$parent] = this;
|
|
1823
|
+
const setRouterContext = (router2) => {
|
|
1824
|
+
router2[$appRoot] = this.root;
|
|
1825
|
+
router2[$childRouters].forEach((child) => setRouterContext(child));
|
|
1826
|
+
};
|
|
1827
|
+
setRouterContext(router);
|
|
1828
|
+
router[$appRoot] = this.root;
|
|
1829
|
+
router[$isMounted] = true;
|
|
1830
|
+
}
|
|
1831
|
+
scanControllerRoutes(prefix, controller) {
|
|
1832
|
+
let instance = controller;
|
|
1833
|
+
if (typeof controller === "function") {
|
|
1834
|
+
instance = Container.resolve(controller);
|
|
1835
|
+
const controllerPath = controller[$controllerPath];
|
|
1836
|
+
if (controllerPath) {
|
|
1837
|
+
const p1 = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
1838
|
+
const p2 = controllerPath.startsWith("/") ? controllerPath : "/" + controllerPath;
|
|
1839
|
+
prefix = p1 + p2;
|
|
1840
|
+
if (!prefix) prefix = "/";
|
|
1841
|
+
}
|
|
1842
|
+
} else {
|
|
1843
|
+
const ctor = instance.constructor;
|
|
1844
|
+
const controllerPath = ctor[$controllerPath];
|
|
1845
|
+
if (controllerPath) {
|
|
1846
|
+
const p1 = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
1847
|
+
const p2 = controllerPath.startsWith("/") ? controllerPath : "/" + controllerPath;
|
|
1848
|
+
prefix = p1 + p2;
|
|
1849
|
+
if (!prefix) prefix = "/";
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
instance[$mountPath] = prefix;
|
|
1853
|
+
const info = getCallerInfo();
|
|
1854
|
+
instance.metadata = {
|
|
1855
|
+
file: info.file,
|
|
1856
|
+
line: info.line,
|
|
1857
|
+
name: instance.constructor.name
|
|
1858
|
+
};
|
|
1859
|
+
this[$childControllers].push(instance);
|
|
1860
|
+
const controllerMiddleware = (typeof controller === "function" ? controller[$middleware] : instance[$middleware]) || [];
|
|
1861
|
+
const proto = Object.getPrototypeOf(instance);
|
|
1862
|
+
const methods = /* @__PURE__ */ new Set();
|
|
1863
|
+
let current = proto;
|
|
1864
|
+
while (current && current !== Object.prototype) {
|
|
1865
|
+
Object.getOwnPropertyNames(current).forEach((name) => methods.add(name));
|
|
1866
|
+
current = Object.getPrototypeOf(current);
|
|
1867
|
+
}
|
|
1868
|
+
Object.getOwnPropertyNames(instance).forEach((name) => methods.add(name));
|
|
1869
|
+
const decoratedRoutes = instance[$routeMethods] || proto && proto[$routeMethods];
|
|
1870
|
+
const decoratedArgs = instance[$routeArgs] || proto && proto[$routeArgs];
|
|
1871
|
+
const methodMiddlewareMap = instance[$middleware] || proto && proto[$middleware];
|
|
1872
|
+
let routesAttached = 0;
|
|
1873
|
+
for (let i = 0; i < Array.from(methods).length; i++) {
|
|
1874
|
+
const name = Array.from(methods)[i];
|
|
1875
|
+
if (name === "constructor") continue;
|
|
1876
|
+
if (["arguments", "caller", "callee"].includes(name)) continue;
|
|
1877
|
+
const originalHandler = instance[name];
|
|
1878
|
+
if (typeof originalHandler !== "function") continue;
|
|
1879
|
+
let method;
|
|
1880
|
+
let subPath = "";
|
|
1881
|
+
if (decoratedRoutes && decoratedRoutes.has(name)) {
|
|
1882
|
+
const config = decoratedRoutes.get(name);
|
|
1883
|
+
method = config.method;
|
|
1884
|
+
subPath = config.path;
|
|
1885
|
+
} else {
|
|
1886
|
+
for (let j = 0; j < HTTPMethods.length; j++) {
|
|
1887
|
+
const m = HTTPMethods[j];
|
|
1888
|
+
if (name.toUpperCase().startsWith(m)) {
|
|
1889
|
+
method = m;
|
|
1890
|
+
const rest = name.slice(m.length);
|
|
1891
|
+
if (rest.length === 0) {
|
|
1892
|
+
subPath = "/";
|
|
1893
|
+
} else {
|
|
1894
|
+
subPath = "";
|
|
1895
|
+
let buffer = "";
|
|
1896
|
+
const flush = () => {
|
|
1897
|
+
if (buffer.length > 0) {
|
|
1898
|
+
subPath += "/" + buffer.toLowerCase();
|
|
1899
|
+
buffer = "";
|
|
1900
|
+
}
|
|
1901
|
+
};
|
|
1902
|
+
for (let i2 = 0; i2 < rest.length; i2++) {
|
|
1903
|
+
const char = rest[i2];
|
|
1904
|
+
if (char === "$") {
|
|
1905
|
+
flush();
|
|
1906
|
+
subPath += "/:";
|
|
1907
|
+
continue;
|
|
1908
|
+
}
|
|
1909
|
+
buffer += char;
|
|
1910
|
+
}
|
|
1911
|
+
if (buffer.length > 0) flush();
|
|
1912
|
+
subPath = rest.replace(/\$/g, "/:").replace(/([a-z0-9])([A-Z])/g, "$1/$2").toLowerCase();
|
|
1913
|
+
if (!subPath.startsWith("/")) {
|
|
1914
|
+
subPath = "/" + subPath;
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
break;
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
if (method) {
|
|
1922
|
+
routesAttached++;
|
|
1923
|
+
const cleanPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
1924
|
+
const cleanSubPath = subPath === "/" ? "" : subPath;
|
|
1925
|
+
let joined;
|
|
1926
|
+
if (cleanSubPath.length === 0) {
|
|
1927
|
+
joined = cleanPrefix;
|
|
1928
|
+
} else if (cleanSubPath.startsWith("/")) {
|
|
1929
|
+
joined = cleanPrefix + cleanSubPath;
|
|
1930
|
+
} else {
|
|
1931
|
+
joined = cleanPrefix + "/" + cleanSubPath;
|
|
1932
|
+
}
|
|
1933
|
+
const fullPath = joined || "/";
|
|
1934
|
+
const normalizedPath = fullPath.replace(/\/+/g, "/");
|
|
1935
|
+
const methodMw = methodMiddlewareMap instanceof Map ? methodMiddlewareMap.get(name) || [] : [];
|
|
1936
|
+
const allMiddleware = [...controllerMiddleware, ...methodMw];
|
|
1937
|
+
const routeArgs = decoratedArgs && decoratedArgs.get(name);
|
|
1938
|
+
const wrappedHandler = async (ctx) => {
|
|
1939
|
+
let args = [ctx];
|
|
1940
|
+
if (routeArgs?.length > 0) {
|
|
1941
|
+
args = [];
|
|
1942
|
+
const sortedArgs = [...routeArgs].sort((a, b) => a.index - b.index);
|
|
1943
|
+
for (let k = 0; k < sortedArgs.length; k++) {
|
|
1944
|
+
const arg = sortedArgs[k];
|
|
1945
|
+
switch (arg.type) {
|
|
1946
|
+
case RouteParamType.BODY:
|
|
1947
|
+
try {
|
|
1948
|
+
if (ctx.req.headers.get("content-type")?.includes("application/json")) {
|
|
1949
|
+
args[arg.index] = await ctx.req.json();
|
|
1950
|
+
} else {
|
|
1951
|
+
const text = await ctx.req.text();
|
|
1952
|
+
if (!text) {
|
|
1953
|
+
args[arg.index] = {};
|
|
1954
|
+
} else {
|
|
1955
|
+
args[arg.index] = JSON.parse(text);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
} catch (e) {
|
|
1959
|
+
const err = new Error("Invalid JSON body");
|
|
1960
|
+
err.status = 400;
|
|
1961
|
+
throw err;
|
|
1962
|
+
}
|
|
1963
|
+
break;
|
|
1964
|
+
case RouteParamType.PARAM:
|
|
1965
|
+
args[arg.index] = arg.name ? ctx.params[arg.name] : ctx.params;
|
|
1966
|
+
break;
|
|
1967
|
+
case RouteParamType.QUERY: {
|
|
1968
|
+
const url = new URL(ctx.req.url);
|
|
1969
|
+
if (arg.name) {
|
|
1970
|
+
const vals = url.searchParams.getAll(arg.name);
|
|
1971
|
+
args[arg.index] = vals.length > 1 ? vals : vals[0];
|
|
1972
|
+
} else {
|
|
1973
|
+
const query = {};
|
|
1974
|
+
const keys = Object.keys(url.searchParams);
|
|
1975
|
+
for (let k2 = 0; k2 < keys.length; k2++) {
|
|
1976
|
+
const key = keys[k2];
|
|
1977
|
+
const vals = url.searchParams.getAll(key);
|
|
1978
|
+
query[key] = vals.length > 1 ? vals : vals[0];
|
|
1979
|
+
}
|
|
1980
|
+
args[arg.index] = query;
|
|
1981
|
+
}
|
|
1982
|
+
break;
|
|
1983
|
+
}
|
|
1984
|
+
case RouteParamType.HEADER:
|
|
1985
|
+
args[arg.index] = arg.name ? ctx.req.headers.get(arg.name) : ctx.req.headers;
|
|
1986
|
+
break;
|
|
1987
|
+
case RouteParamType.REQUEST:
|
|
1988
|
+
args[arg.index] = ctx.req;
|
|
1989
|
+
break;
|
|
1990
|
+
case RouteParamType.CONTEXT:
|
|
1991
|
+
args[arg.index] = ctx;
|
|
1992
|
+
break;
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
const tracedOriginalHandler = ctx.app?.applicationConfig.enableTracing ? traceHandler(originalHandler, normalizedPath) : originalHandler;
|
|
1997
|
+
return tracedOriginalHandler.apply(instance, args);
|
|
1998
|
+
};
|
|
1999
|
+
let finalHandler = wrappedHandler;
|
|
2000
|
+
if (allMiddleware.length > 0) {
|
|
2001
|
+
const composed = compose(allMiddleware);
|
|
2002
|
+
finalHandler = async (ctx) => {
|
|
2003
|
+
return composed(ctx, () => wrappedHandler(ctx));
|
|
2004
|
+
};
|
|
2005
|
+
}
|
|
2006
|
+
finalHandler.originalHandler = originalHandler;
|
|
2007
|
+
if (finalHandler !== wrappedHandler) {
|
|
2008
|
+
wrappedHandler.originalHandler = originalHandler;
|
|
2009
|
+
}
|
|
2010
|
+
const tagName = instance.constructor.name;
|
|
2011
|
+
const decoratedSpecs = instance[$routeSpec] || proto && proto[$routeSpec];
|
|
2012
|
+
const userSpec = decoratedSpecs && decoratedSpecs.get(name);
|
|
2013
|
+
const spec = { tags: [tagName], ...userSpec };
|
|
2014
|
+
this.add({ method, path: normalizedPath, handler: finalHandler, spec, controller: instance });
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
if (routesAttached === 0) {
|
|
2018
|
+
console.warn(`No routes attached to controller ${instance.constructor.name}`);
|
|
2019
|
+
}
|
|
2020
|
+
instance[$isMounted] = true;
|
|
2021
|
+
}
|
|
2118
2022
|
/**
|
|
2119
2023
|
* Find a route matching the given method and path.
|
|
2120
2024
|
* @param method HTTP method
|
|
@@ -2148,10 +2052,13 @@ class ShokupanRouter {
|
|
|
2148
2052
|
}
|
|
2149
2053
|
parsePath(path) {
|
|
2150
2054
|
const keys = [];
|
|
2055
|
+
if (path.length > 2048) {
|
|
2056
|
+
throw new Error("Path too long");
|
|
2057
|
+
}
|
|
2151
2058
|
const pattern = path.replace(/:([a-zA-Z0-9_]+)/g, (_, key) => {
|
|
2152
2059
|
keys.push(key);
|
|
2153
|
-
return "([^/]
|
|
2154
|
-
}).replace(/\*\*/g, "
|
|
2060
|
+
return "([^/]{1,255})";
|
|
2061
|
+
}).replace(/\*\*/g, ".{0,1000}").replace(/\*/g, "[^/]{1,255}");
|
|
2155
2062
|
return {
|
|
2156
2063
|
regex: new RegExp(`^${pattern}$`),
|
|
2157
2064
|
keys
|
|
@@ -2238,7 +2145,7 @@ class ShokupanRouter {
|
|
|
2238
2145
|
if (effectiveRenderer) {
|
|
2239
2146
|
const innerHandler = wrappedHandler;
|
|
2240
2147
|
wrappedHandler = async (ctx) => {
|
|
2241
|
-
ctx.
|
|
2148
|
+
ctx.setRenderer(effectiveRenderer);
|
|
2242
2149
|
return innerHandler(ctx);
|
|
2243
2150
|
};
|
|
2244
2151
|
}
|
|
@@ -2640,6 +2547,13 @@ class Shokupan extends ShokupanRouter {
|
|
|
2640
2547
|
}
|
|
2641
2548
|
return this;
|
|
2642
2549
|
}
|
|
2550
|
+
/**
|
|
2551
|
+
* Registers a plugin.
|
|
2552
|
+
*/
|
|
2553
|
+
register(plugin, options) {
|
|
2554
|
+
plugin.onInit(this, options);
|
|
2555
|
+
return this;
|
|
2556
|
+
}
|
|
2643
2557
|
startupHooks = [];
|
|
2644
2558
|
/**
|
|
2645
2559
|
* Registers a callback to be executed before the server starts listening.
|
|
@@ -2702,7 +2616,7 @@ class Shokupan extends ShokupanRouter {
|
|
|
2702
2616
|
};
|
|
2703
2617
|
let factory = this.applicationConfig.serverFactory;
|
|
2704
2618
|
if (!factory && typeof Bun === "undefined") {
|
|
2705
|
-
const { createHttpServer } = await import("./server-
|
|
2619
|
+
const { createHttpServer } = await import("./http-server-0xH174zz.js");
|
|
2706
2620
|
factory = createHttpServer();
|
|
2707
2621
|
}
|
|
2708
2622
|
const server = factory ? await factory(serveOptions) : Bun.serve(serveOptions);
|
|
@@ -2771,19 +2685,19 @@ class Shokupan extends ShokupanRouter {
|
|
|
2771
2685
|
"http.method": req.method
|
|
2772
2686
|
}
|
|
2773
2687
|
};
|
|
2774
|
-
const parent = store?.
|
|
2688
|
+
const parent = store?.span;
|
|
2775
2689
|
const ctx = parent ? trace.setSpan(context.active(), parent) : void 0;
|
|
2776
2690
|
return tracer2.startActiveSpan(`${req.method} ${new URL(req.url).pathname}`, attrs, ctx, (span) => {
|
|
2777
|
-
const
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
return asyncContext.run(
|
|
2691
|
+
const ctxStore = new RequestContextStore();
|
|
2692
|
+
ctxStore.span = span;
|
|
2693
|
+
ctxStore.request = req;
|
|
2694
|
+
return asyncContext.run(ctxStore, () => this.handleRequest(req, server).finally(() => span.end()));
|
|
2781
2695
|
});
|
|
2782
2696
|
}
|
|
2783
2697
|
if (this.applicationConfig.enableAsyncLocalStorage) {
|
|
2784
|
-
const
|
|
2785
|
-
|
|
2786
|
-
return asyncContext.run(
|
|
2698
|
+
const ctxStore = new RequestContextStore();
|
|
2699
|
+
ctxStore.request = req;
|
|
2700
|
+
return asyncContext.run(ctxStore, () => this.handleRequest(req, server));
|
|
2787
2701
|
}
|
|
2788
2702
|
return this.handleRequest(req, server);
|
|
2789
2703
|
}
|
|
@@ -2805,6 +2719,7 @@ class Shokupan extends ShokupanRouter {
|
|
|
2805
2719
|
const bodyParsing = ["POST", "PUT", "PATCH", "DELETE"].includes(req.method) ? ctx.parseBody() : Promise.resolve();
|
|
2806
2720
|
const match = this.find(req.method, ctx.path);
|
|
2807
2721
|
if (match) {
|
|
2722
|
+
ctx._routeMatched = true;
|
|
2808
2723
|
ctx.params = match.params;
|
|
2809
2724
|
await bodyParsing;
|
|
2810
2725
|
return match.handler(ctx);
|
|
@@ -2819,10 +2734,14 @@ class Shokupan extends ShokupanRouter {
|
|
|
2819
2734
|
} else if (result === null || result === void 0) {
|
|
2820
2735
|
if (ctx._finalResponse instanceof Response) {
|
|
2821
2736
|
response = ctx._finalResponse;
|
|
2822
|
-
} else if (ctx.
|
|
2737
|
+
} else if (ctx._routeMatched) {
|
|
2823
2738
|
response = ctx.send(null, { status: ctx.response.status, headers: ctx.response.headers });
|
|
2824
2739
|
} else {
|
|
2825
|
-
|
|
2740
|
+
if (ctx.response.status !== 200) {
|
|
2741
|
+
response = ctx.send(null, { status: ctx.response.status, headers: ctx.response.headers });
|
|
2742
|
+
} else {
|
|
2743
|
+
response = ctx.text("Not Found", 404);
|
|
2744
|
+
}
|
|
2826
2745
|
}
|
|
2827
2746
|
} else if (typeof result === "object") {
|
|
2828
2747
|
response = ctx.json(result);
|
|
@@ -2834,7 +2753,7 @@ class Shokupan extends ShokupanRouter {
|
|
|
2834
2753
|
return response;
|
|
2835
2754
|
} catch (err) {
|
|
2836
2755
|
console.error(err);
|
|
2837
|
-
const span = asyncContext.getStore()?.
|
|
2756
|
+
const span = asyncContext.getStore()?.span;
|
|
2838
2757
|
if (span) span.setStatus({ code: 2 });
|
|
2839
2758
|
const status = err.status || err.statusCode || 500;
|
|
2840
2759
|
const body = { error: err.message || "Internal Server Error" };
|
|
@@ -2842,31 +2761,195 @@ class Shokupan extends ShokupanRouter {
|
|
|
2842
2761
|
await this.runHooks("onError", ctx, err);
|
|
2843
2762
|
return ctx.json(body, status);
|
|
2844
2763
|
}
|
|
2845
|
-
};
|
|
2846
|
-
let executionPromise = handle();
|
|
2847
|
-
const timeoutMs = this.applicationConfig.requestTimeout;
|
|
2848
|
-
if (timeoutMs && timeoutMs > 0) {
|
|
2849
|
-
let timeoutId;
|
|
2850
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
2851
|
-
timeoutId = setTimeout(async () => {
|
|
2852
|
-
controller.abort();
|
|
2853
|
-
await this.runHooks("onRequestTimeout", ctx);
|
|
2854
|
-
reject(new Error("Request Timeout"));
|
|
2855
|
-
}, timeoutMs);
|
|
2764
|
+
};
|
|
2765
|
+
let executionPromise = handle();
|
|
2766
|
+
const timeoutMs = this.applicationConfig.requestTimeout;
|
|
2767
|
+
if (timeoutMs && timeoutMs > 0) {
|
|
2768
|
+
let timeoutId;
|
|
2769
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
2770
|
+
timeoutId = setTimeout(async () => {
|
|
2771
|
+
controller.abort();
|
|
2772
|
+
await this.runHooks("onRequestTimeout", ctx);
|
|
2773
|
+
reject(new Error("Request Timeout"));
|
|
2774
|
+
}, timeoutMs);
|
|
2775
|
+
});
|
|
2776
|
+
executionPromise = Promise.race([executionPromise, timeoutPromise]).finally(() => clearTimeout(timeoutId));
|
|
2777
|
+
}
|
|
2778
|
+
return executionPromise.catch((err) => {
|
|
2779
|
+
if (err.message === "Request Timeout") {
|
|
2780
|
+
return ctx.text("Request Timeout", 408);
|
|
2781
|
+
}
|
|
2782
|
+
console.error("Unexpected error in request execution:", err);
|
|
2783
|
+
return ctx.text("Internal Server Error", 500);
|
|
2784
|
+
}).then(async (res) => {
|
|
2785
|
+
await this.runHooks("onResponseEnd", ctx, res);
|
|
2786
|
+
return res;
|
|
2787
|
+
});
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
function RateLimitMiddleware(options = {}) {
|
|
2791
|
+
const windowMs = options.windowMs || 60 * 1e3;
|
|
2792
|
+
const max = options.limit || options.max || 5;
|
|
2793
|
+
const message = options.message || "Too many requests, please try again later.";
|
|
2794
|
+
const statusCode = options.statusCode || 429;
|
|
2795
|
+
const headers = options.headers !== false;
|
|
2796
|
+
const mode = options.mode || "user";
|
|
2797
|
+
const trustedProxies = options.trustedProxies || [];
|
|
2798
|
+
const keyGenerator = options.keyGenerator || ((ctx) => {
|
|
2799
|
+
if (mode === "absolute") {
|
|
2800
|
+
return "global";
|
|
2801
|
+
}
|
|
2802
|
+
const xForwardedFor = ctx.headers.get("x-forwarded-for");
|
|
2803
|
+
if (xForwardedFor && trustedProxies.length > 0) {
|
|
2804
|
+
const ips = xForwardedFor.split(",").map((ip) => ip.trim());
|
|
2805
|
+
for (let i = ips.length - 1; i >= 0; i--) {
|
|
2806
|
+
const ip = ips[i];
|
|
2807
|
+
if (!trustedProxies.includes(ip)) {
|
|
2808
|
+
if (/^[\d.:a-fA-F]+$/.test(ip)) {
|
|
2809
|
+
return ip;
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
return ctx.server?.requestIP?.(ctx.request)?.address || "unknown";
|
|
2815
|
+
});
|
|
2816
|
+
const skip = options.skip || (() => false);
|
|
2817
|
+
const hits = /* @__PURE__ */ new Map();
|
|
2818
|
+
const interval = setInterval(() => {
|
|
2819
|
+
const now = Date.now();
|
|
2820
|
+
const entries = Array.from(hits.entries());
|
|
2821
|
+
for (let i = 0; i < entries.length; i++) {
|
|
2822
|
+
const [key, record] = entries[i];
|
|
2823
|
+
if (record.resetTime <= now) {
|
|
2824
|
+
hits.delete(key);
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
}, windowMs);
|
|
2828
|
+
if (interval.unref) interval.unref();
|
|
2829
|
+
const rateLimitMiddleware = async function RateLimitMiddleware2(ctx, next) {
|
|
2830
|
+
if (skip(ctx)) return next();
|
|
2831
|
+
const key = keyGenerator(ctx);
|
|
2832
|
+
const now = Date.now();
|
|
2833
|
+
let record = hits.get(key);
|
|
2834
|
+
if (!record || record.resetTime <= now) {
|
|
2835
|
+
record = {
|
|
2836
|
+
hits: 0,
|
|
2837
|
+
resetTime: now + windowMs
|
|
2838
|
+
};
|
|
2839
|
+
hits.set(key, record);
|
|
2840
|
+
}
|
|
2841
|
+
record.hits++;
|
|
2842
|
+
const remaining = Math.max(0, max - record.hits);
|
|
2843
|
+
const resetTime = Math.ceil(record.resetTime / 1e3);
|
|
2844
|
+
const retryAfter = Math.ceil((record.resetTime - now) / 1e3);
|
|
2845
|
+
const setHeaders = (res) => {
|
|
2846
|
+
if (!headers || !res || !res.headers) return;
|
|
2847
|
+
try {
|
|
2848
|
+
res.headers.set("X-RateLimit-Limit", String(max));
|
|
2849
|
+
res.headers.set("X-RateLimit-Remaining", String(remaining));
|
|
2850
|
+
res.headers.set("X-RateLimit-Reset", String(resetTime));
|
|
2851
|
+
} catch (e) {
|
|
2852
|
+
}
|
|
2853
|
+
};
|
|
2854
|
+
if (record.hits > max) {
|
|
2855
|
+
if (options.onRateLimited) {
|
|
2856
|
+
const result = await options.onRateLimited(ctx, key);
|
|
2857
|
+
if (result instanceof Response) {
|
|
2858
|
+
return result;
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
const msg = typeof message === "function" ? message(ctx, key) : message;
|
|
2862
|
+
typeof msg === "object" ? JSON.stringify(msg) : String(msg);
|
|
2863
|
+
const res = typeof msg === "object" ? ctx.json(msg, statusCode) : ctx.text(String(msg), statusCode);
|
|
2864
|
+
if (headers) {
|
|
2865
|
+
setHeaders(res);
|
|
2866
|
+
res.headers.set("Retry-After", String(retryAfter));
|
|
2867
|
+
}
|
|
2868
|
+
return res;
|
|
2869
|
+
}
|
|
2870
|
+
const response = await next();
|
|
2871
|
+
if (response instanceof Response && headers) {
|
|
2872
|
+
setHeaders(response);
|
|
2873
|
+
}
|
|
2874
|
+
return response;
|
|
2875
|
+
};
|
|
2876
|
+
rateLimitMiddleware.isBuiltin = true;
|
|
2877
|
+
rateLimitMiddleware.pluginName = "RateLimit";
|
|
2878
|
+
return rateLimitMiddleware;
|
|
2879
|
+
}
|
|
2880
|
+
function Controller(path = "/") {
|
|
2881
|
+
return (target) => {
|
|
2882
|
+
target[$controllerPath] = path;
|
|
2883
|
+
};
|
|
2884
|
+
}
|
|
2885
|
+
function Use(...middleware) {
|
|
2886
|
+
return (target, propertyKey, descriptor) => {
|
|
2887
|
+
if (!propertyKey) {
|
|
2888
|
+
const existing = target[$middleware] || [];
|
|
2889
|
+
target[$middleware] = [...existing, ...middleware];
|
|
2890
|
+
} else {
|
|
2891
|
+
if (!target[$middleware]) {
|
|
2892
|
+
target[$middleware] = /* @__PURE__ */ new Map();
|
|
2893
|
+
}
|
|
2894
|
+
const existing = target[$middleware].get(propertyKey) || [];
|
|
2895
|
+
target[$middleware].set(propertyKey, [...existing, ...middleware]);
|
|
2896
|
+
}
|
|
2897
|
+
};
|
|
2898
|
+
}
|
|
2899
|
+
function createParamDecorator(type) {
|
|
2900
|
+
return (name) => {
|
|
2901
|
+
return (target, propertyKey, parameterIndex) => {
|
|
2902
|
+
if (!target[$routeArgs]) {
|
|
2903
|
+
target[$routeArgs] = /* @__PURE__ */ new Map();
|
|
2904
|
+
}
|
|
2905
|
+
if (!target[$routeArgs].has(propertyKey)) {
|
|
2906
|
+
target[$routeArgs].set(propertyKey, []);
|
|
2907
|
+
}
|
|
2908
|
+
target[$routeArgs].get(propertyKey).push({
|
|
2909
|
+
index: parameterIndex,
|
|
2910
|
+
type,
|
|
2911
|
+
name
|
|
2856
2912
|
});
|
|
2857
|
-
|
|
2913
|
+
};
|
|
2914
|
+
};
|
|
2915
|
+
}
|
|
2916
|
+
const Body = createParamDecorator(RouteParamType.BODY);
|
|
2917
|
+
const Param = createParamDecorator(RouteParamType.PARAM);
|
|
2918
|
+
const Query = createParamDecorator(RouteParamType.QUERY);
|
|
2919
|
+
const Headers$1 = createParamDecorator(RouteParamType.HEADER);
|
|
2920
|
+
const Req = createParamDecorator(RouteParamType.REQUEST);
|
|
2921
|
+
const Ctx = createParamDecorator(RouteParamType.CONTEXT);
|
|
2922
|
+
function Spec(spec) {
|
|
2923
|
+
return (target, propertyKey, descriptor) => {
|
|
2924
|
+
if (!target[$routeSpec]) {
|
|
2925
|
+
target[$routeSpec] = /* @__PURE__ */ new Map();
|
|
2858
2926
|
}
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2927
|
+
target[$routeSpec].set(propertyKey, spec);
|
|
2928
|
+
};
|
|
2929
|
+
}
|
|
2930
|
+
function createMethodDecorator(method) {
|
|
2931
|
+
return (path = "/") => {
|
|
2932
|
+
return (target, propertyKey, descriptor) => {
|
|
2933
|
+
if (!target[$routeMethods]) {
|
|
2934
|
+
target[$routeMethods] = /* @__PURE__ */ new Map();
|
|
2862
2935
|
}
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2936
|
+
target[$routeMethods].set(propertyKey, {
|
|
2937
|
+
method,
|
|
2938
|
+
path
|
|
2939
|
+
});
|
|
2940
|
+
};
|
|
2941
|
+
};
|
|
2942
|
+
}
|
|
2943
|
+
const Get = createMethodDecorator("GET");
|
|
2944
|
+
const Post = createMethodDecorator("POST");
|
|
2945
|
+
const Put = createMethodDecorator("PUT");
|
|
2946
|
+
const Delete = createMethodDecorator("DELETE");
|
|
2947
|
+
const Patch = createMethodDecorator("PATCH");
|
|
2948
|
+
const Options = createMethodDecorator("OPTIONS");
|
|
2949
|
+
const Head = createMethodDecorator("HEAD");
|
|
2950
|
+
const All = createMethodDecorator("ALL");
|
|
2951
|
+
function RateLimit(options) {
|
|
2952
|
+
return Use(RateLimitMiddleware(options));
|
|
2870
2953
|
}
|
|
2871
2954
|
class AuthPlugin extends ShokupanRouter {
|
|
2872
2955
|
constructor(authConfig) {
|
|
@@ -2876,6 +2959,13 @@ class AuthPlugin extends ShokupanRouter {
|
|
|
2876
2959
|
this.init();
|
|
2877
2960
|
}
|
|
2878
2961
|
secret;
|
|
2962
|
+
onInit(app, options) {
|
|
2963
|
+
if (options?.path) {
|
|
2964
|
+
app.mount(options.path, this);
|
|
2965
|
+
} else {
|
|
2966
|
+
app.mount(options.path ?? "/", this);
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2879
2969
|
getProviderInstance(name, p) {
|
|
2880
2970
|
switch (name) {
|
|
2881
2971
|
case "github":
|
|
@@ -2939,9 +3029,10 @@ class AuthPlugin extends ShokupanRouter {
|
|
|
2939
3029
|
} else {
|
|
2940
3030
|
return ctx.text("Provider config error", 500);
|
|
2941
3031
|
}
|
|
2942
|
-
|
|
3032
|
+
const isSecure = ctx.secure;
|
|
3033
|
+
ctx.res.headers.set("Set-Cookie", `oauth_state=${state}; Path=/; HttpOnly; SameSite=Lax${isSecure ? "; Secure" : ""}; Max-Age=600`);
|
|
2943
3034
|
if (codeVerifier) {
|
|
2944
|
-
ctx.res.headers.append("Set-Cookie", `oauth_verifier=${codeVerifier}; Path=/; HttpOnly; Max-Age=600`);
|
|
3035
|
+
ctx.res.headers.append("Set-Cookie", `oauth_verifier=${codeVerifier}; Path=/; HttpOnly; SameSite=Lax${isSecure ? "; Secure" : ""}; Max-Age=600`);
|
|
2945
3036
|
}
|
|
2946
3037
|
return ctx.redirect(url.toString());
|
|
2947
3038
|
});
|
|
@@ -2982,7 +3073,7 @@ class AuthPlugin extends ShokupanRouter {
|
|
|
2982
3073
|
return ctx.json({ token: jwt, user });
|
|
2983
3074
|
} catch (e) {
|
|
2984
3075
|
console.error("Auth Error", e);
|
|
2985
|
-
return ctx.text("Authentication failed
|
|
3076
|
+
return ctx.text("Authentication failed. Please try again.", 500);
|
|
2986
3077
|
}
|
|
2987
3078
|
});
|
|
2988
3079
|
}
|
|
@@ -3092,8 +3183,196 @@ class AuthPlugin extends ShokupanRouter {
|
|
|
3092
3183
|
};
|
|
3093
3184
|
}
|
|
3094
3185
|
}
|
|
3186
|
+
class ClusterPlugin {
|
|
3187
|
+
constructor(options = {}) {
|
|
3188
|
+
this.options = options;
|
|
3189
|
+
}
|
|
3190
|
+
onInit(app) {
|
|
3191
|
+
const originalListen = app.listen.bind(app);
|
|
3192
|
+
const { workers = "auto", silent = false, sticky = false } = this.options;
|
|
3193
|
+
const isBun = typeof Bun !== "undefined";
|
|
3194
|
+
const numCPUs = os__default.cpus().length;
|
|
3195
|
+
const numWorkers = workers === "auto" || workers === -1 ? numCPUs : workers;
|
|
3196
|
+
if (numWorkers <= 1) {
|
|
3197
|
+
return;
|
|
3198
|
+
}
|
|
3199
|
+
app.listen = async (port) => {
|
|
3200
|
+
const finalPort = port ?? app.applicationConfig.port ?? 3e3;
|
|
3201
|
+
if (isBun) {
|
|
3202
|
+
return this.handleBun(app, finalPort, numWorkers, originalListen);
|
|
3203
|
+
} else {
|
|
3204
|
+
return this.handleNode(app, finalPort, numWorkers, originalListen, silent, sticky);
|
|
3205
|
+
}
|
|
3206
|
+
};
|
|
3207
|
+
}
|
|
3208
|
+
async handleBun(app, port, workers, originalListen) {
|
|
3209
|
+
const workerId = process.env["SHOKUPAN_WORKER_ID"];
|
|
3210
|
+
if (workerId) {
|
|
3211
|
+
app.applicationConfig.reusePort = true;
|
|
3212
|
+
return originalListen(port);
|
|
3213
|
+
}
|
|
3214
|
+
console.log(`[Cluster] Starting ${workers} Bun workers on port ${port}...`);
|
|
3215
|
+
const spawnWorker = (id) => {
|
|
3216
|
+
Bun.spawn([process.argv0, ...process.argv.slice(1)], {
|
|
3217
|
+
env: { ...process.env, SHOKUPAN_WORKER_ID: id },
|
|
3218
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
3219
|
+
onExit(proc, exitCode, signalCode, error) {
|
|
3220
|
+
console.log(`[Cluster] Worker ${id} died (code: ${exitCode}). Restarting...`);
|
|
3221
|
+
spawnWorker(id);
|
|
3222
|
+
}
|
|
3223
|
+
});
|
|
3224
|
+
};
|
|
3225
|
+
for (let i = 0; i < workers; i++) {
|
|
3226
|
+
spawnWorker(process.pid + "_" + i + 1);
|
|
3227
|
+
}
|
|
3228
|
+
setInterval(() => {
|
|
3229
|
+
}, 1e3 * 60 * 60);
|
|
3230
|
+
return {
|
|
3231
|
+
stop: () => {
|
|
3232
|
+
},
|
|
3233
|
+
port
|
|
3234
|
+
};
|
|
3235
|
+
}
|
|
3236
|
+
async handleNode(app, port, workers, originalListen, silent, sticky) {
|
|
3237
|
+
if (cluster.isPrimary) {
|
|
3238
|
+
console.log(`[Cluster] Master ${process.pid} is running`);
|
|
3239
|
+
const fork = () => cluster.fork(process.env);
|
|
3240
|
+
for (let i = 0; i < workers; i++) {
|
|
3241
|
+
fork();
|
|
3242
|
+
}
|
|
3243
|
+
cluster.on("exit", (worker, code, signal) => {
|
|
3244
|
+
console.log(`[Cluster] Worker ${worker.process.pid} died. Restarting...`);
|
|
3245
|
+
fork();
|
|
3246
|
+
});
|
|
3247
|
+
if (sticky) {
|
|
3248
|
+
const server = net.createServer({ pauseOnConnect: true }, (connection) => {
|
|
3249
|
+
const remote = connection.remoteAddress || "";
|
|
3250
|
+
let hash = 0;
|
|
3251
|
+
for (let i = 0; i < remote.length; i++) {
|
|
3252
|
+
hash = (hash << 5) - hash + remote.charCodeAt(i);
|
|
3253
|
+
hash |= 0;
|
|
3254
|
+
}
|
|
3255
|
+
const index = Math.abs(hash) % workers;
|
|
3256
|
+
const worker = Object.values(cluster.workers)[index];
|
|
3257
|
+
if (worker) {
|
|
3258
|
+
worker.send("sticky-session:connection", connection);
|
|
3259
|
+
} else {
|
|
3260
|
+
connection.end();
|
|
3261
|
+
}
|
|
3262
|
+
});
|
|
3263
|
+
server.listen(port, () => {
|
|
3264
|
+
console.log(`[Cluster] Sticky Load Balancer listening on port ${port}`);
|
|
3265
|
+
});
|
|
3266
|
+
return {
|
|
3267
|
+
close: () => server.close(),
|
|
3268
|
+
port
|
|
3269
|
+
};
|
|
3270
|
+
} else {
|
|
3271
|
+
return {
|
|
3272
|
+
close: () => {
|
|
3273
|
+
},
|
|
3274
|
+
// Master controls
|
|
3275
|
+
port
|
|
3276
|
+
};
|
|
3277
|
+
}
|
|
3278
|
+
} else {
|
|
3279
|
+
if (sticky) {
|
|
3280
|
+
const server = await originalListen(0);
|
|
3281
|
+
process.on("message", (message, handle) => {
|
|
3282
|
+
if (message !== "sticky-session:connection") return;
|
|
3283
|
+
if (!handle) return;
|
|
3284
|
+
server.emit("connection", handle);
|
|
3285
|
+
handle.resume();
|
|
3286
|
+
});
|
|
3287
|
+
return server;
|
|
3288
|
+
} else {
|
|
3289
|
+
return originalListen(port);
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
const eta = new Eta();
|
|
3295
|
+
class ScalarPlugin extends ShokupanRouter {
|
|
3296
|
+
constructor(pluginOptions = {}) {
|
|
3297
|
+
pluginOptions.config ??= {};
|
|
3298
|
+
super();
|
|
3299
|
+
this.pluginOptions = pluginOptions;
|
|
3300
|
+
this.init();
|
|
3301
|
+
}
|
|
3302
|
+
onInit(app, options) {
|
|
3303
|
+
if (options?.path) {
|
|
3304
|
+
app.mount(options.path, this);
|
|
3305
|
+
} else {
|
|
3306
|
+
app.mount(options.path ?? "/", this);
|
|
3307
|
+
}
|
|
3308
|
+
this.onMount(app);
|
|
3309
|
+
}
|
|
3310
|
+
init() {
|
|
3311
|
+
this.get("/", (ctx) => {
|
|
3312
|
+
let path = ctx.url.toString();
|
|
3313
|
+
if (!path.endsWith("/")) path += "/";
|
|
3314
|
+
return ctx.html(eta.renderString(`<!doctype html>
|
|
3315
|
+
<html>
|
|
3316
|
+
<head>
|
|
3317
|
+
<title>API Reference</title>
|
|
3318
|
+
<meta charset = "utf-8" />
|
|
3319
|
+
<meta name="viewport" content = "width=device-width, initial-scale=1" />
|
|
3320
|
+
</head>
|
|
3321
|
+
|
|
3322
|
+
<body>
|
|
3323
|
+
<div id="app"></div>
|
|
3324
|
+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
|
|
3325
|
+
<script>
|
|
3326
|
+
Scalar.createApiReference('#app', [{ ...<%~ JSON.stringify(it.config.baseDocument) %>,
|
|
3327
|
+
url: "<%= it.path %>openapi.json",
|
|
3328
|
+
}
|
|
3329
|
+
])
|
|
3330
|
+
<\/script>
|
|
3331
|
+
</body>
|
|
3332
|
+
|
|
3333
|
+
</html>`, { path, config: this.pluginOptions }));
|
|
3334
|
+
});
|
|
3335
|
+
this.get("/openapi.json", async (ctx) => {
|
|
3336
|
+
let spec;
|
|
3337
|
+
if (this.root.openApiSpec) {
|
|
3338
|
+
try {
|
|
3339
|
+
spec = structuredClone(this.root.openApiSpec);
|
|
3340
|
+
} catch (e) {
|
|
3341
|
+
spec = Object.assign({}, this.root.openApiSpec);
|
|
3342
|
+
}
|
|
3343
|
+
} else {
|
|
3344
|
+
spec = await (this.root || this).generateApiSpec();
|
|
3345
|
+
}
|
|
3346
|
+
if (this.pluginOptions.baseDocument) {
|
|
3347
|
+
deepMerge(spec, this.pluginOptions.baseDocument);
|
|
3348
|
+
}
|
|
3349
|
+
return ctx.json(spec);
|
|
3350
|
+
});
|
|
3351
|
+
}
|
|
3352
|
+
// New lifecycle method to be called by router.mount
|
|
3353
|
+
onMount(parent) {
|
|
3354
|
+
if (parent.onStart) {
|
|
3355
|
+
parent.onStart(async () => {
|
|
3356
|
+
if (this.pluginOptions.enableStaticAnalysis) {
|
|
3357
|
+
try {
|
|
3358
|
+
const entrypoint = process.argv[1];
|
|
3359
|
+
console.log(`[ScalarPlugin] Running eager static analysis on entrypoint: ${entrypoint}`);
|
|
3360
|
+
const analyzer = new OpenAPIAnalyzer(process.cwd(), entrypoint);
|
|
3361
|
+
let staticSpec = await analyzer.analyze();
|
|
3362
|
+
if (!this.pluginOptions.baseDocument) this.pluginOptions.baseDocument = {};
|
|
3363
|
+
deepMerge(this.pluginOptions.baseDocument, staticSpec);
|
|
3364
|
+
console.log("[ScalarPlugin] Static analysis completed successfully.");
|
|
3365
|
+
} catch (err) {
|
|
3366
|
+
console.error("[ScalarPlugin] Failed to run static analysis:", err);
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
});
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3095
3373
|
function Compression(options = {}) {
|
|
3096
3374
|
const threshold = options.threshold ?? 512;
|
|
3375
|
+
const allowedAlgorithms = new Set(options.allowedAlgorithms ?? ["br", "gzip", "zstd", "deflate"]);
|
|
3097
3376
|
const compressionMiddleware = async function CompressionMiddleware(ctx, next) {
|
|
3098
3377
|
const acceptEncoding = ctx.headers.get("accept-encoding") || "";
|
|
3099
3378
|
let method = null;
|
|
@@ -3106,6 +3385,9 @@ function Compression(options = {}) {
|
|
|
3106
3385
|
} else if (acceptEncoding.includes("gzip")) method = "gzip";
|
|
3107
3386
|
else if (acceptEncoding.includes("deflate")) method = "deflate";
|
|
3108
3387
|
if (!method) return next();
|
|
3388
|
+
if (!allowedAlgorithms.has(method)) {
|
|
3389
|
+
return next();
|
|
3390
|
+
}
|
|
3109
3391
|
let response = await next();
|
|
3110
3392
|
if (!(response instanceof Response) && ctx._finalResponse instanceof Response) {
|
|
3111
3393
|
response = ctx._finalResponse;
|
|
@@ -3193,14 +3475,21 @@ function Cors(options = {}) {
|
|
|
3193
3475
|
const origin = ctx.headers.get("origin");
|
|
3194
3476
|
const set = (k, v) => headers.set(k, v);
|
|
3195
3477
|
const append = (k, v) => headers.append(k, v);
|
|
3478
|
+
if (origin === "null" && opts.origin !== "null") {
|
|
3479
|
+
return next();
|
|
3480
|
+
}
|
|
3196
3481
|
if (opts.origin === "*") {
|
|
3197
3482
|
set("Access-Control-Allow-Origin", "*");
|
|
3198
3483
|
} else if (typeof opts.origin === "string") {
|
|
3199
3484
|
set("Access-Control-Allow-Origin", opts.origin);
|
|
3200
3485
|
} else if (Array.isArray(opts.origin)) {
|
|
3201
|
-
if (origin
|
|
3202
|
-
|
|
3203
|
-
|
|
3486
|
+
if (origin) {
|
|
3487
|
+
const normalizedOrigin = origin.toLowerCase();
|
|
3488
|
+
const normalizedAllowed = opts.origin.map((o) => o.toLowerCase());
|
|
3489
|
+
if (normalizedAllowed.includes(normalizedOrigin)) {
|
|
3490
|
+
set("Access-Control-Allow-Origin", origin);
|
|
3491
|
+
append("Vary", "Origin");
|
|
3492
|
+
}
|
|
3204
3493
|
}
|
|
3205
3494
|
} else if (typeof opts.origin === "function") {
|
|
3206
3495
|
const allowed = opts.origin(ctx);
|
|
@@ -3653,77 +3942,6 @@ function enableOpenApiValidation(app) {
|
|
|
3653
3942
|
precompileValidators(app, spec);
|
|
3654
3943
|
});
|
|
3655
3944
|
}
|
|
3656
|
-
const eta = new Eta();
|
|
3657
|
-
class ScalarPlugin extends ShokupanRouter {
|
|
3658
|
-
constructor(pluginOptions = {}) {
|
|
3659
|
-
pluginOptions.config ??= {};
|
|
3660
|
-
super();
|
|
3661
|
-
this.pluginOptions = pluginOptions;
|
|
3662
|
-
this.init();
|
|
3663
|
-
}
|
|
3664
|
-
init() {
|
|
3665
|
-
this.get("/", (ctx) => {
|
|
3666
|
-
let path = ctx.url.toString();
|
|
3667
|
-
if (!path.endsWith("/")) path += "/";
|
|
3668
|
-
return ctx.html(eta.renderString(`<!doctype html>
|
|
3669
|
-
<html>
|
|
3670
|
-
<head>
|
|
3671
|
-
<title>API Reference</title>
|
|
3672
|
-
<meta charset = "utf-8" />
|
|
3673
|
-
<meta name="viewport" content = "width=device-width, initial-scale=1" />
|
|
3674
|
-
</head>
|
|
3675
|
-
|
|
3676
|
-
<body>
|
|
3677
|
-
<div id="app"></div>
|
|
3678
|
-
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
|
|
3679
|
-
<script>
|
|
3680
|
-
Scalar.createApiReference('#app', [{ ...<%~ JSON.stringify(it.config.baseDocument) %>,
|
|
3681
|
-
url: "<%= it.path %>openapi.json",
|
|
3682
|
-
}
|
|
3683
|
-
])
|
|
3684
|
-
<\/script>
|
|
3685
|
-
</body>
|
|
3686
|
-
|
|
3687
|
-
</html>`, { path, config: this.pluginOptions }));
|
|
3688
|
-
});
|
|
3689
|
-
this.get("/openapi.json", async (ctx) => {
|
|
3690
|
-
let spec;
|
|
3691
|
-
if (this.root.openApiSpec) {
|
|
3692
|
-
try {
|
|
3693
|
-
spec = structuredClone(this.root.openApiSpec);
|
|
3694
|
-
} catch (e) {
|
|
3695
|
-
spec = Object.assign({}, this.root.openApiSpec);
|
|
3696
|
-
}
|
|
3697
|
-
} else {
|
|
3698
|
-
spec = await (this.root || this).generateApiSpec();
|
|
3699
|
-
}
|
|
3700
|
-
if (this.pluginOptions.baseDocument) {
|
|
3701
|
-
deepMerge(spec, this.pluginOptions.baseDocument);
|
|
3702
|
-
}
|
|
3703
|
-
return ctx.json(spec);
|
|
3704
|
-
});
|
|
3705
|
-
}
|
|
3706
|
-
// New lifecycle method to be called by router.mount
|
|
3707
|
-
onMount(parent) {
|
|
3708
|
-
if (parent.onStart) {
|
|
3709
|
-
parent.onStart(async () => {
|
|
3710
|
-
if (this.pluginOptions.enableStaticAnalysis) {
|
|
3711
|
-
try {
|
|
3712
|
-
const entrypoint = process.argv[1];
|
|
3713
|
-
console.log(`[ScalarPlugin] Running eager static analysis on entrypoint: ${entrypoint}`);
|
|
3714
|
-
const analyzer = new OpenAPIAnalyzer(process.cwd(), entrypoint);
|
|
3715
|
-
let staticSpec = await analyzer.analyze();
|
|
3716
|
-
if (!this.pluginOptions.baseDocument) this.pluginOptions.baseDocument = {};
|
|
3717
|
-
deepMerge(this.pluginOptions.baseDocument, staticSpec);
|
|
3718
|
-
console.log("[ScalarPlugin] Static analysis completed successfully.");
|
|
3719
|
-
} catch (err) {
|
|
3720
|
-
console.error("[ScalarPlugin] Failed to run static analysis:", err);
|
|
3721
|
-
}
|
|
3722
|
-
}
|
|
3723
|
-
});
|
|
3724
|
-
}
|
|
3725
|
-
}
|
|
3726
|
-
}
|
|
3727
3945
|
function SecurityHeaders(options = {}) {
|
|
3728
3946
|
const securityHeadersMiddleware = async function SecurityHeadersMiddleware(ctx, next) {
|
|
3729
3947
|
const headers = {};
|
|
@@ -3847,18 +4065,18 @@ class MemoryStore extends EventEmitter {
|
|
|
3847
4065
|
}
|
|
3848
4066
|
set(sid, sess, cb) {
|
|
3849
4067
|
this.sessions[sid] = JSON.stringify(sess);
|
|
3850
|
-
cb
|
|
4068
|
+
cb?.();
|
|
3851
4069
|
}
|
|
3852
4070
|
destroy(sid, cb) {
|
|
3853
4071
|
delete this.sessions[sid];
|
|
3854
|
-
cb
|
|
4072
|
+
cb?.();
|
|
3855
4073
|
}
|
|
3856
4074
|
touch(sid, sess, cb) {
|
|
3857
4075
|
const current = this.sessions[sid];
|
|
3858
4076
|
if (current) {
|
|
3859
4077
|
this.sessions[sid] = JSON.stringify(sess);
|
|
3860
4078
|
}
|
|
3861
|
-
cb
|
|
4079
|
+
cb?.();
|
|
3862
4080
|
}
|
|
3863
4081
|
all(cb) {
|
|
3864
4082
|
const result = {};
|
|
@@ -3874,7 +4092,7 @@ class MemoryStore extends EventEmitter {
|
|
|
3874
4092
|
}
|
|
3875
4093
|
clear(cb) {
|
|
3876
4094
|
this.sessions = {};
|
|
3877
|
-
cb
|
|
4095
|
+
cb?.();
|
|
3878
4096
|
}
|
|
3879
4097
|
}
|
|
3880
4098
|
function sign(val, secret) {
|
|
@@ -3887,11 +4105,17 @@ function unsign(input, secret) {
|
|
|
3887
4105
|
if (typeof secret !== "string") throw new TypeError("Secret string must be provided.");
|
|
3888
4106
|
const tentValue = input.slice(0, input.lastIndexOf("."));
|
|
3889
4107
|
const expectedInput = sign(tentValue, secret);
|
|
3890
|
-
const
|
|
3891
|
-
const
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
4108
|
+
const maxLength = Math.max(expectedInput.length, input.length);
|
|
4109
|
+
const paddedExpected = Buffer.alloc(maxLength);
|
|
4110
|
+
const paddedInput = Buffer.alloc(maxLength);
|
|
4111
|
+
Buffer.from(expectedInput).copy(paddedExpected);
|
|
4112
|
+
Buffer.from(input).copy(paddedInput);
|
|
4113
|
+
try {
|
|
4114
|
+
const valid = require("crypto").timingSafeEqual(paddedExpected, paddedInput);
|
|
4115
|
+
return valid ? tentValue : false;
|
|
4116
|
+
} catch {
|
|
4117
|
+
return false;
|
|
4118
|
+
}
|
|
3895
4119
|
}
|
|
3896
4120
|
function Session(options) {
|
|
3897
4121
|
const store = options.store || new MemoryStore();
|
|
@@ -4064,6 +4288,7 @@ export {
|
|
|
4064
4288
|
All,
|
|
4065
4289
|
AuthPlugin,
|
|
4066
4290
|
Body,
|
|
4291
|
+
ClusterPlugin,
|
|
4067
4292
|
Compression,
|
|
4068
4293
|
Container,
|
|
4069
4294
|
Controller,
|
|
@@ -4087,16 +4312,13 @@ export {
|
|
|
4087
4312
|
RateLimitMiddleware,
|
|
4088
4313
|
Req,
|
|
4089
4314
|
RouteParamType,
|
|
4090
|
-
RouterRegistry,
|
|
4091
4315
|
ScalarPlugin,
|
|
4092
4316
|
SecurityHeaders,
|
|
4093
4317
|
Session,
|
|
4094
4318
|
Shokupan,
|
|
4095
|
-
ShokupanApplicationTree,
|
|
4096
4319
|
ShokupanContext,
|
|
4097
4320
|
ShokupanRequest,
|
|
4098
4321
|
ShokupanResponse,
|
|
4099
|
-
ShokupanRouter,
|
|
4100
4322
|
Spec,
|
|
4101
4323
|
Use,
|
|
4102
4324
|
ValidationError,
|