@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,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":[]}
|
|
@@ -1,11 +1,24 @@
|
|
|
1
|
-
import { Request, Response, NextFunction
|
|
1
|
+
import { RequestHandler, Request, Response, NextFunction } from 'express';
|
|
2
2
|
import { Options } from 'multer';
|
|
3
|
+
import { BaseUploadOptions, FileUploadConfig } from '@forinda/kickjs-core';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Resolves a list of file type identifiers to MIME type strings.
|
|
7
|
+
* Accepts short extensions (`'jpg'`, `'pdf'`) or full MIME types (`'image/jpeg'`).
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* resolveMimeTypes(['jpg', 'png', 'application/pdf'])
|
|
12
|
+
* // → ['image/jpeg', 'image/png', 'application/pdf']
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
declare function resolveMimeTypes(types: string[]): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Upload options for the middleware.
|
|
18
|
+
* Extends BaseUploadOptions from core (shared with @FileUpload decorator)
|
|
19
|
+
* and adds Multer-specific storage options.
|
|
20
|
+
*/
|
|
21
|
+
interface UploadOptions extends BaseUploadOptions {
|
|
9
22
|
/** Multer storage config (default: memory storage) */
|
|
10
23
|
storage?: Options['storage'];
|
|
11
24
|
/** Multer dest for disk storage shorthand */
|
|
@@ -17,7 +30,7 @@ interface UploadOptions {
|
|
|
17
30
|
* @example
|
|
18
31
|
* ```ts
|
|
19
32
|
* @Post('/avatar')
|
|
20
|
-
* @Middleware(upload.single('avatar', { maxSize: 2 * 1024 * 1024, allowedTypes: ['
|
|
33
|
+
* @Middleware(upload.single('avatar', { maxSize: 2 * 1024 * 1024, allowedTypes: ['jpg', 'png'] }))
|
|
21
34
|
* async uploadAvatar(ctx: RequestContext) {
|
|
22
35
|
* ctx.json({ filename: ctx.file.originalname })
|
|
23
36
|
* }
|
|
@@ -33,20 +46,26 @@ declare function array(fieldName: string, maxCount?: number, options?: UploadOpt
|
|
|
33
46
|
*/
|
|
34
47
|
declare function none(options?: UploadOptions): RequestHandler;
|
|
35
48
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* Only cleans up disk-stored files (files with a `path` property).
|
|
49
|
+
* Removes temporary files from disk after the response is sent.
|
|
50
|
+
* Attach this as Express middleware after the upload middleware.
|
|
51
|
+
* Works with both `req.file` (single) and `req.files` (array).
|
|
40
52
|
*
|
|
41
53
|
* @example
|
|
42
54
|
* ```ts
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
55
|
+
* @Post('/process')
|
|
56
|
+
* @Middleware(upload.single('document', { dest: '/tmp/uploads' }), cleanupFiles())
|
|
57
|
+
* async processDocument(ctx: RequestContext) {
|
|
58
|
+
* ctx.json({ ok: true })
|
|
59
|
+
* }
|
|
47
60
|
* ```
|
|
48
61
|
*/
|
|
49
62
|
declare function cleanupFiles(): (req: Request, res: Response, next: NextFunction) => void;
|
|
63
|
+
/**
|
|
64
|
+
* Build upload middleware from a @FileUpload decorator config.
|
|
65
|
+
* Used internally by the router builder when it detects FILE_UPLOAD metadata.
|
|
66
|
+
* Accepts the same FileUploadConfig interface used by the @FileUpload decorator.
|
|
67
|
+
*/
|
|
68
|
+
declare function buildUploadMiddleware(config: FileUploadConfig): RequestHandler;
|
|
50
69
|
/** Upload middleware factory with `.single()`, `.array()`, `.none()` methods */
|
|
51
70
|
declare const upload: {
|
|
52
71
|
single: typeof single;
|
|
@@ -54,4 +73,4 @@ declare const upload: {
|
|
|
54
73
|
none: typeof none;
|
|
55
74
|
};
|
|
56
75
|
|
|
57
|
-
export { type UploadOptions, cleanupFiles, upload };
|
|
76
|
+
export { type UploadOptions, buildUploadMiddleware, cleanupFiles, resolveMimeTypes, upload };
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
+
buildUploadMiddleware,
|
|
2
3
|
cleanupFiles,
|
|
4
|
+
resolveMimeTypes,
|
|
3
5
|
upload
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
6
|
+
} from "../chunk-LEILPDMW.js";
|
|
7
|
+
import "../chunk-WCQVDF3K.js";
|
|
6
8
|
export {
|
|
9
|
+
buildUploadMiddleware,
|
|
7
10
|
cleanupFiles,
|
|
11
|
+
resolveMimeTypes,
|
|
8
12
|
upload
|
|
9
13
|
};
|
|
10
14
|
//# sourceMappingURL=upload.js.map
|
package/dist/query/index.js
CHANGED
package/dist/router-builder.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildRoutes,
|
|
3
3
|
getControllerPath
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-VFVMIFNZ.js";
|
|
5
|
+
import "./chunk-LEILPDMW.js";
|
|
6
|
+
import "./chunk-RPN7UFUO.js";
|
|
7
|
+
import "./chunk-LQ6RSWMX.js";
|
|
8
|
+
import "./chunk-VXX2Y3TA.js";
|
|
9
|
+
import "./chunk-WCQVDF3K.js";
|
|
9
10
|
export {
|
|
10
11
|
buildRoutes,
|
|
11
12
|
getControllerPath
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forinda/kickjs-http",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Express 5 integration, router builder, RequestContext, and middleware for KickJS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -30,6 +30,14 @@
|
|
|
30
30
|
"import": "./dist/middleware/csrf.js",
|
|
31
31
|
"types": "./dist/middleware/csrf.d.ts"
|
|
32
32
|
},
|
|
33
|
+
"./middleware/rate-limit": {
|
|
34
|
+
"import": "./dist/middleware/rate-limit.js",
|
|
35
|
+
"types": "./dist/middleware/rate-limit.d.ts"
|
|
36
|
+
},
|
|
37
|
+
"./middleware/session": {
|
|
38
|
+
"import": "./dist/middleware/session.js",
|
|
39
|
+
"types": "./dist/middleware/session.d.ts"
|
|
40
|
+
},
|
|
33
41
|
"./middleware/upload": {
|
|
34
42
|
"import": "./dist/middleware/upload.js",
|
|
35
43
|
"types": "./dist/middleware/upload.d.ts"
|
|
@@ -46,6 +54,10 @@
|
|
|
46
54
|
"import": "./dist/middleware/error-handler.js",
|
|
47
55
|
"types": "./dist/middleware/error-handler.d.ts"
|
|
48
56
|
},
|
|
57
|
+
"./devtools": {
|
|
58
|
+
"import": "./dist/devtools.js",
|
|
59
|
+
"types": "./dist/devtools.d.ts"
|
|
60
|
+
},
|
|
49
61
|
"./query": {
|
|
50
62
|
"import": "./dist/query/index.js",
|
|
51
63
|
"types": "./dist/query/index.d.ts"
|
|
@@ -58,7 +70,7 @@
|
|
|
58
70
|
"cookie-parser": "^1.4.7",
|
|
59
71
|
"multer": "^2.1.1",
|
|
60
72
|
"reflect-metadata": "^0.2.2",
|
|
61
|
-
"@forinda/kickjs-core": "0.
|
|
73
|
+
"@forinda/kickjs-core": "0.4.0"
|
|
62
74
|
},
|
|
63
75
|
"peerDependencies": {
|
|
64
76
|
"express": "^5.1.0"
|
|
@@ -82,6 +94,7 @@
|
|
|
82
94
|
"engines": {
|
|
83
95
|
"node": ">=20.0"
|
|
84
96
|
},
|
|
97
|
+
"homepage": "https://forinda.github.io/kick-js/",
|
|
85
98
|
"scripts": {
|
|
86
99
|
"build": "tsup",
|
|
87
100
|
"dev": "tsup --watch",
|
package/dist/chunk-75Z5FSZN.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
__name
|
|
3
|
-
} from "./chunk-7QVYU63E.js";
|
|
4
|
-
|
|
5
|
-
// src/middleware/upload.ts
|
|
6
|
-
import { unlink } from "fs/promises";
|
|
7
|
-
import multer from "multer";
|
|
8
|
-
function single(fieldName, options = {}) {
|
|
9
|
-
const m = createMulter(options);
|
|
10
|
-
return m.single(fieldName);
|
|
11
|
-
}
|
|
12
|
-
__name(single, "single");
|
|
13
|
-
function array(fieldName, maxCount = 10, options = {}) {
|
|
14
|
-
const m = createMulter(options);
|
|
15
|
-
return m.array(fieldName, maxCount);
|
|
16
|
-
}
|
|
17
|
-
__name(array, "array");
|
|
18
|
-
function none(options = {}) {
|
|
19
|
-
const m = createMulter(options);
|
|
20
|
-
return m.none();
|
|
21
|
-
}
|
|
22
|
-
__name(none, "none");
|
|
23
|
-
function createMulter(options) {
|
|
24
|
-
const limits = {
|
|
25
|
-
fileSize: options.maxSize ?? 5 * 1024 * 1024
|
|
26
|
-
};
|
|
27
|
-
const fileFilter = options.allowedTypes ? (_req, file, cb) => {
|
|
28
|
-
const allowed = options.allowedTypes.some((type) => {
|
|
29
|
-
if (type.endsWith("/*")) {
|
|
30
|
-
return file.mimetype.startsWith(type.replace("/*", "/"));
|
|
31
|
-
}
|
|
32
|
-
return file.mimetype === type;
|
|
33
|
-
});
|
|
34
|
-
if (allowed) {
|
|
35
|
-
cb(null, true);
|
|
36
|
-
} else {
|
|
37
|
-
cb(new Error(`File type ${file.mimetype} is not allowed`));
|
|
38
|
-
}
|
|
39
|
-
} : void 0;
|
|
40
|
-
const multerOptions = {
|
|
41
|
-
limits,
|
|
42
|
-
...fileFilter ? {
|
|
43
|
-
fileFilter
|
|
44
|
-
} : {},
|
|
45
|
-
...options.storage ? {
|
|
46
|
-
storage: options.storage
|
|
47
|
-
} : {},
|
|
48
|
-
...options.dest ? {
|
|
49
|
-
dest: options.dest
|
|
50
|
-
} : {}
|
|
51
|
-
};
|
|
52
|
-
return multer(multerOptions);
|
|
53
|
-
}
|
|
54
|
-
__name(createMulter, "createMulter");
|
|
55
|
-
function cleanupFiles() {
|
|
56
|
-
return (req, res, next) => {
|
|
57
|
-
res.on("finish", async () => {
|
|
58
|
-
const files = [];
|
|
59
|
-
if (req.file?.path) {
|
|
60
|
-
files.push(req.file);
|
|
61
|
-
}
|
|
62
|
-
if (Array.isArray(req.files)) {
|
|
63
|
-
for (const f of req.files) {
|
|
64
|
-
if (f?.path) files.push(f);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
for (const file of files) {
|
|
68
|
-
try {
|
|
69
|
-
await unlink(file.path);
|
|
70
|
-
} catch {
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
next();
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
__name(cleanupFiles, "cleanupFiles");
|
|
78
|
-
var upload = {
|
|
79
|
-
single,
|
|
80
|
-
array,
|
|
81
|
-
none
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
export {
|
|
85
|
-
cleanupFiles,
|
|
86
|
-
upload
|
|
87
|
-
};
|
|
88
|
-
//# sourceMappingURL=chunk-75Z5FSZN.js.map
|
|
@@ -1 +0,0 @@
|
|
|
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'\n\nexport interface UploadOptions {\n /** Max file size in bytes (default: 5MB) */\n maxSize?: number\n /** Allowed MIME types (default: all) */\n allowedTypes?: string[]\n /** Multer storage config (default: memory storage) */\n storage?: MulterOptions['storage']\n /** Multer dest for disk storage shorthand */\n dest?: string\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: ['image/*'] }))\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\nfunction createMulter(options: UploadOptions) {\n const limits: MulterOptions['limits'] = {\n fileSize: options.maxSize ?? 5 * 1024 * 1024,\n }\n\n const fileFilter: MulterOptions['fileFilter'] = options.allowedTypes\n ? (_req, file, cb) => {\n const allowed = options.allowedTypes!.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 : undefined\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 * Middleware that automatically cleans up uploaded files after the response\n * is sent. Attach this AFTER your upload middleware.\n *\n * Only cleans up disk-stored files (files with a `path` property).\n *\n * @example\n * ```ts\n * middleware: [\n * upload.single('file', { dest: '/tmp/uploads' }),\n * cleanupFiles(),\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/** Upload middleware factory with `.single()`, `.array()`, `.none()` methods */\nexport const upload = { single, array, none }\n"],"mappings":";;;;;AAAA,SAASA,cAAc;AAEvB,OAAOC,YAA+C;AAyBtD,SAASC,OAAOC,WAAmBC,UAAyB,CAAC,GAAC;AAC5D,QAAMC,IAAIC,aAAaF,OAAAA;AACvB,SAAOC,EAAEH,OAAOC,SAAAA;AAClB;AAHSD;AAQT,SAASK,MAAMJ,WAAmBK,WAAW,IAAIJ,UAAyB,CAAC,GAAC;AAC1E,QAAMC,IAAIC,aAAaF,OAAAA;AACvB,SAAOC,EAAEE,MAAMJ,WAAWK,QAAAA;AAC5B;AAHSD;AAQT,SAASE,KAAKL,UAAyB,CAAC,GAAC;AACvC,QAAMC,IAAIC,aAAaF,OAAAA;AACvB,SAAOC,EAAEI,KAAI;AACf;AAHSA;AAKT,SAASH,aAAaF,SAAsB;AAC1C,QAAMM,SAAkC;IACtCC,UAAUP,QAAQQ,WAAW,IAAI,OAAO;EAC1C;AAEA,QAAMC,aAA0CT,QAAQU,eACpD,CAACC,MAAMC,MAAMC,OAAAA;AACX,UAAMC,UAAUd,QAAQU,aAAcK,KAAK,CAACC,SAAAA;AAC1C,UAAIA,KAAKC,SAAS,IAAA,GAAO;AACvB,eAAOL,KAAKM,SAASC,WAAWH,KAAKI,QAAQ,MAAM,GAAA,CAAA;MACrD;AACA,aAAOR,KAAKM,aAAaF;IAC3B,CAAA;AACA,QAAIF,SAAS;AACXD,SAAG,MAAM,IAAA;IACX,OAAO;AACLA,SAAG,IAAIQ,MAAM,aAAaT,KAAKM,QAAQ,iBAAiB,CAAA;IAC1D;EACF,IACAI;AAEJ,QAAMC,gBAA+B;IACnCjB;IACA,GAAIG,aAAa;MAAEA;IAAW,IAAI,CAAC;IACnC,GAAIT,QAAQwB,UAAU;MAAEA,SAASxB,QAAQwB;IAAQ,IAAI,CAAC;IACtD,GAAIxB,QAAQyB,OAAO;MAAEA,MAAMzB,QAAQyB;IAAK,IAAI,CAAC;EAC/C;AAEA,SAAOC,OAAOH,aAAAA;AAChB;AA7BSrB;AA6CF,SAASyB,eAAAA;AACd,SAAO,CAACC,KAAcC,KAAeC,SAAAA;AACnCD,QAAIE,GAAG,UAAU,YAAA;AACf,YAAMC,QAAe,CAAA;AAErB,UAAKJ,IAAYhB,MAAMqB,MAAM;AAC3BD,cAAME,KAAMN,IAAYhB,IAAI;MAC9B;AACA,UAAIuB,MAAMC,QAASR,IAAYI,KAAK,GAAG;AACrC,mBAAWK,KAAMT,IAAYI,OAAO;AAClC,cAAIK,GAAGJ,KAAMD,OAAME,KAAKG,CAAAA;QAC1B;MACF;AAEA,iBAAWzB,QAAQoB,OAAO;AACxB,YAAI;AACF,gBAAMM,OAAO1B,KAAKqB,IAAI;QACxB,QAAQ;QAER;MACF;IACF,CAAA;AAEAH,SAAAA;EACF;AACF;AAzBgBH;AA4BT,IAAMY,SAAS;EAAEzC;EAAQK;EAAOE;AAAK;","names":["unlink","multer","single","fieldName","options","m","createMulter","array","maxCount","none","limits","fileSize","maxSize","fileFilter","allowedTypes","_req","file","cb","allowed","some","type","endsWith","mimetype","startsWith","replace","Error","undefined","multerOptions","storage","dest","multer","cleanupFiles","req","res","next","on","files","path","push","Array","isArray","f","unlink","upload"]}
|
package/dist/chunk-7QVYU63E.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/bootstrap.ts"],"sourcesContent":["import { createLogger } from '@forinda/kickjs-core'\nimport { Application, type ApplicationOptions } from './application'\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 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;AAG7B,IAAMC,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;AACTT,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;AAzCgBrB;","names":["createLogger","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 +0,0 @@
|
|
|
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 // ── 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;;;;;;;;;;;;;;;;;;;;;EAuBAE,GAAGC,aAA6C;AAC9C,WAAOC,WAAW,KAAKV,IAAIK,OAA8BI,WAAAA;EAC3D;;;EAKA,IAAIE,OAAY;AACd,WAAQ,KAAKX,IAAYW;EAC3B;;EAGA,IAAIC,QAA2B;AAC7B,WAAQ,KAAKZ,IAAYY;EAC3B;;EAIAC,IAAaC,KAA4B;AACvC,WAAO,KAAKhB,SAASe,IAAIC,GAAAA;EAC3B;EAEAC,IAAID,KAAaE,OAAkB;AACjC,SAAKlB,SAASiB,IAAID,KAAKE,KAAAA;EACzB;;EAIAC,KAAKC,MAAWC,SAAS,KAAK;AAC5B,WAAO,KAAKlB,IAAIkB,OAAOA,MAAAA,EAAQF,KAAKC,IAAAA;EACtC;EAEAE,QAAQF,MAAW;AACjB,WAAO,KAAKjB,IAAIkB,OAAO,GAAA,EAAKF,KAAKC,IAAAA;EACnC;EAEAG,YAAY;AACV,WAAO,KAAKpB,IAAIkB,OAAO,GAAA,EAAKG,IAAG;EACjC;EAEAC,SAASC,UAAU,aAAa;AAC9B,WAAO,KAAKvB,IAAIkB,OAAO,GAAA,EAAKF,KAAK;MAAEO;IAAQ,CAAA;EAC7C;EAEAC,WAAWD,SAAiB;AAC1B,WAAO,KAAKvB,IAAIkB,OAAO,GAAA,EAAKF,KAAK;MAAEO;IAAQ,CAAA;EAC7C;EAEAE,KAAKC,SAAiBR,SAAS,KAAK;AAClC,WAAO,KAAKlB,IAAIkB,OAAOA,MAAAA,EAAQS,KAAK,MAAA,EAAQC,KAAKF,OAAAA;EACnD;EAEAG,SAASC,QAAgBC,UAAkBC,cAAc,4BAA4B;AACnF,SAAKhC,IAAIiC,UAAU,uBAAuB,yBAAyBF,QAAAA,GAAW;AAC9E,SAAK/B,IAAIiC,UAAU,gBAAgBD,WAAAA;AACnC,WAAO,KAAKhC,IAAI4B,KAAKE,MAAAA;EACvB;AACF;","names":["RequestContext","metadata","Map","req","res","next","body","params","query","headers","requestId","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"]}
|
|
@@ -1 +0,0 @@
|
|
|
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} from '@forinda/kickjs-core'\nimport { RequestContext } from './context'\nimport { validate } from './middleware/validate'\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 // 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,gBAGK;AAKA,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,eAAWG,MAAM;SAAIf;SAAqBQ;OAAoB;AAC5DG,eAASE,KAAK,CAACG,KAAcC,KAAeC,SAAAA;AAC1C,cAAMC,MAAM,IAAIC,eAAeJ,KAAKC,KAAKC,IAAAA;AACzCG,gBAAQC,QAAQP,GAAGI,KAAKD,IAAAA,CAAAA,EAAOK,MAAML,IAAAA;MACvC,CAAA;IACF;AAGAP,aAASE,KAAK,OAAOG,KAAcC,KAAeC,SAAAA;AAChD,YAAMC,MAAM,IAAIC,eAAeJ,KAAKC,KAAKC,IAAAA;AACzC,UAAI;AACF,cAAMM,aAAa9B,UAAU4B,QAAQpC,eAAAA;AACrC,cAAMsC,WAAWtB,MAAMQ,WAAW,EAAES,GAAAA;MACtC,SAASM,KAAU;AACjBP,aAAKO,GAAAA;MACP;IACF,CAAA;AACEjC,WAAeW,MAAAA,EAAQI,UAAAA,GAAaI,QAAAA;EACxC;AAEA,SAAOnB;AACT;AAjDgBD;","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","mw","req","res","next","ctx","RequestContext","Promise","resolve","catch","controller","err"]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|