azurajs 3.0.1 → 3.0.2

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.
Files changed (106) hide show
  1. package/dist/config/index.js +128 -6
  2. package/dist/config/index.js.map +1 -1
  3. package/dist/config/index.mjs +130 -1
  4. package/dist/config/index.mjs.map +1 -1
  5. package/dist/core/index.js +1100 -11
  6. package/dist/core/index.js.map +1 -1
  7. package/dist/core/index.mjs +1102 -3
  8. package/dist/core/index.mjs.map +1 -1
  9. package/dist/decorators/index.js +117 -87
  10. package/dist/decorators/index.js.map +1 -1
  11. package/dist/decorators/index.mjs +98 -1
  12. package/dist/decorators/index.mjs.map +1 -1
  13. package/dist/index.js +2592 -236
  14. package/dist/index.js.map +1 -1
  15. package/dist/index.mjs +2537 -9
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/middleware/index.js +16 -7
  18. package/dist/middleware/index.js.map +1 -1
  19. package/dist/middleware/index.mjs +17 -1
  20. package/dist/middleware/index.mjs.map +1 -1
  21. package/dist/plugins/index.js +1056 -73
  22. package/dist/plugins/index.js.map +1 -1
  23. package/dist/plugins/index.mjs +1042 -1
  24. package/dist/plugins/index.mjs.map +1 -1
  25. package/dist/types/index.js +49 -12
  26. package/dist/types/index.js.map +1 -1
  27. package/dist/types/index.mjs +49 -2
  28. package/dist/types/index.mjs.map +1 -1
  29. package/dist/utils/index.js +551 -50
  30. package/dist/utils/index.js.map +1 -1
  31. package/dist/utils/index.mjs +541 -3
  32. package/dist/utils/index.mjs.map +1 -1
  33. package/package.json +35 -17
  34. package/{dist/chunk-DR254CWJ.mjs → src/config/ConfigModule.ts} +169 -132
  35. package/src/config/index.ts +1 -0
  36. package/src/core/index.ts +2 -0
  37. package/src/core/router.ts +284 -0
  38. package/{dist/chunk-EYAHUNC7.mjs → src/core/server.ts} +590 -699
  39. package/src/decorators/Route.ts +110 -0
  40. package/src/decorators/index.ts +23 -0
  41. package/src/index.ts +12 -0
  42. package/src/middleware/LoggingMiddleware.ts +20 -0
  43. package/src/middleware/index.ts +1 -0
  44. package/src/plugins/CORSPlugin.ts +56 -0
  45. package/src/plugins/CircuitBreakerPlugin.ts +84 -0
  46. package/src/plugins/CompressionPlugin.ts +80 -0
  47. package/src/plugins/ETagPlugin.ts +31 -0
  48. package/src/plugins/HealthCheckPlugin.ts +57 -0
  49. package/src/plugins/HelmetPlugin.ts +89 -0
  50. package/src/plugins/JWTPlugin.ts +132 -0
  51. package/src/plugins/MultipartPlugin.ts +168 -0
  52. package/src/plugins/ProxyPlugin.ts +89 -0
  53. package/src/plugins/RateLimitPlugin.ts +96 -0
  54. package/src/plugins/RequestIdPlugin.ts +21 -0
  55. package/src/plugins/SSEPlugin.ts +114 -0
  56. package/src/plugins/SessionPlugin.ts +98 -0
  57. package/src/plugins/StaticPlugin.ts +152 -0
  58. package/src/plugins/TimeoutPlugin.ts +33 -0
  59. package/src/plugins/index.ts +18 -0
  60. package/src/types/common.type.ts +82 -0
  61. package/src/types/config.type.ts +57 -0
  62. package/{dist/chunk-OWUGAI5V.mjs → src/types/http/status.ts} +49 -51
  63. package/src/types/index.ts +55 -0
  64. package/src/types/plugins/plugin.type.ts +170 -0
  65. package/src/types/reflect.d.ts +14 -0
  66. package/src/types/routes.type.ts +70 -0
  67. package/src/utils/HttpError.ts +62 -0
  68. package/src/utils/IpResolver.ts +30 -0
  69. package/src/utils/Logger.ts +144 -0
  70. package/src/utils/Parser.ts +182 -0
  71. package/src/utils/cookies/CookieManager.ts +48 -0
  72. package/src/utils/index.ts +9 -0
  73. package/{dist/chunk-UWIFSGSQ.mjs → src/utils/validators/DTOValidator.ts} +145 -141
  74. package/src/utils/validators/SchemaValidator.ts +45 -0
  75. package/dist/chunk-3UFAWS2V.js +0 -392
  76. package/dist/chunk-3UFAWS2V.js.map +0 -1
  77. package/dist/chunk-4LSFAAZW.js +0 -4
  78. package/dist/chunk-4LSFAAZW.js.map +0 -1
  79. package/dist/chunk-7NSRIVZM.js +0 -54
  80. package/dist/chunk-7NSRIVZM.js.map +0 -1
  81. package/dist/chunk-AOG6NYAM.js +0 -144
  82. package/dist/chunk-AOG6NYAM.js.map +0 -1
  83. package/dist/chunk-DR254CWJ.mjs.map +0 -1
  84. package/dist/chunk-EYAHUNC7.mjs.map +0 -1
  85. package/dist/chunk-HHDQPIJN.mjs +0 -19
  86. package/dist/chunk-HHDQPIJN.mjs.map +0 -1
  87. package/dist/chunk-HHZNAGGI.js +0 -702
  88. package/dist/chunk-HHZNAGGI.js.map +0 -1
  89. package/dist/chunk-KJM5XCAY.js +0 -21
  90. package/dist/chunk-KJM5XCAY.js.map +0 -1
  91. package/dist/chunk-NLSZKAPA.mjs +0 -1044
  92. package/dist/chunk-NLSZKAPA.mjs.map +0 -1
  93. package/dist/chunk-OWUGAI5V.mjs.map +0 -1
  94. package/dist/chunk-POPNQEOK.js +0 -1063
  95. package/dist/chunk-POPNQEOK.js.map +0 -1
  96. package/dist/chunk-QPRW4YU4.js +0 -134
  97. package/dist/chunk-QPRW4YU4.js.map +0 -1
  98. package/dist/chunk-REJDZUZ5.mjs +0 -382
  99. package/dist/chunk-REJDZUZ5.mjs.map +0 -1
  100. package/dist/chunk-TC6N6TJZ.mjs +0 -100
  101. package/dist/chunk-TC6N6TJZ.mjs.map +0 -1
  102. package/dist/chunk-TEUXKMXP.js +0 -122
  103. package/dist/chunk-TEUXKMXP.js.map +0 -1
  104. package/dist/chunk-UWIFSGSQ.mjs.map +0 -1
  105. package/dist/chunk-YPBKY4KY.mjs +0 -3
  106. package/dist/chunk-YPBKY4KY.mjs.map +0 -1
