@dangao/bun-server 0.1.5 → 0.3.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/dist/controller/controller.d.ts.map +1 -1
- package/dist/core/context.d.ts +6 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/error/error-codes.d.ts +33 -0
- package/dist/error/error-codes.d.ts.map +1 -0
- package/dist/error/handler.d.ts.map +1 -1
- package/dist/error/http-exception.d.ts +12 -6
- package/dist/error/http-exception.d.ts.map +1 -1
- package/dist/error/i18n.d.ts +28 -0
- package/dist/error/i18n.d.ts.map +1 -0
- package/dist/error/index.d.ts +3 -0
- package/dist/error/index.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +904 -329
- package/dist/metrics/collector.d.ts +47 -0
- package/dist/metrics/collector.d.ts.map +1 -0
- package/dist/metrics/controller.d.ts +17 -0
- package/dist/metrics/controller.d.ts.map +1 -0
- package/dist/metrics/index.d.ts +7 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/metrics-module.d.ts +9 -0
- package/dist/metrics/metrics-module.d.ts.map +1 -0
- package/dist/metrics/middleware.d.ts +7 -0
- package/dist/metrics/middleware.d.ts.map +1 -0
- package/dist/metrics/prometheus.d.ts +23 -0
- package/dist/metrics/prometheus.d.ts.map +1 -0
- package/dist/metrics/types.d.ts +88 -0
- package/dist/metrics/types.d.ts.map +1 -0
- package/dist/middleware/builtin/error-handler.d.ts.map +1 -1
- package/dist/middleware/builtin/index.d.ts +1 -0
- package/dist/middleware/builtin/index.d.ts.map +1 -1
- package/dist/middleware/builtin/rate-limit.d.ts +104 -0
- package/dist/middleware/builtin/rate-limit.d.ts.map +1 -0
- package/dist/middleware/decorators.d.ts +7 -0
- package/dist/middleware/decorators.d.ts.map +1 -1
- package/dist/middleware/index.d.ts +1 -1
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/security/context.d.ts.map +1 -1
- package/dist/security/filter.d.ts.map +1 -1
- package/dist/security/providers/oauth2-provider.d.ts +3 -1
- package/dist/security/providers/oauth2-provider.d.ts.map +1 -1
- package/dist/security/security-module.d.ts.map +1 -1
- package/package.json +3 -3
- package/readme.md +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
set: (newValue) => all[name] = () => newValue
|
|
10
|
+
});
|
|
11
|
+
};
|
|
2
12
|
var __legacyDecorateClassTS = function(decorators, target, key, desc) {
|
|
3
13
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
14
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
|
|
@@ -14,6 +24,371 @@ var __legacyMetadataTS = (k, v) => {
|
|
|
14
24
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
|
|
15
25
|
return Reflect.metadata(k, v);
|
|
16
26
|
};
|
|
27
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
28
|
+
|
|
29
|
+
// src/validation/decorators.ts
|
|
30
|
+
import"reflect-metadata";
|
|
31
|
+
function getOrCreateMetadata(target, propertyKey) {
|
|
32
|
+
const existing = Reflect.getMetadata(VALIDATION_METADATA_KEY, target, propertyKey);
|
|
33
|
+
if (existing) {
|
|
34
|
+
return existing;
|
|
35
|
+
}
|
|
36
|
+
const metadata = [];
|
|
37
|
+
Reflect.defineMetadata(VALIDATION_METADATA_KEY, metadata, target, propertyKey);
|
|
38
|
+
return metadata;
|
|
39
|
+
}
|
|
40
|
+
function Validate(...rules) {
|
|
41
|
+
return (target, propertyKey, parameterIndex) => {
|
|
42
|
+
if (!propertyKey) {
|
|
43
|
+
throw new Error("@Validate decorator can only be used on methods");
|
|
44
|
+
}
|
|
45
|
+
if (!rules.length) {
|
|
46
|
+
throw new Error("@Validate requires at least one validation rule");
|
|
47
|
+
}
|
|
48
|
+
const metadata = getOrCreateMetadata(target, propertyKey);
|
|
49
|
+
let entry = metadata.find((item) => item.index === parameterIndex);
|
|
50
|
+
if (!entry) {
|
|
51
|
+
entry = { index: parameterIndex, rules: [] };
|
|
52
|
+
metadata.push(entry);
|
|
53
|
+
}
|
|
54
|
+
entry.rules.push(...rules);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function IsString(options = {}) {
|
|
58
|
+
return {
|
|
59
|
+
name: "isString",
|
|
60
|
+
message: options.message ?? "\u5FC5\u987B\u662F\u5B57\u7B26\u4E32",
|
|
61
|
+
validate: (value) => typeof value === "string"
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function IsNumber(options = {}) {
|
|
65
|
+
return {
|
|
66
|
+
name: "isNumber",
|
|
67
|
+
message: options.message ?? "\u5FC5\u987B\u662F\u6570\u5B57",
|
|
68
|
+
validate: (value) => typeof value === "number" && !Number.isNaN(value)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function IsEmail(options = {}) {
|
|
72
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
73
|
+
return {
|
|
74
|
+
name: "isEmail",
|
|
75
|
+
message: options.message ?? "\u5FC5\u987B\u662F\u5408\u6CD5\u7684\u90AE\u7BB1\u5730\u5740",
|
|
76
|
+
validate: (value) => typeof value === "string" && emailRegex.test(value)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function IsOptional() {
|
|
80
|
+
return {
|
|
81
|
+
name: "isOptional",
|
|
82
|
+
message: "",
|
|
83
|
+
optional: true,
|
|
84
|
+
validate: () => true
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function MinLength(min, options = {}) {
|
|
88
|
+
return {
|
|
89
|
+
name: "minLength",
|
|
90
|
+
message: options.message ?? `\u957F\u5EA6\u4E0D\u80FD\u5C0F\u4E8E ${min}`,
|
|
91
|
+
validate: (value) => typeof value === "string" && value.length >= min
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function getValidationMetadata(target, propertyKey) {
|
|
95
|
+
return Reflect.getMetadata(VALIDATION_METADATA_KEY, target, propertyKey) || [];
|
|
96
|
+
}
|
|
97
|
+
var VALIDATION_METADATA_KEY;
|
|
98
|
+
var init_decorators = __esm(() => {
|
|
99
|
+
VALIDATION_METADATA_KEY = Symbol("validation:param");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// src/validation/errors.ts
|
|
103
|
+
var ValidationError;
|
|
104
|
+
var init_errors = __esm(() => {
|
|
105
|
+
ValidationError = class ValidationError extends Error {
|
|
106
|
+
issues;
|
|
107
|
+
constructor(message, issues) {
|
|
108
|
+
super(message);
|
|
109
|
+
this.name = "ValidationError";
|
|
110
|
+
this.issues = issues;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// src/validation/validator.ts
|
|
116
|
+
function shouldSkip(rule, value) {
|
|
117
|
+
return rule.optional === true && (value === undefined || value === null);
|
|
118
|
+
}
|
|
119
|
+
function validateRule(rule, value, index) {
|
|
120
|
+
if (shouldSkip(rule, value)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const passed = rule.validate(value);
|
|
124
|
+
if (passed) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
index,
|
|
129
|
+
rule: rule.name,
|
|
130
|
+
message: rule.message
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function validateParameters(params, metadata) {
|
|
134
|
+
if (!metadata.length) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const issues = [];
|
|
138
|
+
for (const item of metadata) {
|
|
139
|
+
const value = params[item.index];
|
|
140
|
+
let skipped = false;
|
|
141
|
+
for (const rule of item.rules) {
|
|
142
|
+
if (rule.optional && (value === undefined || value === null)) {
|
|
143
|
+
skipped = true;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
const issue = validateRule(rule, value, item.index);
|
|
147
|
+
if (issue) {
|
|
148
|
+
issues.push(issue);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (skipped) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (issues.length > 0) {
|
|
156
|
+
throw new ValidationError("Validation failed", issues);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
var init_validator = __esm(() => {
|
|
160
|
+
init_errors();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// src/validation/index.ts
|
|
164
|
+
var init_validation = __esm(() => {
|
|
165
|
+
init_decorators();
|
|
166
|
+
init_validator();
|
|
167
|
+
init_errors();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// src/error/error-codes.ts
|
|
171
|
+
var ERROR_CODE_TO_STATUS, ERROR_CODE_MESSAGES;
|
|
172
|
+
var init_error_codes = __esm(() => {
|
|
173
|
+
ERROR_CODE_TO_STATUS = {
|
|
174
|
+
["INTERNAL_ERROR" /* INTERNAL_ERROR */]: 500,
|
|
175
|
+
["INVALID_REQUEST" /* INVALID_REQUEST */]: 400,
|
|
176
|
+
["RESOURCE_NOT_FOUND" /* RESOURCE_NOT_FOUND */]: 404,
|
|
177
|
+
["METHOD_NOT_ALLOWED" /* METHOD_NOT_ALLOWED */]: 405,
|
|
178
|
+
["AUTH_REQUIRED" /* AUTH_REQUIRED */]: 401,
|
|
179
|
+
["AUTH_INVALID_TOKEN" /* AUTH_INVALID_TOKEN */]: 401,
|
|
180
|
+
["AUTH_TOKEN_EXPIRED" /* AUTH_TOKEN_EXPIRED */]: 401,
|
|
181
|
+
["AUTH_INSUFFICIENT_PERMISSIONS" /* AUTH_INSUFFICIENT_PERMISSIONS */]: 403,
|
|
182
|
+
["VALIDATION_FAILED" /* VALIDATION_FAILED */]: 400,
|
|
183
|
+
["VALIDATION_REQUIRED_FIELD" /* VALIDATION_REQUIRED_FIELD */]: 400,
|
|
184
|
+
["VALIDATION_INVALID_FORMAT" /* VALIDATION_INVALID_FORMAT */]: 400,
|
|
185
|
+
["VALIDATION_OUT_OF_RANGE" /* VALIDATION_OUT_OF_RANGE */]: 400,
|
|
186
|
+
["OAUTH2_INVALID_CLIENT" /* OAUTH2_INVALID_CLIENT */]: 400,
|
|
187
|
+
["OAUTH2_INVALID_GRANT" /* OAUTH2_INVALID_GRANT */]: 400,
|
|
188
|
+
["OAUTH2_INVALID_SCOPE" /* OAUTH2_INVALID_SCOPE */]: 400,
|
|
189
|
+
["OAUTH2_INVALID_REDIRECT_URI" /* OAUTH2_INVALID_REDIRECT_URI */]: 400,
|
|
190
|
+
["OAUTH2_UNSUPPORTED_GRANT_TYPE" /* OAUTH2_UNSUPPORTED_GRANT_TYPE */]: 400
|
|
191
|
+
};
|
|
192
|
+
ERROR_CODE_MESSAGES = {
|
|
193
|
+
["INTERNAL_ERROR" /* INTERNAL_ERROR */]: "Internal Server Error",
|
|
194
|
+
["INVALID_REQUEST" /* INVALID_REQUEST */]: "Invalid Request",
|
|
195
|
+
["RESOURCE_NOT_FOUND" /* RESOURCE_NOT_FOUND */]: "Resource Not Found",
|
|
196
|
+
["METHOD_NOT_ALLOWED" /* METHOD_NOT_ALLOWED */]: "Method Not Allowed",
|
|
197
|
+
["AUTH_REQUIRED" /* AUTH_REQUIRED */]: "Authentication Required",
|
|
198
|
+
["AUTH_INVALID_TOKEN" /* AUTH_INVALID_TOKEN */]: "Invalid Authentication Token",
|
|
199
|
+
["AUTH_TOKEN_EXPIRED" /* AUTH_TOKEN_EXPIRED */]: "Authentication Token Expired",
|
|
200
|
+
["AUTH_INSUFFICIENT_PERMISSIONS" /* AUTH_INSUFFICIENT_PERMISSIONS */]: "Insufficient Permissions",
|
|
201
|
+
["VALIDATION_FAILED" /* VALIDATION_FAILED */]: "Validation Failed",
|
|
202
|
+
["VALIDATION_REQUIRED_FIELD" /* VALIDATION_REQUIRED_FIELD */]: "Required Field Missing",
|
|
203
|
+
["VALIDATION_INVALID_FORMAT" /* VALIDATION_INVALID_FORMAT */]: "Invalid Format",
|
|
204
|
+
["VALIDATION_OUT_OF_RANGE" /* VALIDATION_OUT_OF_RANGE */]: "Value Out of Range",
|
|
205
|
+
["OAUTH2_INVALID_CLIENT" /* OAUTH2_INVALID_CLIENT */]: "Invalid Client",
|
|
206
|
+
["OAUTH2_INVALID_GRANT" /* OAUTH2_INVALID_GRANT */]: "Invalid Grant",
|
|
207
|
+
["OAUTH2_INVALID_SCOPE" /* OAUTH2_INVALID_SCOPE */]: "Invalid Scope",
|
|
208
|
+
["OAUTH2_INVALID_REDIRECT_URI" /* OAUTH2_INVALID_REDIRECT_URI */]: "Invalid Redirect URI",
|
|
209
|
+
["OAUTH2_UNSUPPORTED_GRANT_TYPE" /* OAUTH2_UNSUPPORTED_GRANT_TYPE */]: "Unsupported Grant Type"
|
|
210
|
+
};
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// src/error/http-exception.ts
|
|
214
|
+
var HttpException, BadRequestException, UnauthorizedException, ForbiddenException, NotFoundException, InternalServerErrorException;
|
|
215
|
+
var init_http_exception = __esm(() => {
|
|
216
|
+
init_error_codes();
|
|
217
|
+
HttpException = class HttpException extends Error {
|
|
218
|
+
status;
|
|
219
|
+
code;
|
|
220
|
+
details;
|
|
221
|
+
constructor(status, message, details, code) {
|
|
222
|
+
super(message);
|
|
223
|
+
this.name = this.constructor.name;
|
|
224
|
+
this.status = status;
|
|
225
|
+
this.code = code;
|
|
226
|
+
this.details = details;
|
|
227
|
+
}
|
|
228
|
+
static withCode(code, message, details) {
|
|
229
|
+
const status = ERROR_CODE_TO_STATUS[code] || 500;
|
|
230
|
+
const finalMessage = message || ERROR_CODE_MESSAGES[code] || "Internal Server Error";
|
|
231
|
+
return new HttpException(status, finalMessage, details, code);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
BadRequestException = class BadRequestException extends HttpException {
|
|
235
|
+
constructor(message = "Bad Request", details, code) {
|
|
236
|
+
super(400, message, details, code);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
UnauthorizedException = class UnauthorizedException extends HttpException {
|
|
240
|
+
constructor(message = "Unauthorized", details, code) {
|
|
241
|
+
super(401, message, details, code);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
ForbiddenException = class ForbiddenException extends HttpException {
|
|
245
|
+
constructor(message = "Forbidden", details, code) {
|
|
246
|
+
super(403, message, details, code);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
NotFoundException = class NotFoundException extends HttpException {
|
|
250
|
+
constructor(message = "Not Found", details, code) {
|
|
251
|
+
super(404, message, details, code);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
InternalServerErrorException = class InternalServerErrorException extends HttpException {
|
|
255
|
+
constructor(message = "Internal Server Error", details, code) {
|
|
256
|
+
super(500, message, details, code);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// src/error/filter.ts
|
|
262
|
+
class ExceptionFilterRegistry {
|
|
263
|
+
static instance;
|
|
264
|
+
filters = [];
|
|
265
|
+
constructor() {}
|
|
266
|
+
static getInstance() {
|
|
267
|
+
if (!ExceptionFilterRegistry.instance) {
|
|
268
|
+
ExceptionFilterRegistry.instance = new ExceptionFilterRegistry;
|
|
269
|
+
}
|
|
270
|
+
return ExceptionFilterRegistry.instance;
|
|
271
|
+
}
|
|
272
|
+
register(filter) {
|
|
273
|
+
this.filters.push(filter);
|
|
274
|
+
}
|
|
275
|
+
clear() {
|
|
276
|
+
this.filters.length = 0;
|
|
277
|
+
}
|
|
278
|
+
async execute(error, context) {
|
|
279
|
+
for (const filter of this.filters) {
|
|
280
|
+
const result = await filter.catch(error, context);
|
|
281
|
+
if (result) {
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/error/i18n.ts
|
|
290
|
+
class ErrorMessageI18n {
|
|
291
|
+
static currentLanguage = "en";
|
|
292
|
+
static setLanguage(language) {
|
|
293
|
+
this.currentLanguage = language;
|
|
294
|
+
}
|
|
295
|
+
static getLanguage() {
|
|
296
|
+
return this.currentLanguage;
|
|
297
|
+
}
|
|
298
|
+
static getMessage(code, language) {
|
|
299
|
+
const lang = language || this.currentLanguage;
|
|
300
|
+
const messages = ERROR_MESSAGES_I18N[lang];
|
|
301
|
+
return messages?.[code] || ERROR_CODE_MESSAGES[code] || "Internal Server Error";
|
|
302
|
+
}
|
|
303
|
+
static parseLanguageFromHeader(acceptLanguage) {
|
|
304
|
+
if (!acceptLanguage) {
|
|
305
|
+
return "en";
|
|
306
|
+
}
|
|
307
|
+
if (acceptLanguage.includes("zh-CN") || acceptLanguage.includes("zh")) {
|
|
308
|
+
return "zh-CN";
|
|
309
|
+
}
|
|
310
|
+
return "en";
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
var ERROR_MESSAGES_I18N;
|
|
314
|
+
var init_i18n = __esm(() => {
|
|
315
|
+
init_error_codes();
|
|
316
|
+
ERROR_MESSAGES_I18N = {
|
|
317
|
+
en: {
|
|
318
|
+
...ERROR_CODE_MESSAGES
|
|
319
|
+
},
|
|
320
|
+
"zh-CN": {
|
|
321
|
+
["INTERNAL_ERROR" /* INTERNAL_ERROR */]: "\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF",
|
|
322
|
+
["INVALID_REQUEST" /* INVALID_REQUEST */]: "\u65E0\u6548\u7684\u8BF7\u6C42",
|
|
323
|
+
["RESOURCE_NOT_FOUND" /* RESOURCE_NOT_FOUND */]: "\u8D44\u6E90\u672A\u627E\u5230",
|
|
324
|
+
["METHOD_NOT_ALLOWED" /* METHOD_NOT_ALLOWED */]: "\u65B9\u6CD5\u4E0D\u5141\u8BB8",
|
|
325
|
+
["AUTH_REQUIRED" /* AUTH_REQUIRED */]: "\u9700\u8981\u8BA4\u8BC1",
|
|
326
|
+
["AUTH_INVALID_TOKEN" /* AUTH_INVALID_TOKEN */]: "\u65E0\u6548\u7684\u8BA4\u8BC1\u4EE4\u724C",
|
|
327
|
+
["AUTH_TOKEN_EXPIRED" /* AUTH_TOKEN_EXPIRED */]: "\u8BA4\u8BC1\u4EE4\u724C\u5DF2\u8FC7\u671F",
|
|
328
|
+
["AUTH_INSUFFICIENT_PERMISSIONS" /* AUTH_INSUFFICIENT_PERMISSIONS */]: "\u6743\u9650\u4E0D\u8DB3",
|
|
329
|
+
["VALIDATION_FAILED" /* VALIDATION_FAILED */]: "\u9A8C\u8BC1\u5931\u8D25",
|
|
330
|
+
["VALIDATION_REQUIRED_FIELD" /* VALIDATION_REQUIRED_FIELD */]: "\u5FC5\u586B\u5B57\u6BB5\u7F3A\u5931",
|
|
331
|
+
["VALIDATION_INVALID_FORMAT" /* VALIDATION_INVALID_FORMAT */]: "\u683C\u5F0F\u65E0\u6548",
|
|
332
|
+
["VALIDATION_OUT_OF_RANGE" /* VALIDATION_OUT_OF_RANGE */]: "\u503C\u8D85\u51FA\u8303\u56F4",
|
|
333
|
+
["OAUTH2_INVALID_CLIENT" /* OAUTH2_INVALID_CLIENT */]: "\u65E0\u6548\u7684\u5BA2\u6237\u7AEF",
|
|
334
|
+
["OAUTH2_INVALID_GRANT" /* OAUTH2_INVALID_GRANT */]: "\u65E0\u6548\u7684\u6388\u6743",
|
|
335
|
+
["OAUTH2_INVALID_SCOPE" /* OAUTH2_INVALID_SCOPE */]: "\u65E0\u6548\u7684\u4F5C\u7528\u57DF",
|
|
336
|
+
["OAUTH2_INVALID_REDIRECT_URI" /* OAUTH2_INVALID_REDIRECT_URI */]: "\u65E0\u6548\u7684\u91CD\u5B9A\u5411 URI",
|
|
337
|
+
["OAUTH2_UNSUPPORTED_GRANT_TYPE" /* OAUTH2_UNSUPPORTED_GRANT_TYPE */]: "\u4E0D\u652F\u6301\u7684\u6388\u6743\u7C7B\u578B"
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// src/error/handler.ts
|
|
343
|
+
var exports_handler = {};
|
|
344
|
+
__export(exports_handler, {
|
|
345
|
+
handleError: () => handleError
|
|
346
|
+
});
|
|
347
|
+
async function handleError(error, context) {
|
|
348
|
+
const registry = ExceptionFilterRegistry.getInstance();
|
|
349
|
+
const filterResponse = await registry.execute(error, context);
|
|
350
|
+
if (filterResponse) {
|
|
351
|
+
return filterResponse;
|
|
352
|
+
}
|
|
353
|
+
if (error instanceof HttpException) {
|
|
354
|
+
context.setStatus(error.status);
|
|
355
|
+
let errorMessage = error.message;
|
|
356
|
+
if (error.code) {
|
|
357
|
+
const acceptLanguage = context.getHeader("accept-language");
|
|
358
|
+
const language = ErrorMessageI18n.parseLanguageFromHeader(acceptLanguage);
|
|
359
|
+
errorMessage = ErrorMessageI18n.getMessage(error.code, language);
|
|
360
|
+
}
|
|
361
|
+
const responseBody = {
|
|
362
|
+
error: errorMessage
|
|
363
|
+
};
|
|
364
|
+
if (error.code) {
|
|
365
|
+
responseBody.code = error.code;
|
|
366
|
+
}
|
|
367
|
+
if (error.details !== undefined) {
|
|
368
|
+
responseBody.details = error.details;
|
|
369
|
+
}
|
|
370
|
+
return context.createResponse(responseBody);
|
|
371
|
+
}
|
|
372
|
+
if (error instanceof ValidationError) {
|
|
373
|
+
context.setStatus(400);
|
|
374
|
+
return context.createResponse({
|
|
375
|
+
error: error.message,
|
|
376
|
+
code: "VALIDATION_FAILED",
|
|
377
|
+
issues: error.issues
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
381
|
+
context.setStatus(500);
|
|
382
|
+
return context.createResponse({
|
|
383
|
+
error: "Internal Server Error",
|
|
384
|
+
details: message
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
var init_handler = __esm(() => {
|
|
388
|
+
init_http_exception();
|
|
389
|
+
init_validation();
|
|
390
|
+
init_i18n();
|
|
391
|
+
});
|
|
17
392
|
|
|
18
393
|
// src/request/body-parser.ts
|
|
19
394
|
class BodyParser {
|
|
@@ -150,6 +525,17 @@ class Context {
|
|
|
150
525
|
getHeader(key) {
|
|
151
526
|
return this.headers.get(key);
|
|
152
527
|
}
|
|
528
|
+
getClientIp() {
|
|
529
|
+
const forwardedFor = this.getHeader("X-Forwarded-For");
|
|
530
|
+
if (forwardedFor) {
|
|
531
|
+
return forwardedFor.split(",")[0].trim();
|
|
532
|
+
}
|
|
533
|
+
const realIp = this.getHeader("X-Real-IP");
|
|
534
|
+
if (realIp) {
|
|
535
|
+
return realIp.trim();
|
|
536
|
+
}
|
|
537
|
+
return "unknown";
|
|
538
|
+
}
|
|
153
539
|
setHeader(key, value) {
|
|
154
540
|
this.responseHeaders.set(key, value);
|
|
155
541
|
}
|
|
@@ -941,310 +1327,211 @@ function PATCH(path) {
|
|
|
941
1327
|
return createRouteDecorator("PATCH", path);
|
|
942
1328
|
}
|
|
943
1329
|
|
|
944
|
-
// src/controller/metadata.ts
|
|
945
|
-
function getControllerMetadata(target) {
|
|
946
|
-
return Reflect.getMetadata(CONTROLLER_METADATA_KEY, target);
|
|
947
|
-
}
|
|
948
|
-
function getRouteMetadata(target) {
|
|
949
|
-
return Reflect.getMetadata(ROUTE_METADATA_KEY, target) || [];
|
|
950
|
-
}
|
|
951
|
-
// src/middleware/decorators.ts
|
|
952
|
-
import"reflect-metadata";
|
|
953
|
-
var CLASS_MIDDLEWARE_METADATA_KEY = Symbol("middleware:class");
|
|
954
|
-
var METHOD_MIDDLEWARE_METADATA_KEY = Symbol("middleware:method");
|
|
955
|
-
function appendMiddlewareMetadata(target, propertyKey, middlewares) {
|
|
956
|
-
if (!middlewares.length) {
|
|
957
|
-
return;
|
|
958
|
-
}
|
|
959
|
-
if (propertyKey === undefined) {
|
|
960
|
-
const existing2 = Reflect.getMetadata(CLASS_MIDDLEWARE_METADATA_KEY, target) || [];
|
|
961
|
-
Reflect.defineMetadata(CLASS_MIDDLEWARE_METADATA_KEY, existing2.concat(middlewares), target);
|
|
962
|
-
return;
|
|
963
|
-
}
|
|
964
|
-
const existing = Reflect.getMetadata(METHOD_MIDDLEWARE_METADATA_KEY, target, propertyKey) || [];
|
|
965
|
-
Reflect.defineMetadata(METHOD_MIDDLEWARE_METADATA_KEY, existing.concat(middlewares), target, propertyKey);
|
|
966
|
-
}
|
|
967
|
-
function UseMiddleware(...middlewares) {
|
|
968
|
-
return function(target, propertyKey) {
|
|
969
|
-
appendMiddlewareMetadata(propertyKey === undefined ? target : target, propertyKey, middlewares);
|
|
970
|
-
};
|
|
971
|
-
}
|
|
972
|
-
function getClassMiddlewares(constructor) {
|
|
973
|
-
return Reflect.getMetadata(CLASS_MIDDLEWARE_METADATA_KEY, constructor) || [];
|
|
974
|
-
}
|
|
975
|
-
function getMethodMiddlewares(target, propertyKey) {
|
|
976
|
-
return Reflect.getMetadata(METHOD_MIDDLEWARE_METADATA_KEY, target, propertyKey) || [];
|
|
977
|
-
}
|
|
978
|
-
// src/middleware/builtin/logger.ts
|
|
979
|
-
import { LoggerManager as LoggerManager4 } from "@dangao/logsmith";
|
|
980
|
-
function createLoggerMiddleware(options = {}) {
|
|
981
|
-
const log = options.logger ?? ((message, details) => {
|
|
982
|
-
const logger = LoggerManager4.getLogger();
|
|
983
|
-
if (details) {
|
|
984
|
-
logger.info(message, details);
|
|
985
|
-
} else {
|
|
986
|
-
logger.info(message);
|
|
987
|
-
}
|
|
988
|
-
});
|
|
989
|
-
const prefix = options.prefix ?? "[Logger]";
|
|
990
|
-
return async (context, next) => {
|
|
991
|
-
let response;
|
|
992
|
-
try {
|
|
993
|
-
response = await next();
|
|
994
|
-
return response;
|
|
995
|
-
} finally {
|
|
996
|
-
const status = response?.status ?? context.statusCode ?? 200;
|
|
997
|
-
log(`${prefix} ${context.method} ${context.path} ${status}`);
|
|
998
|
-
}
|
|
999
|
-
};
|
|
1000
|
-
}
|
|
1001
|
-
function createRequestLoggingMiddleware(options = {}) {
|
|
1002
|
-
const log = options.logger ?? ((message, details) => {
|
|
1003
|
-
const logger = LoggerManager4.getLogger();
|
|
1004
|
-
logger.info(message, details);
|
|
1005
|
-
});
|
|
1006
|
-
const prefix = options.prefix ?? "[Request]";
|
|
1007
|
-
const setHeader = options.setHeader ?? true;
|
|
1008
|
-
return async (context, next) => {
|
|
1009
|
-
const start = performance.now();
|
|
1010
|
-
try {
|
|
1011
|
-
const response = await next();
|
|
1012
|
-
const duration = performance.now() - start;
|
|
1013
|
-
log(`${prefix} ${context.method} ${context.path} ${response.status} ${duration.toFixed(2)}ms`);
|
|
1014
|
-
if (setHeader) {
|
|
1015
|
-
context.setHeader("x-request-duration", duration.toFixed(2));
|
|
1016
|
-
}
|
|
1017
|
-
return response;
|
|
1018
|
-
} catch (error) {
|
|
1019
|
-
const duration = performance.now() - start;
|
|
1020
|
-
log(`${prefix} ${context.method} ${context.path} error ${duration.toFixed(2)}ms`, error instanceof Error ? { error: error.message } : undefined);
|
|
1021
|
-
throw error;
|
|
1022
|
-
}
|
|
1023
|
-
};
|
|
1024
|
-
}
|
|
1025
|
-
// src/validation/decorators.ts
|
|
1026
|
-
import"reflect-metadata";
|
|
1027
|
-
var VALIDATION_METADATA_KEY = Symbol("validation:param");
|
|
1028
|
-
function getOrCreateMetadata(target, propertyKey) {
|
|
1029
|
-
const existing = Reflect.getMetadata(VALIDATION_METADATA_KEY, target, propertyKey);
|
|
1030
|
-
if (existing) {
|
|
1031
|
-
return existing;
|
|
1032
|
-
}
|
|
1033
|
-
const metadata = [];
|
|
1034
|
-
Reflect.defineMetadata(VALIDATION_METADATA_KEY, metadata, target, propertyKey);
|
|
1035
|
-
return metadata;
|
|
1036
|
-
}
|
|
1037
|
-
function Validate(...rules) {
|
|
1038
|
-
return (target, propertyKey, parameterIndex) => {
|
|
1039
|
-
if (!propertyKey) {
|
|
1040
|
-
throw new Error("@Validate decorator can only be used on methods");
|
|
1041
|
-
}
|
|
1042
|
-
if (!rules.length) {
|
|
1043
|
-
throw new Error("@Validate requires at least one validation rule");
|
|
1044
|
-
}
|
|
1045
|
-
const metadata = getOrCreateMetadata(target, propertyKey);
|
|
1046
|
-
let entry = metadata.find((item) => item.index === parameterIndex);
|
|
1047
|
-
if (!entry) {
|
|
1048
|
-
entry = { index: parameterIndex, rules: [] };
|
|
1049
|
-
metadata.push(entry);
|
|
1050
|
-
}
|
|
1051
|
-
entry.rules.push(...rules);
|
|
1052
|
-
};
|
|
1053
|
-
}
|
|
1054
|
-
function IsString(options = {}) {
|
|
1055
|
-
return {
|
|
1056
|
-
name: "isString",
|
|
1057
|
-
message: options.message ?? "\u5FC5\u987B\u662F\u5B57\u7B26\u4E32",
|
|
1058
|
-
validate: (value) => typeof value === "string"
|
|
1059
|
-
};
|
|
1060
|
-
}
|
|
1061
|
-
function IsNumber(options = {}) {
|
|
1062
|
-
return {
|
|
1063
|
-
name: "isNumber",
|
|
1064
|
-
message: options.message ?? "\u5FC5\u987B\u662F\u6570\u5B57",
|
|
1065
|
-
validate: (value) => typeof value === "number" && !Number.isNaN(value)
|
|
1066
|
-
};
|
|
1067
|
-
}
|
|
1068
|
-
function IsEmail(options = {}) {
|
|
1069
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1070
|
-
return {
|
|
1071
|
-
name: "isEmail",
|
|
1072
|
-
message: options.message ?? "\u5FC5\u987B\u662F\u5408\u6CD5\u7684\u90AE\u7BB1\u5730\u5740",
|
|
1073
|
-
validate: (value) => typeof value === "string" && emailRegex.test(value)
|
|
1074
|
-
};
|
|
1075
|
-
}
|
|
1076
|
-
function IsOptional() {
|
|
1077
|
-
return {
|
|
1078
|
-
name: "isOptional",
|
|
1079
|
-
message: "",
|
|
1080
|
-
optional: true,
|
|
1081
|
-
validate: () => true
|
|
1082
|
-
};
|
|
1083
|
-
}
|
|
1084
|
-
function MinLength(min, options = {}) {
|
|
1085
|
-
return {
|
|
1086
|
-
name: "minLength",
|
|
1087
|
-
message: options.message ?? `\u957F\u5EA6\u4E0D\u80FD\u5C0F\u4E8E ${min}`,
|
|
1088
|
-
validate: (value) => typeof value === "string" && value.length >= min
|
|
1089
|
-
};
|
|
1090
|
-
}
|
|
1091
|
-
function getValidationMetadata(target, propertyKey) {
|
|
1092
|
-
return Reflect.getMetadata(VALIDATION_METADATA_KEY, target, propertyKey) || [];
|
|
1093
|
-
}
|
|
1094
|
-
// src/validation/errors.ts
|
|
1095
|
-
class ValidationError extends Error {
|
|
1096
|
-
issues;
|
|
1097
|
-
constructor(message, issues) {
|
|
1098
|
-
super(message);
|
|
1099
|
-
this.name = "ValidationError";
|
|
1100
|
-
this.issues = issues;
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
// src/validation/validator.ts
|
|
1105
|
-
function shouldSkip(rule, value) {
|
|
1106
|
-
return rule.optional === true && (value === undefined || value === null);
|
|
1107
|
-
}
|
|
1108
|
-
function validateRule(rule, value, index) {
|
|
1109
|
-
if (shouldSkip(rule, value)) {
|
|
1110
|
-
return null;
|
|
1111
|
-
}
|
|
1112
|
-
const passed = rule.validate(value);
|
|
1113
|
-
if (passed) {
|
|
1114
|
-
return null;
|
|
1115
|
-
}
|
|
1116
|
-
return {
|
|
1117
|
-
index,
|
|
1118
|
-
rule: rule.name,
|
|
1119
|
-
message: rule.message
|
|
1120
|
-
};
|
|
1121
|
-
}
|
|
1122
|
-
function validateParameters(params, metadata) {
|
|
1123
|
-
if (!metadata.length) {
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
1126
|
-
const issues = [];
|
|
1127
|
-
for (const item of metadata) {
|
|
1128
|
-
const value = params[item.index];
|
|
1129
|
-
let skipped = false;
|
|
1130
|
-
for (const rule of item.rules) {
|
|
1131
|
-
if (rule.optional && (value === undefined || value === null)) {
|
|
1132
|
-
skipped = true;
|
|
1133
|
-
break;
|
|
1134
|
-
}
|
|
1135
|
-
const issue = validateRule(rule, value, item.index);
|
|
1136
|
-
if (issue) {
|
|
1137
|
-
issues.push(issue);
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
if (skipped) {
|
|
1141
|
-
continue;
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
if (issues.length > 0) {
|
|
1145
|
-
throw new ValidationError("Validation failed", issues);
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
// src/middleware/builtin/error-handler.ts
|
|
1149
|
-
import { LoggerManager as LoggerManager5 } from "@dangao/logsmith";
|
|
1150
|
-
|
|
1151
|
-
// src/error/http-exception.ts
|
|
1152
|
-
class HttpException extends Error {
|
|
1153
|
-
status;
|
|
1154
|
-
details;
|
|
1155
|
-
constructor(status, message, details) {
|
|
1156
|
-
super(message);
|
|
1157
|
-
this.name = this.constructor.name;
|
|
1158
|
-
this.status = status;
|
|
1159
|
-
this.details = details;
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
class BadRequestException extends HttpException {
|
|
1164
|
-
constructor(message = "Bad Request", details) {
|
|
1165
|
-
super(400, message, details);
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
class UnauthorizedException extends HttpException {
|
|
1170
|
-
constructor(message = "Unauthorized", details) {
|
|
1171
|
-
super(401, message, details);
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
class ForbiddenException extends HttpException {
|
|
1176
|
-
constructor(message = "Forbidden", details) {
|
|
1177
|
-
super(403, message, details);
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
class NotFoundException extends HttpException {
|
|
1182
|
-
constructor(message = "Not Found", details) {
|
|
1183
|
-
super(404, message, details);
|
|
1184
|
-
}
|
|
1330
|
+
// src/controller/metadata.ts
|
|
1331
|
+
function getControllerMetadata(target) {
|
|
1332
|
+
return Reflect.getMetadata(CONTROLLER_METADATA_KEY, target);
|
|
1185
1333
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
constructor(message = "Internal Server Error", details) {
|
|
1189
|
-
super(500, message, details);
|
|
1190
|
-
}
|
|
1334
|
+
function getRouteMetadata(target) {
|
|
1335
|
+
return Reflect.getMetadata(ROUTE_METADATA_KEY, target) || [];
|
|
1191
1336
|
}
|
|
1192
|
-
// src/
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1337
|
+
// src/middleware/decorators.ts
|
|
1338
|
+
import"reflect-metadata";
|
|
1339
|
+
|
|
1340
|
+
// src/middleware/builtin/rate-limit.ts
|
|
1341
|
+
class MemoryRateLimitStore {
|
|
1342
|
+
store = new Map;
|
|
1343
|
+
async get(key) {
|
|
1344
|
+
const entry = this.store.get(key);
|
|
1345
|
+
if (!entry) {
|
|
1346
|
+
return 0;
|
|
1200
1347
|
}
|
|
1201
|
-
|
|
1348
|
+
if (Date.now() > entry.resetTime) {
|
|
1349
|
+
this.store.delete(key);
|
|
1350
|
+
return 0;
|
|
1351
|
+
}
|
|
1352
|
+
return entry.count;
|
|
1202
1353
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1354
|
+
async increment(key, windowMs) {
|
|
1355
|
+
const now = Date.now();
|
|
1356
|
+
const entry = this.store.get(key);
|
|
1357
|
+
if (!entry || now > entry.resetTime) {
|
|
1358
|
+
const resetTime = now + windowMs;
|
|
1359
|
+
this.store.set(key, { count: 1, resetTime });
|
|
1360
|
+
return 1;
|
|
1361
|
+
}
|
|
1362
|
+
entry.count++;
|
|
1363
|
+
return entry.count;
|
|
1205
1364
|
}
|
|
1206
|
-
|
|
1207
|
-
this.
|
|
1365
|
+
async reset(key) {
|
|
1366
|
+
this.store.delete(key);
|
|
1208
1367
|
}
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
if (
|
|
1213
|
-
|
|
1368
|
+
cleanup() {
|
|
1369
|
+
const now = Date.now();
|
|
1370
|
+
for (const [key, entry] of this.store.entries()) {
|
|
1371
|
+
if (now > entry.resetTime) {
|
|
1372
|
+
this.store.delete(key);
|
|
1214
1373
|
}
|
|
1215
1374
|
}
|
|
1216
|
-
return;
|
|
1217
1375
|
}
|
|
1218
1376
|
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1377
|
+
function defaultKeyGenerator(context) {
|
|
1378
|
+
return `rate-limit:${context.getClientIp()}`;
|
|
1379
|
+
}
|
|
1380
|
+
function createRateLimitMiddleware(options) {
|
|
1381
|
+
const {
|
|
1382
|
+
max,
|
|
1383
|
+
windowMs = 60000,
|
|
1384
|
+
store = new MemoryRateLimitStore,
|
|
1385
|
+
keyGenerator = defaultKeyGenerator,
|
|
1386
|
+
skipSuccessfulRequests = false,
|
|
1387
|
+
skipFailedRequests = false,
|
|
1388
|
+
message = "Too Many Requests",
|
|
1389
|
+
statusCode = 429,
|
|
1390
|
+
standardHeaders = true,
|
|
1391
|
+
legacyHeaders = true
|
|
1392
|
+
} = options;
|
|
1393
|
+
return async (context, next) => {
|
|
1394
|
+
const key = await keyGenerator(context);
|
|
1395
|
+
const currentCount = await store.increment(key, windowMs);
|
|
1396
|
+
const remaining = Math.max(0, max - currentCount);
|
|
1397
|
+
const resetTime = Date.now() + windowMs;
|
|
1398
|
+
if (standardHeaders) {
|
|
1399
|
+
context.setHeader("RateLimit-Limit", max.toString());
|
|
1400
|
+
context.setHeader("RateLimit-Remaining", remaining.toString());
|
|
1401
|
+
context.setHeader("RateLimit-Reset", Math.ceil(resetTime / 1000).toString());
|
|
1402
|
+
}
|
|
1403
|
+
if (legacyHeaders) {
|
|
1404
|
+
context.setHeader("X-RateLimit-Limit", max.toString());
|
|
1405
|
+
context.setHeader("X-RateLimit-Remaining", remaining.toString());
|
|
1406
|
+
context.setHeader("X-RateLimit-Reset", Math.ceil(resetTime / 1000).toString());
|
|
1407
|
+
}
|
|
1408
|
+
if (currentCount > max) {
|
|
1409
|
+
context.setStatus(statusCode);
|
|
1410
|
+
return context.createResponse({
|
|
1411
|
+
error: message,
|
|
1412
|
+
retryAfter: Math.ceil(windowMs / 1000)
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
const response = await next();
|
|
1416
|
+
const shouldSkip = skipSuccessfulRequests && response.status >= 200 && response.status < 300 || skipFailedRequests && response.status >= 400;
|
|
1417
|
+
if (shouldSkip) {
|
|
1418
|
+
const current = await store.get(key);
|
|
1419
|
+
if (current > 0) {}
|
|
1420
|
+
}
|
|
1421
|
+
return response;
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
function createTokenKeyGenerator(tokenHeader = "Authorization") {
|
|
1425
|
+
return (context) => {
|
|
1426
|
+
const token = context.getHeader(tokenHeader);
|
|
1427
|
+
if (token) {
|
|
1428
|
+
const tokenValue = token.startsWith("Bearer ") ? token.substring(7) : token;
|
|
1429
|
+
return `rate-limit:token:${tokenValue}`;
|
|
1430
|
+
}
|
|
1431
|
+
return defaultKeyGenerator(context);
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
function createUserKeyGenerator(getUserId) {
|
|
1435
|
+
return (context) => {
|
|
1436
|
+
const userId = getUserId(context);
|
|
1437
|
+
if (userId) {
|
|
1438
|
+
return `rate-limit:user:${userId}`;
|
|
1439
|
+
}
|
|
1440
|
+
return defaultKeyGenerator(context);
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
// src/middleware/decorators.ts
|
|
1445
|
+
var CLASS_MIDDLEWARE_METADATA_KEY = Symbol("middleware:class");
|
|
1446
|
+
var METHOD_MIDDLEWARE_METADATA_KEY = Symbol("middleware:method");
|
|
1447
|
+
function appendMiddlewareMetadata(target, propertyKey, middlewares) {
|
|
1448
|
+
if (!middlewares.length) {
|
|
1449
|
+
return;
|
|
1232
1450
|
}
|
|
1233
|
-
if (
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
issues: error.issues
|
|
1238
|
-
});
|
|
1451
|
+
if (propertyKey === undefined) {
|
|
1452
|
+
const existing2 = Reflect.getMetadata(CLASS_MIDDLEWARE_METADATA_KEY, target) || [];
|
|
1453
|
+
Reflect.defineMetadata(CLASS_MIDDLEWARE_METADATA_KEY, existing2.concat(middlewares), target);
|
|
1454
|
+
return;
|
|
1239
1455
|
}
|
|
1240
|
-
const
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1456
|
+
const existing = Reflect.getMetadata(METHOD_MIDDLEWARE_METADATA_KEY, target, propertyKey) || [];
|
|
1457
|
+
Reflect.defineMetadata(METHOD_MIDDLEWARE_METADATA_KEY, existing.concat(middlewares), target, propertyKey);
|
|
1458
|
+
}
|
|
1459
|
+
function UseMiddleware(...middlewares) {
|
|
1460
|
+
return function(target, propertyKey) {
|
|
1461
|
+
appendMiddlewareMetadata(propertyKey === undefined ? target : target, propertyKey, middlewares);
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
function RateLimit(options) {
|
|
1465
|
+
return function(target, propertyKey) {
|
|
1466
|
+
const middleware = createRateLimitMiddleware(options);
|
|
1467
|
+
appendMiddlewareMetadata(target, propertyKey, [middleware]);
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
function getClassMiddlewares(constructor) {
|
|
1471
|
+
return Reflect.getMetadata(CLASS_MIDDLEWARE_METADATA_KEY, constructor) || [];
|
|
1472
|
+
}
|
|
1473
|
+
function getMethodMiddlewares(target, propertyKey) {
|
|
1474
|
+
return Reflect.getMetadata(METHOD_MIDDLEWARE_METADATA_KEY, target, propertyKey) || [];
|
|
1475
|
+
}
|
|
1476
|
+
// src/middleware/builtin/logger.ts
|
|
1477
|
+
import { LoggerManager as LoggerManager4 } from "@dangao/logsmith";
|
|
1478
|
+
function createLoggerMiddleware(options = {}) {
|
|
1479
|
+
const log = options.logger ?? ((message, details) => {
|
|
1480
|
+
const logger = LoggerManager4.getLogger();
|
|
1481
|
+
if (details) {
|
|
1482
|
+
logger.info(message, details);
|
|
1483
|
+
} else {
|
|
1484
|
+
logger.info(message);
|
|
1485
|
+
}
|
|
1486
|
+
});
|
|
1487
|
+
const prefix = options.prefix ?? "[Logger]";
|
|
1488
|
+
return async (context, next) => {
|
|
1489
|
+
let response;
|
|
1490
|
+
try {
|
|
1491
|
+
response = await next();
|
|
1492
|
+
return response;
|
|
1493
|
+
} finally {
|
|
1494
|
+
const status = response?.status ?? context.statusCode ?? 200;
|
|
1495
|
+
log(`${prefix} ${context.method} ${context.path} ${status}`);
|
|
1496
|
+
}
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
function createRequestLoggingMiddleware(options = {}) {
|
|
1500
|
+
const log = options.logger ?? ((message, details) => {
|
|
1501
|
+
const logger = LoggerManager4.getLogger();
|
|
1502
|
+
logger.info(message, details);
|
|
1245
1503
|
});
|
|
1504
|
+
const prefix = options.prefix ?? "[Request]";
|
|
1505
|
+
const setHeader = options.setHeader ?? true;
|
|
1506
|
+
return async (context, next) => {
|
|
1507
|
+
const start = performance.now();
|
|
1508
|
+
try {
|
|
1509
|
+
const response = await next();
|
|
1510
|
+
const duration = performance.now() - start;
|
|
1511
|
+
log(`${prefix} ${context.method} ${context.path} ${response.status} ${duration.toFixed(2)}ms`);
|
|
1512
|
+
if (setHeader) {
|
|
1513
|
+
context.setHeader("x-request-duration", duration.toFixed(2));
|
|
1514
|
+
}
|
|
1515
|
+
return response;
|
|
1516
|
+
} catch (error) {
|
|
1517
|
+
const duration = performance.now() - start;
|
|
1518
|
+
log(`${prefix} ${context.method} ${context.path} error ${duration.toFixed(2)}ms`, error instanceof Error ? { error: error.message } : undefined);
|
|
1519
|
+
throw error;
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1246
1522
|
}
|
|
1247
1523
|
// src/middleware/builtin/error-handler.ts
|
|
1524
|
+
init_validation();
|
|
1525
|
+
import { LoggerManager as LoggerManager5 } from "@dangao/logsmith";
|
|
1526
|
+
|
|
1527
|
+
// src/error/index.ts
|
|
1528
|
+
init_http_exception();
|
|
1529
|
+
init_handler();
|
|
1530
|
+
init_error_codes();
|
|
1531
|
+
init_i18n();
|
|
1532
|
+
|
|
1533
|
+
// src/middleware/builtin/error-handler.ts
|
|
1534
|
+
init_handler();
|
|
1248
1535
|
function createErrorHandlingMiddleware(options = {}) {
|
|
1249
1536
|
const log = options.logger ?? ((error, context) => {
|
|
1250
1537
|
LoggerManager5.getLogger().error("[Error]", { ...context, error });
|
|
@@ -1274,13 +1561,7 @@ function createErrorHandlingMiddleware(options = {}) {
|
|
|
1274
1561
|
});
|
|
1275
1562
|
}
|
|
1276
1563
|
if (error instanceof HttpException) {
|
|
1277
|
-
|
|
1278
|
-
return await handleError(error, context);
|
|
1279
|
-
}
|
|
1280
|
-
return context.createResponse({
|
|
1281
|
-
error: error.message,
|
|
1282
|
-
details: error.details
|
|
1283
|
-
}, { status: error.status });
|
|
1564
|
+
return await handleError(error, context);
|
|
1284
1565
|
}
|
|
1285
1566
|
if (error instanceof Error && !expose) {
|
|
1286
1567
|
return await handleError(error, context);
|
|
@@ -1455,6 +1736,7 @@ function createStaticFileMiddleware(options) {
|
|
|
1455
1736
|
};
|
|
1456
1737
|
}
|
|
1457
1738
|
// src/controller/controller.ts
|
|
1739
|
+
init_validation();
|
|
1458
1740
|
var CONTROLLER_METADATA_KEY = Symbol("controller");
|
|
1459
1741
|
function Controller(path = "") {
|
|
1460
1742
|
return function(target) {
|
|
@@ -1538,23 +1820,8 @@ class ControllerRegistry {
|
|
|
1538
1820
|
}
|
|
1539
1821
|
return context.createResponse(responseData);
|
|
1540
1822
|
} catch (error) {
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
return context.createResponse({
|
|
1544
|
-
error: error.message,
|
|
1545
|
-
issues: error.issues
|
|
1546
|
-
});
|
|
1547
|
-
}
|
|
1548
|
-
if (error instanceof HttpException) {
|
|
1549
|
-
context.setStatus(error.status);
|
|
1550
|
-
return context.createResponse({
|
|
1551
|
-
error: error.message,
|
|
1552
|
-
details: error.details
|
|
1553
|
-
});
|
|
1554
|
-
}
|
|
1555
|
-
context.setStatus(500);
|
|
1556
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1557
|
-
return context.createResponse({ error: errorMessage });
|
|
1823
|
+
const { handleError: handleError2 } = await Promise.resolve().then(() => (init_handler(), exports_handler));
|
|
1824
|
+
return await handleError2(error, context);
|
|
1558
1825
|
}
|
|
1559
1826
|
};
|
|
1560
1827
|
const middlewares = [...classMiddlewares];
|
|
@@ -2079,6 +2346,8 @@ class ResponseBuilder {
|
|
|
2079
2346
|
});
|
|
2080
2347
|
}
|
|
2081
2348
|
}
|
|
2349
|
+
// src/index.ts
|
|
2350
|
+
init_validation();
|
|
2082
2351
|
// src/extensions/logger-module.ts
|
|
2083
2352
|
class LoggerModule {
|
|
2084
2353
|
static forRoot(options = {}) {
|
|
@@ -2490,8 +2759,8 @@ class SecurityContextHolder {
|
|
|
2490
2759
|
return context;
|
|
2491
2760
|
}
|
|
2492
2761
|
static runWithContext(callback) {
|
|
2493
|
-
const
|
|
2494
|
-
return this.storage.run(
|
|
2762
|
+
const context = new SecurityContextImpl;
|
|
2763
|
+
return this.storage.run(context, callback);
|
|
2495
2764
|
}
|
|
2496
2765
|
static clearContext() {
|
|
2497
2766
|
const context = this.storage.getStore();
|
|
@@ -2531,6 +2800,10 @@ class RoleBasedAccessDecisionManager {
|
|
|
2531
2800
|
return requiredAuthorities.some((required) => userAuthorities.includes(required));
|
|
2532
2801
|
}
|
|
2533
2802
|
}
|
|
2803
|
+
// src/security/filter.ts
|
|
2804
|
+
init_http_exception();
|
|
2805
|
+
init_error_codes();
|
|
2806
|
+
|
|
2534
2807
|
// src/auth/decorators.ts
|
|
2535
2808
|
import"reflect-metadata";
|
|
2536
2809
|
var AUTH_METADATA_KEY = Symbol("@dangao/bun-server:auth");
|
|
@@ -2601,19 +2874,19 @@ function createSecurityFilter(config) {
|
|
|
2601
2874
|
if (requiresAuth(controllerTarget, method)) {
|
|
2602
2875
|
const authentication = securityContext.authentication;
|
|
2603
2876
|
if (!authentication || !authentication.authenticated) {
|
|
2604
|
-
throw new UnauthorizedException("Authentication required");
|
|
2877
|
+
throw new UnauthorizedException("Authentication required", undefined, "AUTH_REQUIRED" /* AUTH_REQUIRED */);
|
|
2605
2878
|
}
|
|
2606
2879
|
const requiredRoles = getRequiredRoles(controllerTarget, method);
|
|
2607
2880
|
if (requiredRoles.length > 0) {
|
|
2608
2881
|
const hasAccess = accessDecisionManager.decide(authentication, requiredRoles);
|
|
2609
2882
|
if (!hasAccess) {
|
|
2610
2883
|
const userRoles = authentication.authorities || [];
|
|
2611
|
-
throw new ForbiddenException(`Insufficient permissions. Required roles: ${requiredRoles.join(", ")}, User roles: ${userRoles.join(", ")}
|
|
2884
|
+
throw new ForbiddenException(`Insufficient permissions. Required roles: ${requiredRoles.join(", ")}, User roles: ${userRoles.join(", ")}`, { requiredRoles, userRoles }, "AUTH_INSUFFICIENT_PERMISSIONS" /* AUTH_INSUFFICIENT_PERMISSIONS */);
|
|
2612
2885
|
}
|
|
2613
2886
|
}
|
|
2614
2887
|
}
|
|
2615
2888
|
} else if (defaultAuthRequired && !securityContext.isAuthenticated()) {
|
|
2616
|
-
throw new UnauthorizedException("Authentication required");
|
|
2889
|
+
throw new UnauthorizedException("Authentication required", undefined, "AUTH_REQUIRED" /* AUTH_REQUIRED */);
|
|
2617
2890
|
}
|
|
2618
2891
|
ctx.security = securityContext;
|
|
2619
2892
|
ctx.auth = {
|
|
@@ -2634,7 +2907,7 @@ function extractTokenFromHeader(ctx) {
|
|
|
2634
2907
|
return null;
|
|
2635
2908
|
}
|
|
2636
2909
|
const parts = authHeader.split(" ");
|
|
2637
|
-
if (parts.length !== 2 || parts[0] !== "
|
|
2910
|
+
if (parts.length !== 2 || parts[0].toLowerCase() !== "bearer") {
|
|
2638
2911
|
return null;
|
|
2639
2912
|
}
|
|
2640
2913
|
return parts[1];
|
|
@@ -2680,9 +2953,11 @@ class JwtAuthenticationProvider {
|
|
|
2680
2953
|
// src/security/providers/oauth2-provider.ts
|
|
2681
2954
|
class OAuth2AuthenticationProvider {
|
|
2682
2955
|
oauth2Service;
|
|
2956
|
+
jwtUtil;
|
|
2683
2957
|
supportedTypes = ["oauth2", "authorization_code"];
|
|
2684
|
-
constructor(oauth2Service) {
|
|
2958
|
+
constructor(oauth2Service, jwtUtil) {
|
|
2685
2959
|
this.oauth2Service = oauth2Service;
|
|
2960
|
+
this.jwtUtil = jwtUtil;
|
|
2686
2961
|
}
|
|
2687
2962
|
supports(type) {
|
|
2688
2963
|
return this.supportedTypes.includes(type.toLowerCase());
|
|
@@ -2696,15 +2971,14 @@ class OAuth2AuthenticationProvider {
|
|
|
2696
2971
|
if (!tokenResponse) {
|
|
2697
2972
|
return null;
|
|
2698
2973
|
}
|
|
2699
|
-
const
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
};
|
|
2974
|
+
const payload = this.jwtUtil.verify(tokenResponse.accessToken);
|
|
2975
|
+
if (!payload || !payload.sub) {
|
|
2976
|
+
return null;
|
|
2977
|
+
}
|
|
2704
2978
|
const principal = {
|
|
2705
|
-
id:
|
|
2706
|
-
username:
|
|
2707
|
-
roles:
|
|
2979
|
+
id: payload.sub,
|
|
2980
|
+
username: payload.username || payload.sub,
|
|
2981
|
+
roles: payload.roles || []
|
|
2708
2982
|
};
|
|
2709
2983
|
return {
|
|
2710
2984
|
authenticated: true,
|
|
@@ -3088,7 +3362,7 @@ class SecurityModule {
|
|
|
3088
3362
|
const oauth2Service = new OAuth2Service(jwtUtil, config.oauth2Clients || [], {}, userProvider);
|
|
3089
3363
|
const authenticationManager = new AuthenticationManager;
|
|
3090
3364
|
authenticationManager.registerProvider(new JwtAuthenticationProvider(jwtUtil));
|
|
3091
|
-
authenticationManager.registerProvider(new OAuth2AuthenticationProvider(oauth2Service));
|
|
3365
|
+
authenticationManager.registerProvider(new OAuth2AuthenticationProvider(oauth2Service, jwtUtil));
|
|
3092
3366
|
const securityFilter = createSecurityFilter({
|
|
3093
3367
|
authenticationManager,
|
|
3094
3368
|
excludePaths: [
|
|
@@ -3338,6 +3612,297 @@ HealthModule = __legacyDecorateClassTS([
|
|
|
3338
3612
|
providers: []
|
|
3339
3613
|
})
|
|
3340
3614
|
], HealthModule);
|
|
3615
|
+
// src/metrics/collector.ts
|
|
3616
|
+
class MetricsCollector {
|
|
3617
|
+
counters = new Map;
|
|
3618
|
+
gauges = new Map;
|
|
3619
|
+
histograms = new Map;
|
|
3620
|
+
customMetrics = [];
|
|
3621
|
+
registerCustomMetric(metric) {
|
|
3622
|
+
this.customMetrics.push(metric);
|
|
3623
|
+
}
|
|
3624
|
+
incrementCounter(name, labels, value = 1) {
|
|
3625
|
+
const key = this.getKey(name, labels);
|
|
3626
|
+
const counterMap = this.counters.get(name) || new Map;
|
|
3627
|
+
const current = counterMap.get(key) || 0;
|
|
3628
|
+
counterMap.set(key, current + value);
|
|
3629
|
+
this.counters.set(name, counterMap);
|
|
3630
|
+
}
|
|
3631
|
+
setGauge(name, labels, value) {
|
|
3632
|
+
const key = this.getKey(name, labels);
|
|
3633
|
+
const gaugeMap = this.gauges.get(name) || new Map;
|
|
3634
|
+
gaugeMap.set(key, value);
|
|
3635
|
+
this.gauges.set(name, gaugeMap);
|
|
3636
|
+
}
|
|
3637
|
+
observeHistogram(name, labels, value) {
|
|
3638
|
+
const key = this.getKey(name, labels);
|
|
3639
|
+
const histogramMap = this.histograms.get(name) || new Map;
|
|
3640
|
+
const values = histogramMap.get(key) || [];
|
|
3641
|
+
values.push(value);
|
|
3642
|
+
histogramMap.set(key, values);
|
|
3643
|
+
this.histograms.set(name, histogramMap);
|
|
3644
|
+
}
|
|
3645
|
+
async getAllDataPoints() {
|
|
3646
|
+
const dataPoints = [];
|
|
3647
|
+
for (const [name, counterMap] of this.counters.entries()) {
|
|
3648
|
+
for (const [key, value] of counterMap.entries()) {
|
|
3649
|
+
const labels = this.parseKey(key);
|
|
3650
|
+
dataPoints.push({
|
|
3651
|
+
name,
|
|
3652
|
+
type: "counter",
|
|
3653
|
+
value,
|
|
3654
|
+
labels: labels && Object.keys(labels).length > 0 ? labels : undefined
|
|
3655
|
+
});
|
|
3656
|
+
}
|
|
3657
|
+
}
|
|
3658
|
+
for (const [name, gaugeMap] of this.gauges.entries()) {
|
|
3659
|
+
for (const [key, value] of gaugeMap.entries()) {
|
|
3660
|
+
const labels = this.parseKey(key);
|
|
3661
|
+
dataPoints.push({
|
|
3662
|
+
name,
|
|
3663
|
+
type: "gauge",
|
|
3664
|
+
value,
|
|
3665
|
+
labels: labels && Object.keys(labels).length > 0 ? labels : undefined
|
|
3666
|
+
});
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
for (const [name, histogramMap] of this.histograms.entries()) {
|
|
3670
|
+
for (const [key, values] of histogramMap.entries()) {
|
|
3671
|
+
const labels = this.parseKey(key);
|
|
3672
|
+
const sum = values.reduce((a, b) => a + b, 0);
|
|
3673
|
+
const count = values.length;
|
|
3674
|
+
const buckets = this.calculateBuckets(values);
|
|
3675
|
+
dataPoints.push({
|
|
3676
|
+
name: `${name}_sum`,
|
|
3677
|
+
type: "histogram",
|
|
3678
|
+
value: sum,
|
|
3679
|
+
labels: labels && Object.keys(labels).length > 0 ? labels : undefined
|
|
3680
|
+
});
|
|
3681
|
+
dataPoints.push({
|
|
3682
|
+
name: `${name}_count`,
|
|
3683
|
+
type: "histogram",
|
|
3684
|
+
value: count,
|
|
3685
|
+
labels: labels && Object.keys(labels).length > 0 ? labels : undefined
|
|
3686
|
+
});
|
|
3687
|
+
for (const [bucket, bucketCount] of Object.entries(buckets)) {
|
|
3688
|
+
dataPoints.push({
|
|
3689
|
+
name: `${name}_bucket`,
|
|
3690
|
+
type: "histogram",
|
|
3691
|
+
value: bucketCount,
|
|
3692
|
+
labels: {
|
|
3693
|
+
...labels,
|
|
3694
|
+
le: bucket
|
|
3695
|
+
}
|
|
3696
|
+
});
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
for (const metric of this.customMetrics) {
|
|
3701
|
+
try {
|
|
3702
|
+
const value = await metric.getValue();
|
|
3703
|
+
dataPoints.push({
|
|
3704
|
+
name: metric.name,
|
|
3705
|
+
type: metric.type,
|
|
3706
|
+
value,
|
|
3707
|
+
help: metric.help
|
|
3708
|
+
});
|
|
3709
|
+
} catch (error) {
|
|
3710
|
+
console.error(`Failed to collect custom metric ${metric.name}:`, error);
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
return dataPoints;
|
|
3714
|
+
}
|
|
3715
|
+
reset() {
|
|
3716
|
+
this.counters.clear();
|
|
3717
|
+
this.gauges.clear();
|
|
3718
|
+
this.histograms.clear();
|
|
3719
|
+
}
|
|
3720
|
+
getKey(name, labels) {
|
|
3721
|
+
if (!labels || Object.keys(labels).length === 0) {
|
|
3722
|
+
return "";
|
|
3723
|
+
}
|
|
3724
|
+
const sortedLabels = Object.keys(labels).sort().map((key) => `${key}="${labels[key]}"`).join(",");
|
|
3725
|
+
return `{${sortedLabels}}`;
|
|
3726
|
+
}
|
|
3727
|
+
parseKey(key) {
|
|
3728
|
+
if (!key || key === "") {
|
|
3729
|
+
return;
|
|
3730
|
+
}
|
|
3731
|
+
const labels = {};
|
|
3732
|
+
const match = key.match(/\{([^}]+)\}/);
|
|
3733
|
+
if (match) {
|
|
3734
|
+
const labelPairs = match[1].split(",");
|
|
3735
|
+
for (const pair of labelPairs) {
|
|
3736
|
+
const [k, v] = pair.split("=");
|
|
3737
|
+
if (k && v) {
|
|
3738
|
+
labels[k.trim()] = v.trim().replace(/^"|"$/g, "");
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
return Object.keys(labels).length > 0 ? labels : undefined;
|
|
3743
|
+
}
|
|
3744
|
+
calculateBuckets(values) {
|
|
3745
|
+
const defaultBuckets = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];
|
|
3746
|
+
const buckets = {};
|
|
3747
|
+
for (const bucket of defaultBuckets) {
|
|
3748
|
+
buckets[bucket.toString()] = values.filter((v) => v <= bucket).length;
|
|
3749
|
+
}
|
|
3750
|
+
buckets["+Inf"] = values.length;
|
|
3751
|
+
return buckets;
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
|
|
3755
|
+
// src/metrics/prometheus.ts
|
|
3756
|
+
class PrometheusFormatter {
|
|
3757
|
+
format(dataPoints) {
|
|
3758
|
+
const lines = [];
|
|
3759
|
+
const metricGroups = this.groupByMetricName(dataPoints);
|
|
3760
|
+
for (const [metricName, points] of metricGroups.entries()) {
|
|
3761
|
+
const help = points[0]?.help;
|
|
3762
|
+
if (help) {
|
|
3763
|
+
lines.push(`# HELP ${metricName} ${help}`);
|
|
3764
|
+
}
|
|
3765
|
+
const type = points[0]?.type;
|
|
3766
|
+
if (type) {
|
|
3767
|
+
lines.push(`# TYPE ${metricName} ${type}`);
|
|
3768
|
+
}
|
|
3769
|
+
for (const point of points) {
|
|
3770
|
+
const labelString = this.formatLabels(point.labels);
|
|
3771
|
+
const line = labelString ? `${point.name}${labelString} ${point.value}` : `${point.name} ${point.value}`;
|
|
3772
|
+
lines.push(line);
|
|
3773
|
+
}
|
|
3774
|
+
lines.push("");
|
|
3775
|
+
}
|
|
3776
|
+
return lines.join(`
|
|
3777
|
+
`);
|
|
3778
|
+
}
|
|
3779
|
+
groupByMetricName(dataPoints) {
|
|
3780
|
+
const groups = new Map;
|
|
3781
|
+
for (const point of dataPoints) {
|
|
3782
|
+
const name = point.name;
|
|
3783
|
+
const existing = groups.get(name) || [];
|
|
3784
|
+
existing.push(point);
|
|
3785
|
+
groups.set(name, existing);
|
|
3786
|
+
}
|
|
3787
|
+
return groups;
|
|
3788
|
+
}
|
|
3789
|
+
formatLabels(labels) {
|
|
3790
|
+
if (!labels || Object.keys(labels).length === 0) {
|
|
3791
|
+
return "";
|
|
3792
|
+
}
|
|
3793
|
+
const labelPairs = Object.keys(labels).sort().map((key) => `${key}="${this.escapeLabelValue(labels[key])}"`).join(",");
|
|
3794
|
+
return `{${labelPairs}}`;
|
|
3795
|
+
}
|
|
3796
|
+
escapeLabelValue(value) {
|
|
3797
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3800
|
+
|
|
3801
|
+
// src/metrics/types.ts
|
|
3802
|
+
var METRICS_SERVICE_TOKEN = Symbol("@dangao/bun-server:metrics:service");
|
|
3803
|
+
var METRICS_OPTIONS_TOKEN = Symbol("@dangao/bun-server:metrics:options");
|
|
3804
|
+
|
|
3805
|
+
// src/metrics/controller.ts
|
|
3806
|
+
class MetricsController {
|
|
3807
|
+
collector;
|
|
3808
|
+
options;
|
|
3809
|
+
formatter;
|
|
3810
|
+
constructor(collector, options) {
|
|
3811
|
+
this.collector = collector;
|
|
3812
|
+
this.options = options;
|
|
3813
|
+
this.formatter = new PrometheusFormatter;
|
|
3814
|
+
}
|
|
3815
|
+
async metrics() {
|
|
3816
|
+
const dataPoints = await this.collector.getAllDataPoints();
|
|
3817
|
+
const prometheusText = this.formatter.format(dataPoints);
|
|
3818
|
+
return new Response(prometheusText, {
|
|
3819
|
+
headers: {
|
|
3820
|
+
"Content-Type": "text/plain; version=0.0.4; charset=utf-8"
|
|
3821
|
+
}
|
|
3822
|
+
});
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
__legacyDecorateClassTS([
|
|
3826
|
+
GET("/metrics"),
|
|
3827
|
+
__legacyMetadataTS("design:type", Function),
|
|
3828
|
+
__legacyMetadataTS("design:paramtypes", []),
|
|
3829
|
+
__legacyMetadataTS("design:returntype", typeof Promise === "undefined" ? Object : Promise)
|
|
3830
|
+
], MetricsController.prototype, "metrics", null);
|
|
3831
|
+
MetricsController = __legacyDecorateClassTS([
|
|
3832
|
+
Controller("/"),
|
|
3833
|
+
__legacyDecorateParamTS(0, Inject(METRICS_SERVICE_TOKEN)),
|
|
3834
|
+
__legacyDecorateParamTS(1, Inject(METRICS_OPTIONS_TOKEN)),
|
|
3835
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
3836
|
+
typeof MetricsCollector === "undefined" ? Object : MetricsCollector,
|
|
3837
|
+
typeof MetricsModuleOptions === "undefined" ? Object : MetricsModuleOptions
|
|
3838
|
+
])
|
|
3839
|
+
], MetricsController);
|
|
3840
|
+
|
|
3841
|
+
// src/metrics/metrics-module.ts
|
|
3842
|
+
class MetricsModule {
|
|
3843
|
+
static forRoot(options = {}) {
|
|
3844
|
+
const providers2 = [];
|
|
3845
|
+
const collector = new MetricsCollector;
|
|
3846
|
+
if (options.customMetrics) {
|
|
3847
|
+
for (const metric of options.customMetrics) {
|
|
3848
|
+
collector.registerCustomMetric(metric);
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
providers2.push({
|
|
3852
|
+
provide: METRICS_SERVICE_TOKEN,
|
|
3853
|
+
useValue: collector
|
|
3854
|
+
}, {
|
|
3855
|
+
provide: METRICS_OPTIONS_TOKEN,
|
|
3856
|
+
useValue: options
|
|
3857
|
+
}, MetricsCollector);
|
|
3858
|
+
const existingMetadata = Reflect.getMetadata(MODULE_METADATA_KEY, MetricsModule) || {};
|
|
3859
|
+
const metadata = {
|
|
3860
|
+
...existingMetadata,
|
|
3861
|
+
controllers: [...existingMetadata.controllers || [], MetricsController],
|
|
3862
|
+
providers: [...existingMetadata.providers || [], ...providers2],
|
|
3863
|
+
exports: [
|
|
3864
|
+
...existingMetadata.exports || [],
|
|
3865
|
+
METRICS_SERVICE_TOKEN,
|
|
3866
|
+
MetricsCollector
|
|
3867
|
+
]
|
|
3868
|
+
};
|
|
3869
|
+
Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, MetricsModule);
|
|
3870
|
+
return MetricsModule;
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
MetricsModule = __legacyDecorateClassTS([
|
|
3874
|
+
Module({
|
|
3875
|
+
controllers: [MetricsController],
|
|
3876
|
+
providers: []
|
|
3877
|
+
})
|
|
3878
|
+
], MetricsModule);
|
|
3879
|
+
// src/metrics/middleware.ts
|
|
3880
|
+
function createHttpMetricsMiddleware(collector) {
|
|
3881
|
+
return async (context2, next) => {
|
|
3882
|
+
const startTime = Date.now();
|
|
3883
|
+
const response = await next();
|
|
3884
|
+
const duration = Date.now() - startTime;
|
|
3885
|
+
const durationSeconds = duration / 1000;
|
|
3886
|
+
const method = context2.method;
|
|
3887
|
+
const path = context2.path;
|
|
3888
|
+
const statusCode = response.status;
|
|
3889
|
+
collector.incrementCounter("http_requests_total", {
|
|
3890
|
+
method,
|
|
3891
|
+
path,
|
|
3892
|
+
status: statusCode.toString()
|
|
3893
|
+
});
|
|
3894
|
+
collector.observeHistogram("http_request_duration_seconds", {
|
|
3895
|
+
method,
|
|
3896
|
+
path,
|
|
3897
|
+
status: statusCode.toString()
|
|
3898
|
+
}, durationSeconds);
|
|
3899
|
+
collector.observeHistogram("http_request_duration_seconds_summary", {
|
|
3900
|
+
method,
|
|
3901
|
+
path
|
|
3902
|
+
}, durationSeconds);
|
|
3903
|
+
return response;
|
|
3904
|
+
};
|
|
3905
|
+
}
|
|
3341
3906
|
// src/testing/harness.ts
|
|
3342
3907
|
import { performance as performance2 } from "perf_hooks";
|
|
3343
3908
|
|
|
@@ -3401,11 +3966,15 @@ class StressTester {
|
|
|
3401
3966
|
export {
|
|
3402
3967
|
requiresAuth,
|
|
3403
3968
|
getAuthMetadata,
|
|
3969
|
+
createUserKeyGenerator,
|
|
3970
|
+
createTokenKeyGenerator,
|
|
3404
3971
|
createSwaggerUIMiddleware,
|
|
3405
3972
|
createStaticFileMiddleware,
|
|
3406
3973
|
createSecurityFilter,
|
|
3407
3974
|
createRequestLoggingMiddleware,
|
|
3975
|
+
createRateLimitMiddleware,
|
|
3408
3976
|
createLoggerMiddleware,
|
|
3977
|
+
createHttpMetricsMiddleware,
|
|
3409
3978
|
createFileUploadMiddleware,
|
|
3410
3979
|
createErrorHandlingMiddleware,
|
|
3411
3980
|
createCorsMiddleware,
|
|
@@ -3428,7 +3997,9 @@ export {
|
|
|
3428
3997
|
RoleBasedAccessDecisionManager,
|
|
3429
3998
|
ResponseBuilder,
|
|
3430
3999
|
RequestWrapper,
|
|
4000
|
+
RateLimit,
|
|
3431
4001
|
Query,
|
|
4002
|
+
PrometheusFormatter,
|
|
3432
4003
|
PerformanceHarness,
|
|
3433
4004
|
ParamBinder,
|
|
3434
4005
|
Param,
|
|
@@ -3447,6 +4018,10 @@ export {
|
|
|
3447
4018
|
Module,
|
|
3448
4019
|
MinLength,
|
|
3449
4020
|
MiddlewarePipeline,
|
|
4021
|
+
MetricsModule,
|
|
4022
|
+
MetricsCollector,
|
|
4023
|
+
METRICS_SERVICE_TOKEN,
|
|
4024
|
+
METRICS_OPTIONS_TOKEN,
|
|
3450
4025
|
LoggerModule,
|
|
3451
4026
|
LoggerExtension,
|
|
3452
4027
|
LogLevel2 as LogLevel,
|