@forinda/kickjs-http 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/dist/application.d.ts +89 -0
  3. package/dist/application.js +10 -0
  4. package/dist/application.js.map +1 -0
  5. package/dist/bootstrap.d.ts +27 -0
  6. package/dist/bootstrap.js +11 -0
  7. package/dist/bootstrap.js.map +1 -0
  8. package/dist/chunk-75Z5FSZN.js +88 -0
  9. package/dist/chunk-75Z5FSZN.js.map +1 -0
  10. package/dist/chunk-7QVYU63E.js +7 -0
  11. package/dist/chunk-7QVYU63E.js.map +1 -0
  12. package/dist/chunk-BNWCVQQH.js +54 -0
  13. package/dist/chunk-BNWCVQQH.js.map +1 -0
  14. package/dist/chunk-I6UNTOQD.js +52 -0
  15. package/dist/chunk-I6UNTOQD.js.map +1 -0
  16. package/dist/chunk-JD2RKDKH.js +61 -0
  17. package/dist/chunk-JD2RKDKH.js.map +1 -0
  18. package/dist/chunk-JM7X7SAD.js +133 -0
  19. package/dist/chunk-JM7X7SAD.js.map +1 -0
  20. package/dist/chunk-KAWXFLFS.js +50 -0
  21. package/dist/chunk-KAWXFLFS.js.map +1 -0
  22. package/dist/chunk-P3YCN5LK.js +196 -0
  23. package/dist/chunk-P3YCN5LK.js.map +1 -0
  24. package/dist/chunk-RZUH6NBM.js +110 -0
  25. package/dist/chunk-RZUH6NBM.js.map +1 -0
  26. package/dist/chunk-U2JYL2NW.js +62 -0
  27. package/dist/chunk-U2JYL2NW.js.map +1 -0
  28. package/dist/chunk-ZI52TGQ4.js +22 -0
  29. package/dist/chunk-ZI52TGQ4.js.map +1 -0
  30. package/dist/context.d.ts +55 -0
  31. package/dist/context.js +9 -0
  32. package/dist/context.js.map +1 -0
  33. package/dist/index.d.ts +16 -0
  34. package/dist/index.js +64 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/middleware/csrf.d.ts +49 -0
  37. package/dist/middleware/csrf.js +8 -0
  38. package/dist/middleware/csrf.js.map +1 -0
  39. package/dist/middleware/error-handler.d.ts +8 -0
  40. package/dist/middleware/error-handler.js +10 -0
  41. package/dist/middleware/error-handler.js.map +1 -0
  42. package/dist/middleware/request-id.d.ts +7 -0
  43. package/dist/middleware/request-id.js +10 -0
  44. package/dist/middleware/request-id.js.map +1 -0
  45. package/dist/middleware/upload.d.ts +57 -0
  46. package/dist/middleware/upload.js +10 -0
  47. package/dist/middleware/upload.js.map +1 -0
  48. package/dist/middleware/validate.d.ts +15 -0
  49. package/dist/middleware/validate.js +8 -0
  50. package/dist/middleware/validate.js.map +1 -0
  51. package/dist/query/index.d.ts +47 -0
  52. package/dist/query/index.js +20 -0
  53. package/dist/query/index.js.map +1 -0
  54. package/dist/router-builder.d.ts +12 -0
  55. package/dist/router-builder.js +13 -0
  56. package/dist/router-builder.js.map +1 -0
  57. package/dist/types-Doz6f3AB.d.ts +72 -0
  58. package/package.json +94 -0