@@ -0,0 +1,110 @@
1
+ import type { HttpMethod, MiddlewareHandler } from "../types/common.type.js";
2
+ import type { ParamMetadata, RouteMeta } from "../types/routes.type.js";
3
+
4
+ const CONTROLLER_META_KEY = "azura:controller";
5
+ const ROUTES_META_KEY = "azura:routes";
6
+ const PARAMS_META_KEY = "azura:params";
7
+ const MIDDLEWARES_META_KEY = "azura:middlewares";
8
+
9
+ function ensureReflect(): void {
10
+ if (typeof Reflect === "undefined" || !Reflect.getMetadata) {
11
+ const store = new Map<string, Map<any, any>>();
12
+ (Reflect as any).defineMetadata = (key: string, value: any, target: any, prop?: string) => {
13
+ const k = prop ? `${key}::${prop}` : key;
14
+ if (!store.has(k)) store.set(k, new Map());
15
+ store.get(k)!.set(target, value);
16
+ };
17
+ (Reflect as any).getMetadata = (key: string, target: any, prop?: string) => {
18
+ const k = prop ? `${key}::${prop}` : key;
19
+ return store.get(k)?.get(target);
20
+ };
21
+ }
22
+ }
23
+
24
+ ensureReflect();
25
+
26
+ export function Controller(prefix: string = ""): ClassDecorator {
27
+ return (target) => {
28
+ Reflect.defineMetadata(CONTROLLER_META_KEY, prefix, target);
29
+ };
30
+ }
31
+
32
+ function createMethodDecorator(method: HttpMethod) {
33
+ return (path: string = ""): MethodDecorator => {
34
+ return (target, propertyKey, _descriptor) => {
35
+ Reflect.defineMetadata(
36
+ ROUTES_META_KEY,
37
+ { method, path, propertyKey: String(propertyKey), middlewares: [], params: [], meta: undefined },
38
+ target,
39
+ String(propertyKey),
40
+ );
41
+ };
42
+ };
43
+ }
44
+
45
+ export const Get = createMethodDecorator("GET");
46
+ export const Post = createMethodDecorator("POST");
47
+ export const Put = createMethodDecorator("PUT");
48
+ export const Delete = createMethodDecorator("DELETE");
49
+ export const Patch = createMethodDecorator("PATCH");
50
+ export const Head = createMethodDecorator("HEAD");
51
+ export const Options = createMethodDecorator("OPTIONS");
52
+
53
+ function createParamDecorator(type: ParamMetadata["type"]) {
54
+ return (name?: string): ParameterDecorator => {
55
+ return (target, propertyKey, parameterIndex) => {
56
+ const key = String(propertyKey);
57
+ const existing: ParamMetadata[] = Reflect.getMetadata(PARAMS_META_KEY, target, key) ?? [];
58
+ existing.push({ index: parameterIndex, type, name });
59
+ Reflect.defineMetadata(PARAMS_META_KEY, existing, target, key);
60
+ };
61
+ };
62
+ }
63
+
64
+ export const Body = createParamDecorator("body");
65
+ export const Query = createParamDecorator("query");
66
+ export const Param = createParamDecorator("param");
67
+ export const Headers = createParamDecorator("header");
68
+ export const Cookies = createParamDecorator("cookie");
69
+ export const Req = createParamDecorator("req");
70
+ export const Res = createParamDecorator("res");
71
+ export const NextFunc = createParamDecorator("next");
72
+ export const Ip = createParamDecorator("ip");
73
+ export const Session = createParamDecorator("session");
74
+
75
+ export function UseMiddleware(...middlewares: MiddlewareHandler[]): MethodDecorator & ClassDecorator {
76
+ return (target: any, propertyKey?: string | symbol, _descriptor?: PropertyDescriptor) => {
77
+ if (propertyKey) {
78
+ const existing: MiddlewareHandler[] =
79
+ Reflect.getMetadata(MIDDLEWARES_META_KEY, target, String(propertyKey)) ?? [];
80
+ Reflect.defineMetadata(MIDDLEWARES_META_KEY, [...existing, ...middlewares], target, String(propertyKey));
81
+ } else {
82
+ const existing: MiddlewareHandler[] =
83
+ Reflect.getMetadata(MIDDLEWARES_META_KEY, target) ?? [];
84
+ Reflect.defineMetadata(MIDDLEWARES_META_KEY, [...existing, ...middlewares], target);
85
+ }
86
+ };
87
+ }
88
+
89
+ export function applyDecorators(...decorators: (MethodDecorator | ClassDecorator)[]): MethodDecorator & ClassDecorator {
90
+ return (target: any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor) => {
91
+ for (const decorator of decorators) {
92
+ if (propertyKey !== undefined) {
93
+ (decorator as MethodDecorator)(target, propertyKey, descriptor!);
94
+ } else {
95
+ (decorator as ClassDecorator)(target);
96
+ }
97
+ }
98
+ };
99
+ }
100
+
101
+ export function Meta(key: string, value: any): MethodDecorator {
102
+ return (target, propertyKey, _descriptor) => {
103
+ const routeMeta = Reflect.getMetadata(ROUTES_META_KEY, target, String(propertyKey));
104
+ if (routeMeta) {
105
+ if (!routeMeta.meta) routeMeta.meta = {};
106
+ routeMeta.meta[key] = value;
107
+ Reflect.defineMetadata(ROUTES_META_KEY, routeMeta, target, String(propertyKey));
108
+ }
109
+ };
110
+ }
@@ -0,0 +1,23 @@
1
+ export {
2
+ Controller,
3
+ Get,
4
+ Post,
5
+ Put,
6
+ Delete,
7
+ Patch,
8
+ Head,
9
+ Options,
10
+ Body,
11
+ Query,
12
+ Param,
13
+ Headers,
14
+ Cookies,
15
+ Req,
16
+ Res,
17
+ NextFunc,
18
+ Ip,
19
+ Session,
20
+ UseMiddleware,
21
+ applyDecorators,
22
+ Meta,
23
+ } from "./Route.js";
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ // Core — always available from root
2
+ export { AzuraServer } from "./core/server.js";
3
+ export { Router } from "./core/router.js";
4
+
5
+ // Sub-module re-exports for convenience
6
+ // Users can also import directly: "azurats/plugins", "azurats/decorators", etc.
7
+ export * from "./decorators/index.js";
8
+ export * from "./plugins/index.js";
9
+ export * from "./utils/index.js";
10
+ export * from "./config/index.js";
11
+ export * from "./middleware/index.js";
12
+ export * from "./types/index.js";
@@ -0,0 +1,20 @@
1
+ import type { PluginHandler } from "../types/plugins/plugin.type.js";
2
+
3
+ export function LoggingMiddleware(): PluginHandler {
4
+ return (ctx, next) => {
5
+ const { req, res } = ctx;
6
+ const start = process.hrtime.bigint();
7
+
8
+ const originalEnd = res.end;
9
+ (res as any).end = function (...args: any[]) {
10
+ const duration = process.hrtime.bigint() - start;
11
+ const ms = Number(duration) / 1_000_000;
12
+
13
+ res.setHeader("X-Response-Time", `${ms.toFixed(2)}ms`);
14
+
15
+ return (originalEnd as any).apply(this, args);
16
+ };
17
+
18
+ next();
19
+ };
20
+ }
@@ -0,0 +1 @@
1
+ export { LoggingMiddleware } from "./LoggingMiddleware.js";
@@ -0,0 +1,56 @@
1
+ import type { CORSOptions, PluginHandler } from "../types/plugins/plugin.type.js";
2
+
3
+ const DEFAULT_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
4
+ const DEFAULT_HEADERS = ["Content-Type", "Authorization", "Accept", "X-Requested-With"];
5
+
6
+ export function CORSPlugin(options: CORSOptions = {}): PluginHandler {
7
+ const {
8
+ origins = "*",
9
+ methods = DEFAULT_METHODS,
10
+ allowedHeaders = DEFAULT_HEADERS,
11
+ exposedHeaders = [],
12
+ credentials = false,
13
+ maxAge = 86400,
14
+ preflightContinue = false,
15
+ } = options;
16
+
17
+ const methodsStr = methods.join(", ");
18
+ const headersStr = allowedHeaders.join(", ");
19
+ const exposedStr = exposedHeaders.length > 0 ? exposedHeaders.join(", ") : "";
20
+
21
+ const isAllowedOrigin = (origin: string): string | false => {
22
+ if (origins === "*") return credentials ? origin : "*";
23
+ if (typeof origins === "function") return origins(origin) ? origin : false;
24
+ if (typeof origins === "string") return origin === origins ? origin : false;
25
+ if (Array.isArray(origins)) return origins.includes(origin) ? origin : false;
26
+ return false;
27
+ };
28
+
29
+ return (ctx, next) => {
30
+ const { req, res } = ctx;
31
+ const origin = req.headers.origin ?? "";
32
+
33
+ const allowedOrigin = isAllowedOrigin(origin);
34
+
35
+ if (allowedOrigin) {
36
+ res.setHeader("Access-Control-Allow-Origin", allowedOrigin);
37
+ if (credentials) res.setHeader("Access-Control-Allow-Credentials", "true");
38
+ if (exposedStr) res.setHeader("Access-Control-Expose-Headers", exposedStr);
39
+ if (allowedOrigin !== "*") res.setHeader("Vary", "Origin");
40
+ }
41
+
42
+ if (req.method === "OPTIONS") {
43
+ res.setHeader("Access-Control-Allow-Methods", methodsStr);
44
+ res.setHeader("Access-Control-Allow-Headers", headersStr);
45
+ res.setHeader("Access-Control-Max-Age", String(maxAge));
46
+
47
+ if (!preflightContinue) {
48
+ res.statusCode = 204;
49
+ res.end();
50
+ return;
51
+ }
52
+ }
53
+
54
+ next();
55
+ };
56
+ }
@@ -0,0 +1,84 @@
1
+ import type { CircuitBreakerOptions, CircuitBreakerState, PluginHandler } from "../types/plugins/plugin.type.js";
2
+
3
+ export function CircuitBreakerPlugin(options: CircuitBreakerOptions = {}): PluginHandler {
4
+ const {
5
+ threshold = 5,
6
+ timeout: breakerTimeout = 30_000,
7
+ resetTimeout = 60_000,
8
+ halfOpenRequests = 1,
9
+ monitor,
10
+ } = options;
11
+
12
+ let state: CircuitBreakerState = "CLOSED";
13
+ let failures = 0;
14
+ let successes = 0;
15
+ let lastFailureTime = 0;
16
+ let halfOpenCount = 0;
17
+
18
+ const transition = (newState: CircuitBreakerState) => {
19
+ if (state !== newState) {
20
+ state = newState;
21
+ monitor?.(state);
22
+ }
23
+ };
24
+
25
+ return (ctx, next) => {
26
+ const { res } = ctx;
27
+
28
+ if (state === "OPEN") {
29
+ if (Date.now() - lastFailureTime >= resetTimeout) {
30
+ transition("HALF_OPEN");
31
+ halfOpenCount = 0;
32
+ } else {
33
+ res.statusCode = 503;
34
+ res.setHeader("Content-Type", "application/json");
35
+ res.setHeader("Retry-After", String(Math.ceil(resetTimeout / 1000)));
36
+ res.end(
37
+ JSON.stringify({
38
+ error: { statusCode: 503, message: "Service temporarily unavailable (circuit open)" },
39
+ }),
40
+ );
41
+ return;
42
+ }
43
+ }
44
+
45
+ if (state === "HALF_OPEN" && halfOpenCount >= halfOpenRequests) {
46
+ res.statusCode = 503;
47
+ res.setHeader("Content-Type", "application/json");
48
+ res.end(
49
+ JSON.stringify({
50
+ error: { statusCode: 503, message: "Service temporarily unavailable (circuit half-open)" },
51
+ }),
52
+ );
53
+ return;
54
+ }
55
+
56
+ if (state === "HALF_OPEN") halfOpenCount++;
57
+
58
+ const originalEnd = res.end;
59
+ (res as any).end = function (...args: any[]) {
60
+ if (res.statusCode >= 500) {
61
+ failures++;
62
+ lastFailureTime = Date.now();
63
+ if (state === "HALF_OPEN" || failures >= threshold) {
64
+ transition("OPEN");
65
+ }
66
+ } else {
67
+ if (state === "HALF_OPEN") {
68
+ successes++;
69
+ if (successes >= halfOpenRequests) {
70
+ failures = 0;
71
+ successes = 0;
72
+ transition("CLOSED");
73
+ }
74
+ } else {
75
+ failures = Math.max(0, failures - 1);
76
+ }
77
+ }
78
+
79
+ return (originalEnd as any).apply(this, args);
80
+ };
81
+
82
+ next();
83
+ };
84
+ }
@@ -0,0 +1,80 @@
1
+ import { createGzip, createDeflate, type Gzip, type Deflate } from "node:zlib";
2
+ import type { CompressionOptions, PluginHandler } from "../types/plugins/plugin.type.js";
3
+
4
+ const COMPRESSIBLE_TYPES = /^text\/|application\/json|application\/javascript|application\/xml|image\/svg\+xml/;
5
+
6
+ export function CompressionPlugin(options: CompressionOptions = {}): PluginHandler {
7
+ const {
8
+ threshold = 1024,
9
+ level = 6,
10
+ algorithms = ["gzip", "deflate"],
11
+ filter,
12
+ } = options;
13
+
14
+ return (ctx, next) => {
15
+ const { req, res } = ctx;
16
+
17
+ if (filter && !filter(req as any, res as any)) {
18
+ next();
19
+ return;
20
+ }
21
+
22
+ const acceptEncoding = req.headers["accept-encoding"] ?? "";
23
+ let encoding: string | null = null;
24
+
25
+ for (const algo of algorithms) {
26
+ if (typeof acceptEncoding === "string" && acceptEncoding.includes(algo)) {
27
+ encoding = algo;
28
+ break;
29
+ }
30
+ }
31
+
32
+ if (!encoding) {
33
+ next();
34
+ return;
35
+ }
36
+
37
+ const originalEnd = res.end;
38
+ const originalWrite = res.write;
39
+
40
+ (res as any).end = function (chunk?: any, encodingArg?: any, callback?: any) {
41
+ if (!chunk || (typeof chunk !== "string" && !Buffer.isBuffer(chunk))) {
42
+ return (originalEnd as any).call(this, chunk, encodingArg, callback);
43
+ }
44
+
45
+ const buf = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
46
+
47
+ if (buf.length < threshold) {
48
+ return (originalEnd as any).call(this, chunk, encodingArg, callback);
49
+ }
50
+
51
+ const contentType = String(res.getHeader("Content-Type") ?? "");
52
+ if (!COMPRESSIBLE_TYPES.test(contentType)) {
53
+ return (originalEnd as any).call(this, chunk, encodingArg, callback);
54
+ }
55
+
56
+ res.removeHeader("Content-Length");
57
+ res.setHeader("Content-Encoding", encoding!);
58
+ res.setHeader("Vary", "Accept-Encoding");
59
+
60
+ let stream: Gzip | Deflate;
61
+ if (encoding === "gzip") {
62
+ stream = createGzip({ level });
63
+ } else {
64
+ stream = createDeflate({ level });
65
+ }
66
+
67
+ const chunks: Buffer[] = [];
68
+ stream.on("data", (c: Buffer) => chunks.push(c));
69
+ stream.on("end", () => {
70
+ const compressed = Buffer.concat(chunks);
71
+ res.setHeader("Content-Length", compressed.length);
72
+ (originalEnd as any).call(res, compressed);
73
+ });
74
+
75
+ stream.end(buf);
76
+ };
77
+
78
+ next();
79
+ };
80
+ }
@@ -0,0 +1,31 @@
1
+ import { createHash } from "node:crypto";
2
+ import type { ETagOptions, PluginHandler } from "../types/plugins/plugin.type.js";
3
+
4
+ export function ETagPlugin(options: ETagOptions = {}): PluginHandler {
5
+ const { weak = true } = options;
6
+
7
+ return (ctx, next) => {
8
+ const { req, res } = ctx;
9
+
10
+ const originalEnd = res.end;
11
+ (res as any).end = function (chunk?: any, encoding?: any, callback?: any) {
12
+ if (chunk && (typeof chunk === "string" || Buffer.isBuffer(chunk)) && res.statusCode === 200) {
13
+ const buf = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
14
+ const hash = createHash("md5").update(buf).digest("hex").slice(0, 16);
15
+ const etag = weak ? `W/"${hash}"` : `"${hash}"`;
16
+
17
+ res.setHeader("ETag", etag);
18
+
19
+ const ifNoneMatch = req.headers["if-none-match"];
20
+ if (ifNoneMatch === etag) {
21
+ res.statusCode = 304;
22
+ return (originalEnd as any).call(this);
23
+ }
24
+ }
25
+
26
+ return (originalEnd as any).call(this, chunk, encoding, callback);
27
+ };
28
+
29
+ next();
30
+ };
31
+ }
@@ -0,0 +1,57 @@
1
+ import type { HealthCheckOptions, PluginHandler } from "../types/plugins/plugin.type.js";
2
+
3
+ export function HealthCheckPlugin(options: HealthCheckOptions = {}): PluginHandler {
4
+ const {
5
+ path = "/health",
6
+ checks = {},
7
+ timeout: checkTimeout = 5000,
8
+ } = options;
9
+
10
+ return async (ctx, next) => {
11
+ const { req, res } = ctx;
12
+
13
+ if (req.pathname !== path) {
14
+ next();
15
+ return;
16
+ }
17
+
18
+ const results: Record<string, { status: string; duration: number }> = {};
19
+ let allHealthy = true;
20
+
21
+ for (const [name, check] of Object.entries(checks)) {
22
+ const start = Date.now();
23
+ try {
24
+ const result = await Promise.race([
25
+ Promise.resolve(check()),
26
+ new Promise<boolean>((_, reject) =>
27
+ setTimeout(() => reject(new Error("timeout")), checkTimeout),
28
+ ),
29
+ ]);
30
+ results[name] = {
31
+ status: result ? "healthy" : "unhealthy",
32
+ duration: Date.now() - start,
33
+ };
34
+ if (!result) allHealthy = false;
35
+ } catch {
36
+ results[name] = {
37
+ status: "unhealthy",
38
+ duration: Date.now() - start,
39
+ };
40
+ allHealthy = false;
41
+ }
42
+ }
43
+
44
+ const body = JSON.stringify({
45
+ status: allHealthy ? "healthy" : "unhealthy",
46
+ timestamp: new Date().toISOString(),
47
+ uptime: process.uptime(),
48
+ memory: process.memoryUsage(),
49
+ checks: results,
50
+ });
51
+
52
+ res.statusCode = allHealthy ? 200 : 503;
53
+ res.setHeader("Content-Type", "application/json");
54
+ res.setHeader("Cache-Control", "no-cache, no-store");
55
+ res.end(body);
56
+ };
57
+ }
@@ -0,0 +1,89 @@
1
+ import type { HelmetOptions, PluginHandler } from "../types/plugins/plugin.type.js";
2
+
3
+ export function HelmetPlugin(options: HelmetOptions = {}): PluginHandler {
4
+ const headers: [string, string][] = [];
5
+
6
+ if (options.noSniff !== false) {
7
+ headers.push(["X-Content-Type-Options", "nosniff"]);
8
+ }
9
+
10
+ if (options.xssFilter !== false) {
11
+ headers.push(["X-XSS-Protection", "0"]);
12
+ }
13
+
14
+ if (options.ieNoOpen !== false) {
15
+ headers.push(["X-Download-Options", "noopen"]);
16
+ }
17
+
18
+ const frameguard = options.frameguard ?? true;
19
+ if (frameguard !== false) {
20
+ const action = typeof frameguard === "object" ? frameguard.action : "sameorigin";
21
+ headers.push(["X-Frame-Options", action.toUpperCase()]);
22
+ }
23
+
24
+ if (options.dnsPrefetchControl !== false) {
25
+ const allow = typeof options.dnsPrefetchControl === "object"
26
+ ? options.dnsPrefetchControl.allow
27
+ : false;
28
+ headers.push(["X-DNS-Prefetch-Control", allow ? "on" : "off"]);
29
+ }
30
+
31
+ const hsts = options.hsts ?? true;
32
+ if (hsts !== false) {
33
+ const maxAge = typeof hsts === "object" ? hsts.maxAge : 15552000;
34
+ const includeSubDomains = typeof hsts === "object" ? hsts.includeSubDomains !== false : true;
35
+ const preload = typeof hsts === "object" ? hsts.preload : false;
36
+ let value = `max-age=${maxAge}`;
37
+ if (includeSubDomains) value += "; includeSubDomains";
38
+ if (preload) value += "; preload";
39
+ headers.push(["Strict-Transport-Security", value]);
40
+ }
41
+
42
+ if (options.crossOriginEmbedderPolicy !== false) {
43
+ headers.push(["Cross-Origin-Embedder-Policy", "require-corp"]);
44
+ }
45
+
46
+ if (options.crossOriginOpenerPolicy !== false) {
47
+ const policy = typeof options.crossOriginOpenerPolicy === "object"
48
+ ? options.crossOriginOpenerPolicy.policy
49
+ : "same-origin";
50
+ headers.push(["Cross-Origin-Opener-Policy", policy]);
51
+ }
52
+
53
+ if (options.crossOriginResourcePolicy !== false) {
54
+ const policy = typeof options.crossOriginResourcePolicy === "object"
55
+ ? options.crossOriginResourcePolicy.policy
56
+ : "same-origin";
57
+ headers.push(["Cross-Origin-Resource-Policy", policy]);
58
+ }
59
+
60
+ const referrer = options.referrerPolicy ?? true;
61
+ if (referrer !== false) {
62
+ const policy = typeof referrer === "object"
63
+ ? (Array.isArray(referrer.policy) ? referrer.policy.join(", ") : referrer.policy)
64
+ : "no-referrer";
65
+ headers.push(["Referrer-Policy", policy]);
66
+ }
67
+
68
+ if (options.contentSecurityPolicy !== false && options.contentSecurityPolicy) {
69
+ const csp = options.contentSecurityPolicy;
70
+ const directives = Object.entries(csp)
71
+ .map(([key, values]) => `${key} ${values.join(" ")}`)
72
+ .join("; ");
73
+ headers.push(["Content-Security-Policy", directives]);
74
+ }
75
+
76
+ if (options.permissionsPolicy) {
77
+ const pp = Object.entries(options.permissionsPolicy)
78
+ .map(([key, values]) => `${key}=(${values.join(" ")})`)
79
+ .join(", ");
80
+ headers.push(["Permissions-Policy", pp]);
81
+ }
82
+
83
+ return (ctx, next) => {
84
+ for (const [name, value] of headers) {
85
+ ctx.res.setHeader(name, value);
86
+ }
87
+ next();
88
+ };
89
+ }