@forinda/kickjs-http 0.3.2 → 0.4.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/application.js +2 -2
- package/dist/bootstrap.js +3 -3
- package/dist/{chunk-AK5TGZRS.js → chunk-4G2S7T4R.js} +4 -4
- package/dist/chunk-DUQ7SN7N.js +208 -0
- package/dist/chunk-DUQ7SN7N.js.map +1 -0
- package/dist/chunk-H4S527PH.js +97 -0
- package/dist/chunk-H4S527PH.js.map +1 -0
- package/dist/chunk-LEILPDMW.js +183 -0
- package/dist/chunk-LEILPDMW.js.map +1 -0
- package/dist/{chunk-DU3FQYET.js → chunk-LQ6RSWMX.js} +5 -1
- package/dist/chunk-LQ6RSWMX.js.map +1 -0
- package/dist/chunk-NQJNMKW5.js +158 -0
- package/dist/chunk-NQJNMKW5.js.map +1 -0
- package/dist/{chunk-7I33DN2G.js → chunk-OWLI3SBW.js} +2 -2
- package/dist/{chunk-BPXWHHCY.js → chunk-VFVMIFNZ.js} +9 -2
- package/dist/chunk-VFVMIFNZ.js.map +1 -0
- package/dist/context.d.ts +2 -0
- package/dist/context.js +1 -1
- package/dist/devtools.d.ts +85 -0
- package/dist/devtools.js +8 -0
- package/dist/devtools.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +27 -11
- package/dist/middleware/rate-limit.d.ts +53 -0
- package/dist/middleware/rate-limit.js +8 -0
- package/dist/middleware/rate-limit.js.map +1 -0
- package/dist/middleware/session.d.ts +64 -0
- package/dist/middleware/session.js +8 -0
- package/dist/middleware/session.js.map +1 -0
- package/dist/middleware/upload.d.ts +35 -16
- package/dist/middleware/upload.js +5 -1
- package/dist/router-builder.js +3 -2
- package/package.json +15 -2
- package/dist/chunk-BPXWHHCY.js.map +0 -1
- package/dist/chunk-DU3FQYET.js.map +0 -1
- package/dist/chunk-RFOXGJ3G.js +0 -88
- package/dist/chunk-RFOXGJ3G.js.map +0 -1
- /package/dist/{chunk-AK5TGZRS.js.map → chunk-4G2S7T4R.js.map} +0 -0
- /package/dist/{chunk-7I33DN2G.js.map → chunk-OWLI3SBW.js.map} +0 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__name
|
|
3
|
+
} from "./chunk-WCQVDF3K.js";
|
|
4
|
+
|
|
5
|
+
// src/middleware/session.ts
|
|
6
|
+
import { randomUUID, createHmac, timingSafeEqual } from "crypto";
|
|
7
|
+
var MemoryStore = class MemoryStore2 {
|
|
8
|
+
static {
|
|
9
|
+
__name(this, "MemoryStore");
|
|
10
|
+
}
|
|
11
|
+
sessions = /* @__PURE__ */ new Map();
|
|
12
|
+
cleanupInterval;
|
|
13
|
+
constructor() {
|
|
14
|
+
this.cleanupInterval = setInterval(() => this.purge(), 6e4);
|
|
15
|
+
this.cleanupInterval.unref();
|
|
16
|
+
}
|
|
17
|
+
async get(sid) {
|
|
18
|
+
const entry = this.sessions.get(sid);
|
|
19
|
+
if (!entry) return null;
|
|
20
|
+
if (Date.now() > entry.expires) {
|
|
21
|
+
this.sessions.delete(sid);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return entry.data;
|
|
25
|
+
}
|
|
26
|
+
async set(sid, data, maxAge) {
|
|
27
|
+
this.sessions.set(sid, {
|
|
28
|
+
data,
|
|
29
|
+
expires: Date.now() + maxAge
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async destroy(sid) {
|
|
33
|
+
this.sessions.delete(sid);
|
|
34
|
+
}
|
|
35
|
+
async touch(sid, maxAge) {
|
|
36
|
+
const entry = this.sessions.get(sid);
|
|
37
|
+
if (entry) {
|
|
38
|
+
entry.expires = Date.now() + maxAge;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
purge() {
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
for (const [sid, entry] of this.sessions) {
|
|
44
|
+
if (now > entry.expires) {
|
|
45
|
+
this.sessions.delete(sid);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
function sign(value, secret) {
|
|
51
|
+
const signature = createHmac("sha256", secret).update(value).digest("base64url");
|
|
52
|
+
return `s:${value}.${signature}`;
|
|
53
|
+
}
|
|
54
|
+
__name(sign, "sign");
|
|
55
|
+
function unsign(signed, secret) {
|
|
56
|
+
if (!signed.startsWith("s:")) return false;
|
|
57
|
+
const raw = signed.slice(2);
|
|
58
|
+
const dotIndex = raw.lastIndexOf(".");
|
|
59
|
+
if (dotIndex === -1) return false;
|
|
60
|
+
const value = raw.slice(0, dotIndex);
|
|
61
|
+
const providedSig = raw.slice(dotIndex + 1);
|
|
62
|
+
const expectedSig = createHmac("sha256", secret).update(value).digest("base64url");
|
|
63
|
+
const a = Buffer.from(providedSig);
|
|
64
|
+
const b = Buffer.from(expectedSig);
|
|
65
|
+
if (a.length !== b.length || !timingSafeEqual(a, b)) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
__name(unsign, "unsign");
|
|
71
|
+
function session(options) {
|
|
72
|
+
const { secret, cookieName = "kick.sid", maxAge = 864e5, rolling = false, saveUninitialized = true, store = new MemoryStore(), cookie: cookieOpts = {} } = options;
|
|
73
|
+
const cookieDefaults = {
|
|
74
|
+
httpOnly: cookieOpts.httpOnly ?? true,
|
|
75
|
+
secure: cookieOpts.secure ?? process.env.NODE_ENV === "production",
|
|
76
|
+
sameSite: cookieOpts.sameSite ?? "lax",
|
|
77
|
+
path: cookieOpts.path ?? "/",
|
|
78
|
+
...cookieOpts.domain ? {
|
|
79
|
+
domain: cookieOpts.domain
|
|
80
|
+
} : {},
|
|
81
|
+
maxAge
|
|
82
|
+
};
|
|
83
|
+
return async (req, res, next) => {
|
|
84
|
+
const cookies = req.cookies || {};
|
|
85
|
+
const signedCookie = cookies[cookieName];
|
|
86
|
+
let sid = false;
|
|
87
|
+
let sessionData = null;
|
|
88
|
+
let isNew = false;
|
|
89
|
+
if (signedCookie) {
|
|
90
|
+
sid = unsign(signedCookie, secret);
|
|
91
|
+
if (sid) {
|
|
92
|
+
sessionData = await store.get(sid);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (!sid || !sessionData) {
|
|
96
|
+
sid = randomUUID();
|
|
97
|
+
sessionData = {};
|
|
98
|
+
isNew = true;
|
|
99
|
+
}
|
|
100
|
+
let currentSid = sid;
|
|
101
|
+
let currentData = {
|
|
102
|
+
...sessionData
|
|
103
|
+
};
|
|
104
|
+
let destroyed = false;
|
|
105
|
+
const sessionObj = {
|
|
106
|
+
get id() {
|
|
107
|
+
return currentSid;
|
|
108
|
+
},
|
|
109
|
+
data: currentData,
|
|
110
|
+
async regenerate() {
|
|
111
|
+
await store.destroy(currentSid);
|
|
112
|
+
currentSid = randomUUID();
|
|
113
|
+
currentData = {};
|
|
114
|
+
sessionObj.data = currentData;
|
|
115
|
+
await store.set(currentSid, currentData, maxAge);
|
|
116
|
+
res.cookie(cookieName, sign(currentSid, secret), cookieDefaults);
|
|
117
|
+
},
|
|
118
|
+
async destroy() {
|
|
119
|
+
await store.destroy(currentSid);
|
|
120
|
+
destroyed = true;
|
|
121
|
+
currentData = {};
|
|
122
|
+
sessionObj.data = currentData;
|
|
123
|
+
res.clearCookie(cookieName, {
|
|
124
|
+
path: cookieDefaults.path
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
async save() {
|
|
128
|
+
if (!destroyed) {
|
|
129
|
+
await store.set(currentSid, currentData, maxAge);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
req.session = sessionObj;
|
|
134
|
+
if (isNew) {
|
|
135
|
+
res.cookie(cookieName, sign(currentSid, secret), cookieDefaults);
|
|
136
|
+
}
|
|
137
|
+
res.on("finish", async () => {
|
|
138
|
+
if (destroyed) return;
|
|
139
|
+
if (isNew && !saveUninitialized && Object.keys(currentData).length === 0) return;
|
|
140
|
+
await store.set(currentSid, currentData, maxAge);
|
|
141
|
+
if (rolling && !isNew) {
|
|
142
|
+
if (store.touch) {
|
|
143
|
+
await store.touch(currentSid, maxAge);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
if (rolling && !isNew) {
|
|
148
|
+
res.cookie(cookieName, sign(currentSid, secret), cookieDefaults);
|
|
149
|
+
}
|
|
150
|
+
next();
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
__name(session, "session");
|
|
154
|
+
|
|
155
|
+
export {
|
|
156
|
+
session
|
|
157
|
+
};
|
|
158
|
+
//# sourceMappingURL=chunk-NQJNMKW5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/middleware/session.ts"],"sourcesContent":["import { randomUUID, createHmac, timingSafeEqual } from 'node:crypto'\nimport type { Request, Response, NextFunction } from 'express'\n\nexport interface SessionData {\n [key: string]: unknown\n}\n\nexport interface SessionStore {\n get(sid: string): Promise<SessionData | null>\n set(sid: string, data: SessionData, maxAge: number): Promise<void>\n destroy(sid: string): Promise<void>\n touch?(sid: string, maxAge: number): Promise<void>\n}\n\nexport interface Session {\n id: string\n data: SessionData\n regenerate(): Promise<void>\n destroy(): Promise<void>\n save(): Promise<void>\n}\n\nexport interface SessionOptions {\n /** Secret used to sign the session cookie (required) */\n secret: string\n /** Cookie name (default: 'kick.sid') */\n cookieName?: string\n /** Session max age in milliseconds (default: 86400000 = 24h) */\n maxAge?: number\n /** Reset maxAge on every response (default: false) */\n rolling?: boolean\n /** Save new sessions that have not been modified (default: true) */\n saveUninitialized?: boolean\n /** Cookie options */\n cookie?: {\n httpOnly?: boolean\n secure?: boolean\n sameSite?: 'strict' | 'lax' | 'none'\n path?: string\n domain?: string\n }\n /** Custom session store (default: in-memory store with TTL cleanup) */\n store?: SessionStore\n}\n\n// ── In-Memory Store ───────────────────────────────────────────────────\n\nclass MemoryStore implements SessionStore {\n private sessions = new Map<string, { data: SessionData; expires: number }>()\n private cleanupInterval: ReturnType<typeof setInterval>\n\n constructor() {\n // Purge expired sessions every 60 seconds\n this.cleanupInterval = setInterval(() => this.purge(), 60_000)\n this.cleanupInterval.unref()\n }\n\n async get(sid: string): Promise<SessionData | null> {\n const entry = this.sessions.get(sid)\n if (!entry) return null\n if (Date.now() > entry.expires) {\n this.sessions.delete(sid)\n return null\n }\n return entry.data\n }\n\n async set(sid: string, data: SessionData, maxAge: number): Promise<void> {\n this.sessions.set(sid, { data, expires: Date.now() + maxAge })\n }\n\n async destroy(sid: string): Promise<void> {\n this.sessions.delete(sid)\n }\n\n async touch(sid: string, maxAge: number): Promise<void> {\n const entry = this.sessions.get(sid)\n if (entry) {\n entry.expires = Date.now() + maxAge\n }\n }\n\n private purge() {\n const now = Date.now()\n for (const [sid, entry] of this.sessions) {\n if (now > entry.expires) {\n this.sessions.delete(sid)\n }\n }\n }\n}\n\n// ── Cookie Signing ────────────────────────────────────────────────────\n\nfunction sign(value: string, secret: string): string {\n const signature = createHmac('sha256', secret).update(value).digest('base64url')\n return `s:${value}.${signature}`\n}\n\nfunction unsign(signed: string, secret: string): string | false {\n if (!signed.startsWith('s:')) return false\n const raw = signed.slice(2)\n const dotIndex = raw.lastIndexOf('.')\n if (dotIndex === -1) return false\n\n const value = raw.slice(0, dotIndex)\n const providedSig = raw.slice(dotIndex + 1)\n const expectedSig = createHmac('sha256', secret).update(value).digest('base64url')\n\n const a = Buffer.from(providedSig)\n const b = Buffer.from(expectedSig)\n\n if (a.length !== b.length || !timingSafeEqual(a, b)) {\n return false\n }\n\n return value\n}\n\n// ── Middleware Factory ────────────────────────────────────────────────\n\n/**\n * Session management middleware.\n *\n * Attaches a `req.session` object with `id`, `data`, `regenerate()`,\n * `destroy()`, and `save()` methods. Session IDs are signed with\n * HMAC-SHA256 to prevent cookie tampering.\n *\n * @example\n * ```ts\n * import { session } from '@forinda/kickjs-http'\n *\n * bootstrap({\n * modules,\n * middleware: [\n * cookieParser(),\n * session({ secret: process.env.SESSION_SECRET! }),\n * // ... other middleware\n * ],\n * })\n * ```\n */\nexport function session(options: SessionOptions) {\n const {\n secret,\n cookieName = 'kick.sid',\n maxAge = 86_400_000,\n rolling = false,\n saveUninitialized = true,\n store = new MemoryStore(),\n cookie: cookieOpts = {},\n } = options\n\n const cookieDefaults = {\n httpOnly: cookieOpts.httpOnly ?? true,\n secure: cookieOpts.secure ?? process.env.NODE_ENV === 'production',\n sameSite: cookieOpts.sameSite ?? ('lax' as const),\n path: cookieOpts.path ?? '/',\n ...(cookieOpts.domain ? { domain: cookieOpts.domain } : {}),\n maxAge,\n }\n\n return async (req: Request, res: Response, next: NextFunction) => {\n const cookies = (req as any).cookies || {}\n const signedCookie = cookies[cookieName]\n let sid: string | false = false\n let sessionData: SessionData | null = null\n let isNew = false\n\n // Attempt to recover existing session\n if (signedCookie) {\n sid = unsign(signedCookie, secret)\n if (sid) {\n sessionData = await store.get(sid)\n }\n }\n\n // Create new session if none found\n if (!sid || !sessionData) {\n sid = randomUUID()\n sessionData = {}\n isNew = true\n }\n\n let currentSid = sid as string\n let currentData = { ...sessionData }\n let destroyed = false\n\n const sessionObj: Session = {\n get id() {\n return currentSid\n },\n data: currentData,\n\n async regenerate() {\n await store.destroy(currentSid)\n currentSid = randomUUID()\n currentData = {}\n sessionObj.data = currentData\n await store.set(currentSid, currentData, maxAge)\n res.cookie(cookieName, sign(currentSid, secret), cookieDefaults)\n },\n\n async destroy() {\n await store.destroy(currentSid)\n destroyed = true\n currentData = {}\n sessionObj.data = currentData\n res.clearCookie(cookieName, { path: cookieDefaults.path })\n },\n\n async save() {\n if (!destroyed) {\n await store.set(currentSid, currentData, maxAge)\n }\n },\n }\n\n ;(req as any).session = sessionObj\n\n // Set cookie for new sessions\n if (isNew) {\n res.cookie(cookieName, sign(currentSid, secret), cookieDefaults)\n }\n\n // Auto-save on response finish\n res.on('finish', async () => {\n if (destroyed) return\n if (isNew && !saveUninitialized && Object.keys(currentData).length === 0) return\n\n await store.set(currentSid, currentData, maxAge)\n\n if (rolling && !isNew) {\n if (store.touch) {\n await store.touch(currentSid, maxAge)\n }\n }\n })\n\n // Rolling: refresh cookie on every response\n if (rolling && !isNew) {\n res.cookie(cookieName, sign(currentSid, secret), cookieDefaults)\n }\n\n next()\n }\n}\n"],"mappings":"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","names":["randomUUID","createHmac","timingSafeEqual","MemoryStore","sessions","Map","cleanupInterval","setInterval","purge","unref","get","sid","entry","Date","now","expires","delete","data","set","maxAge","destroy","touch","sign","value","secret","signature","createHmac","update","digest","unsign","signed","startsWith","raw","slice","dotIndex","lastIndexOf","providedSig","expectedSig","a","Buffer","from","b","length","timingSafeEqual","session","options","cookieName","rolling","saveUninitialized","store","cookie","cookieOpts","cookieDefaults","httpOnly","secure","process","env","NODE_ENV","sameSite","path","domain","req","res","next","cookies","signedCookie","sessionData","isNew","randomUUID","currentSid","currentData","destroyed","sessionObj","id","regenerate","clearCookie","save","on","Object","keys"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Application
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4G2S7T4R.js";
|
|
4
4
|
import {
|
|
5
5
|
__name,
|
|
6
6
|
__require
|
|
@@ -57,4 +57,4 @@ __name(bootstrap, "bootstrap");
|
|
|
57
57
|
export {
|
|
58
58
|
bootstrap
|
|
59
59
|
};
|
|
60
|
-
//# sourceMappingURL=chunk-
|
|
60
|
+
//# sourceMappingURL=chunk-OWLI3SBW.js.map
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildUploadMiddleware
|
|
3
|
+
} from "./chunk-LEILPDMW.js";
|
|
1
4
|
import {
|
|
2
5
|
validate
|
|
3
6
|
} from "./chunk-RPN7UFUO.js";
|
|
4
7
|
import {
|
|
5
8
|
RequestContext
|
|
6
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-LQ6RSWMX.js";
|
|
7
10
|
import {
|
|
8
11
|
__name
|
|
9
12
|
} from "./chunk-WCQVDF3K.js";
|
|
@@ -31,6 +34,10 @@ function buildRoutes(controllerClass) {
|
|
|
31
34
|
if (route.validation) {
|
|
32
35
|
handlers.push(validate(route.validation));
|
|
33
36
|
}
|
|
37
|
+
const fileUploadConfig = Reflect.getMetadata(METADATA.FILE_UPLOAD, controllerClass, route.handlerName);
|
|
38
|
+
if (fileUploadConfig) {
|
|
39
|
+
handlers.push(buildUploadMiddleware(fileUploadConfig));
|
|
40
|
+
}
|
|
34
41
|
for (const mw of [
|
|
35
42
|
...classMiddlewares,
|
|
36
43
|
...methodMiddlewares
|
|
@@ -59,4 +66,4 @@ export {
|
|
|
59
66
|
getControllerPath,
|
|
60
67
|
buildRoutes
|
|
61
68
|
};
|
|
62
|
-
//# sourceMappingURL=chunk-
|
|
69
|
+
//# sourceMappingURL=chunk-VFVMIFNZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/router-builder.ts"],"sourcesContent":["import 'reflect-metadata'\nimport { Router, type Request, type Response, type NextFunction } from 'express'\nimport {\n Container,\n METADATA,\n type RouteDefinition,\n type MiddlewareHandler,\n type FileUploadConfig,\n} from '@forinda/kickjs-core'\nimport { RequestContext } from './context'\nimport { validate } from './middleware/validate'\nimport { buildUploadMiddleware } from './middleware/upload'\n\n/** Get the controller path set by @Controller('/path') */\nexport function getControllerPath(controllerClass: any): string {\n return Reflect.getMetadata(METADATA.CONTROLLER_PATH, controllerClass) || '/'\n}\n\n/**\n * Build an Express Router from a controller class decorated with @Get, @Post, etc.\n * Resolves the controller from the DI container, wraps handlers in RequestContext,\n * and applies class-level and method-level middleware.\n */\nexport function buildRoutes(controllerClass: any): Router {\n const router = Router()\n const container = Container.getInstance()\n const controllerPath = getControllerPath(controllerClass)\n const routes: RouteDefinition[] = Reflect.getMetadata(METADATA.ROUTES, controllerClass) || []\n\n // Class-level middleware\n const classMiddlewares: MiddlewareHandler[] =\n Reflect.getMetadata(METADATA.CLASS_MIDDLEWARES, controllerClass) || []\n\n for (const route of routes) {\n const method = route.method.toLowerCase() as 'get' | 'post' | 'put' | 'delete' | 'patch'\n let routePath = route.path === '/' ? '' : route.path\n const fullPath = controllerPath === '/' ? routePath || '/' : controllerPath + routePath\n\n // Method-level middleware\n const methodMiddlewares: MiddlewareHandler[] =\n Reflect.getMetadata(METADATA.METHOD_MIDDLEWARES, controllerClass, route.handlerName) || []\n\n // Build handler chain\n const handlers: any[] = []\n\n // Validation middleware (shared with standalone validate() export)\n if (route.validation) {\n handlers.push(validate(route.validation))\n }\n\n // @FileUpload decorator — auto-attach upload middleware from metadata\n const fileUploadConfig: FileUploadConfig | undefined = Reflect.getMetadata(\n METADATA.FILE_UPLOAD,\n controllerClass,\n route.handlerName,\n )\n if (fileUploadConfig) {\n handlers.push(buildUploadMiddleware(fileUploadConfig))\n }\n\n // Class + method middleware (wrapped as Express middleware with error catching)\n for (const mw of [...classMiddlewares, ...methodMiddlewares]) {\n handlers.push((req: Request, res: Response, next: NextFunction) => {\n const ctx = new RequestContext(req, res, next)\n Promise.resolve(mw(ctx, next)).catch(next)\n })\n }\n\n // Main handler — resolve controller per-request to respect DI scoping\n handlers.push(async (req: Request, res: Response, next: NextFunction) => {\n const ctx = new RequestContext(req, res, next)\n try {\n const controller = container.resolve(controllerClass)\n await controller[route.handlerName](ctx)\n } catch (err: any) {\n next(err)\n }\n })\n ;(router as any)[method](fullPath, ...handlers)\n }\n\n return router\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA,OAAO;AACP,SAASA,cAA8D;AACvE,SACEC,WACAC,gBAIK;AAMA,SAASC,kBAAkBC,iBAAoB;AACpD,SAAOC,QAAQC,YAAYC,SAASC,iBAAiBJ,eAAAA,KAAoB;AAC3E;AAFgBD;AAST,SAASM,YAAYL,iBAAoB;AAC9C,QAAMM,SAASC,OAAAA;AACf,QAAMC,YAAYC,UAAUC,YAAW;AACvC,QAAMC,iBAAiBZ,kBAAkBC,eAAAA;AACzC,QAAMY,SAA4BX,QAAQC,YAAYC,SAASU,QAAQb,eAAAA,KAAoB,CAAA;AAG3F,QAAMc,mBACJb,QAAQC,YAAYC,SAASY,mBAAmBf,eAAAA,KAAoB,CAAA;AAEtE,aAAWgB,SAASJ,QAAQ;AAC1B,UAAMK,SAASD,MAAMC,OAAOC,YAAW;AACvC,QAAIC,YAAYH,MAAMI,SAAS,MAAM,KAAKJ,MAAMI;AAChD,UAAMC,WAAWV,mBAAmB,MAAMQ,aAAa,MAAMR,iBAAiBQ;AAG9E,UAAMG,oBACJrB,QAAQC,YAAYC,SAASoB,oBAAoBvB,iBAAiBgB,MAAMQ,WAAW,KAAK,CAAA;AAG1F,UAAMC,WAAkB,CAAA;AAGxB,QAAIT,MAAMU,YAAY;AACpBD,eAASE,KAAKC,SAASZ,MAAMU,UAAU,CAAA;IACzC;AAGA,UAAMG,mBAAiD5B,QAAQC,YAC7DC,SAAS2B,aACT9B,iBACAgB,MAAMQ,WAAW;AAEnB,QAAIK,kBAAkB;AACpBJ,eAASE,KAAKI,sBAAsBF,gBAAAA,CAAAA;IACtC;AAGA,eAAWG,MAAM;SAAIlB;SAAqBQ;OAAoB;AAC5DG,eAASE,KAAK,CAACM,KAAcC,KAAeC,SAAAA;AAC1C,cAAMC,MAAM,IAAIC,eAAeJ,KAAKC,KAAKC,IAAAA;AACzCG,gBAAQC,QAAQP,GAAGI,KAAKD,IAAAA,CAAAA,EAAOK,MAAML,IAAAA;MACvC,CAAA;IACF;AAGAV,aAASE,KAAK,OAAOM,KAAcC,KAAeC,SAAAA;AAChD,YAAMC,MAAM,IAAIC,eAAeJ,KAAKC,KAAKC,IAAAA;AACzC,UAAI;AACF,cAAMM,aAAajC,UAAU+B,QAAQvC,eAAAA;AACrC,cAAMyC,WAAWzB,MAAMQ,WAAW,EAAEY,GAAAA;MACtC,SAASM,KAAU;AACjBP,aAAKO,GAAAA;MACP;IACF,CAAA;AACEpC,WAAeW,MAAAA,EAAQI,UAAAA,GAAaI,QAAAA;EACxC;AAEA,SAAOnB;AACT;AA3DgBD;","names":["Router","Container","METADATA","getControllerPath","controllerClass","Reflect","getMetadata","METADATA","CONTROLLER_PATH","buildRoutes","router","Router","container","Container","getInstance","controllerPath","routes","ROUTES","classMiddlewares","CLASS_MIDDLEWARES","route","method","toLowerCase","routePath","path","fullPath","methodMiddlewares","METHOD_MIDDLEWARES","handlerName","handlers","validation","push","validate","fileUploadConfig","FILE_UPLOAD","buildUploadMiddleware","mw","req","res","next","ctx","RequestContext","Promise","resolve","catch","controller","err"]}
|
package/dist/context.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ declare class RequestContext<TBody = any, TParams = any, TQuery = any> {
|
|
|
17
17
|
get query(): TQuery;
|
|
18
18
|
get headers(): http.IncomingHttpHeaders;
|
|
19
19
|
get requestId(): string | undefined;
|
|
20
|
+
/** Session data (requires session middleware) */
|
|
21
|
+
get session(): any;
|
|
20
22
|
/**
|
|
21
23
|
* Parse the request query string into structured filters, sort, pagination, and search.
|
|
22
24
|
* Pass the result to an ORM query builder adapter (Drizzle, Prisma, Sequelize, etc.).
|
package/dist/context.js
CHANGED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { AppAdapter, Ref, ComputedRef, Container, AdapterMiddleware } from '@forinda/kickjs-core';
|
|
2
|
+
|
|
3
|
+
/** Per-route latency stats */
|
|
4
|
+
interface RouteStats {
|
|
5
|
+
count: number;
|
|
6
|
+
totalMs: number;
|
|
7
|
+
minMs: number;
|
|
8
|
+
maxMs: number;
|
|
9
|
+
}
|
|
10
|
+
interface DevToolsOptions {
|
|
11
|
+
/** Base path for debug endpoints (default: '/_debug') */
|
|
12
|
+
basePath?: string;
|
|
13
|
+
/** Only enable when this is true (default: process.env.NODE_ENV !== 'production') */
|
|
14
|
+
enabled?: boolean;
|
|
15
|
+
/** Include environment variables (sanitized) at /_debug/config (default: false) */
|
|
16
|
+
exposeConfig?: boolean;
|
|
17
|
+
/** Env var prefixes to expose (default: ['APP_', 'NODE_ENV']). Others are redacted. */
|
|
18
|
+
configPrefixes?: string[];
|
|
19
|
+
/** Callback when error rate exceeds threshold */
|
|
20
|
+
onErrorRateExceeded?: (rate: number) => void;
|
|
21
|
+
/** Error rate threshold (default: 0.5 = 50%) */
|
|
22
|
+
errorRateThreshold?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* DevToolsAdapter — Vue-style reactive introspection for KickJS applications.
|
|
26
|
+
*
|
|
27
|
+
* Exposes debug endpoints powered by reactive state (ref, computed, watch):
|
|
28
|
+
* - `GET /_debug/routes` — all registered routes with middleware
|
|
29
|
+
* - `GET /_debug/container` — DI registry with scopes and instantiation status
|
|
30
|
+
* - `GET /_debug/metrics` — live request/error counts, error rate, uptime
|
|
31
|
+
* - `GET /_debug/health` — deep health check with adapter status
|
|
32
|
+
* - `GET /_debug/config` — sanitized environment variables (opt-in)
|
|
33
|
+
* - `GET /_debug/state` — full reactive state snapshot
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { DevToolsAdapter } from '@forinda/kickjs-http/devtools'
|
|
38
|
+
*
|
|
39
|
+
* bootstrap({
|
|
40
|
+
* modules: [UserModule],
|
|
41
|
+
* adapters: [
|
|
42
|
+
* new DevToolsAdapter({
|
|
43
|
+
* enabled: process.env.NODE_ENV !== 'production',
|
|
44
|
+
* exposeConfig: true,
|
|
45
|
+
* configPrefixes: ['APP_', 'DATABASE_'],
|
|
46
|
+
* }),
|
|
47
|
+
* ],
|
|
48
|
+
* })
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
declare class DevToolsAdapter implements AppAdapter {
|
|
52
|
+
readonly name = "DevToolsAdapter";
|
|
53
|
+
private basePath;
|
|
54
|
+
private enabled;
|
|
55
|
+
private exposeConfig;
|
|
56
|
+
private configPrefixes;
|
|
57
|
+
private errorRateThreshold;
|
|
58
|
+
/** Total requests received */
|
|
59
|
+
readonly requestCount: Ref<number>;
|
|
60
|
+
/** Total responses with status >= 500 */
|
|
61
|
+
readonly errorCount: Ref<number>;
|
|
62
|
+
/** Total responses with status >= 400 and < 500 */
|
|
63
|
+
readonly clientErrorCount: Ref<number>;
|
|
64
|
+
/** Server start time */
|
|
65
|
+
readonly startedAt: Ref<number>;
|
|
66
|
+
/** Computed error rate (server errors / total requests) */
|
|
67
|
+
readonly errorRate: ComputedRef<number>;
|
|
68
|
+
/** Computed uptime in seconds */
|
|
69
|
+
readonly uptimeSeconds: ComputedRef<number>;
|
|
70
|
+
/** Per-route latency tracking */
|
|
71
|
+
readonly routeLatency: Record<string, RouteStats>;
|
|
72
|
+
private routes;
|
|
73
|
+
private container;
|
|
74
|
+
private adapterStatuses;
|
|
75
|
+
private stopErrorWatch;
|
|
76
|
+
constructor(options?: DevToolsOptions);
|
|
77
|
+
beforeMount(app: any, container: Container): void;
|
|
78
|
+
middleware(): AdapterMiddleware[];
|
|
79
|
+
onRouteMount(controllerClass: any, mountPath: string): void;
|
|
80
|
+
beforeStart(_app: any, _container: Container): void;
|
|
81
|
+
afterStart(_server: any, _container: Container): void;
|
|
82
|
+
shutdown(): void;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { DevToolsAdapter, type DevToolsOptions };
|
package/dist/devtools.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,10 @@ export { REQUEST_ID_HEADER, requestId } from './middleware/request-id.js';
|
|
|
6
6
|
export { validate } from './middleware/validate.js';
|
|
7
7
|
export { errorHandler, notFoundHandler } from './middleware/error-handler.js';
|
|
8
8
|
export { CsrfOptions, csrf } from './middleware/csrf.js';
|
|
9
|
-
export {
|
|
9
|
+
export { RateLimitOptions, RateLimitStore, rateLimit } from './middleware/rate-limit.js';
|
|
10
|
+
export { Session, SessionData, SessionOptions, SessionStore, session } from './middleware/session.js';
|
|
11
|
+
export { UploadOptions, buildUploadMiddleware, cleanupFiles, resolveMimeTypes, upload } from './middleware/upload.js';
|
|
12
|
+
export { DevToolsAdapter, DevToolsOptions } from './devtools.js';
|
|
10
13
|
export { buildQueryParams, parseFilters, parsePagination, parseQuery, parseSearchQuery, parseSort } from './query/index.js';
|
|
11
14
|
export { F as FILTER_OPERATORS, a as FilterItem, b as FilterOperator, P as PaginationParams, c as ParsedQuery, Q as QueryBuilderAdapter, d as QueryFieldConfig, S as SortItem } from './types-Doz6f3AB.js';
|
|
12
15
|
import 'node:http';
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,38 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
rateLimit
|
|
3
|
+
} from "./chunk-H4S527PH.js";
|
|
4
|
+
import {
|
|
5
|
+
session
|
|
6
|
+
} from "./chunk-NQJNMKW5.js";
|
|
5
7
|
import {
|
|
6
8
|
bootstrap
|
|
7
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-OWLI3SBW.js";
|
|
8
10
|
import {
|
|
9
11
|
Application
|
|
10
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-4G2S7T4R.js";
|
|
13
|
+
import {
|
|
14
|
+
REQUEST_ID_HEADER,
|
|
15
|
+
requestId
|
|
16
|
+
} from "./chunk-35NUARK7.js";
|
|
17
|
+
import {
|
|
18
|
+
DevToolsAdapter
|
|
19
|
+
} from "./chunk-DUQ7SN7N.js";
|
|
11
20
|
import {
|
|
12
21
|
buildRoutes,
|
|
13
22
|
getControllerPath
|
|
14
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-VFVMIFNZ.js";
|
|
24
|
+
import {
|
|
25
|
+
buildUploadMiddleware,
|
|
26
|
+
cleanupFiles,
|
|
27
|
+
resolveMimeTypes,
|
|
28
|
+
upload
|
|
29
|
+
} from "./chunk-LEILPDMW.js";
|
|
15
30
|
import {
|
|
16
31
|
validate
|
|
17
32
|
} from "./chunk-RPN7UFUO.js";
|
|
18
33
|
import {
|
|
19
34
|
RequestContext
|
|
20
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-LQ6RSWMX.js";
|
|
21
36
|
import {
|
|
22
37
|
FILTER_OPERATORS,
|
|
23
38
|
buildQueryParams,
|
|
@@ -34,19 +49,17 @@ import {
|
|
|
34
49
|
errorHandler,
|
|
35
50
|
notFoundHandler
|
|
36
51
|
} from "./chunk-3NEDJA3J.js";
|
|
37
|
-
import {
|
|
38
|
-
REQUEST_ID_HEADER,
|
|
39
|
-
requestId
|
|
40
|
-
} from "./chunk-35NUARK7.js";
|
|
41
52
|
import "./chunk-WCQVDF3K.js";
|
|
42
53
|
export {
|
|
43
54
|
Application,
|
|
55
|
+
DevToolsAdapter,
|
|
44
56
|
FILTER_OPERATORS,
|
|
45
57
|
REQUEST_ID_HEADER,
|
|
46
58
|
RequestContext,
|
|
47
59
|
bootstrap,
|
|
48
60
|
buildQueryParams,
|
|
49
61
|
buildRoutes,
|
|
62
|
+
buildUploadMiddleware,
|
|
50
63
|
cleanupFiles,
|
|
51
64
|
csrf,
|
|
52
65
|
errorHandler,
|
|
@@ -57,7 +70,10 @@ export {
|
|
|
57
70
|
parseQuery,
|
|
58
71
|
parseSearchQuery,
|
|
59
72
|
parseSort,
|
|
73
|
+
rateLimit,
|
|
60
74
|
requestId,
|
|
75
|
+
resolveMimeTypes,
|
|
76
|
+
session,
|
|
61
77
|
upload,
|
|
62
78
|
validate
|
|
63
79
|
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
|
|
3
|
+
interface RateLimitStore {
|
|
4
|
+
increment(key: string): Promise<{
|
|
5
|
+
totalHits: number;
|
|
6
|
+
resetTime: Date;
|
|
7
|
+
}>;
|
|
8
|
+
decrement(key: string): Promise<void>;
|
|
9
|
+
reset(key: string): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
interface RateLimitOptions {
|
|
12
|
+
/** Maximum number of requests per window (default: 100) */
|
|
13
|
+
max?: number;
|
|
14
|
+
/** Time window in milliseconds (default: 60_000) */
|
|
15
|
+
windowMs?: number;
|
|
16
|
+
/** Response message when rate limit is exceeded (default: 'Too Many Requests') */
|
|
17
|
+
message?: string;
|
|
18
|
+
/** HTTP status code when rate limit is exceeded (default: 429) */
|
|
19
|
+
statusCode?: number;
|
|
20
|
+
/** Function to generate the key for rate limiting (default: req.ip) */
|
|
21
|
+
keyGenerator?: (req: Request) => string;
|
|
22
|
+
/** Whether to send rate limit headers (default: true) */
|
|
23
|
+
headers?: boolean;
|
|
24
|
+
/** Custom store implementation (default: in-memory Map) */
|
|
25
|
+
store?: RateLimitStore;
|
|
26
|
+
/** Function to skip rate limiting for certain requests */
|
|
27
|
+
skip?: (req: Request) => boolean;
|
|
28
|
+
/** Paths to exclude from rate limiting */
|
|
29
|
+
skipPaths?: string[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Rate limiting middleware.
|
|
33
|
+
*
|
|
34
|
+
* Limits the number of requests a client can make within a time window.
|
|
35
|
+
* Uses an in-memory store by default, but accepts a custom store for
|
|
36
|
+
* distributed deployments (e.g. Redis).
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { rateLimit } from '@forinda/kickjs-http'
|
|
41
|
+
*
|
|
42
|
+
* bootstrap({
|
|
43
|
+
* modules,
|
|
44
|
+
* middleware: [
|
|
45
|
+
* rateLimit({ max: 100, windowMs: 60_000 }),
|
|
46
|
+
* // ... other middleware
|
|
47
|
+
* ],
|
|
48
|
+
* })
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
declare function rateLimit(options?: RateLimitOptions): (req: Request, res: Response, next: NextFunction) => Promise<void | Response<any, Record<string, any>>>;
|
|
52
|
+
|
|
53
|
+
export { type RateLimitOptions, type RateLimitStore, rateLimit };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
|
|
3
|
+
interface SessionData {
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
}
|
|
6
|
+
interface SessionStore {
|
|
7
|
+
get(sid: string): Promise<SessionData | null>;
|
|
8
|
+
set(sid: string, data: SessionData, maxAge: number): Promise<void>;
|
|
9
|
+
destroy(sid: string): Promise<void>;
|
|
10
|
+
touch?(sid: string, maxAge: number): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
interface Session {
|
|
13
|
+
id: string;
|
|
14
|
+
data: SessionData;
|
|
15
|
+
regenerate(): Promise<void>;
|
|
16
|
+
destroy(): Promise<void>;
|
|
17
|
+
save(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
interface SessionOptions {
|
|
20
|
+
/** Secret used to sign the session cookie (required) */
|
|
21
|
+
secret: string;
|
|
22
|
+
/** Cookie name (default: 'kick.sid') */
|
|
23
|
+
cookieName?: string;
|
|
24
|
+
/** Session max age in milliseconds (default: 86400000 = 24h) */
|
|
25
|
+
maxAge?: number;
|
|
26
|
+
/** Reset maxAge on every response (default: false) */
|
|
27
|
+
rolling?: boolean;
|
|
28
|
+
/** Save new sessions that have not been modified (default: true) */
|
|
29
|
+
saveUninitialized?: boolean;
|
|
30
|
+
/** Cookie options */
|
|
31
|
+
cookie?: {
|
|
32
|
+
httpOnly?: boolean;
|
|
33
|
+
secure?: boolean;
|
|
34
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
35
|
+
path?: string;
|
|
36
|
+
domain?: string;
|
|
37
|
+
};
|
|
38
|
+
/** Custom session store (default: in-memory store with TTL cleanup) */
|
|
39
|
+
store?: SessionStore;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Session management middleware.
|
|
43
|
+
*
|
|
44
|
+
* Attaches a `req.session` object with `id`, `data`, `regenerate()`,
|
|
45
|
+
* `destroy()`, and `save()` methods. Session IDs are signed with
|
|
46
|
+
* HMAC-SHA256 to prevent cookie tampering.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* import { session } from '@forinda/kickjs-http'
|
|
51
|
+
*
|
|
52
|
+
* bootstrap({
|
|
53
|
+
* modules,
|
|
54
|
+
* middleware: [
|
|
55
|
+
* cookieParser(),
|
|
56
|
+
* session({ secret: process.env.SESSION_SECRET! }),
|
|
57
|
+
* // ... other middleware
|
|
58
|
+
* ],
|
|
59
|
+
* })
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
declare function session(options: SessionOptions): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
63
|
+
|
|
64
|
+
export { type Session, type SessionData, type SessionOptions, type SessionStore, session };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|