@forinda/kickjs 7.4.0 → 8.0.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/README.md +1 -0
- package/dist/{adapter-DB1jE7nd.d.mts → adapter-BuOTeSQy.d.mts} +5 -4
- package/dist/{adapter-DB1jE7nd.d.mts.map → adapter-BuOTeSQy.d.mts.map} +1 -1
- package/dist/{app-module-Cr4qnc9a.d.mts → app-module-DZW0uYNA.d.mts} +39 -3
- package/dist/{app-module-Cr4qnc9a.d.mts.map → app-module-DZW0uYNA.d.mts.map} +1 -1
- package/dist/{application-BjsHkh3L.d.mts → application-D1koPFmH.d.mts} +15 -15
- package/dist/application-D1koPFmH.d.mts.map +1 -0
- package/dist/application-IWSzCdB8.mjs +12 -0
- package/dist/application-IWSzCdB8.mjs.map +1 -0
- package/dist/{bootstrap-CK8OXB9Y.mjs → bootstrap-BG86IC_L.mjs} +3 -3
- package/dist/{bootstrap-CK8OXB9Y.mjs.map → bootstrap-BG86IC_L.mjs.map} +1 -1
- package/dist/{bootstrap-DnAlEMPs.d.mts → bootstrap-BIjHgG4v.d.mts} +2 -2
- package/dist/bootstrap-BIjHgG4v.d.mts.map +1 -0
- package/dist/{client-ip-De2ynoAd.mjs → client-ip-C909xeVL.mjs} +2 -2
- package/dist/{client-ip-De2ynoAd.mjs.map → client-ip-C909xeVL.mjs.map} +1 -1
- package/dist/config/index.d.mts +1 -1
- package/dist/config/index.mjs +2 -2
- package/dist/config-ClZsO9eK.mjs +12 -0
- package/dist/{config-BxW5Zr48.mjs.map → config-ClZsO9eK.mjs.map} +1 -1
- package/dist/{container-CGkGmNHM.mjs → container-GbeejFhH.mjs} +3 -3
- package/dist/{container-CGkGmNHM.mjs.map → container-GbeejFhH.mjs.map} +1 -1
- package/dist/{context-Dh01xr3X.mjs → context-BnIvtMUl.mjs} +3 -3
- package/dist/context-BnIvtMUl.mjs.map +1 -0
- package/dist/{context-errors--pMnF37M.mjs → context-errors-vB3ZRXSu.mjs} +2 -2
- package/dist/{context-errors--pMnF37M.mjs.map → context-errors-vB3ZRXSu.mjs.map} +1 -1
- package/dist/core/adapter.d.mts +1 -1
- package/dist/core/adapter.mjs +1 -1
- package/dist/core/app-module.d.mts +2 -2
- package/dist/core/app-module.mjs +1 -1
- package/dist/core/cache.mjs +1 -1
- package/dist/core/container.mjs +2 -2
- package/dist/core/cron.mjs +2 -2
- package/dist/core/decorators.d.mts +1 -1
- package/dist/core/decorators.mjs +2 -2
- package/dist/core/decorators.mjs.map +1 -1
- package/dist/core/errors.mjs +1 -1
- package/dist/core/interfaces.mjs +1 -1
- package/dist/core/logger.mjs +1 -1
- package/dist/core/path.mjs +1 -1
- package/dist/core/plugin.d.mts +1 -1
- package/dist/core/plugin.mjs +1 -1
- package/dist/core/reactivity.mjs +1 -1
- package/dist/{core-BGsV3RjD.mjs → core-DVbWxSyr.mjs} +3 -3
- package/dist/{core-BGsV3RjD.mjs.map → core-DVbWxSyr.mjs.map} +1 -1
- package/dist/{cors-BuDU9CpA.d.mts → cors-CsibNTQ6.d.mts} +3 -3
- package/dist/{cors-BuDU9CpA.d.mts.map → cors-CsibNTQ6.d.mts.map} +1 -1
- package/dist/csrf-DGZqpNSq.mjs +12 -0
- package/dist/csrf-DGZqpNSq.mjs.map +1 -0
- package/dist/{csrf-vR23CJas.d.mts → csrf-DwOhkOC6.d.mts} +3 -3
- package/dist/csrf-DwOhkOC6.d.mts.map +1 -0
- package/dist/decorate-DDLimr-C.mjs +11 -0
- package/dist/{decorators-Bks7cgBJ.d.mts → decorators-CbTBUVOu.d.mts} +10 -1
- package/dist/decorators-CbTBUVOu.d.mts.map +1 -0
- package/dist/{define-adapter-VXKsMUNb.mjs → define-adapter-BIwBwOK_.mjs} +2 -2
- package/dist/{define-adapter-VXKsMUNb.mjs.map → define-adapter-BIwBwOK_.mjs.map} +1 -1
- package/dist/{disposables-Bwm-JdIp.mjs → disposables-Ht0M5KKc.mjs} +2 -2
- package/dist/{disposables-Bwm-JdIp.mjs.map → disposables-Ht0M5KKc.mjs.map} +1 -1
- package/dist/{env-BG0pRPr7.mjs → env-CabVU6cy.mjs} +3 -3
- package/dist/{env-BG0pRPr7.mjs.map → env-CabVU6cy.mjs.map} +1 -1
- package/dist/{error-handler-BE1iKFde.d.mts → error-handler-C-xGxZKP.d.mts} +24 -5
- package/dist/error-handler-C-xGxZKP.d.mts.map +1 -0
- package/dist/handler-9qFN7nRw.mjs +13 -0
- package/dist/handler-9qFN7nRw.mjs.map +1 -0
- package/dist/{helmet-CFQUTVig.d.mts → helmet-C2QEU6S-.d.mts} +5 -3
- package/dist/helmet-C2QEU6S-.d.mts.map +1 -0
- package/dist/http/application.d.mts +1 -1
- package/dist/http/application.mjs +2 -2
- package/dist/http/bootstrap.d.mts +1 -1
- package/dist/http/bootstrap.mjs +2 -2
- package/dist/http/context.d.mts +2 -2
- package/dist/http/context.mjs +2 -2
- package/dist/http/middleware/cors.d.mts +1 -1
- package/dist/http/middleware/cors.mjs +1 -1
- package/dist/http/middleware/cors.mjs.map +1 -1
- package/dist/http/middleware/csrf.d.mts +1 -1
- package/dist/http/middleware/csrf.mjs +2 -2
- package/dist/http/middleware/error-handler.d.mts +2 -2
- package/dist/http/middleware/error-handler.mjs +2 -2
- package/dist/http/middleware/error-handler.mjs.map +1 -1
- package/dist/http/middleware/helmet.d.mts +2 -2
- package/dist/http/middleware/helmet.mjs +2 -2
- package/dist/http/middleware/helmet.mjs.map +1 -1
- package/dist/http/middleware/rate-limit.d.mts +1 -1
- package/dist/http/middleware/rate-limit.mjs +2 -2
- package/dist/http/middleware/rate-limit.mjs.map +1 -1
- package/dist/http/middleware/request-id.mjs +1 -1
- package/dist/http/middleware/request-logger.d.mts +2 -2
- package/dist/http/middleware/request-logger.mjs +2 -2
- package/dist/http/middleware/request-logger.mjs.map +1 -1
- package/dist/http/middleware/session.d.mts +1 -1
- package/dist/http/middleware/session.mjs +2 -2
- package/dist/http/middleware/session.mjs.map +1 -1
- package/dist/http/middleware/spa.d.mts +1 -1
- package/dist/http/middleware/spa.mjs +2 -2
- package/dist/http/middleware/upload.d.mts +1 -1
- package/dist/http/middleware/upload.mjs +3 -3
- package/dist/http/middleware/upload.mjs.map +1 -1
- package/dist/http/middleware/validate.mjs +1 -1
- package/dist/http/middleware/views.d.mts +1 -1
- package/dist/http/middleware/views.mjs +2 -2
- package/dist/http/query/index.mjs +2 -2
- package/dist/http/router-builder.d.mts +1 -1
- package/dist/http/router-builder.mjs +2 -2
- package/dist/http/runtimes/fastify.d.mts +13 -1
- package/dist/http/runtimes/fastify.d.mts.map +1 -1
- package/dist/http/runtimes/fastify.mjs +3 -3
- package/dist/http/runtimes/fastify.mjs.map +1 -1
- package/dist/http/runtimes/h3-web.d.mts +1 -1
- package/dist/http/runtimes/h3-web.mjs +2 -2
- package/dist/http/runtimes/h3.d.mts +1 -1
- package/dist/http/runtimes/h3.d.mts.map +1 -1
- package/dist/http/runtimes/h3.mjs +2 -2
- package/dist/http/runtimes/h3.mjs.map +1 -1
- package/dist/{index-M6TF75MS.d.mts → index-Di64XOKG.d.mts} +3 -3
- package/dist/{index-M6TF75MS.d.mts.map → index-Di64XOKG.d.mts.map} +1 -1
- package/dist/index.d.mts +18 -18
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/{kv-stores-CgwQScG3.d.mts → kv-stores-CbsEHE5J.d.mts} +6 -6
- package/dist/{kv-stores-CgwQScG3.d.mts.map → kv-stores-CbsEHE5J.d.mts.map} +1 -1
- package/dist/{kv-stores-Dgfu4Kgr.mjs → kv-stores-pjWD0-ri.mjs} +2 -2
- package/dist/kv-stores-pjWD0-ri.mjs.map +1 -0
- package/dist/{metadata-B5J7GRqy.mjs → metadata-CeUKYNRZ.mjs} +2 -2
- package/dist/{metadata-B5J7GRqy.mjs.map → metadata-CeUKYNRZ.mjs.map} +1 -1
- package/dist/{module-registry-wgY_h4QH.mjs → module-registry-DIngF0Ne.mjs} +2 -2
- package/dist/{module-registry-wgY_h4QH.mjs.map → module-registry-DIngF0Ne.mjs.map} +1 -1
- package/dist/{query-CrSAtLqV.mjs → query-BzyWJrd6.mjs} +2 -2
- package/dist/{query-CrSAtLqV.mjs.map → query-BzyWJrd6.mjs.map} +1 -1
- package/dist/{rate-limit-Dme747JJ.d.mts → rate-limit-D_cKx6Uw.d.mts} +3 -3
- package/dist/{rate-limit-Dme747JJ.d.mts.map → rate-limit-D_cKx6Uw.d.mts.map} +1 -1
- package/dist/{reply-DVH0keKZ.mjs → reply-CdVb6ipw.mjs} +3 -3
- package/dist/reply-CdVb6ipw.mjs.map +1 -0
- package/dist/{request-logger-D_pf4xzE.d.mts → request-logger-TgcKRNDD.d.mts} +16 -3
- package/dist/request-logger-TgcKRNDD.d.mts.map +1 -0
- package/dist/respond-ERWwiws9.mjs +12 -0
- package/dist/respond-ERWwiws9.mjs.map +1 -0
- package/dist/{router-builder-BlReYbNA.d.mts → router-builder-DptndWhd.d.mts} +2 -2
- package/dist/{router-builder-BlReYbNA.d.mts.map → router-builder-DptndWhd.d.mts.map} +1 -1
- package/dist/{router-builder-A4jcCl4J.mjs → router-builder-FukfDDLt.mjs} +3 -3
- package/dist/{router-builder-A4jcCl4J.mjs.map → router-builder-FukfDDLt.mjs.map} +1 -1
- package/dist/{session-z9iewOXA.d.mts → session-BHgRyfqZ.d.mts} +2 -2
- package/dist/{session-z9iewOXA.d.mts.map → session-BHgRyfqZ.d.mts.map} +1 -1
- package/dist/{spa-1Uc4QIif.d.mts → spa-DhGwM3_A.d.mts} +75 -17
- package/dist/spa-DhGwM3_A.d.mts.map +1 -0
- package/dist/{upload-aQzE6lT0.d.mts → upload-CbJOK-j9.d.mts} +3 -3
- package/dist/{upload-aQzE6lT0.d.mts.map → upload-CbJOK-j9.d.mts.map} +1 -1
- package/dist/{upload-config-BvSLBwom.mjs → upload-config-DtnX4uVR.mjs} +2 -2
- package/dist/{upload-config-BvSLBwom.mjs.map → upload-config-DtnX4uVR.mjs.map} +1 -1
- package/dist/web.d.mts +6 -6
- package/dist/web.d.mts.map +1 -1
- package/dist/web.mjs +2 -2
- package/dist/web.mjs.map +1 -1
- package/package.json +5 -5
- package/dist/application-BjsHkh3L.d.mts.map +0 -1
- package/dist/application-DiDdUVvX.mjs +0 -12
- package/dist/application-DiDdUVvX.mjs.map +0 -1
- package/dist/bootstrap-DnAlEMPs.d.mts.map +0 -1
- package/dist/config-BxW5Zr48.mjs +0 -12
- package/dist/context-Dh01xr3X.mjs.map +0 -1
- package/dist/csrf-vR23CJas.d.mts.map +0 -1
- package/dist/csrf-z7Dd3k3U.mjs +0 -12
- package/dist/csrf-z7Dd3k3U.mjs.map +0 -1
- package/dist/decorators-Bks7cgBJ.d.mts.map +0 -1
- package/dist/error-handler-BE1iKFde.d.mts.map +0 -1
- package/dist/handler-D34xTXN1.mjs +0 -13
- package/dist/handler-D34xTXN1.mjs.map +0 -1
- package/dist/helmet-CFQUTVig.d.mts.map +0 -1
- package/dist/kv-stores-Dgfu4Kgr.mjs.map +0 -1
- package/dist/reply-DVH0keKZ.mjs.map +0 -1
- package/dist/request-logger-D_pf4xzE.d.mts.map +0 -1
- package/dist/spa-1Uc4QIif.d.mts.map +0 -1
|
@@ -6,6 +6,18 @@
|
|
|
6
6
|
* connect middleware the raw node objects — so anything Express-only is
|
|
7
7
|
* `undefined` there.
|
|
8
8
|
*/
|
|
9
|
+
/**
|
|
10
|
+
* The request shape this middleware reads.
|
|
11
|
+
*
|
|
12
|
+
* Exported because it appears in the returned handler's signature: a consumer
|
|
13
|
+
* following the documented `src/middleware/index.ts` layout exports the
|
|
14
|
+
* middleware array with an inferred type, and a type the entry does not
|
|
15
|
+
* re-export cannot be named there — `TS4023: Exported variable 'middleware'
|
|
16
|
+
* has or is using name 'LoggedRequest' … but cannot be named`.
|
|
17
|
+
*
|
|
18
|
+
* Deliberately structural rather than Express's `Request`: the Fastify and h3
|
|
19
|
+
* runtimes pass a raw Node request, which has `url` but no `path`.
|
|
20
|
+
*/
|
|
9
21
|
interface LoggedRequest {
|
|
10
22
|
method?: string;
|
|
11
23
|
url?: string;
|
|
@@ -15,6 +27,7 @@ interface LoggedRequest {
|
|
|
15
27
|
originalUrl?: string;
|
|
16
28
|
headers: Record<string, string | string[] | undefined>;
|
|
17
29
|
}
|
|
30
|
+
/** The response shape this middleware reads. Exported for the same reason as {@link LoggedRequest}. */
|
|
18
31
|
interface LoggedResponse {
|
|
19
32
|
statusCode: number;
|
|
20
33
|
on(event: 'finish', listener: () => void): unknown;
|
|
@@ -33,7 +46,7 @@ interface RequestLoggerOptions {
|
|
|
33
46
|
* @example
|
|
34
47
|
* ```ts
|
|
35
48
|
* bootstrap({
|
|
36
|
-
*
|
|
49
|
+
* middlewares: [requestId(), requestLogger(), express.json()],
|
|
37
50
|
* })
|
|
38
51
|
* ```
|
|
39
52
|
*
|
|
@@ -45,5 +58,5 @@ interface RequestLoggerOptions {
|
|
|
45
58
|
*/
|
|
46
59
|
declare function requestLogger(options?: RequestLoggerOptions): (req: LoggedRequest, res: LoggedResponse, next: () => void) => void;
|
|
47
60
|
//#endregion
|
|
48
|
-
export { requestLogger as n, RequestLoggerOptions as t };
|
|
49
|
-
//# sourceMappingURL=request-logger-
|
|
61
|
+
export { requestLogger as i, LoggedResponse as n, RequestLoggerOptions as r, LoggedRequest as t };
|
|
62
|
+
//# sourceMappingURL=request-logger-TgcKRNDD.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-logger-TgcKRNDD.d.mts","names":[],"sources":["../src/http/middleware/request-logger.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;UAkBiB;EACf;EACA;;EAEA;;EAEA;EACA,SAAS;;;UAIM;EACf;EACA,GAAG,iBAAiB;;UAWL;;EAEf;;EAEA;;EAEA;;;;;;;;;;;;;;;;;;iBAmBc,cAAc,UAAS,wBAAyB,KAKjD,eAAa,KAAO,gBAAc"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @forinda/kickjs v8.0.0
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) Felix Orinda
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*
|
|
9
|
+
* @license MIT
|
|
10
|
+
*/
|
|
11
|
+
function readCookies(req){let source=req;if(source.cookies)return source.cookies;let header=source.headers?.cookie;return typeof header==`string`?parseCookieHeader(header):{}}function parseCookieHeader(header){let out={};for(let pair of header.split(`;`)){let eq=pair.indexOf(`=`);if(eq===-1)continue;let key=pair.slice(0,eq).trim();if(!key)continue;let value=pair.slice(eq+1).trim();value.startsWith(`"`)&&value.endsWith(`"`)&&(value=value.slice(1,-1));try{out[key]=decodeURIComponent(value)}catch{out[key]=value}}return out}function serializeCookie(name,value,options){let parts=[`${name}=${encodeURIComponent(value)}`];if(options.maxAge!==void 0&&parts.push(`Max-Age=${Math.floor(options.maxAge/1e3)}`),options.expires&&parts.push(`Expires=${options.expires.toUTCString()}`),options.domain&&parts.push(`Domain=${options.domain}`),parts.push(`Path=${options.path??`/`}`),options.httpOnly&&parts.push(`HttpOnly`),options.secure&&parts.push(`Secure`),options.sameSite){let value=options.sameSite===!0?`Strict`:options.sameSite;parts.push(`SameSite=${value.charAt(0).toUpperCase()}${value.slice(1)}`)}return parts.join(`; `)}function setCookie(res,name,value,options={}){let target=res;if(typeof target.cookie==`function`){target.cookie(name,value,options);return}let cookie=serializeCookie(name,value,options),prior=target.getHeader?.(`Set-Cookie`),all=prior===void 0?[cookie]:[...Array.isArray(prior)?prior:[prior],cookie];target.setHeader?.(`Set-Cookie`,all)}function sendJson(res,status,body){let target=res;if(typeof target.status==`function`){let chained=target.status(status);if(typeof chained?.json==`function`){chained.json(body);return}}target.statusCode=status,target.getHeader?.(`content-type`)||target.setHeader?.(`content-type`,`application/json; charset=utf-8`),target.end?.(JSON.stringify(body))}export{setCookie as i,readCookies as n,sendJson as r,parseCookieHeader as t};
|
|
12
|
+
//# sourceMappingURL=respond-ERWwiws9.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"respond-ERWwiws9.mjs","names":[],"sources":["../src/http/middleware/respond.ts"],"sourcesContent":["/**\n * Response helpers that work on every runtime.\n *\n * Connect middleware receives an Express response under Express and a raw\n * `ServerResponse` under Fastify and h3. `res.cookie()` and `res.status().json()`\n * are Express conveniences that do not exist on the raw object, so a shipped\n * middleware reaching for them works on one engine and throws on the other two:\n *\n * csrf → `res.cookie()` threw before any check ran, so EVERY request\n * became a 500 — including safe methods the middleware is\n * supposed to wave through\n * rateLimit → `res.status()` threw only once the limit was hit, and the\n * throw left the request hanging rather than answering 429\n * session → `res.cookie()` threw on every response that issued a session\n *\n * These take the Express path when it is there — so behaviour under Express is\n * byte-identical to before — and fall back to the Node primitives otherwise.\n *\n * @module @forinda/kickjs/http/middleware/respond\n */\n\n/**\n * Cookies on the request, whoever parsed them.\n *\n * `req.cookies` exists only when an upstream cookie parser populated it —\n * `cookie-parser` under Express, nothing at all under Fastify and h3, which\n * pass a raw node request. A middleware that reads only `req.cookies` therefore\n * sees no cookies on those runtimes, and none under Express either unless the\n * app happens to mount a parser.\n *\n * For `csrf` that broke the double-submit flow outright: with no cookie visible\n * it minted a fresh token on every request and compared the submitted header\n * against that new value, so a client could never complete a protected request.\n */\nexport function readCookies(req: unknown): Record<string, string> {\n const source = req as { cookies?: Record<string, string>; headers?: Record<string, unknown> }\n if (source.cookies) return source.cookies\n const header = source.headers?.cookie\n return typeof header === 'string' ? parseCookieHeader(header) : {}\n}\n\n/** `a=1; b=2` → `{ a: '1', b: '2' }`. */\nexport function parseCookieHeader(header: string): Record<string, string> {\n const out: Record<string, string> = {}\n for (const pair of header.split(';')) {\n const eq = pair.indexOf('=')\n if (eq === -1) continue\n const key = pair.slice(0, eq).trim()\n if (!key) continue\n let value = pair.slice(eq + 1).trim()\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1)\n }\n try {\n out[key] = decodeURIComponent(value)\n } catch {\n // A malformed escape is not a reason to lose the whole cookie jar.\n out[key] = value\n }\n }\n return out\n}\n\n/** Cookie attributes, matching the subset of Express's `res.cookie` we use. */\nexport interface CookieOptions {\n maxAge?: number\n expires?: Date\n domain?: string\n path?: string\n httpOnly?: boolean\n secure?: boolean\n sameSite?: boolean | 'lax' | 'strict' | 'none'\n}\n\ninterface AnyResponse {\n cookie?: (name: string, value: string, options?: CookieOptions) => unknown\n status?: (code: number) => { json?: (body: unknown) => unknown }\n statusCode?: number\n getHeader?: (name: string) => unknown\n setHeader?: (name: string, value: unknown) => unknown\n end?: (chunk?: unknown) => unknown\n}\n\n/** `Set-Cookie` value for one cookie, per RFC 6265 §4.1. */\nfunction serializeCookie(name: string, value: string, options: CookieOptions): string {\n const parts = [`${name}=${encodeURIComponent(value)}`]\n // Express takes `maxAge` in milliseconds; `Max-Age` is seconds.\n if (options.maxAge !== undefined) parts.push(`Max-Age=${Math.floor(options.maxAge / 1000)}`)\n if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`)\n if (options.domain) parts.push(`Domain=${options.domain}`)\n parts.push(`Path=${options.path ?? '/'}`)\n if (options.httpOnly) parts.push('HttpOnly')\n if (options.secure) parts.push('Secure')\n if (options.sameSite) {\n // `true` means Strict, matching Express.\n const value = options.sameSite === true ? 'Strict' : options.sameSite\n parts.push(`SameSite=${value.charAt(0).toUpperCase()}${value.slice(1)}`)\n }\n return parts.join('; ')\n}\n\n/** Set a cookie without assuming Express. Appends, never replaces. */\nexport function setCookie(\n res: unknown,\n name: string,\n value: string,\n options: CookieOptions = {},\n): void {\n const target = res as AnyResponse\n if (typeof target.cookie === 'function') {\n target.cookie(name, value, options)\n return\n }\n const cookie = serializeCookie(name, value, options)\n const prior = target.getHeader?.('Set-Cookie')\n const all = prior === undefined ? [cookie] : [...(Array.isArray(prior) ? prior : [prior]), cookie]\n target.setHeader?.('Set-Cookie', all)\n}\n\n/** Send a JSON body with a status, without assuming Express. */\nexport function sendJson(res: unknown, status: number, body: unknown): void {\n const target = res as AnyResponse\n if (typeof target.status === 'function') {\n const chained = target.status(status)\n if (typeof chained?.json === 'function') {\n chained.json(body)\n return\n }\n }\n target.statusCode = status\n if (!target.getHeader?.('content-type')) {\n target.setHeader?.('content-type', 'application/json; charset=utf-8')\n }\n target.end?.(JSON.stringify(body))\n}\n"],"mappings":";;;;;;;;;;AAkCA,SAAgB,YAAY,IAAsC,CAChE,IAAM,OAAS,IACf,GAAI,OAAO,QAAS,OAAO,OAAO,QAClC,IAAM,OAAS,OAAO,SAAS,OAC/B,OAAO,OAAO,QAAW,SAAW,kBAAkB,MAAM,EAAI,CAAC,CACnE,CAGA,SAAgB,kBAAkB,OAAwC,CACxE,IAAM,IAA8B,CAAC,EACrC,IAAK,IAAM,QAAQ,OAAO,MAAM,GAAG,EAAG,CACpC,IAAM,GAAK,KAAK,QAAQ,GAAG,EAC3B,GAAI,KAAO,GAAI,SACf,IAAM,IAAM,KAAK,MAAM,EAAG,EAAE,CAAC,CAAC,KAAK,EACnC,GAAI,CAAC,IAAK,SACV,IAAI,MAAQ,KAAK,MAAM,GAAK,CAAC,CAAC,CAAC,KAAK,EAChC,MAAM,WAAW,GAAG,GAAK,MAAM,SAAS,GAAG,IAC7C,MAAQ,MAAM,MAAM,EAAG,EAAE,GAE3B,GAAI,CACF,IAAI,KAAO,mBAAmB,KAAK,CACrC,MAAQ,CAEN,IAAI,KAAO,KACb,CACF,CACA,OAAO,GACT,CAuBA,SAAS,gBAAgB,KAAc,MAAe,QAAgC,CACpF,IAAM,MAAQ,CAAC,GAAG,KAAK,GAAG,mBAAmB,KAAK,GAAG,EAQrD,GANI,QAAQ,SAAW,IAAA,IAAW,MAAM,KAAK,WAAW,KAAK,MAAM,QAAQ,OAAS,GAAI,GAAG,EACvF,QAAQ,SAAS,MAAM,KAAK,WAAW,QAAQ,QAAQ,YAAY,GAAG,EACtE,QAAQ,QAAQ,MAAM,KAAK,UAAU,QAAQ,QAAQ,EACzD,MAAM,KAAK,QAAQ,QAAQ,MAAQ,KAAK,EACpC,QAAQ,UAAU,MAAM,KAAK,UAAU,EACvC,QAAQ,QAAQ,MAAM,KAAK,QAAQ,EACnC,QAAQ,SAAU,CAEpB,IAAM,MAAQ,QAAQ,WAAa,GAAO,SAAW,QAAQ,SAC7D,MAAM,KAAK,YAAY,MAAM,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,MAAM,MAAM,CAAC,GAAG,CACzE,CACA,OAAO,MAAM,KAAK,IAAI,CACxB,CAGA,SAAgB,UACd,IACA,KACA,MACA,QAAyB,CAAC,EACpB,CACN,IAAM,OAAS,IACf,GAAI,OAAO,OAAO,QAAW,WAAY,CACvC,OAAO,OAAO,KAAM,MAAO,OAAO,EAClC,MACF,CACA,IAAM,OAAS,gBAAgB,KAAM,MAAO,OAAO,EAC7C,MAAQ,OAAO,YAAY,YAAY,EACvC,IAAM,QAAU,IAAA,GAAY,CAAC,MAAM,EAAI,CAAC,GAAI,MAAM,QAAQ,KAAK,EAAI,MAAQ,CAAC,KAAK,EAAI,MAAM,EACjG,OAAO,YAAY,aAAc,GAAG,CACtC,CAGA,SAAgB,SAAS,IAAc,OAAgB,KAAqB,CAC1E,IAAM,OAAS,IACf,GAAI,OAAO,OAAO,QAAW,WAAY,CACvC,IAAM,QAAU,OAAO,OAAO,MAAM,EACpC,GAAI,OAAO,SAAS,MAAS,WAAY,CACvC,QAAQ,KAAK,IAAI,EACjB,MACF,CACF,CACA,OAAO,WAAa,OACf,OAAO,YAAY,cAAc,GACpC,OAAO,YAAY,eAAgB,iCAAiC,EAEtE,OAAO,MAAM,KAAK,UAAU,IAAI,CAAC,CACnC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { L as SourcedRegistration, m as RouteEntry } from "./adapter-
|
|
2
|
+
import { L as SourcedRegistration, m as RouteEntry } from "./adapter-BuOTeSQy.mjs";
|
|
3
3
|
//#region src/http/router-builder.d.ts
|
|
4
4
|
/** @internal — set by Application.setup() before each `mod.routes()` call. */
|
|
5
5
|
declare function _setExternalContributorSources(sources: readonly SourcedRegistration[]): void;
|
|
@@ -46,4 +46,4 @@ interface BuildRoutesOptions {
|
|
|
46
46
|
declare function buildRouteTable(controllerClass: any, options?: BuildRoutesOptions): RouteEntry[];
|
|
47
47
|
//#endregion
|
|
48
48
|
export { buildRouteTable as i, _setExternalContributorSources as n, assertRouteUnique as r, BuildRoutesOptions as t };
|
|
49
|
-
//# sourceMappingURL=router-builder-
|
|
49
|
+
//# sourceMappingURL=router-builder-DptndWhd.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router-builder-
|
|
1
|
+
{"version":3,"file":"router-builder-DptndWhd.d.mts","names":[],"sources":["../src/http/router-builder.ts"],"mappings":";;;;iBA+BgB,+BAA+B,kBAAkB;;;;;;;;;;;iBAcjD,kBACd,UAAU,qBACV,gBACA,kBACA;UAUe;;;;;;;EAOf,2BAA2B;;;;;;;;;;;;;;;;;;;;;;iBAuBb,gBACd,sBACA,UAAS,qBACR"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs
|
|
2
|
+
* @forinda/kickjs v8.0.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{METADATA}from"./core/interfaces.mjs";import{o as duplicateRouteError,t as Container}from"./container-
|
|
12
|
-
//# sourceMappingURL=router-builder-
|
|
11
|
+
import{METADATA}from"./core/interfaces.mjs";import{o as duplicateRouteError,t as Container}from"./container-GbeejFhH.mjs";import{a as getMethodMeta,o as getMethodMetaOrUndefined,t as getClassMeta}from"./metadata-CeUKYNRZ.mjs";import{i as MissingContributorError,n as DuplicateContributorError,t as ContributorCycleError}from"./context-errors-vB3ZRXSu.mjs";const PRECEDENCE_ORDER=[`method`,`class`,`module`,`adapter`,`global`];function buildPipeline(sources,options={}){let route=options.route,bySource=new Map;for(let src of PRECEDENCE_ORDER)bySource.set(src,[]);for(let entry of sources)bySource.get(entry.source).push(entry);let winners=new Map;for(let src of PRECEDENCE_ORDER){let rows=bySource.get(src),keysAtThisLevel=new Map;for(let row of rows){let key=row.registration.key,existing=keysAtThisLevel.get(key)??[];existing.push(row),keysAtThisLevel.set(key,existing)}for(let[key,rowsForKey]of keysAtThisLevel){if(rowsForKey.length>1)throw new DuplicateContributorError(key,rowsForKey.map((r,i)=>r.label??`${src}#${i}`));winners.has(key)||winners.set(key,rowsForKey[0])}}let survivors=[...winners.values()].map(r=>r.registration),allKeys=new Set(survivors.map(r=>r.key));for(let reg of survivors)for(let dep of reg.dependsOn)if(!allKeys.has(dep))throw new MissingContributorError(dep,reg.key,route,reg.definedAt);let sorted=topoSort(survivors,route);return Object.freeze({contributors:Object.freeze(sorted),keys:allKeys})}function topoSort(registrations,route){let indegree=new Map,dependents=new Map,byKey=new Map;for(let reg of registrations)indegree.set(reg.key,reg.dependsOn.length),byKey.set(reg.key,reg);for(let reg of registrations)for(let dep of reg.dependsOn){let list=dependents.get(dep)??[];list.push(reg),dependents.set(dep,list)}let queue=[];for(let reg of registrations)indegree.get(reg.key)===0&&queue.push(reg);let sorted=[];for(;queue.length>0;){let node=queue.shift();sorted.push(node);for(let dependent of dependents.get(node.key)??[]){let next=(indegree.get(dependent.key)??0)-1;indegree.set(dependent.key,next),next===0&&queue.push(dependent)}}if(sorted.length!==registrations.length)throw new ContributorCycleError(reconstructCycle(registrations.filter(r=>(indegree.get(r.key)??0)>0),byKey),route);return sorted}function reconstructCycle(residual,byKey){let residualKeys=new Set(residual.map(r=>r.key)),start=residual[0].key,path=[start],seen=new Set([start]),cursor=start;for(;;){let nextDep=byKey.get(cursor).dependsOn.find(d=>residualKeys.has(d));if(nextDep===void 0)break;if(seen.has(nextDep)){let cycleStart=path.indexOf(nextDep);return[...path.slice(cycleStart),nextDep]}path.push(nextDep),seen.add(nextDep),cursor=nextDep}return path}async function runContributors(options){let{pipeline,ctx,container}=options;for(let reg of pipeline.contributors)await runOne(reg,ctx,container)}async function runOne(reg,ctx,container){let value;try{let deps=resolveDeps(reg,container);value=await reg.resolve(ctx,deps)}catch(err){if(reg.optional)return;if(reg.onError){let replacement=await reg.onError(err,ctx);if(replacement===void 0)return;ctx.set(reg.key,replacement);return}throw err}ctx.set(reg.key,value)}function resolveDeps(reg,container){let out={};for(let[key,token]of Object.entries(reg.deps))out[key]=container.resolve(token);return out}let _externalContributorSources=[];function _setExternalContributorSources(sources){_externalContributorSources=sources}function assertRouteUnique(registry,method,fullPath,owner){let key=`${method.toUpperCase()} ${fullPath.replace(/:[^/]+/g,`:*`)}`,prior=registry.get(key);if(prior!==void 0)throw duplicateRouteError(method.toUpperCase(),fullPath,owner,prior);registry.set(key,owner)}function buildRouteTable(controllerClass,options={}){let container=Container.getInstance(),externalSources=options.externalSources??_externalContributorSources,routes=getClassMeta(METADATA.ROUTES,controllerClass,[]),classMiddlewares=getClassMeta(METADATA.CLASS_MIDDLEWARES,controllerClass,[]),classContributors=getClassMeta(METADATA.CLASS_CONTRIBUTORS,controllerClass,[]),entries=[];for(let route of routes){let method=route.method.toUpperCase(),fullPath=route.path||`/`,methodMiddlewares=getMethodMeta(METADATA.METHOD_MIDDLEWARES,controllerClass,route.handlerName,[]),fileUploadConfig=getMethodMetaOrUndefined(METADATA.FILE_UPLOAD,controllerClass,route.handlerName),methodContributors=getMethodMeta(METADATA.METHOD_CONTRIBUTORS,controllerClass,route.handlerName,[]),contributorRunner=null;if(classContributors.length>0||methodContributors.length>0||externalSources.length>0){let pipeline=buildPipeline([...methodContributors.map((registration,i)=>({source:`method`,registration,label:`${controllerClass.name}.${String(route.handlerName)}#${i}(${registration.key})`})),...classContributors.map((registration,i)=>({source:`class`,registration,label:`${controllerClass.name}.@class#${i}(${registration.key})`})),...externalSources],{route:`${method} ${fullPath}`});contributorRunner=ctx=>runContributors({pipeline,ctx,container})}let handler=ctx=>container.resolve(controllerClass)[route.handlerName](ctx);entries.push({method,path:fullPath,middlewares:[...classMiddlewares,...methodMiddlewares],contributorRunner,handler,meta:{controller:controllerClass,handlerName:route.handlerName,validation:route.validation,upload:fileUploadConfig}})}return entries}export{buildPipeline as a,runContributors as i,assertRouteUnique as n,buildRouteTable as r,_setExternalContributorSources as t};
|
|
12
|
+
//# sourceMappingURL=router-builder-FukfDDLt.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router-builder-A4jcCl4J.mjs","names":[],"sources":["../src/core/contributor-pipeline.ts","../src/core/contributor-runner.ts","../src/http/router-builder.ts"],"sourcesContent":["import type { AnyContributorRegistration } from './context-decorator'\nimport {\n ContributorCycleError,\n DuplicateContributorError,\n MissingContributorError,\n} from './context-errors'\n\n/**\n * Where a contributor was registered, ordered by precedence\n * (lower index wins). Mirrors `architecture.md` §20.4.\n *\n * Levels (highest → lowest precedence):\n * - `'method'` — `@LoadX` on a controller method.\n * - `'class'` — `@LoadX` on a controller class.\n * - `'module'` — returned by `AppModule.contributors?()`. Per-module scope.\n * - `'adapter'` — returned by `AppAdapter.contributors?()`. Cross-cutting, applies\n * to every module's routes.\n * - `'global'` — `ApplicationOptions.contributors`. App-wide default.\n */\nexport type ContributorSource = 'method' | 'class' | 'module' | 'adapter' | 'global'\n\n/** Precedence-ranked source list. Index = precedence; method (0) wins ties. */\nconst PRECEDENCE_ORDER: readonly ContributorSource[] = [\n 'method',\n 'class',\n 'module',\n 'adapter',\n 'global',\n]\n\n/**\n * Input row to {@link buildPipeline}. Pairs a registration with its origin\n * so dedup can apply the precedence rule and diagnostic errors can name\n * the offending site.\n *\n * `registration` is the type-erased {@link AnyContributorRegistration}\n * — the runner doesn't care about each registration's narrow `D` /\n * `Ctx` shape, and adopters frequently mix HTTP / transport-agnostic\n * contributors in the same `externalSources` / `contributors()`\n * array. The narrow generics from `defineContextDecorator` survive\n * at the *definition* site (where `resolve` is typed); they're erased\n * here so collections compose without `as any` casts.\n */\nexport interface SourcedRegistration {\n registration: AnyContributorRegistration\n source: ContributorSource\n /** Human label surfaced in DuplicateContributorError messages. */\n label?: string\n}\n\n/**\n * Resolved, validated, topo-sorted pipeline ready for execution.\n *\n * `contributors` is in execution order — every entry's `dependsOn`\n * keys appear earlier. `keys` is the set of all keys this pipeline\n * produces; the runner uses it for fast existence checks and the\n * builder uses it for missing-dep validation.\n */\nexport interface ContributorPipeline {\n readonly contributors: readonly AnyContributorRegistration[]\n readonly keys: ReadonlySet<string>\n}\n\nexport interface BuildPipelineOptions {\n /** Optional route identifier surfaced in diagnostic errors (e.g., 'GET /projects/:id'). */\n route?: string\n}\n\n/**\n * Build, validate, and topo-sort a contributor pipeline.\n *\n * Pipeline:\n *\n * 1. Group sources by precedence (method > class > module > adapter > global).\n * 2. Within each precedence level, two contributors with the same `key`\n * raise {@link DuplicateContributorError}.\n * 3. Across levels, the highest-precedence contributor wins each key\n * (lower-precedence duplicates are silently dropped — that is the\n * documented override mechanism, not an error).\n * 4. Every key referenced by `dependsOn` must be produced by the\n * surviving set, otherwise {@link MissingContributorError}.\n * 5. Kahn's algorithm topo-sorts the survivors. Any nodes left after\n * the queue drains form a cycle → {@link ContributorCycleError}\n * with a reconstructed path.\n *\n * Pure function — no DI, no I/O. Safe to call once at startup per\n * route and cache the result.\n */\nexport function buildPipeline(\n sources: readonly SourcedRegistration[],\n options: BuildPipelineOptions = {},\n): ContributorPipeline {\n const route = options.route\n\n // Step 1: group by source.\n const bySource = new Map<ContributorSource, SourcedRegistration[]>()\n for (const src of PRECEDENCE_ORDER) bySource.set(src, [])\n for (const entry of sources) bySource.get(entry.source)!.push(entry)\n\n // Step 2 + 3: dedup with precedence. We walk highest precedence first,\n // claim its keys, and drop lower-precedence rows that conflict.\n const winners = new Map<string, SourcedRegistration>()\n for (const src of PRECEDENCE_ORDER) {\n const rows = bySource.get(src)!\n const keysAtThisLevel = new Map<string, SourcedRegistration[]>()\n for (const row of rows) {\n const key = row.registration.key\n const existing = keysAtThisLevel.get(key) ?? []\n existing.push(row)\n keysAtThisLevel.set(key, existing)\n }\n for (const [key, rowsForKey] of keysAtThisLevel) {\n if (rowsForKey.length > 1) {\n const labels = rowsForKey.map((r, i) => r.label ?? `${src}#${i}`)\n throw new DuplicateContributorError(key, labels)\n }\n if (!winners.has(key)) winners.set(key, rowsForKey[0])\n }\n }\n\n const survivors = [...winners.values()].map((r) => r.registration)\n const allKeys = new Set(survivors.map((r) => r.key))\n\n // Step 4: dependsOn validation.\n for (const reg of survivors) {\n for (const dep of reg.dependsOn) {\n if (!allKeys.has(dep)) {\n throw new MissingContributorError(dep, reg.key, route, reg.definedAt)\n }\n }\n }\n\n // Step 5: Kahn topo-sort.\n const sorted = topoSort(survivors, route)\n\n return Object.freeze({\n contributors: Object.freeze(sorted),\n keys: allKeys,\n })\n}\n\n/**\n * Kahn's algorithm with deterministic ordering on ties.\n *\n * Nodes with no remaining incoming edges enter the queue in input\n * order. Cycles surface as nodes still present after the queue\n * drains; we reconstruct one cycle path for diagnostics.\n */\nfunction topoSort(\n registrations: readonly AnyContributorRegistration[],\n route: string | undefined,\n): AnyContributorRegistration[] {\n // indegree[key] = number of unresolved dependsOn entries for key\n const indegree = new Map<string, number>()\n // dependents[depKey] = list of registrations that depend on depKey\n const dependents = new Map<string, AnyContributorRegistration[]>()\n const byKey = new Map<string, AnyContributorRegistration>()\n\n for (const reg of registrations) {\n indegree.set(reg.key, reg.dependsOn.length)\n byKey.set(reg.key, reg)\n }\n for (const reg of registrations) {\n for (const dep of reg.dependsOn) {\n const list = dependents.get(dep) ?? []\n list.push(reg)\n dependents.set(dep, list)\n }\n }\n\n // Initial queue: registrations with zero indegree, preserving input order.\n const queue: AnyContributorRegistration[] = []\n for (const reg of registrations) {\n if (indegree.get(reg.key) === 0) queue.push(reg)\n }\n\n const sorted: AnyContributorRegistration[] = []\n while (queue.length > 0) {\n const node = queue.shift()!\n sorted.push(node)\n for (const dependent of dependents.get(node.key) ?? []) {\n const next = (indegree.get(dependent.key) ?? 0) - 1\n indegree.set(dependent.key, next)\n if (next === 0) queue.push(dependent)\n }\n }\n\n if (sorted.length !== registrations.length) {\n const remaining = registrations.filter((r) => (indegree.get(r.key) ?? 0) > 0)\n throw new ContributorCycleError(reconstructCycle(remaining, byKey), route)\n }\n\n return sorted\n}\n\n/**\n * Reconstruct one cycle path from the residual graph.\n *\n * Walks from any remaining node, following its first dependency that's\n * still in the residual set, until we revisit a node. The path returned\n * starts and ends with the same key so the message reads naturally:\n * `tenant → project → tenant`.\n */\nfunction reconstructCycle(\n residual: readonly AnyContributorRegistration[],\n byKey: ReadonlyMap<string, AnyContributorRegistration>,\n): string[] {\n const residualKeys = new Set(residual.map((r) => r.key))\n const start = residual[0].key\n const path: string[] = [start]\n const seen = new Set<string>([start])\n let cursor: string = start\n\n while (true) {\n const reg = byKey.get(cursor)!\n const nextDep = reg.dependsOn.find((d) => residualKeys.has(d))\n if (nextDep === undefined) break\n if (seen.has(nextDep)) {\n // Trim the prefix that isn't part of the cycle.\n const cycleStart = path.indexOf(nextDep)\n return [...path.slice(cycleStart), nextDep]\n }\n path.push(nextDep)\n seen.add(nextDep)\n cursor = nextDep\n }\n\n // Fallback — couldn't reconstruct a strict cycle, return what we walked.\n return path\n}\n","import type { Container } from './container'\nimport type { ContributorRegistration } from './context-decorator'\nimport type { ExecutionContext } from './execution-context'\nimport type { ContributorPipeline } from './contributor-pipeline'\n\nexport interface RunContributorsOptions {\n /** Pre-built, validated, topo-sorted pipeline. */\n pipeline: ContributorPipeline\n /** The execution context contributors read from and write into. */\n ctx: ExecutionContext\n /** DI container used to resolve declared `deps`. */\n container: Container\n}\n\n/**\n * Execute a built {@link ContributorPipeline} against an {@link ExecutionContext}.\n *\n * Sequential — one contributor at a time, in the topo order chosen at\n * pipeline-build time. Per `architecture.md` §20.10, V1 does not run\n * contributors in parallel even within the same topo level; that is a\n * V2 follow-up gated on profiling.\n *\n * ## Error matrix (architecture.md §20.9)\n *\n * | resolve outcome | optional | onError defined | runner behaviour |\n * |-----------------|----------|-----------------|--------------------------------------------------|\n * | throws | true | — | skip; `ctx.get(key)` remains undefined |\n * | throws | false | yes | call hook; returned value (if any) stored |\n * | throws | false | no | propagate the original error |\n * | hook throws | — | yes | propagate the new error |\n * | hook returns | — | yes | undefined/void → skip; value → `ctx.set(key, v)` |\n * | resolves | — | — | `ctx.set(key, value)` |\n *\n * Container.resolve() throwing while building `deps` is treated as a\n * resolve-side throw and flows through the same matrix — adopters who\n * need different behaviour for missing-DI vs. data-lookup failures can\n * wrap the container access inside their own `resolve()`.\n */\nexport async function runContributors(options: RunContributorsOptions): Promise<void> {\n const { pipeline, ctx, container } = options\n\n for (const reg of pipeline.contributors) {\n await runOne(reg, ctx, container)\n }\n}\n\nasync function runOne(\n reg: ContributorRegistration,\n ctx: ExecutionContext,\n container: Container,\n): Promise<void> {\n let value: unknown\n try {\n const deps = resolveDeps(reg, container)\n value = await reg.resolve(ctx, deps)\n } catch (err) {\n if (reg.optional) return\n if (reg.onError) {\n // Hook throws are unconditionally propagated — there is no\n // second-chance hook. Returning undefined/void from the hook\n // means \"skip\"; returning a value means \"use this instead\".\n const replacement = await reg.onError(err, ctx)\n if (replacement === undefined) return\n ctx.set(reg.key, replacement as never)\n return\n }\n throw err\n }\n ctx.set(reg.key, value as never)\n}\n\n/**\n * Resolve a contributor's declared dependencies through the container,\n * preserving the property names from the spec so `resolve(ctx, deps)`\n * receives exactly the shape the user wrote.\n *\n * Empty `deps` produces an empty object — `resolve()` always sees an\n * object, never `undefined`.\n */\nfunction resolveDeps(reg: ContributorRegistration, container: Container): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const [key, token] of Object.entries(reg.deps)) {\n out[key] = container.resolve(token as never)\n }\n return out\n}\n","// Concrete-module imports (NOT the `../core` barrel): the barrel re-exports\n// the asset manager, whose eager `node:fs` import would poison the edge-safe\n// `@forinda/kickjs/web` entry graph that flows through this file.\nimport { buildPipeline, type SourcedRegistration } from '../core/contributor-pipeline'\nimport { runContributors } from '../core/contributor-runner'\nimport { Container } from '../core/container'\nimport { METADATA } from '../core/interfaces'\nimport type { ContributorRegistration } from '../core/context-decorator'\nimport type { FileUploadConfig, MiddlewareHandler, RouteDefinition } from '../core/decorators'\nimport { getClassMeta, getMethodMeta, getMethodMetaOrUndefined } from '../core/metadata'\nimport { duplicateRouteError } from '../core/kick-errors'\nimport type { CtxHandler, RouteEntry, RouteMethod } from './runtime'\n\n/**\n * Per-module SourcedRegistration[] threaded through the route-mount loop by\n * Application.setup(). Carries module + adapter + global contributors so\n * buildRouteTable() can merge them with class + method ones into a single pipeline.\n *\n * Module setup is sequential, so this slot is race-free. Cleared in a finally\n * block after each module mounts. Outside of Application setup the slot is\n * empty — direct buildRouteTable()/buildRoutes() callers (mostly tests) see only\n * class + method contributors unless they pass `externalSources` explicitly.\n *\n * Same idiom as `Container._requestStoreProvider`: an internal escape hatch for\n * cross-module wiring without inversion-of-control gymnastics.\n *\n * @internal\n */\nlet _externalContributorSources: readonly SourcedRegistration[] = []\n\n/** @internal — set by Application.setup() before each `mod.routes()` call. */\nexport function _setExternalContributorSources(sources: readonly SourcedRegistration[]): void {\n _externalContributorSources = sources\n}\n\n/**\n * Boot-time duplicate-route guard shared by the node and web mount loops.\n *\n * Registers `METHOD /mounted/path` into `registry` and throws\n * {@link duplicateRouteError} (KICK006) if another handler already claimed\n * it. Param *names* are ignored — `/tasks/:id` and `/tasks/:taskId` are the\n * same route at dispatch time, so they collide here too.\n *\n * @internal\n */\nexport function assertRouteUnique(\n registry: Map<string, string>,\n method: string,\n fullPath: string,\n owner: string,\n): void {\n const key = `${method.toUpperCase()} ${fullPath.replace(/:[^/]+/g, ':*')}`\n const prior = registry.get(key)\n if (prior !== undefined) {\n throw duplicateRouteError(method.toUpperCase(), fullPath, owner, prior)\n }\n registry.set(key, owner)\n}\n\nexport interface BuildRoutesOptions {\n /**\n * Extra contributors to merge into the per-route pipeline at their declared\n * precedence levels. Pass explicitly when calling buildRouteTable/buildRoutes\n * outside the Application route-mount loop (typically in tests). When omitted,\n * falls back to the slot set by Application.setup().\n */\n externalSources?: readonly SourcedRegistration[]\n}\n\n/**\n * Turn a controller class decorated with @Get, @Post, etc. into a plain-data\n * {@link RouteEntry}[] — the engine-neutral route table an {@link HttpRuntime}\n * materializes (see `docs/http/spec-http-runtimes.md`, Avenue B).\n *\n * What used to be per-handler Express `(req, res, next)` closures is now\n * captured as data: `middlewares` keep their `(ctx, next)` shape, the contributor\n * pipeline is pre-built into a `contributorRunner` closure, validation / upload\n * stay as metadata, and the terminal `handler` resolves the controller per-request\n * (to respect DI scoping) and invokes it. The runtime owns how these get wrapped\n * onto its engine.\n *\n * Routes use only the method-level decorator paths (e.g. @Get('/me') → '/me').\n * The @Controller path is NOT baked in — it is metadata for Swagger/introspection.\n * The module's routes().path is the single source of truth for the mount prefix,\n * which avoids path doubling when both the module and controller specify the path.\n *\n * The contributor pipeline is built here, so a cycle or missing `dependsOn`\n * throws at table-build time (boot) rather than on first request.\n */\nexport function buildRouteTable(\n controllerClass: any,\n options: BuildRoutesOptions = {},\n): RouteEntry[] {\n const container = Container.getInstance()\n const externalSources = options.externalSources ?? _externalContributorSources\n const routes: RouteDefinition[] = getClassMeta<RouteDefinition[]>(\n METADATA.ROUTES,\n controllerClass,\n [],\n )\n\n // Class-level middleware\n const classMiddlewares: MiddlewareHandler[] = getClassMeta<MiddlewareHandler[]>(\n METADATA.CLASS_MIDDLEWARES,\n controllerClass,\n [],\n )\n\n // Class-level Context Contributors (#107) — applied to every method on the\n // controller. Method-level contributors are added per-route below.\n const classContributors: ContributorRegistration[] = getClassMeta<ContributorRegistration[]>(\n METADATA.CLASS_CONTRIBUTORS,\n controllerClass,\n [],\n )\n\n const entries: RouteEntry[] = []\n\n for (const route of routes) {\n const method = route.method.toUpperCase() as RouteMethod\n const fullPath = route.path || '/'\n\n // Method-level middleware\n const methodMiddlewares: MiddlewareHandler[] = getMethodMeta<MiddlewareHandler[]>(\n METADATA.METHOD_MIDDLEWARES,\n controllerClass,\n route.handlerName,\n [],\n )\n\n // @FileUpload decorator — runtime supplies the upload backend.\n const fileUploadConfig = getMethodMetaOrUndefined<FileUploadConfig>(\n METADATA.FILE_UPLOAD,\n controllerClass,\n route.handlerName,\n )\n\n // Context Contributor pipeline (#107) — class + method contributors,\n // built once at mount time so cycle/missing-dep failures abort boot.\n const methodContributors: ContributorRegistration[] = getMethodMeta<ContributorRegistration[]>(\n METADATA.METHOD_CONTRIBUTORS,\n controllerClass,\n route.handlerName,\n [],\n )\n\n let contributorRunner: CtxHandler | null = null\n if (\n classContributors.length > 0 ||\n methodContributors.length > 0 ||\n externalSources.length > 0\n ) {\n // Labels are surfaced in DuplicateContributorError messages — include\n // the registration's key + the original index so a same-key collision\n // at one precedence level points at the conflicting decorator slot,\n // not just the host method/class.\n const sources: SourcedRegistration[] = [\n ...methodContributors.map((registration, i): SourcedRegistration => ({\n source: 'method',\n registration,\n label: `${controllerClass.name}.${String(route.handlerName)}#${i}(${registration.key})`,\n })),\n ...classContributors.map((registration, i): SourcedRegistration => ({\n source: 'class',\n registration,\n label: `${controllerClass.name}.@class#${i}(${registration.key})`,\n })),\n ...externalSources,\n ]\n const pipeline = buildPipeline(sources, {\n route: `${method} ${fullPath}`,\n })\n contributorRunner = (ctx) => runContributors({ pipeline, ctx, container })\n }\n\n // Terminal handler — resolve controller per-request to respect DI scoping.\n const handler: CtxHandler = (ctx) => container.resolve(controllerClass)[route.handlerName](ctx)\n\n entries.push({\n method,\n path: fullPath,\n middlewares: [...classMiddlewares, ...methodMiddlewares],\n contributorRunner,\n handler,\n meta: {\n controller: controllerClass,\n handlerName: route.handlerName,\n validation: route.validation,\n upload: fileUploadConfig,\n },\n })\n }\n\n return entries\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"router-builder-FukfDDLt.mjs","names":[],"sources":["../src/core/contributor-pipeline.ts","../src/core/contributor-runner.ts","../src/http/router-builder.ts"],"sourcesContent":["import type { AnyContributorRegistration } from './context-decorator'\nimport {\n ContributorCycleError,\n DuplicateContributorError,\n MissingContributorError,\n} from './context-errors'\n\n/**\n * Where a contributor was registered, ordered by precedence\n * (lower index wins). Mirrors `architecture.md` §20.4.\n *\n * Levels (highest → lowest precedence):\n * - `'method'` — `@LoadX` on a controller method.\n * - `'class'` — `@LoadX` on a controller class.\n * - `'module'` — returned by `AppModule.contributors?()`. Per-module scope.\n * - `'adapter'` — returned by `AppAdapter.contributors?()`. Cross-cutting, applies\n * to every module's routes.\n * - `'global'` — `ApplicationOptions.contributors`. App-wide default.\n */\nexport type ContributorSource = 'method' | 'class' | 'module' | 'adapter' | 'global'\n\n/** Precedence-ranked source list. Index = precedence; method (0) wins ties. */\nconst PRECEDENCE_ORDER: readonly ContributorSource[] = [\n 'method',\n 'class',\n 'module',\n 'adapter',\n 'global',\n]\n\n/**\n * Input row to {@link buildPipeline}. Pairs a registration with its origin\n * so dedup can apply the precedence rule and diagnostic errors can name\n * the offending site.\n *\n * `registration` is the type-erased {@link AnyContributorRegistration}\n * — the runner doesn't care about each registration's narrow `D` /\n * `Ctx` shape, and adopters frequently mix HTTP / transport-agnostic\n * contributors in the same `externalSources` / `contributors()`\n * array. The narrow generics from `defineContextDecorator` survive\n * at the *definition* site (where `resolve` is typed); they're erased\n * here so collections compose without `as any` casts.\n */\nexport interface SourcedRegistration {\n registration: AnyContributorRegistration\n source: ContributorSource\n /** Human label surfaced in DuplicateContributorError messages. */\n label?: string\n}\n\n/**\n * Resolved, validated, topo-sorted pipeline ready for execution.\n *\n * `contributors` is in execution order — every entry's `dependsOn`\n * keys appear earlier. `keys` is the set of all keys this pipeline\n * produces; the runner uses it for fast existence checks and the\n * builder uses it for missing-dep validation.\n */\nexport interface ContributorPipeline {\n readonly contributors: readonly AnyContributorRegistration[]\n readonly keys: ReadonlySet<string>\n}\n\nexport interface BuildPipelineOptions {\n /** Optional route identifier surfaced in diagnostic errors (e.g., 'GET /projects/:id'). */\n route?: string\n}\n\n/**\n * Build, validate, and topo-sort a contributor pipeline.\n *\n * Pipeline:\n *\n * 1. Group sources by precedence (method > class > module > adapter > global).\n * 2. Within each precedence level, two contributors with the same `key`\n * raise {@link DuplicateContributorError}.\n * 3. Across levels, the highest-precedence contributor wins each key\n * (lower-precedence duplicates are silently dropped — that is the\n * documented override mechanism, not an error).\n * 4. Every key referenced by `dependsOn` must be produced by the\n * surviving set, otherwise {@link MissingContributorError}.\n * 5. Kahn's algorithm topo-sorts the survivors. Any nodes left after\n * the queue drains form a cycle → {@link ContributorCycleError}\n * with a reconstructed path.\n *\n * Pure function — no DI, no I/O. Safe to call once at startup per\n * route and cache the result.\n */\nexport function buildPipeline(\n sources: readonly SourcedRegistration[],\n options: BuildPipelineOptions = {},\n): ContributorPipeline {\n const route = options.route\n\n // Step 1: group by source.\n const bySource = new Map<ContributorSource, SourcedRegistration[]>()\n for (const src of PRECEDENCE_ORDER) bySource.set(src, [])\n for (const entry of sources) bySource.get(entry.source)!.push(entry)\n\n // Step 2 + 3: dedup with precedence. We walk highest precedence first,\n // claim its keys, and drop lower-precedence rows that conflict.\n const winners = new Map<string, SourcedRegistration>()\n for (const src of PRECEDENCE_ORDER) {\n const rows = bySource.get(src)!\n const keysAtThisLevel = new Map<string, SourcedRegistration[]>()\n for (const row of rows) {\n const key = row.registration.key\n const existing = keysAtThisLevel.get(key) ?? []\n existing.push(row)\n keysAtThisLevel.set(key, existing)\n }\n for (const [key, rowsForKey] of keysAtThisLevel) {\n if (rowsForKey.length > 1) {\n const labels = rowsForKey.map((r, i) => r.label ?? `${src}#${i}`)\n throw new DuplicateContributorError(key, labels)\n }\n if (!winners.has(key)) winners.set(key, rowsForKey[0])\n }\n }\n\n const survivors = [...winners.values()].map((r) => r.registration)\n const allKeys = new Set(survivors.map((r) => r.key))\n\n // Step 4: dependsOn validation.\n for (const reg of survivors) {\n for (const dep of reg.dependsOn) {\n if (!allKeys.has(dep)) {\n throw new MissingContributorError(dep, reg.key, route, reg.definedAt)\n }\n }\n }\n\n // Step 5: Kahn topo-sort.\n const sorted = topoSort(survivors, route)\n\n return Object.freeze({\n contributors: Object.freeze(sorted),\n keys: allKeys,\n })\n}\n\n/**\n * Kahn's algorithm with deterministic ordering on ties.\n *\n * Nodes with no remaining incoming edges enter the queue in input\n * order. Cycles surface as nodes still present after the queue\n * drains; we reconstruct one cycle path for diagnostics.\n */\nfunction topoSort(\n registrations: readonly AnyContributorRegistration[],\n route: string | undefined,\n): AnyContributorRegistration[] {\n // indegree[key] = number of unresolved dependsOn entries for key\n const indegree = new Map<string, number>()\n // dependents[depKey] = list of registrations that depend on depKey\n const dependents = new Map<string, AnyContributorRegistration[]>()\n const byKey = new Map<string, AnyContributorRegistration>()\n\n for (const reg of registrations) {\n indegree.set(reg.key, reg.dependsOn.length)\n byKey.set(reg.key, reg)\n }\n for (const reg of registrations) {\n for (const dep of reg.dependsOn) {\n const list = dependents.get(dep) ?? []\n list.push(reg)\n dependents.set(dep, list)\n }\n }\n\n // Initial queue: registrations with zero indegree, preserving input order.\n const queue: AnyContributorRegistration[] = []\n for (const reg of registrations) {\n if (indegree.get(reg.key) === 0) queue.push(reg)\n }\n\n const sorted: AnyContributorRegistration[] = []\n while (queue.length > 0) {\n const node = queue.shift()!\n sorted.push(node)\n for (const dependent of dependents.get(node.key) ?? []) {\n const next = (indegree.get(dependent.key) ?? 0) - 1\n indegree.set(dependent.key, next)\n if (next === 0) queue.push(dependent)\n }\n }\n\n if (sorted.length !== registrations.length) {\n const remaining = registrations.filter((r) => (indegree.get(r.key) ?? 0) > 0)\n throw new ContributorCycleError(reconstructCycle(remaining, byKey), route)\n }\n\n return sorted\n}\n\n/**\n * Reconstruct one cycle path from the residual graph.\n *\n * Walks from any remaining node, following its first dependency that's\n * still in the residual set, until we revisit a node. The path returned\n * starts and ends with the same key so the message reads naturally:\n * `tenant → project → tenant`.\n */\nfunction reconstructCycle(\n residual: readonly AnyContributorRegistration[],\n byKey: ReadonlyMap<string, AnyContributorRegistration>,\n): string[] {\n const residualKeys = new Set(residual.map((r) => r.key))\n const start = residual[0].key\n const path: string[] = [start]\n const seen = new Set<string>([start])\n let cursor: string = start\n\n while (true) {\n const reg = byKey.get(cursor)!\n const nextDep = reg.dependsOn.find((d) => residualKeys.has(d))\n if (nextDep === undefined) break\n if (seen.has(nextDep)) {\n // Trim the prefix that isn't part of the cycle.\n const cycleStart = path.indexOf(nextDep)\n return [...path.slice(cycleStart), nextDep]\n }\n path.push(nextDep)\n seen.add(nextDep)\n cursor = nextDep\n }\n\n // Fallback — couldn't reconstruct a strict cycle, return what we walked.\n return path\n}\n","import type { Container } from './container'\nimport type { ContributorRegistration } from './context-decorator'\nimport type { ExecutionContext } from './execution-context'\nimport type { ContributorPipeline } from './contributor-pipeline'\n\nexport interface RunContributorsOptions {\n /** Pre-built, validated, topo-sorted pipeline. */\n pipeline: ContributorPipeline\n /** The execution context contributors read from and write into. */\n ctx: ExecutionContext\n /** DI container used to resolve declared `deps`. */\n container: Container\n}\n\n/**\n * Execute a built {@link ContributorPipeline} against an {@link ExecutionContext}.\n *\n * Sequential — one contributor at a time, in the topo order chosen at\n * pipeline-build time. Per `architecture.md` §20.10, V1 does not run\n * contributors in parallel even within the same topo level; that is a\n * V2 follow-up gated on profiling.\n *\n * ## Error matrix (architecture.md §20.9)\n *\n * | resolve outcome | optional | onError defined | runner behaviour |\n * |-----------------|----------|-----------------|--------------------------------------------------|\n * | throws | true | — | skip; `ctx.get(key)` remains undefined |\n * | throws | false | yes | call hook; returned value (if any) stored |\n * | throws | false | no | propagate the original error |\n * | hook throws | — | yes | propagate the new error |\n * | hook returns | — | yes | undefined/void → skip; value → `ctx.set(key, v)` |\n * | resolves | — | — | `ctx.set(key, value)` |\n *\n * Container.resolve() throwing while building `deps` is treated as a\n * resolve-side throw and flows through the same matrix — adopters who\n * need different behaviour for missing-DI vs. data-lookup failures can\n * wrap the container access inside their own `resolve()`.\n */\nexport async function runContributors(options: RunContributorsOptions): Promise<void> {\n const { pipeline, ctx, container } = options\n\n for (const reg of pipeline.contributors) {\n await runOne(reg, ctx, container)\n }\n}\n\nasync function runOne(\n reg: ContributorRegistration,\n ctx: ExecutionContext,\n container: Container,\n): Promise<void> {\n let value: unknown\n try {\n const deps = resolveDeps(reg, container)\n value = await reg.resolve(ctx, deps)\n } catch (err) {\n if (reg.optional) return\n if (reg.onError) {\n // Hook throws are unconditionally propagated — there is no\n // second-chance hook. Returning undefined/void from the hook\n // means \"skip\"; returning a value means \"use this instead\".\n const replacement = await reg.onError(err, ctx)\n if (replacement === undefined) return\n ctx.set(reg.key, replacement as never)\n return\n }\n throw err\n }\n ctx.set(reg.key, value as never)\n}\n\n/**\n * Resolve a contributor's declared dependencies through the container,\n * preserving the property names from the spec so `resolve(ctx, deps)`\n * receives exactly the shape the user wrote.\n *\n * Empty `deps` produces an empty object — `resolve()` always sees an\n * object, never `undefined`.\n */\nfunction resolveDeps(reg: ContributorRegistration, container: Container): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const [key, token] of Object.entries(reg.deps)) {\n out[key] = container.resolve(token as never)\n }\n return out\n}\n","// Concrete-module imports (NOT the `../core` barrel): the barrel re-exports\n// the asset manager, whose eager `node:fs` import would poison the edge-safe\n// `@forinda/kickjs/web` entry graph that flows through this file.\nimport { buildPipeline, type SourcedRegistration } from '../core/contributor-pipeline'\nimport { runContributors } from '../core/contributor-runner'\nimport { Container } from '../core/container'\nimport { METADATA } from '../core/interfaces'\nimport type { ContributorRegistration } from '../core/context-decorator'\nimport type { FileUploadConfig, MiddlewareHandler, RouteDefinition } from '../core/decorators'\nimport { getClassMeta, getMethodMeta, getMethodMetaOrUndefined } from '../core/metadata'\nimport { duplicateRouteError } from '../core/kick-errors'\nimport type { CtxHandler, RouteEntry, RouteMethod } from './runtime'\n\n/**\n * Per-module SourcedRegistration[] threaded through the route-mount loop by\n * Application.setup(). Carries module + adapter + global contributors so\n * buildRouteTable() can merge them with class + method ones into a single pipeline.\n *\n * Module setup is sequential, so this slot is race-free. Cleared in a finally\n * block after each module mounts. Outside of Application setup the slot is\n * empty — direct buildRouteTable()/buildRoutes() callers (mostly tests) see only\n * class + method contributors unless they pass `externalSources` explicitly.\n *\n * Same idiom as `Container._requestStoreProvider`: an internal escape hatch for\n * cross-module wiring without inversion-of-control gymnastics.\n *\n * @internal\n */\nlet _externalContributorSources: readonly SourcedRegistration[] = []\n\n/** @internal — set by Application.setup() before each `mod.routes()` call. */\nexport function _setExternalContributorSources(sources: readonly SourcedRegistration[]): void {\n _externalContributorSources = sources\n}\n\n/**\n * Boot-time duplicate-route guard shared by the node and web mount loops.\n *\n * Registers `METHOD /mounted/path` into `registry` and throws\n * {@link duplicateRouteError} (KICK006) if another handler already claimed\n * it. Param *names* are ignored — `/tasks/:id` and `/tasks/:taskId` are the\n * same route at dispatch time, so they collide here too.\n *\n * @internal\n */\nexport function assertRouteUnique(\n registry: Map<string, string>,\n method: string,\n fullPath: string,\n owner: string,\n): void {\n const key = `${method.toUpperCase()} ${fullPath.replace(/:[^/]+/g, ':*')}`\n const prior = registry.get(key)\n if (prior !== undefined) {\n throw duplicateRouteError(method.toUpperCase(), fullPath, owner, prior)\n }\n registry.set(key, owner)\n}\n\nexport interface BuildRoutesOptions {\n /**\n * Extra contributors to merge into the per-route pipeline at their declared\n * precedence levels. Pass explicitly when calling buildRouteTable/buildRoutes\n * outside the Application route-mount loop (typically in tests). When omitted,\n * falls back to the slot set by Application.setup().\n */\n externalSources?: readonly SourcedRegistration[]\n}\n\n/**\n * Turn a controller class decorated with @Get, @Post, etc. into a plain-data\n * {@link RouteEntry}[] — the engine-neutral route table an {@link HttpRuntime}\n * materializes (see `docs/http/spec-http-runtimes.md`, Avenue B).\n *\n * What used to be per-handler Express `(req, res, next)` closures is now\n * captured as data: `middlewares` keep their `(ctx, next)` shape, the contributor\n * pipeline is pre-built into a `contributorRunner` closure, validation / upload\n * stay as metadata, and the terminal `handler` resolves the controller per-request\n * (to respect DI scoping) and invokes it. The runtime owns how these get wrapped\n * onto its engine.\n *\n * Routes use only the method-level decorator paths (e.g. @Get('/me') → '/me').\n * The @Controller path is NOT baked in — it is metadata for Swagger/introspection.\n * The module's routes().path is the single source of truth for the mount prefix,\n * which avoids path doubling when both the module and controller specify the path.\n *\n * The contributor pipeline is built here, so a cycle or missing `dependsOn`\n * throws at table-build time (boot) rather than on first request.\n */\nexport function buildRouteTable(\n controllerClass: any,\n options: BuildRoutesOptions = {},\n): RouteEntry[] {\n const container = Container.getInstance()\n const externalSources = options.externalSources ?? _externalContributorSources\n const routes: RouteDefinition[] = getClassMeta<RouteDefinition[]>(\n METADATA.ROUTES,\n controllerClass,\n [],\n )\n\n // Class-level middleware\n const classMiddlewares: MiddlewareHandler[] = getClassMeta<MiddlewareHandler[]>(\n METADATA.CLASS_MIDDLEWARES,\n controllerClass,\n [],\n )\n\n // Class-level Context Contributors (#107) — applied to every method on the\n // controller. Method-level contributors are added per-route below.\n const classContributors: ContributorRegistration[] = getClassMeta<ContributorRegistration[]>(\n METADATA.CLASS_CONTRIBUTORS,\n controllerClass,\n [],\n )\n\n const entries: RouteEntry[] = []\n\n for (const route of routes) {\n const method = route.method.toUpperCase() as RouteMethod\n const fullPath = route.path || '/'\n\n // Method-level middleware\n const methodMiddlewares: MiddlewareHandler[] = getMethodMeta<MiddlewareHandler[]>(\n METADATA.METHOD_MIDDLEWARES,\n controllerClass,\n route.handlerName,\n [],\n )\n\n // @FileUpload decorator — runtime supplies the upload backend.\n const fileUploadConfig = getMethodMetaOrUndefined<FileUploadConfig>(\n METADATA.FILE_UPLOAD,\n controllerClass,\n route.handlerName,\n )\n\n // Context Contributor pipeline (#107) — class + method contributors,\n // built once at mount time so cycle/missing-dep failures abort boot.\n const methodContributors: ContributorRegistration[] = getMethodMeta<ContributorRegistration[]>(\n METADATA.METHOD_CONTRIBUTORS,\n controllerClass,\n route.handlerName,\n [],\n )\n\n let contributorRunner: CtxHandler | null = null\n if (\n classContributors.length > 0 ||\n methodContributors.length > 0 ||\n externalSources.length > 0\n ) {\n // Labels are surfaced in DuplicateContributorError messages — include\n // the registration's key + the original index so a same-key collision\n // at one precedence level points at the conflicting decorator slot,\n // not just the host method/class.\n const sources: SourcedRegistration[] = [\n ...methodContributors.map((registration, i): SourcedRegistration => ({\n source: 'method',\n registration,\n label: `${controllerClass.name}.${String(route.handlerName)}#${i}(${registration.key})`,\n })),\n ...classContributors.map((registration, i): SourcedRegistration => ({\n source: 'class',\n registration,\n label: `${controllerClass.name}.@class#${i}(${registration.key})`,\n })),\n ...externalSources,\n ]\n const pipeline = buildPipeline(sources, {\n route: `${method} ${fullPath}`,\n })\n contributorRunner = (ctx) => runContributors({ pipeline, ctx, container })\n }\n\n // Terminal handler — resolve controller per-request to respect DI scoping.\n const handler: CtxHandler = (ctx) => container.resolve(controllerClass)[route.handlerName](ctx)\n\n entries.push({\n method,\n path: fullPath,\n middlewares: [...classMiddlewares, ...methodMiddlewares],\n contributorRunner,\n handler,\n meta: {\n controller: controllerClass,\n handlerName: route.handlerName,\n validation: route.validation,\n upload: fileUploadConfig,\n },\n })\n }\n\n return entries\n}\n"],"mappings":"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"}
|
|
@@ -56,7 +56,7 @@ interface SessionOptions {
|
|
|
56
56
|
*
|
|
57
57
|
* bootstrap({
|
|
58
58
|
* modules,
|
|
59
|
-
*
|
|
59
|
+
* middlewares: [
|
|
60
60
|
* session({ secret: process.env.SESSION_SECRET! }),
|
|
61
61
|
* // ... other middleware
|
|
62
62
|
* ],
|
|
@@ -66,4 +66,4 @@ interface SessionOptions {
|
|
|
66
66
|
declare function session(options: SessionOptions): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
67
67
|
//#endregion
|
|
68
68
|
export { session as a, SessionStore as i, SessionData as n, SessionOptions as r, Session as t };
|
|
69
|
-
//# sourceMappingURL=session-
|
|
69
|
+
//# sourceMappingURL=session-BHgRyfqZ.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-
|
|
1
|
+
{"version":3,"file":"session-BHgRyfqZ.d.mts","names":[],"sources":["../src/http/middleware/session.ts"],"mappings":";;;UAMiB;GACd;;UAGc;EACf,IAAI,cAAc,QAAQ;EAC1B,IAAI,aAAa,MAAM,aAAa,iBAAiB;EACrD,QAAQ,cAAc;EACtB,OAAO,aAAa,iBAAiB;;UAGtB;EACf;EACA,MAAM;EACN,cAAc;EACd,WAAW;EACX,QAAQ;;UAGO;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;IACE;IACA;IACA;IACA;IACA;;;EAGF,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiHM,QAAQ,SAAS,kBAAc,KAoB1B,SAAO,KAAO,UAAQ,MAAQ,iBAAY"}
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
|
|
2
|
-
import "./
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import "./
|
|
6
|
-
import "./
|
|
7
|
-
import "./
|
|
8
|
-
import "./
|
|
9
|
-
import "./
|
|
10
|
-
import "./
|
|
11
|
-
import "./
|
|
2
|
+
import { s as InjectionToken, t as Container } from "./container-BW_MlgLj.mjs";
|
|
3
|
+
import "./index-Di64XOKG.mjs";
|
|
4
|
+
import { C as MetaValue, c as ContextDecorator, h as DepValue, u as ContextDecoratorSpec, x as ContextMetaKey } from "./app-module-DZW0uYNA.mjs";
|
|
5
|
+
import { T as RequestContext, f as HttpRuntime, k as AdapterFactory, m as RouteEntry, tt as ModuleFactory } from "./adapter-BuOTeSQy.mjs";
|
|
6
|
+
import "./application-D1koPFmH.mjs";
|
|
7
|
+
import "./bootstrap-BIjHgG4v.mjs";
|
|
8
|
+
import "./cors-CsibNTQ6.mjs";
|
|
9
|
+
import "./csrf-DwOhkOC6.mjs";
|
|
10
|
+
import "./error-handler-C-xGxZKP.mjs";
|
|
11
|
+
import "./helmet-C2QEU6S-.mjs";
|
|
12
|
+
import "./rate-limit-D_cKx6Uw.mjs";
|
|
12
13
|
import "./request-id-CVzJ1uHD.mjs";
|
|
13
|
-
import "./session-
|
|
14
|
-
import "./kv-stores-
|
|
15
|
-
import { t as BuildRoutesOptions } from "./router-builder-
|
|
14
|
+
import "./session-BHgRyfqZ.mjs";
|
|
15
|
+
import { l as Reply } from "./kv-stores-CbsEHE5J.mjs";
|
|
16
|
+
import { t as BuildRoutesOptions } from "./router-builder-DptndWhd.mjs";
|
|
16
17
|
import "./validate-DMYpUnrf.mjs";
|
|
17
|
-
import "./upload-
|
|
18
|
+
import "./upload-CbJOK-j9.mjs";
|
|
18
19
|
import { Express, NextFunction, Request, Response, Router } from "express";
|
|
19
20
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
20
21
|
//#region src/core/disposables.d.ts
|
|
@@ -387,6 +388,63 @@ declare function groupAssetKeys(namespace: string, paths: readonly string[], opt
|
|
|
387
388
|
/** Strip the final extension from a path. `'mails/welcome.ejs'` → `'mails/welcome'`. */
|
|
388
389
|
declare function stripExt(path: string): string;
|
|
389
390
|
//#endregion
|
|
391
|
+
//#region src/http/health-module.d.ts
|
|
392
|
+
/** One adapter's answer to `onHealthCheck()`. */
|
|
393
|
+
interface HealthCheckResult {
|
|
394
|
+
name: string;
|
|
395
|
+
status: 'up' | 'down';
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* What the health routes need from the running Application.
|
|
399
|
+
*
|
|
400
|
+
* A controller cannot reach `Application` internals, and should not: this is
|
|
401
|
+
* the whole surface the endpoints depend on, so a replacement module can
|
|
402
|
+
* satisfy it without knowing anything else.
|
|
403
|
+
*/
|
|
404
|
+
interface HealthProbe {
|
|
405
|
+
/** True once shutdown has begun and traffic should drain away. */
|
|
406
|
+
isDraining(): boolean;
|
|
407
|
+
/** Aggregated `onHealthCheck()` results from every adapter that has one. */
|
|
408
|
+
checks(): Promise<HealthCheckResult[]>;
|
|
409
|
+
}
|
|
410
|
+
declare const HEALTH_PROBE: InjectionToken<HealthProbe>;
|
|
411
|
+
declare class HealthController {
|
|
412
|
+
private readonly probe;
|
|
413
|
+
constructor(probe: HealthProbe);
|
|
414
|
+
/** Liveness: is the process up and not shutting down. */
|
|
415
|
+
live(): Reply<200, {
|
|
416
|
+
status: string;
|
|
417
|
+
uptime: number;
|
|
418
|
+
}> | Reply<503, {
|
|
419
|
+
status: string;
|
|
420
|
+
uptime: number;
|
|
421
|
+
}>;
|
|
422
|
+
/** Readiness: is every adapter's dependency answering. */
|
|
423
|
+
ready(): Promise<Reply<200 | 503, {
|
|
424
|
+
status: string;
|
|
425
|
+
checks: HealthCheckResult[];
|
|
426
|
+
}>>;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* `GET /health/live` and `GET /health/ready`, mounted at the root.
|
|
430
|
+
*
|
|
431
|
+
* A `defineModule` factory like any other module — the framework's own routes
|
|
432
|
+
* should be declared the way it asks adopters to declare theirs, not through a
|
|
433
|
+
* shape only the framework can produce.
|
|
434
|
+
*
|
|
435
|
+
* Registered automatically unless the app passes `health: false` or supplies a
|
|
436
|
+
* module of its own.
|
|
437
|
+
*/
|
|
438
|
+
declare const healthModule: ModuleFactory<Record<string, unknown>, {
|
|
439
|
+
register(container: Container): void;
|
|
440
|
+
routes(): {
|
|
441
|
+
path: string;
|
|
442
|
+
controller: typeof HealthController;
|
|
443
|
+
version: false;
|
|
444
|
+
prefix: false;
|
|
445
|
+
};
|
|
446
|
+
}>;
|
|
447
|
+
//#endregion
|
|
390
448
|
//#region src/http/cluster.d.ts
|
|
391
449
|
interface ClusterOptions {
|
|
392
450
|
/** Number of worker processes (default: os.cpus().length) */
|
|
@@ -587,7 +645,7 @@ declare function parseTraceparent(header: string): TraceContext | null;
|
|
|
587
645
|
* @example
|
|
588
646
|
* ```ts
|
|
589
647
|
* bootstrap({
|
|
590
|
-
*
|
|
648
|
+
* middlewares: [
|
|
591
649
|
* requestScopeMiddleware(),
|
|
592
650
|
* traceContext(), // extracts or generates traceId
|
|
593
651
|
* requestLogger(),
|
|
@@ -780,5 +838,5 @@ declare function resolveClientDir(clientDir: string, cwd?: string, entry?: strin
|
|
|
780
838
|
*/
|
|
781
839
|
declare const SpaAdapter: AdapterFactory<SpaAdapterOptions, unknown>;
|
|
782
840
|
//#endregion
|
|
783
|
-
export {
|
|
784
|
-
//# sourceMappingURL=spa-
|
|
841
|
+
export { disposeAll as $, GroupAssetKeysResult as A, DuplicateContributorError as B, HealthCheckResult as C, AssetKeyPair as D, healthModule as E, MountCycleError as F, CircuitBreakerState as G, MissingContributorError as H, MountKind as I, FormatKickErrorOptions as J, CircuitBreakerStats as K, MountSortItem as L, stripExt as M, DuplicateMountNameError as N, AssetKeyStrategy as O, MissingMountDepError as P, Disposable as Q, mountSort as R, HEALTH_PROBE as S, HealthProbe as T, CircuitBreaker as U, MissingContextValueError as V, CircuitBreakerOptions as W, KickErrorInit as X, KickError as Y, formatKickError as Z, expressRuntime as _, ViewAdapterOptions as a, ClusterOptions as b, TraceContextOptions as c, requestScopeMiddleware as d, registerDisposable as et, RequestStore as f, buildRoutes as g, requestStore as h, ViewAdapter as i, groupAssetKeys as j, GroupAssetKeysOptions as k, parseTraceparent as l, getRequestValue as m, SpaAdapterOptions as n, TRACEPARENT_HEADER as o, getRequestStore as p, CircuitOpenError as q, resolveClientDir as r, TraceContext as s, SpaAdapter as t, traceContext as u, materializeRouter as v, HealthController as w, isClusterPrimary as x, defineHttpContextDecorator as y, ContributorCycleError as z };
|
|
842
|
+
//# sourceMappingURL=spa-DhGwM3_A.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spa-DhGwM3_A.d.mts","names":[],"sources":["../src/core/disposables.ts","../src/core/kick-error.ts","../src/core/circuit-breaker.ts","../src/core/context-errors.ts","../src/core/mount-sort.ts","../src/core/asset-keys.ts","../src/http/health-module.ts","../src/http/cluster.ts","../src/http/define-http-context-decorator.ts","../src/http/runtimes/express.ts","../src/http/request-store.ts","../src/http/middleware/request-scope.ts","../src/http/middleware/trace-context.ts","../src/http/middleware/views.ts","../src/http/middleware/spa.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAaY,0BAA0B;;iBAKtB,mBAAmB,IAAI;;iBA6BjB,cAAc;;;;;;;;;;;;;;;;;;;;UC9BnB;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA,UAAU;;cAGC,kBAAkB;WACb;WACA;WACA;WACA;WACA;WACA,UAAU;EAE1B,YAAY,MAAM;;UAiDH;;;;;;EAMf;;;;;;;iBAQc,gBACd,KAAK,gBAAgB,WACrB,UAAS;;;;;;;;;;;;;;;;;;;;KCvFC;UAEK;;EAEf;;EAEA;;EAEA;;UAGe;EACf;EACA;EACA,OAAO;EACP,cAAc;;;;;cAMH,yBAAyB;WACR;EAA5B,YAA4B;;cAMjB;WAaO;UAZV;UACA;UACA;UACA;UACA;UACA;mBAES;mBACA;mBACA;EAEjB,YACkB,cAChB,SAAS;;;;;EAWL,QAAQ,GAAG,UAAU,QAAQ,KAAK,QAAQ;;EA0BhD,YAAY;;EAMZ,YAAY;;EAWZ;UASQ;UAWA;UAeA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cC1HG,gCAAgC;WAClC;WACA;WACA;WACA;EAET,YAAY,aAAa,mBAAmB,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA0CjD,iCAAiC;WACnC;WACA;EAET,YAAY,aAAa;;;;;;;;;;;;cA6Cd,8BAA8B;WAChC;WACA;EAET,YAAY,0BAA0B;;;;;;;;;;;;;;;;;;;cA0B3B,kCAAkC;WACpC;WACA;EAET,YAAY,aAAa;;;;;;;;;;;;;;;;;;;;;UC5IV;;EAEf;;;;;;EAMA;;;KAIU;;;;;cAMC,gCAAgC;WAClC,MAAM;WACN;EAET,YAAY,MAAM,WAAW;;;;;;;;cAclB,6BAA6B;WAC/B,MAAM;WACN;WACA;EAET,YAAY,MAAM,WAAW,iBAAiB;;;;;;;cAiBnC,wBAAwB;WAC1B,MAAM;WACN;EAET,YAAY,MAAM,WAAW;;;;;;;;;;;;;;;;iBAsBf,UAAU,UAAU,eAAe,gBAAgB,KAAK,MAAM,YAAY;;;KC/E9E;UAEK;;EAEf;;EAEA;;UAGe;;;;;;;;EAQf,WAAW;;UAGI;EACf,OAAO;;;;;;;;EAQP;;;;;;;;;;;;iBAac,eACd,mBACA,0BACA,OAAM,wBACL;;iBA2Da,SAAS;;;;UC/FR;EACf;EACA;;;;;;;;;UAUe;;EAEf;;EAEA,UAAU,QAAQ;;cAGP,cAAY,eAAA;cAGZ;mBACkB;EAA7B,YAA6B,OAAO;;EAIpC,QAJoC;;;OAIhC;;;;;EAQE,SAAK;;;;;;;;;;;;;;;cAuBA,4BAAY;EAGD,SAAA,WAAA;;;;;;;;;;UCvFP;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;;iBAIc;;;;;;;;UCwDC,qCAAqC,UAAU;GAC7D,UAAU,gBAAgB,UAAU,eAAe,YAAY,uBAC9D,MAAM,qBAAqB,GAAG,GAAG,GAAG,kBACnC,iBAAiB,GAAG,GAAG,GAAG;;;;;;;UAQd;GAEb,UAAU,gBACV,UAAU,eAAe,YAAY,uBACrC,UAAU,0BAA0B,uBAEpC,MAAM,qBAAqB,GAAG,GAAG,GAAG,kBACnC,iBAAiB,GAAG,GAAG,GAAG;;;;;;;;;;;;;;;;EAiB7B,WAAW,UAAU,4BAA4B,qCAAqC;;cAW3E,4BAA4B;;;;;;;;;;;iBCpFzB,kBAAkB,SAAS,eAAe;;;;;;;;;iBAiE1C,YAAY,sBAAsB,UAAS,qBAA0B;;;;;iBAQrE,kBAAkB,YAAY;;;;UC5G7B;;EAEf;;;;;;EAMA,WAAW;;;;;;;;;;;;;;;;;;;;;EAqBX,QAAQ;;EAER;;;cAIW,cAAY,kBAAA;;;;;iBAMT,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoCnB,gBAAgB,UAAU,gBAAgB,KAAK,IAAI,UAAU;;;;;;;;;;;iBCe7D,2BAAsB,KACnB,SAAO,KAAO,UAAQ,MAAQ;;;;;;;cCxFpC;UAKI;;;;;EAKf;;;;;UAMe;;EAEf;;EAEA;;EAEA;;EAEA;;;;;;iBAqBc,iBAAiB,iBAAiB;;;;;;;;;;;;;;;;;;;;;;iBA0ClC,aAAa,UAAS,uBAAwB,KAC/C,SAAO,KAAO,UAAQ,MAAQ;;;UC5F5B;;;;;;;;;;;;;;;EAef;;EAGA;;EAGA;;EAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAgCW,aAAW,eAAA;;;UCtDP;;;;;;;;;;;;;;EAcf;;;;;;;;;;;;;;;EAgBA;;;;;;;;;;EAWA;;;;;EAMA;;;;;EAMA;;;;;;;;;;;;;;EAeA;;;;;;;;;;;;;;;;;;;;;iBAoCc,iBACd,mBAKA,cACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAoJW,YAAU,eAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
import { g as FileUploadConfig, o as BaseUploadOptions } from "./decorators-
|
|
3
|
-
import "./adapter-
|
|
2
|
+
import { g as FileUploadConfig, o as BaseUploadOptions } from "./decorators-CbTBUVOu.mjs";
|
|
3
|
+
import "./adapter-BuOTeSQy.mjs";
|
|
4
4
|
import { NextFunction, Request, RequestHandler, Response } from "express";
|
|
5
5
|
import { Options } from "multer";
|
|
6
6
|
//#region src/http/middleware/upload-config.d.ts
|
|
@@ -113,4 +113,4 @@ declare const upload: {
|
|
|
113
113
|
};
|
|
114
114
|
//#endregion
|
|
115
115
|
export { RawUploadPart as a, buildFileTypeFilter as c, upload as i, resolveMimeTypes as l, buildUploadMiddleware as n, UploadedFileLike as o, cleanupFiles as r, applyUploadConfig as s, UploadOptions as t };
|
|
116
|
-
//# sourceMappingURL=upload-
|
|
116
|
+
//# sourceMappingURL=upload-CbJOK-j9.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload-
|
|
1
|
+
{"version":3,"file":"upload-CbJOK-j9.d.mts","names":[],"sources":["../src/http/middleware/upload-config.ts","../src/http/middleware/upload.ts"],"mappings":";;;;;;;;;;;;;;;;iBAuEgB,iBAAiB;;;;;;;;iBAcjB,oBACd,cAAc,mCACd,gBAAgB,0BACd,kBAAkB;;UAYL;EACf;EACA;EACA;EACA;EACA;EACA,QAAQ;;;UAIO;EACf;EACA;EACA;EACA,QAAQ;;;;;;;;;iBAYM,kBACd,OAAO,iBACP,QAAQ;EACL,OAAO;EAAkB,QAAQ;;;;;;;;;UC/ErB,sBAAsB;;EAErC,UAAU;;EAEV;;;;;;;;;;;;;;iBA6EO,OAAO,mBAAmB,UAAS,gBAAqB;;;;iBAQxD,MAAM,mBAAmB,mBAAe,UAAS,gBAAqB;;;;iBAQtE,KAAK,UAAS,gBAAqB;;;;;;;;;;;;;;;iBAmB5B,iBAAY,KACb,SAAO,KAAO,UAAQ,MAAQ;;;;;;iBA+B7B,sBAAsB,QAAQ,mBAAmB;;cAmBpD"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs
|
|
2
|
+
* @forinda/kickjs v8.0.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -9,4 +9,4 @@
|
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
11
|
import{HttpException,HttpStatus}from"./core/errors.mjs";const MIME_MAP={jpg:`image/jpeg`,jpeg:`image/jpeg`,png:`image/png`,gif:`image/gif`,webp:`image/webp`,svg:`image/svg+xml`,bmp:`image/bmp`,ico:`image/x-icon`,tiff:`image/tiff`,tif:`image/tiff`,avif:`image/avif`,pdf:`application/pdf`,doc:`application/msword`,docx:`application/vnd.openxmlformats-officedocument.wordprocessingml.document`,xls:`application/vnd.ms-excel`,xlsx:`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`,ppt:`application/vnd.ms-powerpoint`,pptx:`application/vnd.openxmlformats-officedocument.presentationml.presentation`,odt:`application/vnd.oasis.opendocument.text`,ods:`application/vnd.oasis.opendocument.spreadsheet`,odp:`application/vnd.oasis.opendocument.presentation`,rtf:`application/rtf`,txt:`text/plain`,csv:`text/csv`,zip:`application/zip`,gz:`application/gzip`,tar:`application/x-tar`,rar:`application/vnd.rar`,"7z":`application/x-7z-compressed`,mp3:`audio/mpeg`,wav:`audio/wav`,ogg:`audio/ogg`,flac:`audio/flac`,aac:`audio/aac`,mp4:`video/mp4`,webm:`video/webm`,avi:`video/x-msvideo`,mov:`video/quicktime`,mkv:`video/x-matroska`,json:`application/json`,xml:`application/xml`,html:`text/html`};function resolveMimeTypes(types){return types.map(t=>{let lower=t.toLowerCase().replace(/^\./,``);return MIME_MAP[lower]??t})}function buildFileTypeFilter(allowedTypes,customMimeMap){if(!allowedTypes)return()=>!0;if(typeof allowedTypes==`function`)return allowedTypes;let mimeMap=customMimeMap?{...MIME_MAP,...customMimeMap}:MIME_MAP,resolved=allowedTypes.map(t=>mimeMap[t.toLowerCase().replace(/^\./,``)]??t);return mimetype=>resolved.some(type=>type.endsWith(`/*`)?mimetype.startsWith(type.replace(`/*`,`/`)):mimetype===type)}function applyUploadConfig(parts,config){if(config.mode===`none`)return{};let field=config.fieldName??`file`,maxSize=config.maxSize??5242880,typeAllowed=buildFileTypeFilter(config.allowedTypes,config.customMimeMap),files=parts.filter(p=>p.fieldname===field).map(p=>{if(!typeAllowed(p.mimetype,p.filename))throw new HttpException(HttpStatus.UNSUPPORTED_MEDIA_TYPE,`File type ${p.mimetype} is not allowed`);if(p.buffer.length>maxSize)throw new HttpException(HttpStatus.PAYLOAD_TOO_LARGE,`File ${p.filename} exceeds the ${maxSize}-byte limit`);return{fieldname:p.fieldname,originalname:p.filename,encoding:`7bit`,mimetype:p.mimetype,size:p.buffer.length,buffer:p.buffer}});if(config.mode===`single`)return{file:files[0]};let maxCount=config.maxCount??10;return{files:files.slice(0,maxCount)}}export{buildFileTypeFilter as n,resolveMimeTypes as r,applyUploadConfig as t};
|
|
12
|
-
//# sourceMappingURL=upload-config-
|
|
12
|
+
//# sourceMappingURL=upload-config-DtnX4uVR.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload-config-
|
|
1
|
+
{"version":3,"file":"upload-config-DtnX4uVR.mjs","names":[],"sources":["../src/http/middleware/upload-config.ts"],"sourcesContent":["// Pure (edge-safe) upload core — the engine-neutral half of the upload\n// middleware. Extracted from `upload.ts` so the `@forinda/kickjs/web` entry\n// graph can validate/shape buffered multipart parts WITHOUT dragging in\n// upload.ts's node-only surface (multer peer loading via `node:module`,\n// disk cleanup via `node:fs/promises`). `upload.ts` re-exports everything\n// here, so existing import sites are unchanged.\n\nimport { HttpException, HttpStatus } from '../../core/errors'\nimport type { BaseUploadOptions, FileUploadConfig } from '../../core/decorators'\n\nexport const 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 * Build a runtime-agnostic `(mimetype, filename) => boolean` file-type filter\n * from `allowedTypes` (a predicate, or a list of extensions / MIME types /\n * wildcards). Returned by both the multer middleware (Express) and the\n * Fastify / h3 / web multipart paths so the accept/reject rule stays\n * identical across engines. No `allowedTypes` → accept everything.\n */\nexport function buildFileTypeFilter(\n allowedTypes: BaseUploadOptions['allowedTypes'],\n customMimeMap?: Record<string, string>,\n): (mimetype: string, filename: string) => boolean {\n if (!allowedTypes) return () => true\n if (typeof allowedTypes === 'function') return allowedTypes\n const mimeMap = customMimeMap ? { ...MIME_MAP, ...customMimeMap } : MIME_MAP\n const resolved = allowedTypes.map((t) => mimeMap[t.toLowerCase().replace(/^\\./, '')] ?? t)\n return (mimetype: string) =>\n resolved.some((type) =>\n type.endsWith('/*') ? mimetype.startsWith(type.replace('/*', '/')) : mimetype === type,\n )\n}\n\n/** A multer-shaped uploaded file — what `ctx.file` / `ctx.files` expose. */\nexport interface UploadedFileLike {\n fieldname: string\n originalname: string\n encoding: string\n mimetype: string\n size: number\n buffer: Buffer\n}\n\n/** A buffered multipart file part, as the runtime hands it to {@link applyUploadConfig}. */\nexport interface RawUploadPart {\n fieldname: string\n filename: string\n mimetype: string\n buffer: Buffer\n}\n\nconst DEFAULT_MAX_SIZE = 5 * 1024 * 1024\n\n/**\n * Apply a `@FileUpload` config to already-buffered multipart parts and return\n * the multer-shaped `file` / `files` for `ctx`. Enforces the field name, type\n * filter, per-file size limit, and (array mode) max count — throwing\n * `HttpException` on a violation, mirroring the Express/multer error path.\n * Used by the Fastify, h3 and web runtimes; Express keeps using multer directly.\n */\nexport function applyUploadConfig(\n parts: RawUploadPart[],\n config: FileUploadConfig,\n): { file?: UploadedFileLike; files?: UploadedFileLike[] } {\n if (config.mode === 'none') return {}\n\n const field = config.fieldName ?? 'file'\n const maxSize = config.maxSize ?? DEFAULT_MAX_SIZE\n const typeAllowed = buildFileTypeFilter(config.allowedTypes, config.customMimeMap)\n\n const matched = parts.filter((p) => p.fieldname === field)\n const files: UploadedFileLike[] = matched.map((p) => {\n if (!typeAllowed(p.mimetype, p.filename)) {\n throw new HttpException(\n HttpStatus.UNSUPPORTED_MEDIA_TYPE,\n `File type ${p.mimetype} is not allowed`,\n )\n }\n if (p.buffer.length > maxSize) {\n throw new HttpException(\n HttpStatus.PAYLOAD_TOO_LARGE,\n `File ${p.filename} exceeds the ${maxSize}-byte limit`,\n )\n }\n return {\n fieldname: p.fieldname,\n originalname: p.filename,\n encoding: '7bit',\n mimetype: p.mimetype,\n size: p.buffer.length,\n buffer: p.buffer,\n }\n })\n\n if (config.mode === 'single') return { file: files[0] }\n const maxCount = config.maxCount ?? 10\n return { files: files.slice(0, maxCount) }\n}\n"],"mappings":";;;;;;;;;;wDAUA,MAAa,SAAmC,CAE9C,IAAK,aACL,KAAM,aACN,IAAK,YACL,IAAK,YACL,KAAM,aACN,IAAK,gBACL,IAAK,YACL,IAAK,eACL,KAAM,aACN,IAAK,aACL,KAAM,aAEN,IAAK,kBACL,IAAK,qBACL,KAAM,0EACN,IAAK,2BACL,KAAM,oEACN,IAAK,gCACL,KAAM,4EACN,IAAK,0CACL,IAAK,iDACL,IAAK,kDACL,IAAK,kBACL,IAAK,aACL,IAAK,WAEL,IAAK,kBACL,GAAI,mBACJ,IAAK,oBACL,IAAK,sBACL,KAAM,8BAEN,IAAK,aACL,IAAK,YACL,IAAK,YACL,KAAM,aACN,IAAK,YAEL,IAAK,YACL,KAAM,aACN,IAAK,kBACL,IAAK,kBACL,IAAK,mBAEL,KAAM,mBACN,IAAK,kBACL,KAAM,WACR,EAYA,SAAgB,iBAAiB,MAA2B,CAC1D,OAAO,MAAM,IAAK,GAAM,CACtB,IAAM,MAAQ,EAAE,YAAY,CAAC,CAAC,QAAQ,MAAO,EAAE,EAC/C,OAAO,SAAS,QAAU,CAC5B,CAAC,CACH,CASA,SAAgB,oBACd,aACA,cACiD,CACjD,GAAI,CAAC,aAAc,UAAa,GAChC,GAAI,OAAO,cAAiB,WAAY,OAAO,aAC/C,IAAM,QAAU,cAAgB,CAAE,GAAG,SAAU,GAAG,aAAc,EAAI,SAC9D,SAAW,aAAa,IAAK,GAAM,QAAQ,EAAE,YAAY,CAAC,CAAC,QAAQ,MAAO,EAAE,IAAM,CAAC,EACzF,MAAQ,WACN,SAAS,KAAM,MACb,KAAK,SAAS,IAAI,EAAI,SAAS,WAAW,KAAK,QAAQ,KAAM,GAAG,CAAC,EAAI,WAAa,IACpF,CACJ,CA6BA,SAAgB,kBACd,MACA,OACyD,CACzD,GAAI,OAAO,OAAS,OAAQ,MAAO,CAAC,EAEpC,IAAM,MAAQ,OAAO,WAAa,OAC5B,QAAU,OAAO,SAAW,QAC5B,YAAc,oBAAoB,OAAO,aAAc,OAAO,aAAa,EAG3E,MADU,MAAM,OAAQ,GAAM,EAAE,YAAc,KACZ,CAAC,CAAC,IAAK,GAAM,CACnD,GAAI,CAAC,YAAY,EAAE,SAAU,EAAE,QAAQ,EACrC,MAAM,IAAI,cACR,WAAW,uBACX,aAAa,EAAE,SAAS,gBAC1B,EAEF,GAAI,EAAE,OAAO,OAAS,QACpB,MAAM,IAAI,cACR,WAAW,kBACX,QAAQ,EAAE,SAAS,eAAe,QAAQ,YAC5C,EAEF,MAAO,CACL,UAAW,EAAE,UACb,aAAc,EAAE,SAChB,SAAU,OACV,SAAU,EAAE,SACZ,KAAM,EAAE,OAAO,OACf,OAAQ,EAAE,MACZ,CACF,CAAC,EAED,GAAI,OAAO,OAAS,SAAU,MAAO,CAAE,KAAM,MAAM,EAAG,EACtD,IAAM,SAAW,OAAO,UAAY,GACpC,MAAO,CAAE,MAAO,MAAM,MAAM,EAAG,QAAQ,CAAE,CAC3C"}
|
package/dist/web.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
|
-
import { x as MiddlewareHandler } from "./decorators-
|
|
3
|
-
import {
|
|
4
|
-
import { D as SseHandler, b as RuntimeResponse, m as RouteEntry } from "./adapter-
|
|
5
|
-
import { a as KvSessionStoreOptions, c as InferHandlerResponse, d as isReply, f as reply, i as KvSessionStore, l as Reply, n as KvRateLimitStore, o as RateLimitGuardOptions, r as KvRateLimitStoreOptions, s as rateLimitGuard, t as KvLike } from "./kv-stores-
|
|
2
|
+
import { x as MiddlewareHandler } from "./decorators-CbTBUVOu.mjs";
|
|
3
|
+
import { m as ContributorRegistrations, r as AppModuleEntry } from "./app-module-DZW0uYNA.mjs";
|
|
4
|
+
import { D as SseHandler, b as RuntimeResponse, m as RouteEntry } from "./adapter-BuOTeSQy.mjs";
|
|
5
|
+
import { a as KvSessionStoreOptions, c as InferHandlerResponse, d as isReply, f as reply, i as KvSessionStore, l as Reply, n as KvRateLimitStore, o as RateLimitGuardOptions, r as KvRateLimitStoreOptions, s as rateLimitGuard, t as KvLike } from "./kv-stores-CbsEHE5J.mjs";
|
|
6
6
|
//#region src/http/web/driver.d.ts
|
|
7
7
|
/**
|
|
8
8
|
* Presents a web `Request` as the express-shaped `req` object that
|
|
@@ -125,10 +125,10 @@ interface CreateWebAppOptions {
|
|
|
125
125
|
env?: Record<string, string | undefined>;
|
|
126
126
|
/**
|
|
127
127
|
* Global `(ctx, next)` middlewares, run before route middlewares on every
|
|
128
|
-
* route — the web-entry counterpart of `bootstrap({
|
|
128
|
+
* route — the web-entry counterpart of `bootstrap({ middlewares })`. Only
|
|
129
129
|
* ctx-style handlers are accepted (no connect/express middleware here).
|
|
130
130
|
*/
|
|
131
|
-
|
|
131
|
+
middlewares?: MiddlewareHandler[];
|
|
132
132
|
}
|
|
133
133
|
interface WebApp {
|
|
134
134
|
/** Web-standard entry: `Request` in, `Response` out. */
|
package/dist/web.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.d.mts","names":[],"sources":["../src/http/web/driver.ts","../src/http/web/handler.ts","../src/web.ts"],"mappings":";;;;;;;;;;;;;cAiBa;WACF;WACA;;WAEA,SAAS;EAClB;EACA,QAAQ;EACR,OAAO;EACP;EACA;EACA;GAGC;mBAEgB;EAEjB,YAAY,SAAS,SAAS,KAAK;;EAmBnC,KAAK,eAAe;;EAMpB,GAAG,eAAe;;;;;;;;;;;;;;;;cA0BP,6BAA6B;mBAYX;;WAVpB,OAAO,QAAQ;;EAExB;UAEQ;mBACS;UACT;UACA;UACA;EAER,YAA6B,SAAS;EAMtC,OAAO;EAKP,KAAK;EAML,KAAK;EAYL,KAAK;EAKL,UAAU,cAAc;EAUxB;EAIA,UAAU,oBAAoB,UAAU;EASxC;EAKA,MAAM;EAQN,IAAI;EAUJ,KAAK,eAAe,cAAc;MAM9B;;UAKI;;UAgBA;;;;
|
|
1
|
+
{"version":3,"file":"web.d.mts","names":[],"sources":["../src/http/web/driver.ts","../src/http/web/handler.ts","../src/web.ts"],"mappings":";;;;;;;;;;;;;cAiBa;WACF;WACA;;WAEA,SAAS;EAClB;EACA,QAAQ;EACR,OAAO;EACP;EACA;EACA;GAGC;mBAEgB;EAEjB,YAAY,SAAS,SAAS,KAAK;;EAmBnC,KAAK,eAAe;;EAMpB,GAAG,eAAe;;;;;;;;;;;;;;;;cA0BP,6BAA6B;mBAYX;;WAVpB,OAAO,QAAQ;;EAExB;UAEQ;mBACS;UACT;UACA;UACA;EAER,YAA6B,SAAS;EAMtC,OAAO;EAKP,KAAK;EAML,KAAK;EAYL,KAAK;EAKL,UAAU,cAAc;EAUxB;EAIA,UAAU,oBAAoB,UAAU;EASxC;EAKA,MAAM;EAQN,IAAI;EAUJ,KAAK,eAAe,cAAc;MAM9B;;UAKI;;UAgBA;;;;UCvLO;EACf,SAAS;EACT,KAAK;EACL,QAAQ;;UAGO;;;;;;;;EAQf,WAAW,cAAc,KAAK,gBAAgB,KAAK,6BAA6B;;;;;;;iBAkDlE,gBACd,OAAO,YACP,QAAQ,iBACN,YAAY,uBAAuB,QAAQ;;;UCvB9B;;;;;;;EAOf;;EAEA,SAAS;;EAET;;;;;EAKA;;EAEA,eAAe;;;;;;EAMf,MAAM;;;;;;EAMN,cAAc;;UAGC;;EAEf,QAAQ,SAAS,YAAY,QAAQ;;EAErC;;;;;;;;iBASc,aAAa,SAAS,sBAAsB;;;;;;;;;iBAiH5C,mBACd,QAAQ,KAAK,uCAAuC;EACjD,QAAQ,SAAS,SAAS,MAAM,uCAAuC,QAAQ"}
|
package/dist/web.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs
|
|
2
|
+
* @forinda/kickjs v8.0.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{t as Container}from"./container-
|
|
11
|
+
import{t as Container}from"./container-GbeejFhH.mjs";import{t as MutableModuleRegistry}from"./module-registry-DIngF0Ne.mjs";import{buildMountPath}from"./core/path.mjs";import{n as assertRouteUnique,r as buildRouteTable,t as _setExternalContributorSources}from"./router-builder-FukfDDLt.mjs";import{n as isReply,r as reply}from"./reply-CdVb6ipw.mjs";import{i as requestStore}from"./context-BnIvtMUl.mjs";import{n as KvSessionStore,r as rateLimitGuard,t as KvRateLimitStore}from"./kv-stores-pjWD0-ri.mjs";import{n as WebRequestShim,r as WebResponseDriver,t as compileWebRoute}from"./handler-9qFN7nRw.mjs";function createWebApp(options){let h3mod=options.h3;if(typeof h3mod?.H3!=`function`)throw Error(`@forinda/kickjs/web: pass the h3 v2 module — \`createWebApp({ h3: await import("h3"), ... })\`. The installed h3 must be v2 (npm dist-tag 'latest'); v1 has no H3 class.`);let container=Container.getInstance();if(options.env){let env=options.env;Container._envResolver=key=>env[key]}Container._requestStoreProvider=()=>requestStore.getStore()??null;let registry=new MutableModuleRegistry;for(let m of options.modules)registry.mount(m);let modules=registry.entries.map(entry=>{let mod=typeof entry==`function`?new entry:entry;return mod.register?.(container),mod});container.bootstrap();let app=new h3mod.H3,apiPrefix=options.apiPrefix??`/api`,defaultVersion=options.defaultVersion??1,globalMiddleware=options.middlewares??[],globalSources=(options.contributors??[]).map(registration=>({source:`global`,registration,label:`createWebApp`})),seenRoutes=new Map;for(let mod of modules){let declaredName=mod.name,moduleLabel=typeof declaredName==`string`&&declaredName.length>0?declaredName:mod.constructor?.name??`module`;_setExternalContributorSources([...(mod.contributors?.()??[]).map(registration=>({source:`module`,registration,label:moduleLabel})),...globalSources]);try{let result=mod.routes();if(!result)continue;let routeSets=Array.isArray(result)?result:[result];for(let route of routeSets){if(!route.controller)throw Error(`@forinda/kickjs/web: module route at '${route.path}' has no controller. Express-style \`router\` mounts are not supported on the web entry.`);let mountPath=buildMountPath(apiPrefix,route.version??defaultVersion,route.path);for(let entry of buildRouteTable(route.controller)){let url=joinPath(mountPath,entry.path),owner=`${route.controller.name??`controller`}.${String(entry.meta.handlerName??`?`)}`;assertRouteUnique(seenRoutes,entry.method,url,owner),globalMiddleware.length>0&&(entry.middlewares=[...globalMiddleware,...entry.middlewares]);let run=compileWebRoute(entry);app.on(entry.method,url,event=>run({request:event.req,url:event.url,params:event.context.params??{}}))}}}finally{_setExternalContributorSources([])}}return{fetch:request=>app.fetch(request),h3:app}}function createFetchHandler(build){let app;return{fetch:(request,env={})=>(app??=createWebApp(build(env)),app.fetch(request))}}function joinPath(mountPath,path){let a=mountPath.endsWith(`/`)?mountPath.slice(0,-1):mountPath;return path===`/`||path===``?a===``?`/`:a:`${a}${path.startsWith(`/`)?path:`/${path}`}`}export{KvRateLimitStore,KvSessionStore,WebRequestShim,WebResponseDriver,compileWebRoute,createFetchHandler,createWebApp,isReply,rateLimitGuard,reply};
|
|
12
12
|
//# sourceMappingURL=web.mjs.map
|