@@ -0,0 +1,50 @@
1
+ import {
2
+ Application
3
+ } from "./chunk-P3YCN5LK.js";
4
+ import {
5
+ __name
6
+ } from "./chunk-7QVYU63E.js";
7
+
8
+ // src/bootstrap.ts
9
+ import { createLogger } from "@forinda/kickjs-core";
10
+ var log = createLogger("Process");
11
+ function bootstrap(options) {
12
+ const g = globalThis;
13
+ if (!g.__kickBootstrapped) {
14
+ process.on("uncaughtException", (err) => {
15
+ log.error(err, "Uncaught exception");
16
+ });
17
+ process.on("unhandledRejection", (reason) => {
18
+ log.error(reason, "Unhandled rejection");
19
+ });
20
+ for (const signal of [
21
+ "SIGINT",
22
+ "SIGTERM"
23
+ ]) {
24
+ process.on(signal, async () => {
25
+ log.info(`Received ${signal}, shutting down...`);
26
+ if (g.__app) await g.__app.shutdown();
27
+ process.exit(0);
28
+ });
29
+ }
30
+ g.__kickBootstrapped = true;
31
+ }
32
+ if (g.__app) {
33
+ log.info("HMR: Rebuilding application...");
34
+ g.__app.rebuild();
35
+ return;
36
+ }
37
+ const app = new Application(options);
38
+ g.__app = app;
39
+ app.start();
40
+ const meta = import.meta;
41
+ if (meta.hot) {
42
+ meta.hot.accept();
43
+ }
44
+ }
45
+ __name(bootstrap, "bootstrap");
46
+
47
+ export {
48
+ bootstrap
49
+ };
50
+ //# sourceMappingURL=chunk-KAWXFLFS.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\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"]}
@@ -0,0 +1,196 @@
1
+ import {
2
+ errorHandler,
3
+ notFoundHandler
4
+ } from "./chunk-BNWCVQQH.js";
5
+ import {
6
+ requestId
7
+ } from "./chunk-ZI52TGQ4.js";
8
+ import {
9
+ __name
10
+ } from "./chunk-7QVYU63E.js";
11
+
12
+ // src/application.ts
13
+ import http from "http";
14
+ import express from "express";
15
+ import { Container, createLogger } from "@forinda/kickjs-core";
16
+ var log = createLogger("Application");
17
+ var Application = class {
18
+ static {
19
+ __name(this, "Application");
20
+ }
21
+ options;
22
+ app;
23
+ container;
24
+ httpServer = null;
25
+ adapters;
26
+ constructor(options) {
27
+ this.options = options;
28
+ this.app = express();
29
+ this.container = Container.getInstance();
30
+ this.adapters = options.adapters ?? [];
31
+ }
32
+ /**
33
+ * Full setup pipeline:
34
+ * 1. Adapter beforeMount hooks (early routes — docs, health)
35
+ * 2. Adapter middleware (phase: beforeGlobal)
36
+ * 3. Global middleware (user-declared or defaults)
37
+ * 4. Adapter middleware (phase: afterGlobal)
38
+ * 5. Module registration + DI bootstrap
39
+ * 6. Adapter middleware (phase: beforeRoutes)
40
+ * 7. Module route mounting
41
+ * 8. Adapter middleware (phase: afterRoutes)
42
+ * 9. Error handlers (notFound + global)
43
+ * 10. Adapter beforeStart hooks
44
+ */
45
+ setup() {
46
+ log.info("Bootstrapping application...");
47
+ const adapterMw = this.collectAdapterMiddleware();
48
+ for (const adapter of this.adapters) {
49
+ adapter.beforeMount?.(this.app, this.container);
50
+ }
51
+ this.app.disable("x-powered-by");
52
+ this.app.set("trust proxy", this.options.trustProxy ?? false);
53
+ this.mountMiddlewareList(adapterMw.beforeGlobal);
54
+ if (this.options.middleware) {
55
+ for (const entry of this.options.middleware) {
56
+ this.mountMiddlewareEntry(entry);
57
+ }
58
+ } else {
59
+ this.app.use(requestId());
60
+ this.app.use(express.json({
61
+ limit: this.options.jsonLimit ?? "100kb"
62
+ }));
63
+ }
64
+ this.mountMiddlewareList(adapterMw.afterGlobal);
65
+ const modules = this.options.modules.map((ModuleClass) => {
66
+ const mod = new ModuleClass();
67
+ mod.register(this.container);
68
+ return mod;
69
+ });
70
+ this.container.bootstrap();
71
+ this.mountMiddlewareList(adapterMw.beforeRoutes);
72
+ const apiPrefix = this.options.apiPrefix ?? "/api";
73
+ const defaultVersion = this.options.defaultVersion ?? 1;
74
+ for (const mod of modules) {
75
+ const result = mod.routes();
76
+ const routeSets = Array.isArray(result) ? result : [
77
+ result
78
+ ];
79
+ for (const route of routeSets) {
80
+ const version = route.version ?? defaultVersion;
81
+ const mountPath = `${apiPrefix}/v${version}${route.path}`;
82
+ this.app.use(mountPath, route.router);
83
+ if (route.controller) {
84
+ for (const adapter of this.adapters) {
85
+ adapter.onRouteMount?.(route.controller, mountPath);
86
+ }
87
+ }
88
+ }
89
+ }
90
+ this.mountMiddlewareList(adapterMw.afterRoutes);
91
+ this.app.use(notFoundHandler());
92
+ this.app.use(errorHandler());
93
+ for (const adapter of this.adapters) {
94
+ adapter.beforeStart?.(this.app, this.container);
95
+ }
96
+ }
97
+ /** Start the HTTP server, retrying up to 3 times on port conflict */
98
+ start() {
99
+ this.setup();
100
+ const basePort = this.options.port ?? parseInt(process.env.PORT || "3000", 10);
101
+ const maxRetries = 3;
102
+ const tryListen = /* @__PURE__ */ __name((port, attempt) => {
103
+ this.httpServer = http.createServer(this.app);
104
+ this.httpServer.on("error", (err) => {
105
+ if (err.code === "EADDRINUSE" && attempt < maxRetries) {
106
+ const nextPort = port + 1;
107
+ log.warn(`Port ${port} in use, trying ${nextPort}... (${attempt + 1}/${maxRetries})`);
108
+ tryListen(nextPort, attempt + 1);
109
+ } else {
110
+ throw err;
111
+ }
112
+ });
113
+ this.httpServer.listen(port, () => {
114
+ if (port !== basePort) {
115
+ log.warn(`Port ${basePort} was in use, using ${port} instead`);
116
+ }
117
+ log.info(`Server running on http://localhost:${port}`);
118
+ for (const adapter of this.adapters) {
119
+ adapter.afterStart?.(this.httpServer, this.container);
120
+ }
121
+ });
122
+ }, "tryListen");
123
+ tryListen(basePort, 0);
124
+ }
125
+ /** HMR rebuild: swap Express handler without restarting the server */
126
+ rebuild() {
127
+ Container.reset();
128
+ this.container = Container.getInstance();
129
+ this.app = express();
130
+ this.setup();
131
+ if (this.httpServer) {
132
+ this.httpServer.removeAllListeners("request");
133
+ this.httpServer.on("request", this.app);
134
+ log.info("HMR: Express app rebuilt and swapped");
135
+ }
136
+ }
137
+ /** Graceful shutdown — runs all adapter shutdowns in parallel, resilient to failures */
138
+ async shutdown() {
139
+ log.info("Shutting down...");
140
+ const results = await Promise.allSettled(this.adapters.map((adapter) => Promise.resolve(adapter.shutdown?.())));
141
+ for (const result of results) {
142
+ if (result.status === "rejected") {
143
+ log.error({
144
+ err: result.reason
145
+ }, "Adapter shutdown failed");
146
+ }
147
+ }
148
+ if (this.httpServer) {
149
+ await new Promise((resolve) => this.httpServer.close(() => resolve()));
150
+ }
151
+ }
152
+ getExpressApp() {
153
+ return this.app;
154
+ }
155
+ getHttpServer() {
156
+ return this.httpServer;
157
+ }
158
+ // ── Internal helpers ────────────────────────────────────────────────
159
+ collectAdapterMiddleware() {
160
+ const result = {
161
+ beforeGlobal: [],
162
+ afterGlobal: [],
163
+ beforeRoutes: [],
164
+ afterRoutes: []
165
+ };
166
+ for (const adapter of this.adapters) {
167
+ const entries = adapter.middleware?.() ?? [];
168
+ for (const entry of entries) {
169
+ const phase = entry.phase ?? "afterGlobal";
170
+ result[phase].push(entry);
171
+ }
172
+ }
173
+ return result;
174
+ }
175
+ mountMiddlewareList(entries) {
176
+ for (const entry of entries) {
177
+ if (entry.path) {
178
+ this.app.use(entry.path, entry.handler);
179
+ } else {
180
+ this.app.use(entry.handler);
181
+ }
182
+ }
183
+ }
184
+ mountMiddlewareEntry(entry) {
185
+ if (typeof entry === "function") {
186
+ this.app.use(entry);
187
+ } else {
188
+ this.app.use(entry.path, entry.handler);
189
+ }
190
+ }
191
+ };
192
+
193
+ export {
194
+ Application
195
+ };
196
+ //# sourceMappingURL=chunk-P3YCN5LK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/application.ts"],"sourcesContent":["import http from 'node:http'\nimport express, { type Express, type RequestHandler } from 'express'\nimport {\n Container,\n createLogger,\n type AppModuleClass,\n type AppAdapter,\n type AdapterMiddleware,\n} from '@forinda/kickjs-core'\nimport { buildRoutes } from './router-builder'\nimport { requestId } from './middleware/request-id'\nimport { notFoundHandler, errorHandler } from './middleware/error-handler'\n\nconst log = createLogger('Application')\n\n/**\n * A middleware entry in the declarative pipeline.\n * Can be a bare handler or an object with path scoping.\n */\nexport type MiddlewareEntry = RequestHandler | { path: string; handler: RequestHandler }\n\nexport interface ApplicationOptions {\n /** Feature modules to load */\n modules: AppModuleClass[]\n /** Adapters that hook into the lifecycle (DB, Redis, Swagger, etc.) */\n adapters?: AppAdapter[]\n /** Server port (falls back to PORT env var, then 3000) */\n port?: number\n /** Global API prefix (default: '/api') */\n apiPrefix?: string\n /** Default API version (default: 1) — routes become /{prefix}/v{version}/{path} */\n defaultVersion?: number\n\n /**\n * Global middleware pipeline. Declared in order.\n * Replaces the hardcoded middleware stack — you control exactly what runs.\n *\n * @example\n * ```ts\n * bootstrap({\n * modules,\n * middleware: [\n * helmet(),\n * cors(),\n * compression(),\n * morgan('dev'),\n * express.json({ limit: '1mb' }),\n * ],\n * })\n * ```\n *\n * If omitted, a sensible default is applied:\n * requestId(), express.json({ limit: '100kb' })\n */\n middleware?: MiddlewareEntry[]\n\n /** Express `trust proxy` setting */\n trustProxy?: boolean | number | string | ((ip: string, hopIndex: number) => boolean)\n /** Maximum JSON body size (only used when middleware is not provided) */\n jsonLimit?: string | number\n}\n\n/**\n * The main application class. Wires together Express, the DI container,\n * feature modules, adapters, and the middleware pipeline.\n */\nexport class Application {\n private app: Express\n private container: Container\n private httpServer: http.Server | null = null\n private adapters: AppAdapter[]\n\n constructor(private readonly options: ApplicationOptions) {\n this.app = express()\n this.container = Container.getInstance()\n this.adapters = options.adapters ?? []\n }\n\n /**\n * Full setup pipeline:\n * 1. Adapter beforeMount hooks (early routes — docs, health)\n * 2. Adapter middleware (phase: beforeGlobal)\n * 3. Global middleware (user-declared or defaults)\n * 4. Adapter middleware (phase: afterGlobal)\n * 5. Module registration + DI bootstrap\n * 6. Adapter middleware (phase: beforeRoutes)\n * 7. Module route mounting\n * 8. Adapter middleware (phase: afterRoutes)\n * 9. Error handlers (notFound + global)\n * 10. Adapter beforeStart hooks\n */\n setup(): void {\n log.info('Bootstrapping application...')\n\n // Collect adapter middleware by phase\n const adapterMw = this.collectAdapterMiddleware()\n\n // ── 1. Adapter beforeMount hooks ──────────────────────────────────\n for (const adapter of this.adapters) {\n adapter.beforeMount?.(this.app, this.container)\n }\n\n // ── 2. Hardened defaults ──────────────────────────────────────────\n this.app.disable('x-powered-by')\n this.app.set('trust proxy', this.options.trustProxy ?? false)\n\n // ── 3. Adapter middleware: beforeGlobal ───────────────────────────\n this.mountMiddlewareList(adapterMw.beforeGlobal)\n\n // ── 4. Global middleware ─────────────────────────────────────────\n if (this.options.middleware) {\n // User-declared pipeline — full control\n for (const entry of this.options.middleware) {\n this.mountMiddlewareEntry(entry)\n }\n } else {\n // Sensible defaults when no middleware declared\n this.app.use(requestId())\n this.app.use(express.json({ limit: this.options.jsonLimit ?? '100kb' }))\n }\n\n // ── 5. Adapter middleware: afterGlobal ────────────────────────────\n this.mountMiddlewareList(adapterMw.afterGlobal)\n\n // ── 6. Module registration + DI bootstrap ────────────────────────\n const modules = this.options.modules.map((ModuleClass) => {\n const mod = new ModuleClass()\n mod.register(this.container)\n return mod\n })\n this.container.bootstrap()\n\n // ── 7. Adapter middleware: beforeRoutes ───────────────────────────\n this.mountMiddlewareList(adapterMw.beforeRoutes)\n\n // ── 8. Mount module routes with versioning ───────────────────────\n const apiPrefix = this.options.apiPrefix ?? '/api'\n const defaultVersion = this.options.defaultVersion ?? 1\n\n for (const mod of modules) {\n const result = mod.routes()\n const routeSets = Array.isArray(result) ? result : [result]\n\n for (const route of routeSets) {\n const version = route.version ?? defaultVersion\n const mountPath = `${apiPrefix}/v${version}${route.path}`\n this.app.use(mountPath, route.router)\n\n // Notify adapters (e.g. SwaggerAdapter for OpenAPI spec generation)\n if (route.controller) {\n for (const adapter of this.adapters) {\n adapter.onRouteMount?.(route.controller, mountPath)\n }\n }\n }\n }\n\n // ── 9. Adapter middleware: afterRoutes ────────────────────────────\n this.mountMiddlewareList(adapterMw.afterRoutes)\n\n // ── 10. Error handlers ───────────────────────────────────────────\n this.app.use(notFoundHandler())\n this.app.use(errorHandler())\n\n // ── 11. Adapter beforeStart hooks ────────────────────────────────\n for (const adapter of this.adapters) {\n adapter.beforeStart?.(this.app, this.container)\n }\n }\n\n /** Start the HTTP server, retrying up to 3 times on port conflict */\n start(): void {\n this.setup()\n\n const basePort = this.options.port ?? parseInt(process.env.PORT || '3000', 10)\n const maxRetries = 3\n\n const tryListen = (port: number, attempt: number) => {\n this.httpServer = http.createServer(this.app)\n\n this.httpServer.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE' && attempt < maxRetries) {\n const nextPort = port + 1\n log.warn(`Port ${port} in use, trying ${nextPort}... (${attempt + 1}/${maxRetries})`)\n tryListen(nextPort, attempt + 1)\n } else {\n throw err\n }\n })\n\n this.httpServer.listen(port, () => {\n if (port !== basePort) {\n log.warn(`Port ${basePort} was in use, using ${port} instead`)\n }\n log.info(`Server running on http://localhost:${port}`)\n\n for (const adapter of this.adapters) {\n adapter.afterStart?.(this.httpServer!, this.container)\n }\n })\n }\n\n tryListen(basePort, 0)\n }\n\n /** HMR rebuild: swap Express handler without restarting the server */\n rebuild(): void {\n // Reset the DI container so singletons are re-created with fresh code\n Container.reset()\n this.container = Container.getInstance()\n\n this.app = express()\n this.setup()\n\n if (this.httpServer) {\n this.httpServer.removeAllListeners('request')\n this.httpServer.on('request', this.app)\n log.info('HMR: Express app rebuilt and swapped')\n }\n }\n\n /** Graceful shutdown — runs all adapter shutdowns in parallel, resilient to failures */\n async shutdown(): Promise<void> {\n log.info('Shutting down...')\n\n // Run all adapter shutdowns concurrently — don't let one failure block the rest\n const results = await Promise.allSettled(\n this.adapters.map((adapter) => Promise.resolve(adapter.shutdown?.())),\n )\n for (const result of results) {\n if (result.status === 'rejected') {\n log.error({ err: result.reason }, 'Adapter shutdown failed')\n }\n }\n\n if (this.httpServer) {\n await new Promise<void>((resolve) => this.httpServer!.close(() => resolve()))\n }\n }\n\n getExpressApp(): Express {\n return this.app\n }\n\n getHttpServer(): http.Server | null {\n return this.httpServer\n }\n\n // ── Internal helpers ────────────────────────────────────────────────\n\n private collectAdapterMiddleware() {\n const result = {\n beforeGlobal: [] as AdapterMiddleware[],\n afterGlobal: [] as AdapterMiddleware[],\n beforeRoutes: [] as AdapterMiddleware[],\n afterRoutes: [] as AdapterMiddleware[],\n }\n\n for (const adapter of this.adapters) {\n const entries = adapter.middleware?.() ?? []\n for (const entry of entries) {\n const phase = entry.phase ?? 'afterGlobal'\n result[phase].push(entry)\n }\n }\n\n return result\n }\n\n private mountMiddlewareList(entries: AdapterMiddleware[]): void {\n for (const entry of entries) {\n if (entry.path) {\n this.app.use(entry.path, entry.handler)\n } else {\n this.app.use(entry.handler)\n }\n }\n }\n\n private mountMiddlewareEntry(entry: MiddlewareEntry): void {\n if (typeof entry === 'function') {\n this.app.use(entry)\n } else {\n this.app.use(entry.path, entry.handler)\n }\n }\n}\n"],"mappings":"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","names":["http","express","Container","createLogger","log","createLogger","Application","app","container","httpServer","adapters","options","express","Container","getInstance","setup","info","adapterMw","collectAdapterMiddleware","adapter","beforeMount","disable","set","trustProxy","mountMiddlewareList","beforeGlobal","middleware","entry","mountMiddlewareEntry","use","requestId","json","limit","jsonLimit","afterGlobal","modules","map","ModuleClass","mod","register","bootstrap","beforeRoutes","apiPrefix","defaultVersion","result","routes","routeSets","Array","isArray","route","version","mountPath","path","router","controller","onRouteMount","afterRoutes","notFoundHandler","errorHandler","beforeStart","start","basePort","port","parseInt","process","env","PORT","maxRetries","tryListen","attempt","http","createServer","on","err","code","nextPort","warn","listen","afterStart","rebuild","reset","removeAllListeners","shutdown","results","Promise","allSettled","resolve","status","error","reason","close","getExpressApp","getHttpServer","entries","phase","push","handler"]}
@@ -0,0 +1,110 @@
1
+ import {
2
+ parseQuery
3
+ } from "./chunk-JM7X7SAD.js";
4
+ import {
5
+ __name
6
+ } from "./chunk-7QVYU63E.js";
7
+
8
+ // src/context.ts
9
+ var RequestContext = class {
10
+ static {
11
+ __name(this, "RequestContext");
12
+ }
13
+ req;
14
+ res;
15
+ next;
16
+ metadata = /* @__PURE__ */ new Map();
17
+ constructor(req, res, next) {
18
+ this.req = req;
19
+ this.res = res;
20
+ this.next = next;
21
+ }
22
+ // ── Request Data ────────────────────────────────────────────────────
23
+ get body() {
24
+ return this.req.body;
25
+ }
26
+ get params() {
27
+ return this.req.params;
28
+ }
29
+ get query() {
30
+ return this.req.query;
31
+ }
32
+ get headers() {
33
+ return this.req.headers;
34
+ }
35
+ get requestId() {
36
+ return this.req.requestId ?? this.req.headers["x-request-id"];
37
+ }
38
+ // ── Query String Parsing ───────────────────────────────────────────
39
+ /**
40
+ * Parse the request query string into structured filters, sort, pagination, and search.
41
+ * Pass the result to an ORM query builder adapter (Drizzle, Prisma, Sequelize, etc.).
42
+ *
43
+ * @param fieldConfig - Optional whitelist for filterable, sortable, and searchable fields
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * @Get('/')
48
+ * async list(ctx: RequestContext) {
49
+ * const parsed = ctx.qs({
50
+ * filterable: ['status', 'priority'],
51
+ * sortable: ['createdAt', 'title'],
52
+ * })
53
+ * const q = drizzleAdapter.build(parsed, { columns })
54
+ * // ... use q.where, q.orderBy, q.limit, q.offset
55
+ * }
56
+ * ```
57
+ */
58
+ qs(fieldConfig) {
59
+ return parseQuery(this.req.query, fieldConfig);
60
+ }
61
+ // ── File Uploads ────────────────────────────────────────────────────
62
+ /** Single uploaded file (requires @FileUpload({ mode: 'single' })) */
63
+ get file() {
64
+ return this.req.file;
65
+ }
66
+ /** Array of uploaded files (requires @FileUpload({ mode: 'array' })) */
67
+ get files() {
68
+ return this.req.files;
69
+ }
70
+ // ── Metadata Store ──────────────────────────────────────────────────
71
+ get(key) {
72
+ return this.metadata.get(key);
73
+ }
74
+ set(key, value) {
75
+ this.metadata.set(key, value);
76
+ }
77
+ // ── Response Helpers ────────────────────────────────────────────────
78
+ json(data, status = 200) {
79
+ return this.res.status(status).json(data);
80
+ }
81
+ created(data) {
82
+ return this.res.status(201).json(data);
83
+ }
84
+ noContent() {
85
+ return this.res.status(204).end();
86
+ }
87
+ notFound(message = "Not Found") {
88
+ return this.res.status(404).json({
89
+ message
90
+ });
91
+ }
92
+ badRequest(message) {
93
+ return this.res.status(400).json({
94
+ message
95
+ });
96
+ }
97
+ html(content, status = 200) {
98
+ return this.res.status(status).type("html").send(content);
99
+ }
100
+ download(buffer, filename, contentType = "application/octet-stream") {
101
+ this.res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
102
+ this.res.setHeader("Content-Type", contentType);
103
+ return this.res.send(buffer);
104
+ }
105
+ };
106
+
107
+ export {
108
+ RequestContext
109
+ };
110
+ //# sourceMappingURL=chunk-RZUH6NBM.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 // ── 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"]}
@@ -0,0 +1,62 @@
1
+ import {
2
+ validate
3
+ } from "./chunk-JD2RKDKH.js";
4
+ import {
5
+ RequestContext
6
+ } from "./chunk-RZUH6NBM.js";
7
+ import {
8
+ __name
9
+ } from "./chunk-7QVYU63E.js";
10
+
11
+ // src/router-builder.ts
12
+ import "reflect-metadata";
13
+ import { Router } from "express";
14
+ import { Container, METADATA } from "@forinda/kickjs-core";
15
+ function getControllerPath(controllerClass) {
16
+ return Reflect.getMetadata(METADATA.CONTROLLER_PATH, controllerClass) || "/";
17
+ }
18
+ __name(getControllerPath, "getControllerPath");
19
+ function buildRoutes(controllerClass) {
20
+ const router = Router();
21
+ const container = Container.getInstance();
22
+ const controllerPath = getControllerPath(controllerClass);
23
+ const routes = Reflect.getMetadata(METADATA.ROUTES, controllerClass) || [];
24
+ const classMiddlewares = Reflect.getMetadata(METADATA.CLASS_MIDDLEWARES, controllerClass) || [];
25
+ for (const route of routes) {
26
+ const method = route.method.toLowerCase();
27
+ let routePath = route.path === "/" ? "" : route.path;
28
+ const fullPath = controllerPath === "/" ? routePath || "/" : controllerPath + routePath;
29
+ const methodMiddlewares = Reflect.getMetadata(METADATA.METHOD_MIDDLEWARES, controllerClass, route.handlerName) || [];
30
+ const handlers = [];
31
+ if (route.validation) {
32
+ handlers.push(validate(route.validation));
33
+ }
34
+ for (const mw of [
35
+ ...classMiddlewares,
36
+ ...methodMiddlewares
37
+ ]) {
38
+ handlers.push((req, res, next) => {
39
+ const ctx = new RequestContext(req, res, next);
40
+ Promise.resolve(mw(ctx, next)).catch(next);
41
+ });
42
+ }
43
+ handlers.push(async (req, res, next) => {
44
+ const ctx = new RequestContext(req, res, next);
45
+ try {
46
+ const controller = container.resolve(controllerClass);
47
+ await controller[route.handlerName](ctx);
48
+ } catch (err) {
49
+ next(err);
50
+ }
51
+ });
52
+ router[method](fullPath, ...handlers);
53
+ }
54
+ return router;
55
+ }
56
+ __name(buildRoutes, "buildRoutes");
57
+
58
+ export {
59
+ getControllerPath,
60
+ buildRoutes
61
+ };
62
+ //# sourceMappingURL=chunk-U2JYL2NW.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} 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"]}
@@ -0,0 +1,22 @@
1
+ import {
2
+ __name
3
+ } from "./chunk-7QVYU63E.js";
4
+
5
+ // src/middleware/request-id.ts
6
+ import { randomUUID } from "crypto";
7
+ var REQUEST_ID_HEADER = "x-request-id";
8
+ function requestId() {
9
+ return (req, res, next) => {
10
+ const id = req.headers[REQUEST_ID_HEADER] || randomUUID();
11
+ req.requestId = id;
12
+ res.setHeader(REQUEST_ID_HEADER, id);
13
+ next();
14
+ };
15
+ }
16
+ __name(requestId, "requestId");
17
+
18
+ export {
19
+ REQUEST_ID_HEADER,
20
+ requestId
21
+ };
22
+ //# sourceMappingURL=chunk-ZI52TGQ4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware/request-id.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\nimport type { Request, Response, NextFunction } from 'express'\n\nexport const REQUEST_ID_HEADER = 'x-request-id'\n\n/** Middleware that generates or propagates a unique request ID */\nexport function requestId() {\n return (req: Request, res: Response, next: NextFunction) => {\n const id = (req.headers[REQUEST_ID_HEADER] as string) || randomUUID()\n // Attach to req as a dedicated property (don't mutate headers)\n ;(req as any).requestId = id\n res.setHeader(REQUEST_ID_HEADER, id)\n next()\n }\n}\n"],"mappings":";;;;;AAAA,SAASA,kBAAkB;AAGpB,IAAMC,oBAAoB;AAG1B,SAASC,YAAAA;AACd,SAAO,CAACC,KAAcC,KAAeC,SAAAA;AACnC,UAAMC,KAAMH,IAAII,QAAQN,iBAAAA,KAAiCO,WAAAA;AAEvDL,QAAYD,YAAYI;AAC1BF,QAAIK,UAAUR,mBAAmBK,EAAAA;AACjCD,SAAAA;EACF;AACF;AARgBH;","names":["randomUUID","REQUEST_ID_HEADER","requestId","req","res","next","id","headers","randomUUID","setHeader"]}
@@ -0,0 +1,55 @@
1
+ import * as http from 'http';
2
+ import { Request, Response, NextFunction } from 'express';
3
+ import { d as QueryFieldConfig, c as ParsedQuery } from './types-Doz6f3AB.js';
4
+
5
+ /**
6
+ * Unified request/response abstraction passed to every controller method.
7
+ * Shields handlers from raw Express objects and provides convenience methods.
8
+ */
9
+ declare class RequestContext<TBody = any, TParams = any, TQuery = any> {
10
+ readonly req: Request;
11
+ readonly res: Response;
12
+ readonly next: NextFunction;
13
+ private metadata;
14
+ constructor(req: Request, res: Response, next: NextFunction);
15
+ get body(): TBody;
16
+ get params(): TParams;
17
+ get query(): TQuery;
18
+ get headers(): http.IncomingHttpHeaders;
19
+ get requestId(): string | undefined;
20
+ /**
21
+ * Parse the request query string into structured filters, sort, pagination, and search.
22
+ * Pass the result to an ORM query builder adapter (Drizzle, Prisma, Sequelize, etc.).
23
+ *
24
+ * @param fieldConfig - Optional whitelist for filterable, sortable, and searchable fields
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * @Get('/')
29
+ * async list(ctx: RequestContext) {
30
+ * const parsed = ctx.qs({
31
+ * filterable: ['status', 'priority'],
32
+ * sortable: ['createdAt', 'title'],
33
+ * })
34
+ * const q = drizzleAdapter.build(parsed, { columns })
35
+ * // ... use q.where, q.orderBy, q.limit, q.offset
36
+ * }
37
+ * ```
38
+ */
39
+ qs(fieldConfig?: QueryFieldConfig): ParsedQuery;
40
+ /** Single uploaded file (requires @FileUpload({ mode: 'single' })) */
41
+ get file(): any;
42
+ /** Array of uploaded files (requires @FileUpload({ mode: 'array' })) */
43
+ get files(): any[] | undefined;
44
+ get<T = any>(key: string): T | undefined;
45
+ set(key: string, value: any): void;
46
+ json(data: any, status?: number): Response<any, Record<string, any>>;
47
+ created(data: any): Response<any, Record<string, any>>;
48
+ noContent(): Response<any, Record<string, any>>;
49
+ notFound(message?: string): Response<any, Record<string, any>>;
50
+ badRequest(message: string): Response<any, Record<string, any>>;
51
+ html(content: string, status?: number): Response<any, Record<string, any>>;
52
+ download(buffer: Buffer, filename: string, contentType?: string): Response<any, Record<string, any>>;
53
+ }
54
+
55
+ export { RequestContext };
@@ -0,0 +1,9 @@
1
+ import {
2
+ RequestContext
3
+ } from "./chunk-RZUH6NBM.js";
4
+ import "./chunk-JM7X7SAD.js";
5
+ import "./chunk-7QVYU63E.js";
6
+ export {
7
+ RequestContext
8
+ };
9
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,16 @@
1
+ export { Application, ApplicationOptions, MiddlewareEntry } from './application.js';
2
+ export { bootstrap } from './bootstrap.js';
3
+ export { RequestContext } from './context.js';
4
+ export { buildRoutes, getControllerPath } from './router-builder.js';
5
+ export { REQUEST_ID_HEADER, requestId } from './middleware/request-id.js';
6
+ export { validate } from './middleware/validate.js';
7
+ export { errorHandler, notFoundHandler } from './middleware/error-handler.js';
8
+ export { CsrfOptions, csrf } from './middleware/csrf.js';
9
+ export { UploadOptions, cleanupFiles, upload } from './middleware/upload.js';
10
+ export { buildQueryParams, parseFilters, parsePagination, parseQuery, parseSearchQuery, parseSort } from './query/index.js';
11
+ 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
+ import 'node:http';
13
+ import 'express';
14
+ import '@forinda/kickjs-core';
15
+ import 'http';
16
+ import 'multer';