shokupan 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/{openapi-analyzer-Bei1sVWp.cjs → analyzer-Bei1sVWp.cjs} +1 -1
- package/dist/analyzer-Bei1sVWp.cjs.map +1 -0
- package/dist/{openapi-analyzer-Ce_7JxZh.js → analyzer-Ce_7JxZh.js} +1 -1
- package/dist/analyzer-Ce_7JxZh.js.map +1 -0
- package/dist/cli.cjs +2 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/context.d.ts +9 -9
- package/dist/{server-adapter-0xH174zz.js → http-server-0xH174zz.js} +1 -1
- package/dist/http-server-0xH174zz.js.map +1 -0
- package/dist/{server-adapter-DFhwlK8e.cjs → http-server-DFhwlK8e.cjs} +1 -1
- package/dist/http-server-DFhwlK8e.cjs.map +1 -0
- package/dist/index.cjs +928 -767
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +17 -17
- package/dist/index.js +953 -791
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts +1 -1
- package/dist/plugins/{auth.d.ts → application/auth.d.ts} +72 -3
- package/dist/plugins/application/cluster.d.ts +33 -0
- package/dist/plugins/{failed-request-recorder.d.ts → application/dashboard/failed-request-recorder.d.ts} +1 -1
- package/dist/plugins/{debugview → application/dashboard}/plugin.d.ts +13 -6
- package/dist/plugins/{server-adapter.d.ts → application/http-server.d.ts} +1 -1
- package/dist/plugins/{idempotency → application/idempotency}/plugin.d.ts +7 -1
- package/dist/plugins/{openapi.d.ts → application/openapi/openapi.d.ts} +2 -2
- package/dist/plugins/application/scalar.d.ts +36 -0
- package/dist/plugins/middleware/compression.d.ts +17 -0
- package/dist/plugins/middleware/cors.d.ts +34 -0
- package/dist/plugins/{express.d.ts → middleware/express.d.ts} +1 -1
- package/dist/plugins/{openapi-validator.d.ts → middleware/openapi-validator.d.ts} +2 -2
- package/dist/plugins/middleware/proxy.d.ts +37 -0
- package/dist/plugins/middleware/rate-limit.d.ts +58 -0
- package/dist/plugins/{security-headers.d.ts → middleware/security-headers.d.ts} +51 -1
- package/dist/plugins/{serve-static.d.ts → middleware/serve-static.d.ts} +1 -1
- package/dist/plugins/{session.d.ts → middleware/session.d.ts} +89 -3
- package/dist/plugins/{validation.d.ts → middleware/validation.d.ts} +6 -1
- package/dist/router.d.ts +5 -5
- package/dist/shokupan.d.ts +10 -4
- package/dist/util/async-hooks.d.ts +8 -2
- package/dist/{decorators.d.ts → util/decorators.d.ts} +1 -1
- package/dist/util/http-status.d.ts +2 -0
- package/dist/util/instrumentation.d.ts +1 -1
- package/dist/{router → util}/trie.d.ts +1 -1
- package/dist/{types.d.ts → util/types.d.ts} +8 -1
- package/package.json +4 -4
- package/dist/openapi-analyzer-Bei1sVWp.cjs.map +0 -1
- package/dist/openapi-analyzer-Ce_7JxZh.js.map +0 -1
- package/dist/plugins/compression.d.ts +0 -5
- package/dist/plugins/cors.d.ts +0 -11
- package/dist/plugins/proxy.d.ts +0 -11
- package/dist/plugins/rate-limit.d.ts +0 -15
- package/dist/plugins/scalar.d.ts +0 -15
- package/dist/server-adapter-0xH174zz.js.map +0 -1
- package/dist/server-adapter-DFhwlK8e.cjs.map +0 -1
- /package/dist/{analysis/openapi-analyzer.d.ts → plugins/application/openapi/analyzer.d.ts} +0 -0
- /package/dist/{di.d.ts → util/di.d.ts} +0 -0
- /package/dist/{request.d.ts → util/request.d.ts} +0 -0
- /package/dist/{response.d.ts → util/response.d.ts} +0 -0
- /package/dist/{symbol.d.ts → util/symbol.d.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,18 +1,87 @@
|
|
|
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
6
|
import { resolve, join, sep, basename } from "path";
|
|
5
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
|
-
import { trace, SpanKind, SpanStatusCode, context } from "@opentelemetry/api";
|
|
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";
|
|
19
|
+
const VALID_HTTP_STATUSES = /* @__PURE__ */ new Set([
|
|
20
|
+
100,
|
|
21
|
+
101,
|
|
22
|
+
102,
|
|
23
|
+
103,
|
|
24
|
+
200,
|
|
25
|
+
201,
|
|
26
|
+
202,
|
|
27
|
+
203,
|
|
28
|
+
204,
|
|
29
|
+
205,
|
|
30
|
+
206,
|
|
31
|
+
207,
|
|
32
|
+
208,
|
|
33
|
+
226,
|
|
34
|
+
300,
|
|
35
|
+
301,
|
|
36
|
+
302,
|
|
37
|
+
303,
|
|
38
|
+
304,
|
|
39
|
+
305,
|
|
40
|
+
306,
|
|
41
|
+
307,
|
|
42
|
+
308,
|
|
43
|
+
400,
|
|
44
|
+
401,
|
|
45
|
+
402,
|
|
46
|
+
403,
|
|
47
|
+
404,
|
|
48
|
+
405,
|
|
49
|
+
406,
|
|
50
|
+
407,
|
|
51
|
+
408,
|
|
52
|
+
409,
|
|
53
|
+
410,
|
|
54
|
+
411,
|
|
55
|
+
412,
|
|
56
|
+
413,
|
|
57
|
+
414,
|
|
58
|
+
415,
|
|
59
|
+
416,
|
|
60
|
+
417,
|
|
61
|
+
418,
|
|
62
|
+
421,
|
|
63
|
+
422,
|
|
64
|
+
423,
|
|
65
|
+
424,
|
|
66
|
+
425,
|
|
67
|
+
426,
|
|
68
|
+
428,
|
|
69
|
+
429,
|
|
70
|
+
431,
|
|
71
|
+
451,
|
|
72
|
+
500,
|
|
73
|
+
501,
|
|
74
|
+
502,
|
|
75
|
+
503,
|
|
76
|
+
504,
|
|
77
|
+
505,
|
|
78
|
+
506,
|
|
79
|
+
507,
|
|
80
|
+
508,
|
|
81
|
+
510,
|
|
82
|
+
511
|
|
83
|
+
]);
|
|
84
|
+
const VALID_REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
16
85
|
class ShokupanResponse {
|
|
17
86
|
_headers = null;
|
|
18
87
|
_status = 200;
|
|
@@ -85,72 +154,6 @@ function isValidCookieDomain(domain, currentHost) {
|
|
|
85
154
|
}
|
|
86
155
|
return false;
|
|
87
156
|
}
|
|
88
|
-
const VALID_HTTP_STATUSES = /* @__PURE__ */ new Set([
|
|
89
|
-
100,
|
|
90
|
-
101,
|
|
91
|
-
102,
|
|
92
|
-
103,
|
|
93
|
-
200,
|
|
94
|
-
201,
|
|
95
|
-
202,
|
|
96
|
-
203,
|
|
97
|
-
204,
|
|
98
|
-
205,
|
|
99
|
-
206,
|
|
100
|
-
207,
|
|
101
|
-
208,
|
|
102
|
-
226,
|
|
103
|
-
300,
|
|
104
|
-
301,
|
|
105
|
-
302,
|
|
106
|
-
303,
|
|
107
|
-
304,
|
|
108
|
-
305,
|
|
109
|
-
306,
|
|
110
|
-
307,
|
|
111
|
-
308,
|
|
112
|
-
400,
|
|
113
|
-
401,
|
|
114
|
-
402,
|
|
115
|
-
403,
|
|
116
|
-
404,
|
|
117
|
-
405,
|
|
118
|
-
406,
|
|
119
|
-
407,
|
|
120
|
-
408,
|
|
121
|
-
409,
|
|
122
|
-
410,
|
|
123
|
-
411,
|
|
124
|
-
412,
|
|
125
|
-
413,
|
|
126
|
-
414,
|
|
127
|
-
415,
|
|
128
|
-
416,
|
|
129
|
-
417,
|
|
130
|
-
418,
|
|
131
|
-
421,
|
|
132
|
-
422,
|
|
133
|
-
423,
|
|
134
|
-
424,
|
|
135
|
-
425,
|
|
136
|
-
426,
|
|
137
|
-
428,
|
|
138
|
-
429,
|
|
139
|
-
431,
|
|
140
|
-
451,
|
|
141
|
-
500,
|
|
142
|
-
501,
|
|
143
|
-
502,
|
|
144
|
-
503,
|
|
145
|
-
504,
|
|
146
|
-
505,
|
|
147
|
-
506,
|
|
148
|
-
507,
|
|
149
|
-
508,
|
|
150
|
-
510,
|
|
151
|
-
511
|
|
152
|
-
]);
|
|
153
|
-
const VALID_REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
154
157
|
class ShokupanContext {
|
|
155
158
|
constructor(request, server, state, app, signal, enableMiddlewareTracking = false) {
|
|
156
159
|
this.request = request;
|
|
@@ -189,12 +192,20 @@ class ShokupanContext {
|
|
|
189
192
|
_bodyType;
|
|
190
193
|
_bodyParsed = false;
|
|
191
194
|
_bodyParseError;
|
|
195
|
+
_routeMatched = false;
|
|
192
196
|
// Cached URL properties to avoid repeated parsing
|
|
193
197
|
_cachedHostname;
|
|
194
198
|
_cachedProtocol;
|
|
195
199
|
_cachedHost;
|
|
196
200
|
_cachedOrigin;
|
|
197
201
|
_cachedQuery;
|
|
202
|
+
/**
|
|
203
|
+
* JSX Rendering Function
|
|
204
|
+
*/
|
|
205
|
+
renderer;
|
|
206
|
+
setRenderer(renderer) {
|
|
207
|
+
this.renderer = renderer;
|
|
208
|
+
}
|
|
198
209
|
get url() {
|
|
199
210
|
if (!this._url) {
|
|
200
211
|
const urlString = this.request.url || "http://localhost/";
|
|
@@ -412,11 +423,15 @@ class ShokupanContext {
|
|
|
412
423
|
}
|
|
413
424
|
const contentType = this.request.headers.get("content-type") || "";
|
|
414
425
|
if (contentType.includes("application/json") || contentType.includes("+json")) {
|
|
415
|
-
const rawText = await this.readRawBody();
|
|
416
426
|
const parserType = this.app?.applicationConfig?.jsonParser || "native";
|
|
417
427
|
if (parserType === "native") {
|
|
418
|
-
|
|
428
|
+
try {
|
|
429
|
+
this._cachedBody = await this.request.json();
|
|
430
|
+
} catch (e) {
|
|
431
|
+
throw e;
|
|
432
|
+
}
|
|
419
433
|
} else {
|
|
434
|
+
const rawText = await this.request.text();
|
|
420
435
|
const { getJSONParser } = await import("./json-parser-B3dnQmCC.js");
|
|
421
436
|
const parser = getJSONParser(parserType);
|
|
422
437
|
this._cachedBody = parser(rawText);
|
|
@@ -426,7 +441,7 @@ class ShokupanContext {
|
|
|
426
441
|
this._cachedBody = await this.request.formData();
|
|
427
442
|
this._bodyType = "formData";
|
|
428
443
|
} else {
|
|
429
|
-
this._cachedBody = await this.
|
|
444
|
+
this._cachedBody = await this.request.text();
|
|
430
445
|
this._bodyType = "text";
|
|
431
446
|
}
|
|
432
447
|
this._bodyParsed = true;
|
|
@@ -604,10 +619,6 @@ class ShokupanContext {
|
|
|
604
619
|
return this._finalResponse;
|
|
605
620
|
}
|
|
606
621
|
}
|
|
607
|
-
/**
|
|
608
|
-
* JSX Rendering Function
|
|
609
|
-
*/
|
|
610
|
-
renderer;
|
|
611
622
|
/**
|
|
612
623
|
* Render a JSX element
|
|
613
624
|
* @param element JSX Element
|
|
@@ -626,284 +637,67 @@ class ShokupanContext {
|
|
|
626
637
|
return this.html(html, status, headers);
|
|
627
638
|
}
|
|
628
639
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
if (xForwardedFor && trustedProxies.length > 0) {
|
|
643
|
-
const ips = xForwardedFor.split(",").map((ip) => ip.trim());
|
|
644
|
-
for (let i = ips.length - 1; i >= 0; i--) {
|
|
645
|
-
const ip = ips[i];
|
|
646
|
-
if (!trustedProxies.includes(ip)) {
|
|
647
|
-
if (/^[\d.:a-fA-F]+$/.test(ip)) {
|
|
648
|
-
return ip;
|
|
649
|
-
}
|
|
650
|
-
}
|
|
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();
|
|
651
653
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
const skip = options.skip || (() => false);
|
|
656
|
-
const hits = /* @__PURE__ */ new Map();
|
|
657
|
-
const interval = setInterval(() => {
|
|
658
|
-
const now = Date.now();
|
|
659
|
-
const entries = Array.from(hits.entries());
|
|
660
|
-
for (let i = 0; i < entries.length; i++) {
|
|
661
|
-
const [key, record] = entries[i];
|
|
662
|
-
if (record.resetTime <= now) {
|
|
663
|
-
hits.delete(key);
|
|
654
|
+
const fn = middleware[i];
|
|
655
|
+
if (!context2._debug) {
|
|
656
|
+
return fn(context2, () => runner(i + 1));
|
|
664
657
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
const now = Date.now();
|
|
672
|
-
let record = hits.get(key);
|
|
673
|
-
if (!record || record.resetTime <= now) {
|
|
674
|
-
record = {
|
|
675
|
-
hits: 0,
|
|
676
|
-
resetTime: now + windowMs
|
|
677
|
-
};
|
|
678
|
-
hits.set(key, record);
|
|
679
|
-
}
|
|
680
|
-
record.hits++;
|
|
681
|
-
const remaining = Math.max(0, max - record.hits);
|
|
682
|
-
const resetTime = Math.ceil(record.resetTime / 1e3);
|
|
683
|
-
const retryAfter = Math.ceil((record.resetTime - now) / 1e3);
|
|
684
|
-
const setHeaders = (res) => {
|
|
685
|
-
if (!headers || !res || !res.headers) return;
|
|
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();
|
|
686
664
|
try {
|
|
687
|
-
res.
|
|
688
|
-
|
|
689
|
-
res
|
|
690
|
-
} catch (
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
const res = typeof message === "object" ? ctx.json(message, statusCode) : ctx.text(String(message), statusCode);
|
|
696
|
-
if (headers) {
|
|
697
|
-
setHeaders(res);
|
|
698
|
-
res.headers.set("Retry-After", String(retryAfter));
|
|
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);
|
|
699
673
|
}
|
|
700
|
-
return res;
|
|
701
|
-
}
|
|
702
|
-
const response = await next();
|
|
703
|
-
if (response instanceof Response && headers) {
|
|
704
|
-
setHeaders(response);
|
|
705
674
|
}
|
|
706
|
-
return
|
|
675
|
+
return runner(0);
|
|
707
676
|
};
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
const $controllerPath = /* @__PURE__ */ Symbol("Shokupan.controllerPath");
|
|
718
|
-
const $middleware = /* @__PURE__ */ Symbol("Shokupan.middleware");
|
|
719
|
-
const $isRouter = /* @__PURE__ */ Symbol.for("Shokupan.router");
|
|
720
|
-
const $parent = /* @__PURE__ */ Symbol.for("Shokupan.parent");
|
|
721
|
-
const $childRouters = /* @__PURE__ */ Symbol.for("Shokupan.child-routers");
|
|
722
|
-
const $childControllers = /* @__PURE__ */ Symbol.for("Shokupan.child-controllers");
|
|
723
|
-
const $mountPath = /* @__PURE__ */ Symbol.for("Shokupan.mount-path");
|
|
724
|
-
const $dispatch = /* @__PURE__ */ Symbol.for("Shokupan.dispatch");
|
|
725
|
-
const $routes = /* @__PURE__ */ Symbol.for("Shokupan.routes");
|
|
726
|
-
const $routeSpec = /* @__PURE__ */ Symbol.for("Shokupan.routeSpec");
|
|
727
|
-
const HTTPMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "ALL"];
|
|
728
|
-
var RouteParamType = /* @__PURE__ */ ((RouteParamType2) => {
|
|
729
|
-
RouteParamType2["BODY"] = "BODY";
|
|
730
|
-
RouteParamType2["PARAM"] = "PARAM";
|
|
731
|
-
RouteParamType2["QUERY"] = "QUERY";
|
|
732
|
-
RouteParamType2["HEADER"] = "HEADER";
|
|
733
|
-
RouteParamType2["REQUEST"] = "REQUEST";
|
|
734
|
-
RouteParamType2["CONTEXT"] = "CONTEXT";
|
|
735
|
-
return RouteParamType2;
|
|
736
|
-
})(RouteParamType || {});
|
|
737
|
-
function Controller(path = "/") {
|
|
738
|
-
return (target) => {
|
|
739
|
-
target[$controllerPath] = path;
|
|
740
|
-
};
|
|
741
|
-
}
|
|
742
|
-
function Use(...middleware) {
|
|
743
|
-
return (target, propertyKey, descriptor) => {
|
|
744
|
-
if (!propertyKey) {
|
|
745
|
-
const existing = target[$middleware] || [];
|
|
746
|
-
target[$middleware] = [...existing, ...middleware];
|
|
747
|
-
} else {
|
|
748
|
-
if (!target[$middleware]) {
|
|
749
|
-
target[$middleware] = /* @__PURE__ */ new Map();
|
|
750
|
-
}
|
|
751
|
-
const existing = target[$middleware].get(propertyKey) || [];
|
|
752
|
-
target[$middleware].set(propertyKey, [...existing, ...middleware]);
|
|
753
|
-
}
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
|
-
function createParamDecorator(type) {
|
|
757
|
-
return (name) => {
|
|
758
|
-
return (target, propertyKey, parameterIndex) => {
|
|
759
|
-
if (!target[$routeArgs]) {
|
|
760
|
-
target[$routeArgs] = /* @__PURE__ */ new Map();
|
|
761
|
-
}
|
|
762
|
-
if (!target[$routeArgs].has(propertyKey)) {
|
|
763
|
-
target[$routeArgs].set(propertyKey, []);
|
|
764
|
-
}
|
|
765
|
-
target[$routeArgs].get(propertyKey).push({
|
|
766
|
-
index: parameterIndex,
|
|
767
|
-
type,
|
|
768
|
-
name
|
|
769
|
-
});
|
|
770
|
-
};
|
|
771
|
-
};
|
|
772
|
-
}
|
|
773
|
-
const Body = createParamDecorator(RouteParamType.BODY);
|
|
774
|
-
const Param = createParamDecorator(RouteParamType.PARAM);
|
|
775
|
-
const Query = createParamDecorator(RouteParamType.QUERY);
|
|
776
|
-
const Headers$1 = createParamDecorator(RouteParamType.HEADER);
|
|
777
|
-
const Req = createParamDecorator(RouteParamType.REQUEST);
|
|
778
|
-
const Ctx = createParamDecorator(RouteParamType.CONTEXT);
|
|
779
|
-
function Spec(spec) {
|
|
780
|
-
return (target, propertyKey, descriptor) => {
|
|
781
|
-
if (!target[$routeSpec]) {
|
|
782
|
-
target[$routeSpec] = /* @__PURE__ */ new Map();
|
|
783
|
-
}
|
|
784
|
-
target[$routeSpec].set(propertyKey, spec);
|
|
785
|
-
};
|
|
786
|
-
}
|
|
787
|
-
function createMethodDecorator(method) {
|
|
788
|
-
return (path = "/") => {
|
|
789
|
-
return (target, propertyKey, descriptor) => {
|
|
790
|
-
if (!target[$routeMethods]) {
|
|
791
|
-
target[$routeMethods] = /* @__PURE__ */ new Map();
|
|
792
|
-
}
|
|
793
|
-
target[$routeMethods].set(propertyKey, {
|
|
794
|
-
method,
|
|
795
|
-
path
|
|
796
|
-
});
|
|
797
|
-
};
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
const Get = createMethodDecorator("GET");
|
|
801
|
-
const Post = createMethodDecorator("POST");
|
|
802
|
-
const Put = createMethodDecorator("PUT");
|
|
803
|
-
const Delete = createMethodDecorator("DELETE");
|
|
804
|
-
const Patch = createMethodDecorator("PATCH");
|
|
805
|
-
const Options = createMethodDecorator("OPTIONS");
|
|
806
|
-
const Head = createMethodDecorator("HEAD");
|
|
807
|
-
const All = createMethodDecorator("ALL");
|
|
808
|
-
function RateLimit(options) {
|
|
809
|
-
return Use(RateLimitMiddleware(options));
|
|
810
|
-
}
|
|
811
|
-
class Container {
|
|
812
|
-
static services = /* @__PURE__ */ new Map();
|
|
813
|
-
static register(target, instance) {
|
|
814
|
-
this.services.set(target, instance);
|
|
815
|
-
}
|
|
816
|
-
static get(target) {
|
|
817
|
-
return this.services.get(target);
|
|
818
|
-
}
|
|
819
|
-
static has(target) {
|
|
820
|
-
return this.services.has(target);
|
|
821
|
-
}
|
|
822
|
-
static resolve(target) {
|
|
823
|
-
if (this.services.has(target)) {
|
|
824
|
-
return this.services.get(target);
|
|
825
|
-
}
|
|
826
|
-
const instance = new target();
|
|
827
|
-
this.services.set(target, instance);
|
|
828
|
-
return instance;
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
function Injectable() {
|
|
832
|
-
return (target) => {
|
|
833
|
-
};
|
|
834
|
-
}
|
|
835
|
-
function Inject(token) {
|
|
836
|
-
return (target, key) => {
|
|
837
|
-
Object.defineProperty(target, key, {
|
|
838
|
-
get: () => Container.resolve(token),
|
|
839
|
-
enumerable: true,
|
|
840
|
-
configurable: true
|
|
841
|
-
});
|
|
842
|
-
};
|
|
843
|
-
}
|
|
844
|
-
const compose = (middleware) => {
|
|
845
|
-
if (!middleware.length) {
|
|
846
|
-
return (context2, next) => {
|
|
847
|
-
return next ? next() : Promise.resolve();
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
return function dispatch(context2, next) {
|
|
851
|
-
let index = -1;
|
|
852
|
-
async function runner(i) {
|
|
853
|
-
if (i <= index) return Promise.reject(new Error("next() called multiple times"));
|
|
854
|
-
index = i;
|
|
855
|
-
if (i >= middleware.length) {
|
|
856
|
-
return next ? next() : Promise.resolve();
|
|
857
|
-
}
|
|
858
|
-
const fn = middleware[i];
|
|
859
|
-
if (!context2._debug) {
|
|
860
|
-
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"
|
|
861
686
|
}
|
|
862
|
-
|
|
863
|
-
const debugId = fn._debugId || fn.name || "anonymous";
|
|
864
|
-
const previousNode = debug.getCurrentNode();
|
|
865
|
-
debug.trackEdge(previousNode, debugId);
|
|
866
|
-
debug.setNode(debugId);
|
|
867
|
-
const start = performance.now();
|
|
687
|
+
}, async (span) => {
|
|
868
688
|
try {
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
return res;
|
|
689
|
+
const result = await fn.apply(this, args);
|
|
690
|
+
return result;
|
|
872
691
|
} catch (err) {
|
|
873
|
-
|
|
874
|
-
|
|
692
|
+
span.recordException(err);
|
|
693
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
|
|
694
|
+
throw err;
|
|
875
695
|
} finally {
|
|
876
|
-
|
|
696
|
+
span.end();
|
|
877
697
|
}
|
|
878
|
-
}
|
|
879
|
-
return runner(0);
|
|
698
|
+
});
|
|
880
699
|
};
|
|
881
|
-
};
|
|
882
|
-
class ShokupanRequestBase {
|
|
883
|
-
method;
|
|
884
|
-
url;
|
|
885
|
-
headers;
|
|
886
|
-
body;
|
|
887
|
-
async json() {
|
|
888
|
-
return JSON.parse(this.body);
|
|
889
|
-
}
|
|
890
|
-
async text() {
|
|
891
|
-
return this.body;
|
|
892
|
-
}
|
|
893
|
-
async formData() {
|
|
894
|
-
if (this.body instanceof FormData) {
|
|
895
|
-
return this.body;
|
|
896
|
-
}
|
|
897
|
-
return new Response(this.body, { headers: this.headers }).formData();
|
|
898
|
-
}
|
|
899
|
-
constructor(props) {
|
|
900
|
-
Object.assign(this, props);
|
|
901
|
-
if (!(this.headers instanceof Headers)) {
|
|
902
|
-
this.headers = new Headers(this.headers);
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
700
|
}
|
|
906
|
-
const ShokupanRequest = ShokupanRequestBase;
|
|
907
701
|
function isObject(item) {
|
|
908
702
|
return item && typeof item === "object" && !Array.isArray(item);
|
|
909
703
|
}
|
|
@@ -937,6 +731,21 @@ function deepMerge(target, ...sources) {
|
|
|
937
731
|
}
|
|
938
732
|
return deepMerge(target, ...sources);
|
|
939
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");
|
|
940
749
|
const REGEX_PATTERNS = {
|
|
941
750
|
QUERY_INT: /parseInt\(ctx\.query\.(\w+)\)/g,
|
|
942
751
|
QUERY_FLOAT: /parseFloat\(ctx\.query\.(\w+)\)/g,
|
|
@@ -1129,7 +938,7 @@ async function generateOpenApi(rootRouter, options = {}) {
|
|
|
1129
938
|
const defaultTagName = options.defaultTag || "Application";
|
|
1130
939
|
let astRoutes = [];
|
|
1131
940
|
try {
|
|
1132
|
-
const { OpenAPIAnalyzer: OpenAPIAnalyzer2 } = await import("./
|
|
941
|
+
const { OpenAPIAnalyzer: OpenAPIAnalyzer2 } = await import("./analyzer-Ce_7JxZh.js");
|
|
1133
942
|
const analyzer = new OpenAPIAnalyzer2(process.cwd());
|
|
1134
943
|
const { applications } = await analyzer.analyze();
|
|
1135
944
|
astRoutes = await getAstRoutes(applications);
|
|
@@ -1318,6 +1127,11 @@ async function generateOpenApi(rootRouter, options = {}) {
|
|
|
1318
1127
|
"x-tagGroups": xTagGroups
|
|
1319
1128
|
};
|
|
1320
1129
|
}
|
|
1130
|
+
class RequestContextStore {
|
|
1131
|
+
request;
|
|
1132
|
+
span;
|
|
1133
|
+
}
|
|
1134
|
+
const asyncContext = new AsyncLocalStorage();
|
|
1321
1135
|
const eta$1 = new Eta();
|
|
1322
1136
|
function serveStatic(config, prefix) {
|
|
1323
1137
|
const rootPath = resolve(config.root || ".");
|
|
@@ -1470,107 +1284,6 @@ function serveStatic(config, prefix) {
|
|
|
1470
1284
|
serveStaticMiddleware.pluginName = "ServeStatic";
|
|
1471
1285
|
return serveStaticMiddleware;
|
|
1472
1286
|
}
|
|
1473
|
-
class RouterTrie {
|
|
1474
|
-
root;
|
|
1475
|
-
constructor() {
|
|
1476
|
-
this.root = this.createNode();
|
|
1477
|
-
}
|
|
1478
|
-
createNode() {
|
|
1479
|
-
return {
|
|
1480
|
-
children: {}
|
|
1481
|
-
};
|
|
1482
|
-
}
|
|
1483
|
-
insert(method, path, handler) {
|
|
1484
|
-
let node = this.root;
|
|
1485
|
-
const segments = this.splitPath(path);
|
|
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, path) {
|
|
1519
|
-
const segments = this.splitPath(path);
|
|
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(path) {
|
|
1567
|
-
if (path === "/" || path === "") return [];
|
|
1568
|
-
const s = path.startsWith("/") ? path.slice(1) : path;
|
|
1569
|
-
if (s === "") return [];
|
|
1570
|
-
return s.split("/");
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
const asyncContext = new AsyncLocalStorage();
|
|
1574
1287
|
let db;
|
|
1575
1288
|
let dbPromise = null;
|
|
1576
1289
|
let RecordId;
|
|
@@ -1634,29 +1347,64 @@ const datastore = {
|
|
|
1634
1347
|
process.on("exit", async () => {
|
|
1635
1348
|
if (db) await db.close();
|
|
1636
1349
|
});
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
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
|
|
1657
1380
|
});
|
|
1658
1381
|
};
|
|
1659
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;
|
|
1660
1408
|
function getCallerInfo(skipFrames = 1) {
|
|
1661
1409
|
let file = "unknown";
|
|
1662
1410
|
let line = 0;
|
|
@@ -1682,12 +1430,120 @@ function getCallerInfo(skipFrames = 1) {
|
|
|
1682
1430
|
}
|
|
1683
1431
|
}
|
|
1684
1432
|
}
|
|
1685
|
-
} 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("/");
|
|
1686
1535
|
}
|
|
1687
|
-
return { file, line };
|
|
1688
1536
|
}
|
|
1689
|
-
const
|
|
1690
|
-
|
|
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 || {});
|
|
1691
1547
|
class ShokupanRouter {
|
|
1692
1548
|
constructor(config) {
|
|
1693
1549
|
this.config = config;
|
|
@@ -1798,216 +1654,9 @@ class ShokupanRouter {
|
|
|
1798
1654
|
throw new Error(`[Shokupan] strict controller check failed: ${controller.constructor.name || typeof controller} is not a class constructor.`);
|
|
1799
1655
|
}
|
|
1800
1656
|
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;
|
|
1657
|
+
this.mountRouter(prefix, controller);
|
|
1823
1658
|
} 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;
|
|
1659
|
+
this.scanControllerRoutes(prefix, controller);
|
|
2011
1660
|
}
|
|
2012
1661
|
return this;
|
|
2013
1662
|
}
|
|
@@ -2045,8 +1694,6 @@ class ShokupanRouter {
|
|
|
2045
1694
|
*/
|
|
2046
1695
|
async internalRequest(arg) {
|
|
2047
1696
|
const options = typeof arg === "string" ? { path: arg } : arg;
|
|
2048
|
-
const store = asyncContext.getStore();
|
|
2049
|
-
store?.get("req");
|
|
2050
1697
|
let url = options.path;
|
|
2051
1698
|
if (!url.startsWith("http")) {
|
|
2052
1699
|
const base = `http://${this.rootConfig?.hostname || "localhost"}:${this.rootConfig.port || 3e3}`;
|
|
@@ -2158,6 +1805,220 @@ class ShokupanRouter {
|
|
|
2158
1805
|
wrapped.originalHandler = originalHandler.originalHandler ?? originalHandler;
|
|
2159
1806
|
return wrapped;
|
|
2160
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
|
+
}
|
|
2161
2022
|
/**
|
|
2162
2023
|
* Find a route matching the given method and path.
|
|
2163
2024
|
* @param method HTTP method
|
|
@@ -2284,7 +2145,7 @@ class ShokupanRouter {
|
|
|
2284
2145
|
if (effectiveRenderer) {
|
|
2285
2146
|
const innerHandler = wrappedHandler;
|
|
2286
2147
|
wrappedHandler = async (ctx) => {
|
|
2287
|
-
ctx.
|
|
2148
|
+
ctx.setRenderer(effectiveRenderer);
|
|
2288
2149
|
return innerHandler(ctx);
|
|
2289
2150
|
};
|
|
2290
2151
|
}
|
|
@@ -2686,6 +2547,13 @@ class Shokupan extends ShokupanRouter {
|
|
|
2686
2547
|
}
|
|
2687
2548
|
return this;
|
|
2688
2549
|
}
|
|
2550
|
+
/**
|
|
2551
|
+
* Registers a plugin.
|
|
2552
|
+
*/
|
|
2553
|
+
register(plugin, options) {
|
|
2554
|
+
plugin.onInit(this, options);
|
|
2555
|
+
return this;
|
|
2556
|
+
}
|
|
2689
2557
|
startupHooks = [];
|
|
2690
2558
|
/**
|
|
2691
2559
|
* Registers a callback to be executed before the server starts listening.
|
|
@@ -2748,7 +2616,7 @@ class Shokupan extends ShokupanRouter {
|
|
|
2748
2616
|
};
|
|
2749
2617
|
let factory = this.applicationConfig.serverFactory;
|
|
2750
2618
|
if (!factory && typeof Bun === "undefined") {
|
|
2751
|
-
const { createHttpServer } = await import("./server-
|
|
2619
|
+
const { createHttpServer } = await import("./http-server-0xH174zz.js");
|
|
2752
2620
|
factory = createHttpServer();
|
|
2753
2621
|
}
|
|
2754
2622
|
const server = factory ? await factory(serveOptions) : Bun.serve(serveOptions);
|
|
@@ -2817,19 +2685,19 @@ class Shokupan extends ShokupanRouter {
|
|
|
2817
2685
|
"http.method": req.method
|
|
2818
2686
|
}
|
|
2819
2687
|
};
|
|
2820
|
-
const parent = store?.
|
|
2688
|
+
const parent = store?.span;
|
|
2821
2689
|
const ctx = parent ? trace.setSpan(context.active(), parent) : void 0;
|
|
2822
2690
|
return tracer2.startActiveSpan(`${req.method} ${new URL(req.url).pathname}`, attrs, ctx, (span) => {
|
|
2823
|
-
const
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
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()));
|
|
2827
2695
|
});
|
|
2828
2696
|
}
|
|
2829
2697
|
if (this.applicationConfig.enableAsyncLocalStorage) {
|
|
2830
|
-
const
|
|
2831
|
-
|
|
2832
|
-
return asyncContext.run(
|
|
2698
|
+
const ctxStore = new RequestContextStore();
|
|
2699
|
+
ctxStore.request = req;
|
|
2700
|
+
return asyncContext.run(ctxStore, () => this.handleRequest(req, server));
|
|
2833
2701
|
}
|
|
2834
2702
|
return this.handleRequest(req, server);
|
|
2835
2703
|
}
|
|
@@ -2851,6 +2719,7 @@ class Shokupan extends ShokupanRouter {
|
|
|
2851
2719
|
const bodyParsing = ["POST", "PUT", "PATCH", "DELETE"].includes(req.method) ? ctx.parseBody() : Promise.resolve();
|
|
2852
2720
|
const match = this.find(req.method, ctx.path);
|
|
2853
2721
|
if (match) {
|
|
2722
|
+
ctx._routeMatched = true;
|
|
2854
2723
|
ctx.params = match.params;
|
|
2855
2724
|
await bodyParsing;
|
|
2856
2725
|
return match.handler(ctx);
|
|
@@ -2865,10 +2734,14 @@ class Shokupan extends ShokupanRouter {
|
|
|
2865
2734
|
} else if (result === null || result === void 0) {
|
|
2866
2735
|
if (ctx._finalResponse instanceof Response) {
|
|
2867
2736
|
response = ctx._finalResponse;
|
|
2868
|
-
} else if (ctx.
|
|
2737
|
+
} else if (ctx._routeMatched) {
|
|
2869
2738
|
response = ctx.send(null, { status: ctx.response.status, headers: ctx.response.headers });
|
|
2870
2739
|
} else {
|
|
2871
|
-
|
|
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
|
+
}
|
|
2872
2745
|
}
|
|
2873
2746
|
} else if (typeof result === "object") {
|
|
2874
2747
|
response = ctx.json(result);
|
|
@@ -2880,7 +2753,7 @@ class Shokupan extends ShokupanRouter {
|
|
|
2880
2753
|
return response;
|
|
2881
2754
|
} catch (err) {
|
|
2882
2755
|
console.error(err);
|
|
2883
|
-
const span = asyncContext.getStore()?.
|
|
2756
|
+
const span = asyncContext.getStore()?.span;
|
|
2884
2757
|
if (span) span.setStatus({ code: 2 });
|
|
2885
2758
|
const status = err.status || err.statusCode || 500;
|
|
2886
2759
|
const body = { error: err.message || "Internal Server Error" };
|
|
@@ -2888,31 +2761,195 @@ class Shokupan extends ShokupanRouter {
|
|
|
2888
2761
|
await this.runHooks("onError", ctx, err);
|
|
2889
2762
|
return ctx.json(body, status);
|
|
2890
2763
|
}
|
|
2891
|
-
};
|
|
2892
|
-
let executionPromise = handle();
|
|
2893
|
-
const timeoutMs = this.applicationConfig.requestTimeout;
|
|
2894
|
-
if (timeoutMs && timeoutMs > 0) {
|
|
2895
|
-
let timeoutId;
|
|
2896
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
2897
|
-
timeoutId = setTimeout(async () => {
|
|
2898
|
-
controller.abort();
|
|
2899
|
-
await this.runHooks("onRequestTimeout", ctx);
|
|
2900
|
-
reject(new Error("Request Timeout"));
|
|
2901
|
-
}, 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
|
|
2902
2912
|
});
|
|
2903
|
-
|
|
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();
|
|
2904
2926
|
}
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
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();
|
|
2908
2935
|
}
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
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));
|
|
2916
2953
|
}
|
|
2917
2954
|
class AuthPlugin extends ShokupanRouter {
|
|
2918
2955
|
constructor(authConfig) {
|
|
@@ -2922,6 +2959,13 @@ class AuthPlugin extends ShokupanRouter {
|
|
|
2922
2959
|
this.init();
|
|
2923
2960
|
}
|
|
2924
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
|
+
}
|
|
2925
2969
|
getProviderInstance(name, p) {
|
|
2926
2970
|
switch (name) {
|
|
2927
2971
|
case "github":
|
|
@@ -3139,8 +3183,196 @@ class AuthPlugin extends ShokupanRouter {
|
|
|
3139
3183
|
};
|
|
3140
3184
|
}
|
|
3141
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
|
+
}
|
|
3142
3373
|
function Compression(options = {}) {
|
|
3143
3374
|
const threshold = options.threshold ?? 512;
|
|
3375
|
+
const allowedAlgorithms = new Set(options.allowedAlgorithms ?? ["br", "gzip", "zstd", "deflate"]);
|
|
3144
3376
|
const compressionMiddleware = async function CompressionMiddleware(ctx, next) {
|
|
3145
3377
|
const acceptEncoding = ctx.headers.get("accept-encoding") || "";
|
|
3146
3378
|
let method = null;
|
|
@@ -3153,6 +3385,9 @@ function Compression(options = {}) {
|
|
|
3153
3385
|
} else if (acceptEncoding.includes("gzip")) method = "gzip";
|
|
3154
3386
|
else if (acceptEncoding.includes("deflate")) method = "deflate";
|
|
3155
3387
|
if (!method) return next();
|
|
3388
|
+
if (!allowedAlgorithms.has(method)) {
|
|
3389
|
+
return next();
|
|
3390
|
+
}
|
|
3156
3391
|
let response = await next();
|
|
3157
3392
|
if (!(response instanceof Response) && ctx._finalResponse instanceof Response) {
|
|
3158
3393
|
response = ctx._finalResponse;
|
|
@@ -3707,77 +3942,6 @@ function enableOpenApiValidation(app) {
|
|
|
3707
3942
|
precompileValidators(app, spec);
|
|
3708
3943
|
});
|
|
3709
3944
|
}
|
|
3710
|
-
const eta = new Eta();
|
|
3711
|
-
class ScalarPlugin extends ShokupanRouter {
|
|
3712
|
-
constructor(pluginOptions = {}) {
|
|
3713
|
-
pluginOptions.config ??= {};
|
|
3714
|
-
super();
|
|
3715
|
-
this.pluginOptions = pluginOptions;
|
|
3716
|
-
this.init();
|
|
3717
|
-
}
|
|
3718
|
-
init() {
|
|
3719
|
-
this.get("/", (ctx) => {
|
|
3720
|
-
let path = ctx.url.toString();
|
|
3721
|
-
if (!path.endsWith("/")) path += "/";
|
|
3722
|
-
return ctx.html(eta.renderString(`<!doctype html>
|
|
3723
|
-
<html>
|
|
3724
|
-
<head>
|
|
3725
|
-
<title>API Reference</title>
|
|
3726
|
-
<meta charset = "utf-8" />
|
|
3727
|
-
<meta name="viewport" content = "width=device-width, initial-scale=1" />
|
|
3728
|
-
</head>
|
|
3729
|
-
|
|
3730
|
-
<body>
|
|
3731
|
-
<div id="app"></div>
|
|
3732
|
-
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
|
|
3733
|
-
<script>
|
|
3734
|
-
Scalar.createApiReference('#app', [{ ...<%~ JSON.stringify(it.config.baseDocument) %>,
|
|
3735
|
-
url: "<%= it.path %>openapi.json",
|
|
3736
|
-
}
|
|
3737
|
-
])
|
|
3738
|
-
<\/script>
|
|
3739
|
-
</body>
|
|
3740
|
-
|
|
3741
|
-
</html>`, { path, config: this.pluginOptions }));
|
|
3742
|
-
});
|
|
3743
|
-
this.get("/openapi.json", async (ctx) => {
|
|
3744
|
-
let spec;
|
|
3745
|
-
if (this.root.openApiSpec) {
|
|
3746
|
-
try {
|
|
3747
|
-
spec = structuredClone(this.root.openApiSpec);
|
|
3748
|
-
} catch (e) {
|
|
3749
|
-
spec = Object.assign({}, this.root.openApiSpec);
|
|
3750
|
-
}
|
|
3751
|
-
} else {
|
|
3752
|
-
spec = await (this.root || this).generateApiSpec();
|
|
3753
|
-
}
|
|
3754
|
-
if (this.pluginOptions.baseDocument) {
|
|
3755
|
-
deepMerge(spec, this.pluginOptions.baseDocument);
|
|
3756
|
-
}
|
|
3757
|
-
return ctx.json(spec);
|
|
3758
|
-
});
|
|
3759
|
-
}
|
|
3760
|
-
// New lifecycle method to be called by router.mount
|
|
3761
|
-
onMount(parent) {
|
|
3762
|
-
if (parent.onStart) {
|
|
3763
|
-
parent.onStart(async () => {
|
|
3764
|
-
if (this.pluginOptions.enableStaticAnalysis) {
|
|
3765
|
-
try {
|
|
3766
|
-
const entrypoint = process.argv[1];
|
|
3767
|
-
console.log(`[ScalarPlugin] Running eager static analysis on entrypoint: ${entrypoint}`);
|
|
3768
|
-
const analyzer = new OpenAPIAnalyzer(process.cwd(), entrypoint);
|
|
3769
|
-
let staticSpec = await analyzer.analyze();
|
|
3770
|
-
if (!this.pluginOptions.baseDocument) this.pluginOptions.baseDocument = {};
|
|
3771
|
-
deepMerge(this.pluginOptions.baseDocument, staticSpec);
|
|
3772
|
-
console.log("[ScalarPlugin] Static analysis completed successfully.");
|
|
3773
|
-
} catch (err) {
|
|
3774
|
-
console.error("[ScalarPlugin] Failed to run static analysis:", err);
|
|
3775
|
-
}
|
|
3776
|
-
}
|
|
3777
|
-
});
|
|
3778
|
-
}
|
|
3779
|
-
}
|
|
3780
|
-
}
|
|
3781
3945
|
function SecurityHeaders(options = {}) {
|
|
3782
3946
|
const securityHeadersMiddleware = async function SecurityHeadersMiddleware(ctx, next) {
|
|
3783
3947
|
const headers = {};
|
|
@@ -3901,18 +4065,18 @@ class MemoryStore extends EventEmitter {
|
|
|
3901
4065
|
}
|
|
3902
4066
|
set(sid, sess, cb) {
|
|
3903
4067
|
this.sessions[sid] = JSON.stringify(sess);
|
|
3904
|
-
cb
|
|
4068
|
+
cb?.();
|
|
3905
4069
|
}
|
|
3906
4070
|
destroy(sid, cb) {
|
|
3907
4071
|
delete this.sessions[sid];
|
|
3908
|
-
cb
|
|
4072
|
+
cb?.();
|
|
3909
4073
|
}
|
|
3910
4074
|
touch(sid, sess, cb) {
|
|
3911
4075
|
const current = this.sessions[sid];
|
|
3912
4076
|
if (current) {
|
|
3913
4077
|
this.sessions[sid] = JSON.stringify(sess);
|
|
3914
4078
|
}
|
|
3915
|
-
cb
|
|
4079
|
+
cb?.();
|
|
3916
4080
|
}
|
|
3917
4081
|
all(cb) {
|
|
3918
4082
|
const result = {};
|
|
@@ -3928,7 +4092,7 @@ class MemoryStore extends EventEmitter {
|
|
|
3928
4092
|
}
|
|
3929
4093
|
clear(cb) {
|
|
3930
4094
|
this.sessions = {};
|
|
3931
|
-
cb
|
|
4095
|
+
cb?.();
|
|
3932
4096
|
}
|
|
3933
4097
|
}
|
|
3934
4098
|
function sign(val, secret) {
|
|
@@ -4124,6 +4288,7 @@ export {
|
|
|
4124
4288
|
All,
|
|
4125
4289
|
AuthPlugin,
|
|
4126
4290
|
Body,
|
|
4291
|
+
ClusterPlugin,
|
|
4127
4292
|
Compression,
|
|
4128
4293
|
Container,
|
|
4129
4294
|
Controller,
|
|
@@ -4147,16 +4312,13 @@ export {
|
|
|
4147
4312
|
RateLimitMiddleware,
|
|
4148
4313
|
Req,
|
|
4149
4314
|
RouteParamType,
|
|
4150
|
-
RouterRegistry,
|
|
4151
4315
|
ScalarPlugin,
|
|
4152
4316
|
SecurityHeaders,
|
|
4153
4317
|
Session,
|
|
4154
4318
|
Shokupan,
|
|
4155
|
-
ShokupanApplicationTree,
|
|
4156
4319
|
ShokupanContext,
|
|
4157
4320
|
ShokupanRequest,
|
|
4158
4321
|
ShokupanResponse,
|
|
4159
|
-
ShokupanRouter,
|
|
4160
4322
|
Spec,
|
|
4161
4323
|
Use,
|
|
4162
4324
|
ValidationError,
|