@forinda/kickjs-http 0.3.1 → 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 +4 -4
- package/dist/bootstrap.js +5 -5
- package/dist/{chunk-ZI52TGQ4.js → chunk-35NUARK7.js} +2 -2
- package/dist/{chunk-BNWCVQQH.js → chunk-3NEDJA3J.js} +2 -2
- package/dist/{chunk-P3YCN5LK.js → chunk-4G2S7T4R.js} +6 -6
- 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-I6UNTOQD.js → chunk-I32MVBEG.js} +2 -2
- package/dist/chunk-LEILPDMW.js +183 -0
- package/dist/chunk-LEILPDMW.js.map +1 -0
- package/dist/{chunk-RZUH6NBM.js → chunk-LQ6RSWMX.js} +7 -3
- 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-KAWXFLFS.js → chunk-OWLI3SBW.js} +14 -4
- package/dist/chunk-OWLI3SBW.js.map +1 -0
- package/dist/{chunk-JD2RKDKH.js → chunk-RPN7UFUO.js} +2 -2
- package/dist/{chunk-U2JYL2NW.js → chunk-VFVMIFNZ.js} +11 -4
- package/dist/chunk-VFVMIFNZ.js.map +1 -0
- package/dist/{chunk-JM7X7SAD.js → chunk-VXX2Y3TA.js} +2 -2
- package/dist/chunk-WCQVDF3K.js +14 -0
- package/dist/context.d.ts +2 -0
- package/dist/context.js +3 -3
- 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 +32 -16
- package/dist/middleware/csrf.js +2 -2
- package/dist/middleware/error-handler.js +2 -2
- 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/request-id.js +2 -2
- 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 +6 -2
- package/dist/middleware/validate.js +2 -2
- package/dist/query/index.js +2 -2
- package/dist/router-builder.js +6 -5
- package/package.json +15 -2
- package/dist/chunk-75Z5FSZN.js +0 -88
- package/dist/chunk-75Z5FSZN.js.map +0 -1
- package/dist/chunk-7QVYU63E.js +0 -7
- package/dist/chunk-KAWXFLFS.js.map +0 -1
- package/dist/chunk-RZUH6NBM.js.map +0 -1
- package/dist/chunk-U2JYL2NW.js.map +0 -1
- /package/dist/{chunk-ZI52TGQ4.js.map → chunk-35NUARK7.js.map} +0 -0
- /package/dist/{chunk-BNWCVQQH.js.map → chunk-3NEDJA3J.js.map} +0 -0
- /package/dist/{chunk-P3YCN5LK.js.map → chunk-4G2S7T4R.js.map} +0 -0
- /package/dist/{chunk-I6UNTOQD.js.map → chunk-I32MVBEG.js.map} +0 -0
- /package/dist/{chunk-JD2RKDKH.js.map → chunk-RPN7UFUO.js.map} +0 -0
- /package/dist/{chunk-JM7X7SAD.js.map → chunk-VXX2Y3TA.js.map} +0 -0
- /package/dist/{chunk-7QVYU63E.js.map → chunk-WCQVDF3K.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/middleware/upload.ts"],"sourcesContent":["import { unlink } from 'node:fs/promises'\nimport type { Request, Response, NextFunction, RequestHandler } from 'express'\nimport multer, { type Options as MulterOptions } from 'multer'\nimport type { BaseUploadOptions, FileUploadConfig } from '@forinda/kickjs-core'\n\n/**\n * Maps short file extensions to their MIME types.\n * Users can pass `['jpg', 'png', 'pdf']` instead of full MIME strings.\n */\nconst MIME_MAP: Record<string, string> = {\n // Images\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n png: 'image/png',\n gif: 'image/gif',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n bmp: 'image/bmp',\n ico: 'image/x-icon',\n tiff: 'image/tiff',\n tif: 'image/tiff',\n avif: 'image/avif',\n // Documents\n pdf: 'application/pdf',\n doc: 'application/msword',\n docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n xls: 'application/vnd.ms-excel',\n xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n ppt: 'application/vnd.ms-powerpoint',\n pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n odt: 'application/vnd.oasis.opendocument.text',\n ods: 'application/vnd.oasis.opendocument.spreadsheet',\n odp: 'application/vnd.oasis.opendocument.presentation',\n rtf: 'application/rtf',\n txt: 'text/plain',\n csv: 'text/csv',\n // Archives\n zip: 'application/zip',\n gz: 'application/gzip',\n tar: 'application/x-tar',\n rar: 'application/vnd.rar',\n '7z': 'application/x-7z-compressed',\n // Audio\n mp3: 'audio/mpeg',\n wav: 'audio/wav',\n ogg: 'audio/ogg',\n flac: 'audio/flac',\n aac: 'audio/aac',\n // Video\n mp4: 'video/mp4',\n webm: 'video/webm',\n avi: 'video/x-msvideo',\n mov: 'video/quicktime',\n mkv: 'video/x-matroska',\n // Other\n json: 'application/json',\n xml: 'application/xml',\n html: 'text/html',\n}\n\n/**\n * Resolves a list of file type identifiers to MIME type strings.\n * Accepts short extensions (`'jpg'`, `'pdf'`) or full MIME types (`'image/jpeg'`).\n *\n * @example\n * ```ts\n * resolveMimeTypes(['jpg', 'png', 'application/pdf'])\n * // → ['image/jpeg', 'image/png', 'application/pdf']\n * ```\n */\nexport function resolveMimeTypes(types: string[]): string[] {\n return types.map((t) => {\n const lower = t.toLowerCase().replace(/^\\./, '')\n return MIME_MAP[lower] ?? t\n })\n}\n\n/**\n * Upload options for the middleware.\n * Extends BaseUploadOptions from core (shared with @FileUpload decorator)\n * and adds Multer-specific storage options.\n */\nexport interface UploadOptions extends BaseUploadOptions {\n /** Multer storage config (default: memory storage) */\n storage?: MulterOptions['storage']\n /** Multer dest for disk storage shorthand */\n dest?: string\n}\n\nfunction createMulter(options: UploadOptions) {\n const mimeMap = options.customMimeMap ? { ...MIME_MAP, ...options.customMimeMap } : MIME_MAP\n\n const limits: MulterOptions['limits'] = {\n fileSize: options.maxSize ?? 5 * 1024 * 1024,\n }\n\n let fileFilter: MulterOptions['fileFilter'] | undefined\n\n if (typeof options.allowedTypes === 'function') {\n // Custom filter function\n const filterFn = options.allowedTypes\n fileFilter = (_req, file, cb) => {\n if (filterFn(file.mimetype, file.originalname)) {\n cb(null, true)\n } else {\n cb(new Error(`File type ${file.mimetype} is not allowed`))\n }\n }\n } else if (Array.isArray(options.allowedTypes)) {\n // String array — resolve short extensions using the (possibly extended) MIME map\n const resolvedTypes = options.allowedTypes.map((t) => {\n const lower = t.toLowerCase().replace(/^\\./, '')\n return mimeMap[lower] ?? t\n })\n fileFilter = (_req, file, cb) => {\n const allowed = resolvedTypes.some((type) => {\n if (type.endsWith('/*')) {\n return file.mimetype.startsWith(type.replace('/*', '/'))\n }\n return file.mimetype === type\n })\n if (allowed) {\n cb(null, true)\n } else {\n cb(new Error(`File type ${file.mimetype} is not allowed`))\n }\n }\n }\n\n const multerOptions: MulterOptions = {\n limits,\n ...(fileFilter ? { fileFilter } : {}),\n ...(options.storage ? { storage: options.storage } : {}),\n ...(options.dest ? { dest: options.dest } : {}),\n }\n\n return multer(multerOptions)\n}\n\n/**\n * Single file upload middleware. Attaches the file to `req.file`.\n *\n * @example\n * ```ts\n * @Post('/avatar')\n * @Middleware(upload.single('avatar', { maxSize: 2 * 1024 * 1024, allowedTypes: ['jpg', 'png'] }))\n * async uploadAvatar(ctx: RequestContext) {\n * ctx.json({ filename: ctx.file.originalname })\n * }\n * ```\n */\nfunction single(fieldName: string, options: UploadOptions = {}): RequestHandler {\n const m = createMulter(options)\n return m.single(fieldName) as RequestHandler\n}\n\n/**\n * Multiple file upload middleware. Attaches files to `req.files`.\n */\nfunction array(fieldName: string, maxCount = 10, options: UploadOptions = {}): RequestHandler {\n const m = createMulter(options)\n return m.array(fieldName, maxCount) as RequestHandler\n}\n\n/**\n * No file upload — just parse multipart form data without file fields.\n */\nfunction none(options: UploadOptions = {}): RequestHandler {\n const m = createMulter(options)\n return m.none() as RequestHandler\n}\n\n/**\n * Removes temporary files from disk after the response is sent.\n * Attach this as Express middleware after the upload middleware.\n * Works with both `req.file` (single) and `req.files` (array).\n *\n * @example\n * ```ts\n * @Post('/process')\n * @Middleware(upload.single('document', { dest: '/tmp/uploads' }), cleanupFiles())\n * async processDocument(ctx: RequestContext) {\n * ctx.json({ ok: true })\n * }\n * ```\n */\nexport function cleanupFiles() {\n return (req: Request, res: Response, next: NextFunction) => {\n res.on('finish', async () => {\n const files: any[] = []\n\n if ((req as any).file?.path) {\n files.push((req as any).file)\n }\n if (Array.isArray((req as any).files)) {\n for (const f of (req as any).files) {\n if (f?.path) files.push(f)\n }\n }\n\n for (const file of files) {\n try {\n await unlink(file.path)\n } catch {\n // File may already be moved/deleted by the handler — ignore\n }\n }\n })\n\n next()\n }\n}\n\n/**\n * Build upload middleware from a @FileUpload decorator config.\n * Used internally by the router builder when it detects FILE_UPLOAD metadata.\n * Accepts the same FileUploadConfig interface used by the @FileUpload decorator.\n */\nexport function buildUploadMiddleware(config: FileUploadConfig): RequestHandler {\n const options: UploadOptions = {}\n if (config.maxSize) options.maxSize = config.maxSize\n if (config.allowedTypes) options.allowedTypes = config.allowedTypes\n if (config.customMimeMap) options.customMimeMap = config.customMimeMap\n\n const fieldName = config.fieldName ?? 'file'\n\n switch (config.mode) {\n case 'single':\n return single(fieldName, options)\n case 'array':\n return array(fieldName, config.maxCount ?? 10, options)\n case 'none':\n return none(options)\n }\n}\n\n/** Upload middleware factory with `.single()`, `.array()`, `.none()` methods */\nexport const upload = { single, array, none }\n"],"mappings":"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","names":["unlink","multer","MIME_MAP","jpg","jpeg","png","gif","webp","svg","bmp","ico","tiff","tif","avif","pdf","doc","docx","xls","xlsx","ppt","pptx","odt","ods","odp","rtf","txt","csv","zip","gz","tar","rar","mp3","wav","ogg","flac","aac","mp4","webm","avi","mov","mkv","json","xml","html","resolveMimeTypes","types","map","t","lower","toLowerCase","replace","createMulter","options","mimeMap","customMimeMap","limits","fileSize","maxSize","fileFilter","allowedTypes","filterFn","_req","file","cb","mimetype","originalname","Error","Array","isArray","resolvedTypes","allowed","some","type","endsWith","startsWith","multerOptions","storage","dest","multer","single","fieldName","m","array","maxCount","none","cleanupFiles","req","res","next","on","files","path","push","f","unlink","buildUploadMiddleware","config","mode","upload"]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseQuery
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-VXX2Y3TA.js";
|
|
4
4
|
import {
|
|
5
5
|
__name
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-WCQVDF3K.js";
|
|
7
7
|
|
|
8
8
|
// src/context.ts
|
|
9
9
|
var RequestContext = class {
|
|
@@ -35,6 +35,10 @@ var RequestContext = class {
|
|
|
35
35
|
get requestId() {
|
|
36
36
|
return this.req.requestId ?? this.req.headers["x-request-id"];
|
|
37
37
|
}
|
|
38
|
+
/** Session data (requires session middleware) */
|
|
39
|
+
get session() {
|
|
40
|
+
return this.req.session;
|
|
41
|
+
}
|
|
38
42
|
// ── Query String Parsing ───────────────────────────────────────────
|
|
39
43
|
/**
|
|
40
44
|
* Parse the request query string into structured filters, sort, pagination, and search.
|
|
@@ -107,4 +111,4 @@ var RequestContext = class {
|
|
|
107
111
|
export {
|
|
108
112
|
RequestContext
|
|
109
113
|
};
|
|
110
|
-
//# sourceMappingURL=chunk-
|
|
114
|
+
//# sourceMappingURL=chunk-LQ6RSWMX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/context.ts"],"sourcesContent":["import type { Request, Response, NextFunction } from 'express'\nimport { parseQuery, type ParsedQuery, type QueryFieldConfig } from './query'\n\n/**\n * Unified request/response abstraction passed to every controller method.\n * Shields handlers from raw Express objects and provides convenience methods.\n */\nexport class RequestContext<TBody = any, TParams = any, TQuery = any> {\n private metadata = new Map<string, any>()\n\n constructor(\n public readonly req: Request,\n public readonly res: Response,\n public readonly next: NextFunction,\n ) {}\n\n // ── Request Data ────────────────────────────────────────────────────\n\n get body(): TBody {\n return this.req.body as TBody\n }\n\n get params(): TParams {\n return this.req.params as TParams\n }\n\n get query(): TQuery {\n return this.req.query as TQuery\n }\n\n get headers() {\n return this.req.headers\n }\n\n get requestId(): string | undefined {\n return (this.req as any).requestId ?? (this.req.headers['x-request-id'] as string | undefined)\n }\n\n /** Session data (requires session middleware) */\n get session(): any {\n return (this.req as any).session\n }\n\n // ── Query String Parsing ───────────────────────────────────────────\n\n /**\n * Parse the request query string into structured filters, sort, pagination, and search.\n * Pass the result to an ORM query builder adapter (Drizzle, Prisma, Sequelize, etc.).\n *\n * @param fieldConfig - Optional whitelist for filterable, sortable, and searchable fields\n *\n * @example\n * ```ts\n * @Get('/')\n * async list(ctx: RequestContext) {\n * const parsed = ctx.qs({\n * filterable: ['status', 'priority'],\n * sortable: ['createdAt', 'title'],\n * })\n * const q = drizzleAdapter.build(parsed, { columns })\n * // ... use q.where, q.orderBy, q.limit, q.offset\n * }\n * ```\n */\n qs(fieldConfig?: QueryFieldConfig): ParsedQuery {\n return parseQuery(this.req.query as Record<string, any>, fieldConfig)\n }\n\n // ── File Uploads ────────────────────────────────────────────────────\n\n /** Single uploaded file (requires @FileUpload({ mode: 'single' })) */\n get file(): any {\n return (this.req as any).file\n }\n\n /** Array of uploaded files (requires @FileUpload({ mode: 'array' })) */\n get files(): any[] | undefined {\n return (this.req as any).files\n }\n\n // ── Metadata Store ──────────────────────────────────────────────────\n\n get<T = any>(key: string): T | undefined {\n return this.metadata.get(key) as T | undefined\n }\n\n set(key: string, value: any): void {\n this.metadata.set(key, value)\n }\n\n // ── Response Helpers ────────────────────────────────────────────────\n\n json(data: any, status = 200) {\n return this.res.status(status).json(data)\n }\n\n created(data: any) {\n return this.res.status(201).json(data)\n }\n\n noContent() {\n return this.res.status(204).end()\n }\n\n notFound(message = 'Not Found') {\n return this.res.status(404).json({ message })\n }\n\n badRequest(message: string) {\n return this.res.status(400).json({ message })\n }\n\n html(content: string, status = 200) {\n return this.res.status(status).type('html').send(content)\n }\n\n download(buffer: Buffer, filename: string, contentType = 'application/octet-stream') {\n this.res.setHeader('Content-Disposition', `attachment; filename=\"${filename}\"`)\n this.res.setHeader('Content-Type', contentType)\n return this.res.send(buffer)\n }\n}\n"],"mappings":";;;;;;;;AAOO,IAAMA,iBAAN,MAAMA;EANb,OAMaA;;;;;;EACHC,WAAW,oBAAIC,IAAAA;EAEvB,YACkBC,KACAC,KACAC,MAChB;SAHgBF,MAAAA;SACAC,MAAAA;SACAC,OAAAA;EACf;;EAIH,IAAIC,OAAc;AAChB,WAAO,KAAKH,IAAIG;EAClB;EAEA,IAAIC,SAAkB;AACpB,WAAO,KAAKJ,IAAII;EAClB;EAEA,IAAIC,QAAgB;AAClB,WAAO,KAAKL,IAAIK;EAClB;EAEA,IAAIC,UAAU;AACZ,WAAO,KAAKN,IAAIM;EAClB;EAEA,IAAIC,YAAgC;AAClC,WAAQ,KAAKP,IAAYO,aAAc,KAAKP,IAAIM,QAAQ,cAAA;EAC1D;;EAGA,IAAIE,UAAe;AACjB,WAAQ,KAAKR,IAAYQ;EAC3B;;;;;;;;;;;;;;;;;;;;;EAuBAC,GAAGC,aAA6C;AAC9C,WAAOC,WAAW,KAAKX,IAAIK,OAA8BK,WAAAA;EAC3D;;;EAKA,IAAIE,OAAY;AACd,WAAQ,KAAKZ,IAAYY;EAC3B;;EAGA,IAAIC,QAA2B;AAC7B,WAAQ,KAAKb,IAAYa;EAC3B;;EAIAC,IAAaC,KAA4B;AACvC,WAAO,KAAKjB,SAASgB,IAAIC,GAAAA;EAC3B;EAEAC,IAAID,KAAaE,OAAkB;AACjC,SAAKnB,SAASkB,IAAID,KAAKE,KAAAA;EACzB;;EAIAC,KAAKC,MAAWC,SAAS,KAAK;AAC5B,WAAO,KAAKnB,IAAImB,OAAOA,MAAAA,EAAQF,KAAKC,IAAAA;EACtC;EAEAE,QAAQF,MAAW;AACjB,WAAO,KAAKlB,IAAImB,OAAO,GAAA,EAAKF,KAAKC,IAAAA;EACnC;EAEAG,YAAY;AACV,WAAO,KAAKrB,IAAImB,OAAO,GAAA,EAAKG,IAAG;EACjC;EAEAC,SAASC,UAAU,aAAa;AAC9B,WAAO,KAAKxB,IAAImB,OAAO,GAAA,EAAKF,KAAK;MAAEO;IAAQ,CAAA;EAC7C;EAEAC,WAAWD,SAAiB;AAC1B,WAAO,KAAKxB,IAAImB,OAAO,GAAA,EAAKF,KAAK;MAAEO;IAAQ,CAAA;EAC7C;EAEAE,KAAKC,SAAiBR,SAAS,KAAK;AAClC,WAAO,KAAKnB,IAAImB,OAAOA,MAAAA,EAAQS,KAAK,MAAA,EAAQC,KAAKF,OAAAA;EACnD;EAEAG,SAASC,QAAgBC,UAAkBC,cAAc,4BAA4B;AACnF,SAAKjC,IAAIkC,UAAU,uBAAuB,yBAAyBF,QAAAA,GAAW;AAC9E,SAAKhC,IAAIkC,UAAU,gBAAgBD,WAAAA;AACnC,WAAO,KAAKjC,IAAI6B,KAAKE,MAAAA;EACvB;AACF;","names":["RequestContext","metadata","Map","req","res","next","body","params","query","headers","requestId","session","qs","fieldConfig","parseQuery","file","files","get","key","set","value","json","data","status","created","noContent","end","notFound","message","badRequest","html","content","type","send","download","buffer","filename","contentType","setHeader"]}
|
|
@@ -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,12 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Application
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4G2S7T4R.js";
|
|
4
4
|
import {
|
|
5
|
-
__name
|
|
6
|
-
|
|
5
|
+
__name,
|
|
6
|
+
__require
|
|
7
|
+
} from "./chunk-WCQVDF3K.js";
|
|
7
8
|
|
|
8
9
|
// src/bootstrap.ts
|
|
9
10
|
import { createLogger } from "@forinda/kickjs-core";
|
|
11
|
+
function tryReloadEnv() {
|
|
12
|
+
try {
|
|
13
|
+
const config = __require("@forinda/kickjs-config");
|
|
14
|
+
config.reloadEnv?.();
|
|
15
|
+
} catch {
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
__name(tryReloadEnv, "tryReloadEnv");
|
|
10
19
|
var log = createLogger("Process");
|
|
11
20
|
function bootstrap(options) {
|
|
12
21
|
const g = globalThis;
|
|
@@ -31,6 +40,7 @@ function bootstrap(options) {
|
|
|
31
40
|
}
|
|
32
41
|
if (g.__app) {
|
|
33
42
|
log.info("HMR: Rebuilding application...");
|
|
43
|
+
tryReloadEnv();
|
|
34
44
|
g.__app.rebuild();
|
|
35
45
|
return;
|
|
36
46
|
}
|
|
@@ -47,4 +57,4 @@ __name(bootstrap, "bootstrap");
|
|
|
47
57
|
export {
|
|
48
58
|
bootstrap
|
|
49
59
|
};
|
|
50
|
-
//# sourceMappingURL=chunk-
|
|
60
|
+
//# sourceMappingURL=chunk-OWLI3SBW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bootstrap.ts"],"sourcesContent":["import { createLogger } from '@forinda/kickjs-core'\nimport { Application, type ApplicationOptions } from './application'\n\n/** Try to reload env from .env file if config package is available */\nfunction tryReloadEnv(): void {\n try {\n const config = require('@forinda/kickjs-config')\n config.reloadEnv?.()\n } catch {\n // Config package not installed — skip\n }\n}\n\nconst log = createLogger('Process')\n\n/**\n * Bootstrap a KickJS application with zero boilerplate.\n *\n * Handles:\n * - Vite HMR (hot-swaps Express handler without restarting the server)\n * - Graceful shutdown on SIGINT / SIGTERM\n * - Global uncaughtException / unhandledRejection handlers\n * - globalThis app storage for HMR rebuild\n *\n * @example\n * ```ts\n * // src/index.ts — that's it, the whole file\n * import 'reflect-metadata'\n * import { bootstrap } from '@forinda/kickjs-http'\n * import { modules } from './modules'\n *\n * bootstrap({ modules })\n * ```\n */\nexport function bootstrap(options: ApplicationOptions): void {\n const g = globalThis as any\n\n // ── Global error handlers ────────────────────────────────────────────\n if (!g.__kickBootstrapped) {\n process.on('uncaughtException', (err) => {\n log.error(err, 'Uncaught exception')\n })\n\n process.on('unhandledRejection', (reason) => {\n log.error(reason as any, 'Unhandled rejection')\n })\n\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.on(signal, async () => {\n log.info(`Received ${signal}, shutting down...`)\n if (g.__app) await g.__app.shutdown()\n process.exit(0)\n })\n }\n\n g.__kickBootstrapped = true\n }\n\n // ── HMR rebuild ──────────────────────────────────────────────────────\n if (g.__app) {\n log.info('HMR: Rebuilding application...')\n tryReloadEnv()\n g.__app.rebuild()\n return\n }\n\n // ── First boot ───────────────────────────────────────────────────────\n const app = new Application(options)\n g.__app = app\n app.start()\n\n // ── Vite HMR acceptance ──────────────────────────────────────────────\n const meta = import.meta as any\n if (meta.hot) {\n meta.hot.accept()\n }\n}\n"],"mappings":";;;;;;;;;AAAA,SAASA,oBAAoB;AAI7B,SAASC,eAAAA;AACP,MAAI;AACF,UAAMC,SAASC,UAAQ,wBAAA;AACvBD,WAAOE,YAAS;EAClB,QAAQ;EAER;AACF;AAPSH;AAST,IAAMI,MAAMC,aAAa,SAAA;AAqBlB,SAASC,UAAUC,SAA2B;AACnD,QAAMC,IAAIC;AAGV,MAAI,CAACD,EAAEE,oBAAoB;AACzBC,YAAQC,GAAG,qBAAqB,CAACC,QAAAA;AAC/BT,UAAIU,MAAMD,KAAK,oBAAA;IACjB,CAAA;AAEAF,YAAQC,GAAG,sBAAsB,CAACG,WAAAA;AAChCX,UAAIU,MAAMC,QAAe,qBAAA;IAC3B,CAAA;AAEA,eAAWC,UAAU;MAAC;MAAU;OAAqB;AACnDL,cAAQC,GAAGI,QAAQ,YAAA;AACjBZ,YAAIa,KAAK,YAAYD,MAAAA,oBAA0B;AAC/C,YAAIR,EAAEU,MAAO,OAAMV,EAAEU,MAAMC,SAAQ;AACnCR,gBAAQS,KAAK,CAAA;MACf,CAAA;IACF;AAEAZ,MAAEE,qBAAqB;EACzB;AAGA,MAAIF,EAAEU,OAAO;AACXd,QAAIa,KAAK,gCAAA;AACTjB,iBAAAA;AACAQ,MAAEU,MAAMG,QAAO;AACf;EACF;AAGA,QAAMC,MAAM,IAAIC,YAAYhB,OAAAA;AAC5BC,IAAEU,QAAQI;AACVA,MAAIE,MAAK;AAGT,QAAMC,OAAO;AACb,MAAIA,KAAKC,KAAK;AACZD,SAAKC,IAAIC,OAAM;EACjB;AACF;AA1CgBrB;","names":["createLogger","tryReloadEnv","config","require","reloadEnv","log","createLogger","bootstrap","options","g","globalThis","__kickBootstrapped","process","on","err","error","reason","signal","info","__app","shutdown","exit","rebuild","app","Application","start","meta","hot","accept"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
__name
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-WCQVDF3K.js";
|
|
4
4
|
|
|
5
5
|
// src/middleware/validate.ts
|
|
6
6
|
function validate(schema) {
|
|
@@ -58,4 +58,4 @@ __name(validate, "validate");
|
|
|
58
58
|
export {
|
|
59
59
|
validate
|
|
60
60
|
};
|
|
61
|
-
//# sourceMappingURL=chunk-
|
|
61
|
+
//# sourceMappingURL=chunk-RPN7UFUO.js.map
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildUploadMiddleware
|
|
3
|
+
} from "./chunk-LEILPDMW.js";
|
|
1
4
|
import {
|
|
2
5
|
validate
|
|
3
|
-
} from "./chunk-
|
|
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
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-WCQVDF3K.js";
|
|
10
13
|
|
|
11
14
|
// src/router-builder.ts
|
|
12
15
|
import "reflect-metadata";
|
|
@@ -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"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
__name
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-WCQVDF3K.js";
|
|
4
4
|
|
|
5
5
|
// src/query/types.ts
|
|
6
6
|
var FILTER_OPERATORS = /* @__PURE__ */ new Set([
|
|
@@ -130,4 +130,4 @@ export {
|
|
|
130
130
|
parseQuery,
|
|
131
131
|
buildQueryParams
|
|
132
132
|
};
|
|
133
|
-
//# sourceMappingURL=chunk-
|
|
133
|
+
//# sourceMappingURL=chunk-VXX2Y3TA.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
+
}) : x)(function(x) {
|
|
6
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
__name,
|
|
12
|
+
__require
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=chunk-WCQVDF3K.js.map
|
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
|
-
} from "./chunk-
|
|
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,
|
|
@@ -26,27 +41,25 @@ import {
|
|
|
26
41
|
parseQuery,
|
|
27
42
|
parseSearchQuery,
|
|
28
43
|
parseSort
|
|
29
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-VXX2Y3TA.js";
|
|
30
45
|
import {
|
|
31
46
|
csrf
|
|
32
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-I32MVBEG.js";
|
|
33
48
|
import {
|
|
34
49
|
errorHandler,
|
|
35
50
|
notFoundHandler
|
|
36
|
-
} from "./chunk-
|
|
37
|
-
import
|
|
38
|
-
REQUEST_ID_HEADER,
|
|
39
|
-
requestId
|
|
40
|
-
} from "./chunk-ZI52TGQ4.js";
|
|
41
|
-
import "./chunk-7QVYU63E.js";
|
|
51
|
+
} from "./chunk-3NEDJA3J.js";
|
|
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
|
};
|