@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
|
@@ -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{r as registerDisposable}from"../../disposables-
|
|
11
|
+
import{r as registerDisposable}from"../../disposables-Ht0M5KKc.mjs";import{n as resolvePathname,t as resolveClientIp}from"../../client-ip-C909xeVL.mjs";import{r as sendJson}from"../../respond-ERWwiws9.mjs";var MemoryStore=class{windowMs;hits=new Map;cleanupTimer;constructor(windowMs){this.windowMs=windowMs,this.cleanupTimer=setInterval(()=>this.cleanup(),windowMs),this.cleanupTimer.unref&&this.cleanupTimer.unref(),registerDisposable(()=>this.dispose())}dispose(){clearInterval(this.cleanupTimer),this.hits.clear()}async increment(key){let now=Date.now(),entry=this.hits.get(key);if(entry&&entry.resetTime.getTime()>now)return entry.totalHits++,{totalHits:entry.totalHits,resetTime:entry.resetTime};let resetTime=new Date(now+this.windowMs),newEntry={totalHits:1,resetTime};return this.hits.set(key,newEntry),{totalHits:1,resetTime}}async decrement(key){let entry=this.hits.get(key);entry&&entry.totalHits>0&&entry.totalHits--}async reset(key){this.hits.delete(key)}cleanup(){let now=Date.now();for(let[key,entry]of this.hits)entry.resetTime.getTime()<=now&&this.hits.delete(key)}};function rateLimit(options={}){let max=options.max??100,windowMs=options.windowMs??6e4,message=options.message??`Too Many Requests`,statusCode=options.statusCode??429,keyGenerator=options.keyGenerator??(req=>resolveClientIp(req)??`global`),sendHeaders=options.headers??!0,store=options.store??new MemoryStore(windowMs),skip=options.skip,skipPaths=new Set(options.skipPaths??[]);return async(req,res,next)=>{if(skipPaths.has(resolvePathname(req))||skip&&skip(req))return next();let key=keyGenerator(req),{totalHits,resetTime}=await store.increment(key),remaining=Math.max(0,max-totalHits);if(sendHeaders&&(res.setHeader(`RateLimit-Limit`,max),res.setHeader(`RateLimit-Remaining`,remaining),res.setHeader(`RateLimit-Reset`,Math.ceil(resetTime.getTime()/1e3))),totalHits>max)return sendJson(res,statusCode,{message});next()}}export{rateLimit};
|
|
12
12
|
//# sourceMappingURL=rate-limit.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.mjs","names":[],"sources":["../../../src/http/middleware/rate-limit.ts"],"sourcesContent":["import type { Request, Response, NextFunction } from 'express'\nimport { resolveClientIp, resolvePathname, type ClientRequestLike } from '../client-ip'\n\nimport { registerDisposable } from '../../core/disposables'\n\nexport interface RateLimitStore {\n increment(key: string): Promise<{ totalHits: number; resetTime: Date }>\n decrement(key: string): Promise<void>\n reset(key: string): Promise<void>\n}\n\nexport interface RateLimitOptions {\n /** Maximum number of requests per window (default: 100) */\n max?: number\n /** Time window in milliseconds (default: 60_000) */\n windowMs?: number\n /** Response message when rate limit is exceeded (default: 'Too Many Requests') */\n message?: string\n /** HTTP status code when rate limit is exceeded (default: 429) */\n statusCode?: number\n /** Function to generate the key for rate limiting (default: req.ip) */\n keyGenerator?: (req: Request) => string\n /** Whether to send rate limit headers (default: true) */\n headers?: boolean\n /** Custom store implementation (default: in-memory Map) */\n store?: RateLimitStore\n /** Function to skip rate limiting for certain requests */\n skip?: (req: Request) => boolean\n /** Paths to exclude from rate limiting */\n skipPaths?: string[]\n}\n\ninterface MemoryStoreEntry {\n totalHits: number\n resetTime: Date\n}\n\nclass MemoryStore implements RateLimitStore {\n private hits = new Map<string, MemoryStoreEntry>()\n private cleanupTimer: ReturnType<typeof setInterval>\n\n constructor(private windowMs: number) {\n this.cleanupTimer = setInterval(() => this.cleanup(), windowMs)\n // Allow the process to exit without waiting for the timer\n if (this.cleanupTimer.unref) {\n this.cleanupTimer.unref()\n }\n // Symmetric teardown: Application.shutdown() drains the disposables\n // registry — without this, every in-process app re-create (tests, HMR)\n // leaked one live interval + its hits Map.\n registerDisposable(() => this.dispose())\n }\n\n /** Clear the cleanup interval and drop all hit counters. */\n dispose(): void {\n clearInterval(this.cleanupTimer)\n this.hits.clear()\n }\n\n async increment(key: string): Promise<{ totalHits: number; resetTime: Date }> {\n const now = Date.now()\n const entry = this.hits.get(key)\n\n if (entry && entry.resetTime.getTime() > now) {\n entry.totalHits++\n return { totalHits: entry.totalHits, resetTime: entry.resetTime }\n }\n\n const resetTime = new Date(now + this.windowMs)\n const newEntry: MemoryStoreEntry = { totalHits: 1, resetTime }\n this.hits.set(key, newEntry)\n return { totalHits: 1, resetTime }\n }\n\n async decrement(key: string): Promise<void> {\n const entry = this.hits.get(key)\n if (entry && entry.totalHits > 0) {\n entry.totalHits--\n }\n }\n\n async reset(key: string): Promise<void> {\n this.hits.delete(key)\n }\n\n private cleanup(): void {\n const now = Date.now()\n for (const [key, entry] of this.hits) {\n if (entry.resetTime.getTime() <= now) {\n this.hits.delete(key)\n }\n }\n }\n}\n\n/**\n * Rate limiting middleware.\n *\n * Limits the number of requests a client can make within a time window.\n * Uses an in-memory store by default, but accepts a custom store for\n * distributed deployments (e.g. Redis).\n *\n * @example\n * ```ts\n * import { rateLimit } from '@forinda/kickjs'\n *\n * bootstrap({\n * modules,\n *
|
|
1
|
+
{"version":3,"file":"rate-limit.mjs","names":[],"sources":["../../../src/http/middleware/rate-limit.ts"],"sourcesContent":["import { sendJson } from './respond'\nimport type { Request, Response, NextFunction } from 'express'\nimport { resolveClientIp, resolvePathname, type ClientRequestLike } from '../client-ip'\n\nimport { registerDisposable } from '../../core/disposables'\n\nexport interface RateLimitStore {\n increment(key: string): Promise<{ totalHits: number; resetTime: Date }>\n decrement(key: string): Promise<void>\n reset(key: string): Promise<void>\n}\n\nexport interface RateLimitOptions {\n /** Maximum number of requests per window (default: 100) */\n max?: number\n /** Time window in milliseconds (default: 60_000) */\n windowMs?: number\n /** Response message when rate limit is exceeded (default: 'Too Many Requests') */\n message?: string\n /** HTTP status code when rate limit is exceeded (default: 429) */\n statusCode?: number\n /** Function to generate the key for rate limiting (default: req.ip) */\n keyGenerator?: (req: Request) => string\n /** Whether to send rate limit headers (default: true) */\n headers?: boolean\n /** Custom store implementation (default: in-memory Map) */\n store?: RateLimitStore\n /** Function to skip rate limiting for certain requests */\n skip?: (req: Request) => boolean\n /** Paths to exclude from rate limiting */\n skipPaths?: string[]\n}\n\ninterface MemoryStoreEntry {\n totalHits: number\n resetTime: Date\n}\n\nclass MemoryStore implements RateLimitStore {\n private hits = new Map<string, MemoryStoreEntry>()\n private cleanupTimer: ReturnType<typeof setInterval>\n\n constructor(private windowMs: number) {\n this.cleanupTimer = setInterval(() => this.cleanup(), windowMs)\n // Allow the process to exit without waiting for the timer\n if (this.cleanupTimer.unref) {\n this.cleanupTimer.unref()\n }\n // Symmetric teardown: Application.shutdown() drains the disposables\n // registry — without this, every in-process app re-create (tests, HMR)\n // leaked one live interval + its hits Map.\n registerDisposable(() => this.dispose())\n }\n\n /** Clear the cleanup interval and drop all hit counters. */\n dispose(): void {\n clearInterval(this.cleanupTimer)\n this.hits.clear()\n }\n\n async increment(key: string): Promise<{ totalHits: number; resetTime: Date }> {\n const now = Date.now()\n const entry = this.hits.get(key)\n\n if (entry && entry.resetTime.getTime() > now) {\n entry.totalHits++\n return { totalHits: entry.totalHits, resetTime: entry.resetTime }\n }\n\n const resetTime = new Date(now + this.windowMs)\n const newEntry: MemoryStoreEntry = { totalHits: 1, resetTime }\n this.hits.set(key, newEntry)\n return { totalHits: 1, resetTime }\n }\n\n async decrement(key: string): Promise<void> {\n const entry = this.hits.get(key)\n if (entry && entry.totalHits > 0) {\n entry.totalHits--\n }\n }\n\n async reset(key: string): Promise<void> {\n this.hits.delete(key)\n }\n\n private cleanup(): void {\n const now = Date.now()\n for (const [key, entry] of this.hits) {\n if (entry.resetTime.getTime() <= now) {\n this.hits.delete(key)\n }\n }\n }\n}\n\n/**\n * Rate limiting middleware.\n *\n * Limits the number of requests a client can make within a time window.\n * Uses an in-memory store by default, but accepts a custom store for\n * distributed deployments (e.g. Redis).\n *\n * @example\n * ```ts\n * import { rateLimit } from '@forinda/kickjs'\n *\n * bootstrap({\n * modules,\n * middlewares: [\n * rateLimit({ max: 100, windowMs: 60_000 }),\n * // ... other middleware\n * ],\n * })\n * ```\n */\nexport function rateLimit(options: RateLimitOptions = {}) {\n const max = options.max ?? 100\n const windowMs = options.windowMs ?? 60_000\n const message = options.message ?? 'Too Many Requests'\n const statusCode = options.statusCode ?? 429\n // `req.ip` is Express-only. Under Fastify and h3 it is undefined, so EVERY\n // caller fell back to the same `'127.0.0.1'` literal — one shared bucket,\n // letting a single client exhaust the limit for everyone.\n const keyGenerator =\n options.keyGenerator ??\n ((req: Request) => resolveClientIp(req as ClientRequestLike) ?? 'global')\n const sendHeaders = options.headers ?? true\n const store = options.store ?? new MemoryStore(windowMs)\n const skip = options.skip\n const skipPaths = new Set(options.skipPaths ?? [])\n\n return async (req: Request, res: Response, next: NextFunction) => {\n // Skip if path is in the skip list\n // `req.path` is Express-only; `has(undefined)` never matched, so\n // configured skips were silently dead on the other runtimes.\n if (skipPaths.has(resolvePathname(req as ClientRequestLike))) {\n return next()\n }\n\n // Skip if the skip function returns true\n if (skip && skip(req)) {\n return next()\n }\n\n const key = keyGenerator(req)\n const { totalHits, resetTime } = await store.increment(key)\n const remaining = Math.max(0, max - totalHits)\n\n if (sendHeaders) {\n res.setHeader('RateLimit-Limit', max)\n res.setHeader('RateLimit-Remaining', remaining)\n res.setHeader('RateLimit-Reset', Math.ceil(resetTime.getTime() / 1000))\n }\n\n if (totalHits > max) {\n return sendJson(res, statusCode, { message })\n }\n\n next()\n }\n}\n"],"mappings":";;;;;;;;;;8MAsCA,IAAM,YAAN,KAA4C,CAItB,SAHpB,KAAe,IAAI,IACnB,aAEA,YAAY,SAA0B,CAAlB,KAAA,SAAA,SAClB,KAAK,aAAe,gBAAkB,KAAK,QAAQ,EAAG,QAAQ,EAE1D,KAAK,aAAa,OACpB,KAAK,aAAa,MAAM,EAK1B,uBAAyB,KAAK,QAAQ,CAAC,CACzC,CAGA,SAAgB,CACd,cAAc,KAAK,YAAY,EAC/B,KAAK,KAAK,MAAM,CAClB,CAEA,MAAM,UAAU,IAA8D,CAC5E,IAAM,IAAM,KAAK,IAAI,EACf,MAAQ,KAAK,KAAK,IAAI,GAAG,EAE/B,GAAI,OAAS,MAAM,UAAU,QAAQ,EAAI,IAEvC,MADA,OAAM,YACC,CAAE,UAAW,MAAM,UAAW,UAAW,MAAM,SAAU,EAGlE,IAAM,UAAY,IAAI,KAAK,IAAM,KAAK,QAAQ,EACxC,SAA6B,CAAE,UAAW,EAAG,SAAU,EAE7D,OADA,KAAK,KAAK,IAAI,IAAK,QAAQ,EACpB,CAAE,UAAW,EAAG,SAAU,CACnC,CAEA,MAAM,UAAU,IAA4B,CAC1C,IAAM,MAAQ,KAAK,KAAK,IAAI,GAAG,EAC3B,OAAS,MAAM,UAAY,GAC7B,MAAM,WAEV,CAEA,MAAM,MAAM,IAA4B,CACtC,KAAK,KAAK,OAAO,GAAG,CACtB,CAEA,SAAwB,CACtB,IAAM,IAAM,KAAK,IAAI,EACrB,IAAK,GAAM,CAAC,IAAK,SAAU,KAAK,KAC1B,MAAM,UAAU,QAAQ,GAAK,KAC/B,KAAK,KAAK,OAAO,GAAG,CAG1B,CACF,EAsBA,SAAgB,UAAU,QAA4B,CAAC,EAAG,CACxD,IAAM,IAAM,QAAQ,KAAO,IACrB,SAAW,QAAQ,UAAY,IAC/B,QAAU,QAAQ,SAAW,oBAC7B,WAAa,QAAQ,YAAc,IAInC,aACJ,QAAQ,eACN,KAAiB,gBAAgB,GAAwB,GAAK,UAC5D,YAAc,QAAQ,SAAW,GACjC,MAAQ,QAAQ,OAAS,IAAI,YAAY,QAAQ,EACjD,KAAO,QAAQ,KACf,UAAY,IAAI,IAAI,QAAQ,WAAa,CAAC,CAAC,EAEjD,OAAO,MAAO,IAAc,IAAe,OAAuB,CAShE,GALI,UAAU,IAAI,gBAAgB,GAAwB,CAAC,GAKvD,MAAQ,KAAK,GAAG,EAClB,OAAO,KAAK,EAGd,IAAM,IAAM,aAAa,GAAG,EACtB,CAAE,UAAW,WAAc,MAAM,MAAM,UAAU,GAAG,EACpD,UAAY,KAAK,IAAI,EAAG,IAAM,SAAS,EAQ7C,GANI,cACF,IAAI,UAAU,kBAAmB,GAAG,EACpC,IAAI,UAAU,sBAAuB,SAAS,EAC9C,IAAI,UAAU,kBAAmB,KAAK,KAAK,UAAU,QAAQ,EAAI,GAAI,CAAC,GAGpE,UAAY,IACd,OAAO,SAAS,IAAK,WAAY,CAAE,OAAQ,CAAC,EAG9C,KAAK,CACP,CACF"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
3
|
-
export { RequestLoggerOptions, requestLogger };
|
|
2
|
+
import { i as requestLogger, n as LoggedResponse, r as RequestLoggerOptions, t as LoggedRequest } from "../../request-logger-TgcKRNDD.mjs";
|
|
3
|
+
export { LoggedRequest, LoggedResponse, RequestLoggerOptions, requestLogger };
|
|
@@ -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{createLogger}from"../../core/logger.mjs";import"../../core-
|
|
11
|
+
import{createLogger}from"../../core/logger.mjs";import"../../core-DVbWxSyr.mjs";function pathnameOf(url){if(!url)return`/`;let q=url.indexOf(`?`);return(q===-1?url:url.slice(0,q))||`/`}function requestLogger(options={}){let log=createLogger(options.name??`HTTP`),level=options.level??`info`,skip=options.skip??[];return(req,res,next)=>{let pathname=req.path??pathnameOf(req.url);if(skip.some(prefix=>pathname.startsWith(prefix)))return next();let start=Date.now();res.on(`finish`,()=>{let duration=Date.now()-start,requestId=req.requestId||req.headers[`x-request-id`]||`-`,status=res.statusCode,path=req.originalUrl??req.url??`/`;log[level](`${req.method} ${path} ${status} ${duration}ms ${requestId}`)}),next()}}export{requestLogger};
|
|
12
12
|
//# sourceMappingURL=request-logger.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-logger.mjs","names":[],"sources":["../../../src/http/middleware/request-logger.ts"],"sourcesContent":["/**\n * The slice of the request/response this logger touches, from node's shapes\n * rather than Express's. This runs under EVERY runtime — Fastify and h3 hand\n * connect middleware the raw node objects — so anything Express-only is\n * `undefined` there.\n */\
|
|
1
|
+
{"version":3,"file":"request-logger.mjs","names":[],"sources":["../../../src/http/middleware/request-logger.ts"],"sourcesContent":["/**\n * The slice of the request/response this logger touches, from node's shapes\n * rather than Express's. This runs under EVERY runtime — Fastify and h3 hand\n * connect middleware the raw node objects — so anything Express-only is\n * `undefined` there.\n */\n/**\n * The request shape this middleware reads.\n *\n * Exported because it appears in the returned handler's signature: a consumer\n * following the documented `src/middleware/index.ts` layout exports the\n * middleware array with an inferred type, and a type the entry does not\n * re-export cannot be named there — `TS4023: Exported variable 'middleware'\n * has or is using name 'LoggedRequest' … but cannot be named`.\n *\n * Deliberately structural rather than Express's `Request`: the Fastify and h3\n * runtimes pass a raw Node request, which has `url` but no `path`.\n */\nexport interface LoggedRequest {\n method?: string\n url?: string\n /** Express-only convenience; the raw node request has only `url`. */\n path?: string\n /** Express-only; absent on the raw node request the other runtimes pass. */\n originalUrl?: string\n headers: Record<string, string | string[] | undefined>\n}\n\n/** The response shape this middleware reads. Exported for the same reason as {@link LoggedRequest}. */\nexport interface LoggedResponse {\n statusCode: number\n on(event: 'finish', listener: () => void): unknown\n}\n\n/** Pathname only — `req.url` carries the query string. */\nfunction pathnameOf(url: string | undefined): string {\n if (!url) return '/'\n const q = url.indexOf('?')\n return (q === -1 ? url : url.slice(0, q)) || '/'\n}\nimport { createLogger } from '../../core'\n\nexport interface RequestLoggerOptions {\n /** Logger name (default: 'HTTP') */\n name?: string\n /** Log level for successful requests (default: 'info') */\n level?: 'info' | 'debug' | 'trace'\n /** Skip logging for paths matching these prefixes (e.g. ['/health', '/_debug']) */\n skip?: string[]\n}\n\n/**\n * Middleware that logs every request with method, URL, status, duration, and request ID.\n *\n * @example\n * ```ts\n * bootstrap({\n * middlewares: [requestId(), requestLogger(), express.json()],\n * })\n * ```\n *\n * Output:\n * ```\n * [HTTP] GET /api/v1/users 200 12ms req-abc123\n * [HTTP] POST /api/v1/users 201 45ms req-def456\n * ```\n */\nexport function requestLogger(options: RequestLoggerOptions = {}) {\n const log = createLogger(options.name ?? 'HTTP')\n const level = options.level ?? 'info'\n const skip = options.skip ?? []\n\n return (req: LoggedRequest, res: LoggedResponse, next: () => void) => {\n // `req.path` is Express-only. Under Fastify and h3 it is `undefined`, so\n // this threw `Cannot read properties of undefined (reading 'startsWith')`\n // on EVERY request as soon as a `skip` prefix was configured — the\n // middleware crashed the request rather than declining to log it.\n const pathname = req.path ?? pathnameOf(req.url)\n if (skip.some((prefix) => pathname.startsWith(prefix))) {\n return next()\n }\n\n const start = Date.now()\n\n res.on('finish', () => {\n const duration = Date.now() - start\n const requestId = (req as any).requestId || req.headers['x-request-id'] || '-'\n const status = res.statusCode\n\n // `originalUrl` is Express-only — on Fastify and h3 this logged\n // `GET undefined 200 12ms`, dropping the path from every access-log\n // line under those runtimes.\n // Both are optional, so the pair still needs a floor — otherwise the\n // line reads `GET undefined` again, just via a different route.\n const path = req.originalUrl ?? req.url ?? '/'\n log[level](`${req.method} ${path} ${status} ${duration}ms ${requestId}`)\n })\n\n next()\n }\n}\n"],"mappings":";;;;;;;;;;gFAmCA,SAAS,WAAW,IAAiC,CACnD,GAAI,CAAC,IAAK,MAAO,IACjB,IAAM,EAAI,IAAI,QAAQ,GAAG,EACzB,OAAQ,IAAM,GAAK,IAAM,IAAI,MAAM,EAAG,CAAC,IAAM,GAC/C,CA4BA,SAAgB,cAAc,QAAgC,CAAC,EAAG,CAChE,IAAM,IAAM,aAAa,QAAQ,MAAQ,MAAM,EACzC,MAAQ,QAAQ,OAAS,OACzB,KAAO,QAAQ,MAAQ,CAAC,EAE9B,OAAQ,IAAoB,IAAqB,OAAqB,CAKpE,IAAM,SAAW,IAAI,MAAQ,WAAW,IAAI,GAAG,EAC/C,GAAI,KAAK,KAAM,QAAW,SAAS,WAAW,MAAM,CAAC,EACnD,OAAO,KAAK,EAGd,IAAM,MAAQ,KAAK,IAAI,EAEvB,IAAI,GAAG,aAAgB,CACrB,IAAM,SAAW,KAAK,IAAI,EAAI,MACxB,UAAa,IAAY,WAAa,IAAI,QAAQ,iBAAmB,IACrE,OAAS,IAAI,WAOb,KAAO,IAAI,aAAe,IAAI,KAAO,IAC3C,IAAI,MAAM,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,GAAG,OAAO,GAAG,SAAS,KAAK,WAAW,CACzE,CAAC,EAED,KAAK,CACP,CACF"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
|
|
2
|
-
import { a as session, i as SessionStore, n as SessionData, r as SessionOptions, t as Session } from "../../session-
|
|
2
|
+
import { a as session, i as SessionStore, n as SessionData, r as SessionOptions, t as Session } from "../../session-BHgRyfqZ.mjs";
|
|
3
3
|
export { Session, SessionData, SessionOptions, SessionStore, session };
|
|
@@ -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{r as registerDisposable}from"../../disposables-
|
|
11
|
+
import{r as registerDisposable}from"../../disposables-Ht0M5KKc.mjs";import{i as setCookie,t as parseCookieHeader}from"../../respond-ERWwiws9.mjs";import{createHmac,randomUUID,timingSafeEqual}from"node:crypto";var MemoryStore=class{sessions=new Map;cleanupInterval;constructor(){this.cleanupInterval=setInterval(()=>this.purge(),6e4),this.cleanupInterval.unref(),registerDisposable(()=>this.dispose())}dispose(){clearInterval(this.cleanupInterval),this.sessions.clear()}async get(sid){let entry=this.sessions.get(sid);return entry?Date.now()>entry.expires?(this.sessions.delete(sid),null):entry.data:null}async set(sid,data,maxAge){this.sessions.set(sid,{data,expires:Date.now()+maxAge})}async destroy(sid){this.sessions.delete(sid)}async touch(sid,maxAge){let entry=this.sessions.get(sid);entry&&(entry.expires=Date.now()+maxAge)}purge(){let now=Date.now();for(let[sid,entry]of this.sessions)now>entry.expires&&this.sessions.delete(sid)}};function sign(value,secret){return`s:${value}.${createHmac(`sha256`,secret).update(value).digest(`base64url`)}`}function unsign(signed,secret){if(!signed.startsWith(`s:`))return!1;let raw=signed.slice(2),dotIndex=raw.lastIndexOf(`.`);if(dotIndex===-1)return!1;let value=raw.slice(0,dotIndex),providedSig=raw.slice(dotIndex+1),expectedSig=createHmac(`sha256`,secret).update(value).digest(`base64url`),a=Buffer.from(providedSig),b=Buffer.from(expectedSig);return a.length!==b.length||!timingSafeEqual(a,b)?!1:value}function session(options){let{secret,cookieName=`kick.sid`,maxAge=864e5,rolling=!1,saveUninitialized=!0,store=new MemoryStore,cookie:cookieOpts={}}=options,cookieDefaults={httpOnly:cookieOpts.httpOnly??!0,secure:cookieOpts.secure??process.env.NODE_ENV===`production`,sameSite:cookieOpts.sameSite??`lax`,path:cookieOpts.path??`/`,...cookieOpts.domain?{domain:cookieOpts.domain}:{},maxAge};return async(req,res,next)=>{let cookies=req.cookies;if(!cookies){let header=req.headers.cookie;cookies=header?parseCookieHeader(header):{}}let signedCookie=cookies[cookieName],sid=!1,sessionData=null,isNew=!1;signedCookie&&(sid=unsign(signedCookie,secret),sid&&(sessionData=await store.get(sid))),(!sid||!sessionData)&&(sid=randomUUID(),sessionData={},isNew=!0);let currentSid=sid,currentData={...sessionData},destroyed=!1,sessionObj={get id(){return currentSid},data:currentData,async regenerate(){await store.destroy(currentSid),currentSid=randomUUID(),currentData={},sessionObj.data=currentData,await store.set(currentSid,currentData,maxAge),setCookie(res,cookieName,sign(currentSid,secret),cookieDefaults)},async destroy(){await store.destroy(currentSid),destroyed=!0,currentData={},sessionObj.data=currentData,res.clearCookie(cookieName,{path:cookieDefaults.path})},async save(){destroyed||await store.set(currentSid,currentData,maxAge)}};req.session=sessionObj,isNew&&setCookie(res,cookieName,sign(currentSid,secret),cookieDefaults),res.on(`finish`,async()=>{destroyed||isNew&&!saveUninitialized&&Object.keys(currentData).length===0||(await store.set(currentSid,currentData,maxAge),rolling&&!isNew&&store.touch&&await store.touch(currentSid,maxAge))}),rolling&&!isNew&&setCookie(res,cookieName,sign(currentSid,secret),cookieDefaults),next()}}export{session};
|
|
12
12
|
//# sourceMappingURL=session.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.mjs","names":[],"sources":["../../../src/http/middleware/session.ts"],"sourcesContent":["import { randomUUID, createHmac, timingSafeEqual } from 'node:crypto'\nimport type { Request, Response, NextFunction } from 'express'\n\nimport { registerDisposable } from '../../core/disposables'\n\nexport interface SessionData {\n [key: string]: unknown\n}\n\nexport interface SessionStore {\n get(sid: string): Promise<SessionData | null>\n set(sid: string, data: SessionData, maxAge: number): Promise<void>\n destroy(sid: string): Promise<void>\n touch?(sid: string, maxAge: number): Promise<void>\n}\n\nexport interface Session {\n id: string\n data: SessionData\n regenerate(): Promise<void>\n destroy(): Promise<void>\n save(): Promise<void>\n}\n\nexport interface SessionOptions {\n /** Secret used to sign the session cookie (required) */\n secret: string\n /** Cookie name (default: 'kick.sid') */\n cookieName?: string\n /** Session max age in milliseconds (default: 86400000 = 24h) */\n maxAge?: number\n /** Reset maxAge on every response (default: false) */\n rolling?: boolean\n /** Save new sessions that have not been modified (default: true) */\n saveUninitialized?: boolean\n /** Cookie options */\n cookie?: {\n httpOnly?: boolean\n secure?: boolean\n sameSite?: 'strict' | 'lax' | 'none'\n path?: string\n domain?: string\n }\n /** Custom session store (default: in-memory store with TTL cleanup) */\n store?: SessionStore\n}\n\n// ── In-Memory Store ───────────────────────────────────────────────────\n\nclass MemoryStore implements SessionStore {\n private readonly sessions = new Map<string, { data: SessionData; expires: number }>()\n private readonly cleanupInterval: ReturnType<typeof setInterval>\n\n constructor() {\n // Purge expired sessions every 60 seconds\n this.cleanupInterval = setInterval(() => this.purge(), 60_000)\n this.cleanupInterval.unref()\n // Symmetric teardown: Application.shutdown() drains the disposables\n // registry — without this, every in-process app re-create (tests, HMR)\n // leaked one live interval + its session Map.\n registerDisposable(() => this.dispose())\n }\n\n /** Clear the cleanup interval and drop all sessions. */\n dispose(): void {\n clearInterval(this.cleanupInterval)\n this.sessions.clear()\n }\n\n async get(sid: string): Promise<SessionData | null> {\n const entry = this.sessions.get(sid)\n if (!entry) return null\n if (Date.now() > entry.expires) {\n this.sessions.delete(sid)\n return null\n }\n return entry.data\n }\n\n async set(sid: string, data: SessionData, maxAge: number): Promise<void> {\n this.sessions.set(sid, { data, expires: Date.now() + maxAge })\n }\n\n async destroy(sid: string): Promise<void> {\n this.sessions.delete(sid)\n }\n\n async touch(sid: string, maxAge: number): Promise<void> {\n const entry = this.sessions.get(sid)\n if (entry) {\n entry.expires = Date.now() + maxAge\n }\n }\n\n private purge() {\n const now = Date.now()\n for (const [sid, entry] of this.sessions) {\n if (now > entry.expires) {\n this.sessions.delete(sid)\n }\n }\n }\n}\n\n// ── Cookie Header Parsing ─────────────────────────────────────────────\n\nfunction 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 out[key] = value\n }\n }\n return out\n}\n\n// ── Cookie Signing ────────────────────────────────────────────────────\n\nfunction sign(value: string, secret: string): string {\n const signature = createHmac('sha256', secret).update(value).digest('base64url')\n return `s:${value}.${signature}`\n}\n\nfunction unsign(signed: string, secret: string): string | false {\n if (!signed.startsWith('s:')) return false\n const raw = signed.slice(2)\n const dotIndex = raw.lastIndexOf('.')\n if (dotIndex === -1) return false\n\n const value = raw.slice(0, dotIndex)\n const providedSig = raw.slice(dotIndex + 1)\n const expectedSig = createHmac('sha256', secret).update(value).digest('base64url')\n\n const a = Buffer.from(providedSig)\n const b = Buffer.from(expectedSig)\n\n if (a.length !== b.length || !timingSafeEqual(a, b)) {\n return false\n }\n\n return value\n}\n\n// ── Middleware Factory ────────────────────────────────────────────────\n\n/**\n * Session management middleware.\n *\n * Attaches a `req.session` object with `id`, `data`, `regenerate()`,\n * `destroy()`, and `save()` methods. Session IDs are signed with\n * HMAC-SHA256 to prevent cookie tampering.\n *\n * Reads the session cookie from `req.cookies` when an upstream cookie\n * parser is present; otherwise parses `req.headers.cookie` directly, so\n * no cookie parser is required upstream.\n *\n * @example\n * ```ts\n * import { session } from '@forinda/kickjs'\n *\n * bootstrap({\n * modules,\n * middleware: [\n * session({ secret: process.env.SESSION_SECRET! }),\n * // ... other middleware\n * ],\n * })\n * ```\n */\nexport function session(options: SessionOptions) {\n const {\n secret,\n cookieName = 'kick.sid',\n maxAge = 86_400_000,\n rolling = false,\n saveUninitialized = true,\n store = new MemoryStore(),\n cookie: cookieOpts = {},\n } = options\n\n const cookieDefaults = {\n httpOnly: cookieOpts.httpOnly ?? true,\n secure: cookieOpts.secure ?? process.env.NODE_ENV === 'production',\n sameSite: cookieOpts.sameSite ?? ('lax' as const),\n path: cookieOpts.path ?? '/',\n ...(cookieOpts.domain ? { domain: cookieOpts.domain } : {}),\n maxAge,\n }\n\n return async (req: Request, res: Response, next: NextFunction) => {\n let cookies = (req as any).cookies as Record<string, string> | undefined\n if (!cookies) {\n const header = req.headers.cookie\n cookies = header ? parseCookieHeader(header) : {}\n }\n const signedCookie = cookies[cookieName]\n let sid: string | false = false\n let sessionData: SessionData | null = null\n let isNew = false\n\n // Attempt to recover existing session\n if (signedCookie) {\n sid = unsign(signedCookie, secret)\n if (sid) {\n sessionData = await store.get(sid)\n }\n }\n\n // Create new session if none found\n if (!sid || !sessionData) {\n sid = randomUUID()\n sessionData = {}\n isNew = true\n }\n\n let currentSid = sid\n let currentData = { ...sessionData }\n let destroyed = false\n\n const sessionObj: Session = {\n get id() {\n return currentSid\n },\n data: currentData,\n\n async regenerate() {\n await store.destroy(currentSid)\n currentSid = randomUUID()\n currentData = {}\n sessionObj.data = currentData\n await store.set(currentSid, currentData, maxAge)\n res.cookie(cookieName, sign(currentSid, secret), cookieDefaults)\n },\n\n async destroy() {\n await store.destroy(currentSid)\n destroyed = true\n currentData = {}\n sessionObj.data = currentData\n res.clearCookie(cookieName, { path: cookieDefaults.path })\n },\n\n async save() {\n if (!destroyed) {\n await store.set(currentSid, currentData, maxAge)\n }\n },\n }\n\n ;(req as any).session = sessionObj\n\n // Set cookie for new sessions\n if (isNew) {\n res.cookie(cookieName, sign(currentSid, secret), cookieDefaults)\n }\n\n // Auto-save on response finish\n res.on('finish', async () => {\n if (destroyed) return\n if (isNew && !saveUninitialized && Object.keys(currentData).length === 0) return\n\n await store.set(currentSid, currentData, maxAge)\n\n if (rolling && !isNew) {\n if (store.touch) {\n await store.touch(currentSid, maxAge)\n }\n }\n })\n\n // Rolling: refresh cookie on every response\n if (rolling && !isNew) {\n res.cookie(cookieName, sign(currentSid, secret), cookieDefaults)\n }\n\n next()\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session.mjs","names":[],"sources":["../../../src/http/middleware/session.ts"],"sourcesContent":["import { parseCookieHeader, setCookie } from './respond'\nimport { randomUUID, createHmac, timingSafeEqual } from 'node:crypto'\nimport type { Request, Response, NextFunction } from 'express'\n\nimport { registerDisposable } from '../../core/disposables'\n\nexport interface SessionData {\n [key: string]: unknown\n}\n\nexport interface SessionStore {\n get(sid: string): Promise<SessionData | null>\n set(sid: string, data: SessionData, maxAge: number): Promise<void>\n destroy(sid: string): Promise<void>\n touch?(sid: string, maxAge: number): Promise<void>\n}\n\nexport interface Session {\n id: string\n data: SessionData\n regenerate(): Promise<void>\n destroy(): Promise<void>\n save(): Promise<void>\n}\n\nexport interface SessionOptions {\n /** Secret used to sign the session cookie (required) */\n secret: string\n /** Cookie name (default: 'kick.sid') */\n cookieName?: string\n /** Session max age in milliseconds (default: 86400000 = 24h) */\n maxAge?: number\n /** Reset maxAge on every response (default: false) */\n rolling?: boolean\n /** Save new sessions that have not been modified (default: true) */\n saveUninitialized?: boolean\n /** Cookie options */\n cookie?: {\n httpOnly?: boolean\n secure?: boolean\n sameSite?: 'strict' | 'lax' | 'none'\n path?: string\n domain?: string\n }\n /** Custom session store (default: in-memory store with TTL cleanup) */\n store?: SessionStore\n}\n\n// ── In-Memory Store ───────────────────────────────────────────────────\n\nclass MemoryStore implements SessionStore {\n private readonly sessions = new Map<string, { data: SessionData; expires: number }>()\n private readonly cleanupInterval: ReturnType<typeof setInterval>\n\n constructor() {\n // Purge expired sessions every 60 seconds\n this.cleanupInterval = setInterval(() => this.purge(), 60_000)\n this.cleanupInterval.unref()\n // Symmetric teardown: Application.shutdown() drains the disposables\n // registry — without this, every in-process app re-create (tests, HMR)\n // leaked one live interval + its session Map.\n registerDisposable(() => this.dispose())\n }\n\n /** Clear the cleanup interval and drop all sessions. */\n dispose(): void {\n clearInterval(this.cleanupInterval)\n this.sessions.clear()\n }\n\n async get(sid: string): Promise<SessionData | null> {\n const entry = this.sessions.get(sid)\n if (!entry) return null\n if (Date.now() > entry.expires) {\n this.sessions.delete(sid)\n return null\n }\n return entry.data\n }\n\n async set(sid: string, data: SessionData, maxAge: number): Promise<void> {\n this.sessions.set(sid, { data, expires: Date.now() + maxAge })\n }\n\n async destroy(sid: string): Promise<void> {\n this.sessions.delete(sid)\n }\n\n async touch(sid: string, maxAge: number): Promise<void> {\n const entry = this.sessions.get(sid)\n if (entry) {\n entry.expires = Date.now() + maxAge\n }\n }\n\n private purge() {\n const now = Date.now()\n for (const [sid, entry] of this.sessions) {\n if (now > entry.expires) {\n this.sessions.delete(sid)\n }\n }\n }\n}\n\n// ── Cookie Signing ────────────────────────────────────────────────────\n\nfunction sign(value: string, secret: string): string {\n const signature = createHmac('sha256', secret).update(value).digest('base64url')\n return `s:${value}.${signature}`\n}\n\nfunction unsign(signed: string, secret: string): string | false {\n if (!signed.startsWith('s:')) return false\n const raw = signed.slice(2)\n const dotIndex = raw.lastIndexOf('.')\n if (dotIndex === -1) return false\n\n const value = raw.slice(0, dotIndex)\n const providedSig = raw.slice(dotIndex + 1)\n const expectedSig = createHmac('sha256', secret).update(value).digest('base64url')\n\n const a = Buffer.from(providedSig)\n const b = Buffer.from(expectedSig)\n\n if (a.length !== b.length || !timingSafeEqual(a, b)) {\n return false\n }\n\n return value\n}\n\n// ── Middleware Factory ────────────────────────────────────────────────\n\n/**\n * Session management middleware.\n *\n * Attaches a `req.session` object with `id`, `data`, `regenerate()`,\n * `destroy()`, and `save()` methods. Session IDs are signed with\n * HMAC-SHA256 to prevent cookie tampering.\n *\n * Reads the session cookie from `req.cookies` when an upstream cookie\n * parser is present; otherwise parses `req.headers.cookie` directly, so\n * no cookie parser is required upstream.\n *\n * @example\n * ```ts\n * import { session } from '@forinda/kickjs'\n *\n * bootstrap({\n * modules,\n * middlewares: [\n * session({ secret: process.env.SESSION_SECRET! }),\n * // ... other middleware\n * ],\n * })\n * ```\n */\nexport function session(options: SessionOptions) {\n const {\n secret,\n cookieName = 'kick.sid',\n maxAge = 86_400_000,\n rolling = false,\n saveUninitialized = true,\n store = new MemoryStore(),\n cookie: cookieOpts = {},\n } = options\n\n const cookieDefaults = {\n httpOnly: cookieOpts.httpOnly ?? true,\n secure: cookieOpts.secure ?? process.env.NODE_ENV === 'production',\n sameSite: cookieOpts.sameSite ?? ('lax' as const),\n path: cookieOpts.path ?? '/',\n ...(cookieOpts.domain ? { domain: cookieOpts.domain } : {}),\n maxAge,\n }\n\n return async (req: Request, res: Response, next: NextFunction) => {\n let cookies = (req as any).cookies as Record<string, string> | undefined\n if (!cookies) {\n const header = req.headers.cookie\n cookies = header ? parseCookieHeader(header) : {}\n }\n const signedCookie = cookies[cookieName]\n let sid: string | false = false\n let sessionData: SessionData | null = null\n let isNew = false\n\n // Attempt to recover existing session\n if (signedCookie) {\n sid = unsign(signedCookie, secret)\n if (sid) {\n sessionData = await store.get(sid)\n }\n }\n\n // Create new session if none found\n if (!sid || !sessionData) {\n sid = randomUUID()\n sessionData = {}\n isNew = true\n }\n\n let currentSid = sid\n let currentData = { ...sessionData }\n let destroyed = false\n\n const sessionObj: Session = {\n get id() {\n return currentSid\n },\n data: currentData,\n\n async regenerate() {\n await store.destroy(currentSid)\n currentSid = randomUUID()\n currentData = {}\n sessionObj.data = currentData\n await store.set(currentSid, currentData, maxAge)\n setCookie(res, cookieName, sign(currentSid, secret), cookieDefaults)\n },\n\n async destroy() {\n await store.destroy(currentSid)\n destroyed = true\n currentData = {}\n sessionObj.data = currentData\n res.clearCookie(cookieName, { path: cookieDefaults.path })\n },\n\n async save() {\n if (!destroyed) {\n await store.set(currentSid, currentData, maxAge)\n }\n },\n }\n\n ;(req as any).session = sessionObj\n\n // Set cookie for new sessions\n if (isNew) {\n setCookie(res, cookieName, sign(currentSid, secret), cookieDefaults)\n }\n\n // Auto-save on response finish\n res.on('finish', async () => {\n if (destroyed) return\n if (isNew && !saveUninitialized && Object.keys(currentData).length === 0) return\n\n await store.set(currentSid, currentData, maxAge)\n\n if (rolling && !isNew) {\n if (store.touch) {\n await store.touch(currentSid, maxAge)\n }\n }\n })\n\n // Rolling: refresh cookie on every response\n if (rolling && !isNew) {\n setCookie(res, cookieName, sign(currentSid, secret), cookieDefaults)\n }\n\n next()\n }\n}\n"],"mappings":";;;;;;;;;;iNAkDA,IAAM,YAAN,KAA0C,CACxC,SAA4B,IAAI,IAChC,gBAEA,aAAc,CAEZ,KAAK,gBAAkB,gBAAkB,KAAK,MAAM,EAAG,GAAM,EAC7D,KAAK,gBAAgB,MAAM,EAI3B,uBAAyB,KAAK,QAAQ,CAAC,CACzC,CAGA,SAAgB,CACd,cAAc,KAAK,eAAe,EAClC,KAAK,SAAS,MAAM,CACtB,CAEA,MAAM,IAAI,IAA0C,CAClD,IAAM,MAAQ,KAAK,SAAS,IAAI,GAAG,EAMnC,OALK,MACD,KAAK,IAAI,EAAI,MAAM,SACrB,KAAK,SAAS,OAAO,GAAG,EACjB,MAEF,MAAM,KALM,IAMrB,CAEA,MAAM,IAAI,IAAa,KAAmB,OAA+B,CACvE,KAAK,SAAS,IAAI,IAAK,CAAE,KAAM,QAAS,KAAK,IAAI,EAAI,MAAO,CAAC,CAC/D,CAEA,MAAM,QAAQ,IAA4B,CACxC,KAAK,SAAS,OAAO,GAAG,CAC1B,CAEA,MAAM,MAAM,IAAa,OAA+B,CACtD,IAAM,MAAQ,KAAK,SAAS,IAAI,GAAG,EAC/B,QACF,MAAM,QAAU,KAAK,IAAI,EAAI,OAEjC,CAEA,OAAgB,CACd,IAAM,IAAM,KAAK,IAAI,EACrB,IAAK,GAAM,CAAC,IAAK,SAAU,KAAK,SAC1B,IAAM,MAAM,SACd,KAAK,SAAS,OAAO,GAAG,CAG9B,CACF,EAIA,SAAS,KAAK,MAAe,OAAwB,CAEnD,MAAO,KAAK,MAAM,GADA,WAAW,SAAU,MAAM,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,WACvC,GAC/B,CAEA,SAAS,OAAO,OAAgB,OAAgC,CAC9D,GAAI,CAAC,OAAO,WAAW,IAAI,EAAG,MAAO,GACrC,IAAM,IAAM,OAAO,MAAM,CAAC,EACpB,SAAW,IAAI,YAAY,GAAG,EACpC,GAAI,WAAa,GAAI,MAAO,GAE5B,IAAM,MAAQ,IAAI,MAAM,EAAG,QAAQ,EAC7B,YAAc,IAAI,MAAM,SAAW,CAAC,EACpC,YAAc,WAAW,SAAU,MAAM,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,WAAW,EAE3E,EAAI,OAAO,KAAK,WAAW,EAC3B,EAAI,OAAO,KAAK,WAAW,EAMjC,OAJI,EAAE,SAAW,EAAE,QAAU,CAAC,gBAAgB,EAAG,CAAC,EACzC,GAGF,KACT,CA4BA,SAAgB,QAAQ,QAAyB,CAC/C,GAAM,CACJ,OACA,WAAa,WACb,OAAS,MACT,QAAU,GACV,kBAAoB,GACpB,MAAQ,IAAI,YACZ,OAAQ,WAAa,CAAC,GACpB,QAEE,eAAiB,CACrB,SAAU,WAAW,UAAY,GACjC,OAAQ,WAAW,QAAU,QAAQ,IAAI,WAAa,aACtD,SAAU,WAAW,UAAa,MAClC,KAAM,WAAW,MAAQ,IACzB,GAAI,WAAW,OAAS,CAAE,OAAQ,WAAW,MAAO,EAAI,CAAC,EACzD,MACF,EAEA,OAAO,MAAO,IAAc,IAAe,OAAuB,CAChE,IAAI,QAAW,IAAY,QAC3B,GAAI,CAAC,QAAS,CACZ,IAAM,OAAS,IAAI,QAAQ,OAC3B,QAAU,OAAS,kBAAkB,MAAM,EAAI,CAAC,CAClD,CACA,IAAM,aAAe,QAAQ,YACzB,IAAsB,GACtB,YAAkC,KAClC,MAAQ,GAGR,eACF,IAAM,OAAO,aAAc,MAAM,EAC7B,MACF,YAAc,MAAM,MAAM,IAAI,GAAG,KAKjC,CAAC,KAAO,CAAC,eACX,IAAM,WAAW,EACjB,YAAc,CAAC,EACf,MAAQ,IAGV,IAAI,WAAa,IACb,YAAc,CAAE,GAAG,WAAY,EAC/B,UAAY,GAEV,WAAsB,CAC1B,IAAI,IAAK,CACP,OAAO,UACT,EACA,KAAM,YAEN,MAAM,YAAa,CACjB,MAAM,MAAM,QAAQ,UAAU,EAC9B,WAAa,WAAW,EACxB,YAAc,CAAC,EACf,WAAW,KAAO,YAClB,MAAM,MAAM,IAAI,WAAY,YAAa,MAAM,EAC/C,UAAU,IAAK,WAAY,KAAK,WAAY,MAAM,EAAG,cAAc,CACrE,EAEA,MAAM,SAAU,CACd,MAAM,MAAM,QAAQ,UAAU,EAC9B,UAAY,GACZ,YAAc,CAAC,EACf,WAAW,KAAO,YAClB,IAAI,YAAY,WAAY,CAAE,KAAM,eAAe,IAAK,CAAC,CAC3D,EAEA,MAAM,MAAO,CACN,WACH,MAAM,MAAM,IAAI,WAAY,YAAa,MAAM,CAEnD,CACF,EAEC,IAAa,QAAU,WAGpB,OACF,UAAU,IAAK,WAAY,KAAK,WAAY,MAAM,EAAG,cAAc,EAIrE,IAAI,GAAG,SAAU,SAAY,CACvB,WACA,OAAS,CAAC,mBAAqB,OAAO,KAAK,WAAW,CAAC,CAAC,SAAW,IAEvE,MAAM,MAAM,IAAI,WAAY,YAAa,MAAM,EAE3C,SAAW,CAAC,OACV,MAAM,OACR,MAAM,MAAM,MAAM,WAAY,MAAM,EAG1C,CAAC,EAGG,SAAW,CAAC,OACd,UAAU,IAAK,WAAY,KAAK,WAAY,MAAM,EAAG,cAAc,EAGrE,KAAK,CACP,CACF"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
|
|
2
|
-
import { n as SpaAdapterOptions, r as resolveClientDir, t as SpaAdapter } from "../../spa-
|
|
2
|
+
import { n as SpaAdapterOptions, r as resolveClientDir, t as SpaAdapter } from "../../spa-DhGwM3_A.mjs";
|
|
3
3
|
export { SpaAdapter, SpaAdapterOptions, resolveClientDir };
|
|
@@ -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{Logger}from"../../core/logger.mjs";import"../../core-
|
|
11
|
+
import{Logger}from"../../core/logger.mjs";import"../../core-DVbWxSyr.mjs";import{t as defineAdapter}from"../../define-adapter-BIwBwOK_.mjs";import{existsSync,readFileSync,readdirSync}from"node:fs";import{dirname,isAbsolute,join,resolve}from"node:path";const log=Logger.for(`SpaAdapter`);function nearestPackageRoot(from){let dir=from;for(;;){if(existsSync(join(dir,`package.json`)))return dir;let parent=dirname(dir);if(parent===dir)return null;dir=parent}}function resolveClientDir(clientDir,cwd=process.cwd(),entry=process.argv[1]){if(isAbsolute(clientDir))return resolve(clientDir);let fromCwd=resolve(cwd,clientDir);if(existsSync(fromCwd))return fromCwd;if(entry){let root=nearestPackageRoot(dirname(resolve(entry)));if(root){let fromEntry=resolve(root,clientDir);if(existsSync(fromEntry))return fromEntry}}return fromCwd}function pathnameOf(url){if(!url)return`/`;let q=url.indexOf(`?`),h=url.indexOf(`#`),end=Math.min(q===-1?url.length:q,h===-1?url.length:h);return url.slice(0,end)||`/`}function isUnder(pathname,prefix){if(!prefix||prefix===`/`)return!0;let p=prefix.endsWith(`/`)?prefix.slice(0,-1):prefix;return pathname===p||pathname.startsWith(`${p}/`)}const HTML_TYPES=[`text/html`,`application/xhtml+xml`];function qualityOf(params){let q=params.find(param=>param.startsWith(`q=`));if(!q)return 1;let value=Number.parseFloat(q.slice(2));return Number.isNaN(value)?1:value}function acceptsHtml(accept){let raw=Array.isArray(accept)?accept.join(`,`):accept??``;if(!raw)return!1;let ranges=raw.split(`,`).map(part=>part.split(`;`).map(t=>t.trim().toLowerCase())).filter(([type])=>!!type);return HTML_TYPES.some(htmlType=>{let[group]=htmlType.split(`/`),exact=ranges.find(([type])=>type===htmlType),wildcard=ranges.find(([type])=>type===`${group}/*`),match=exact??wildcard;return match?qualityOf(match.slice(1))>0:!1})}const SpaAdapter=defineAdapter({name:`SpaAdapter`,defaults:{clientDir:`dist/client`,apiPrefix:`/api`,exclude:[],cacheControl:`public, max-age=31536000, immutable`,indexCacheControl:`no-cache`,alwaysFallback:!1},build:config=>{let clientDir=resolveClientDir(config.clientDir??`dist/client`),rawPrefix=config.apiPrefix??`/api`,apiPrefixes=Array.isArray(rawPrefix)?rawPrefix:[rawPrefix],excludePaths=config.exclude??[],cacheControl=config.cacheControl??`public, max-age=31536000, immutable`,indexCacheControl=config.indexCacheControl??`no-cache`,alwaysFallback=config.alwaysFallback??!1,indexHtml=null,assetPaths=new Set;function snapshotClientDir(){let paths=new Set,walk=(absDir,prefix)=>{for(let entry of readdirSync(absDir,{withFileTypes:!0})){let child=`${prefix}/${entry.name}`;entry.isDirectory()?walk(join(absDir,entry.name),child):entry.isFile()&&paths.add(child)}};try{walk(clientDir,``)}catch{return new Set}return paths}let classifyPath=pathname=>{let decoded;try{decoded=decodeURIComponent(pathname)}catch{return null}if(decoded.includes(`\0`))return null;let asIndex=decoded.endsWith(`/`)?`${decoded}index.html`:null;return asIndex&&assetPaths.has(asIndex)?`index`:assetPaths.has(decoded)?decoded.endsWith(`.html`)?`index`:`file`:null},isReserved=pathname=>apiPrefixes.some(p=>isUnder(pathname,p))||excludePaths.some(p=>isUnder(pathname,p));return{beforeMount({http}){if(!existsSync(clientDir)){log.warn(`SPA client directory not found: ${clientDir}`),log.warn(`Build your frontend first, or set clientDir to the correct path (relative paths resolve from ${process.cwd()}).`);return}let indexPath=join(clientDir,`index.html`);if(!existsSync(indexPath)){log.warn(`index.html not found in ${clientDir}`);return}indexHtml=readFileSync(indexPath,`utf-8`),assetPaths=snapshotClientDir(),cacheControl!==!1&&http.use(((req,res,next)=>{let pathname=pathnameOf(req.url),kind=isReserved(pathname)?null:classifyPath(pathname);kind&&res.setHeader(`Cache-Control`,kind===`index`?indexCacheControl:cacheControl),next()})),http.serveStatic(`/`,clientDir),log.debug(`Serving SPA from ${clientDir}`)},beforeStart({http}){indexHtml&&http.use(((req,res,next)=>{let method=(req.method??`GET`).toUpperCase();if(method!==`GET`&&method!==`HEAD`)return next();let pathname=pathnameOf(req.url);if(isReserved(pathname))return next();let accept=req.headers?.accept;if(!alwaysFallback&&!acceptsHtml(accept))return next();let body=indexHtml;res.setHeader(`Content-Type`,`text/html; charset=utf-8`),res.setHeader(`Cache-Control`,indexCacheControl),res.setHeader(`Content-Length`,String(Buffer.byteLength(body))),res.statusCode=200,res.end(method===`HEAD`?void 0:body)}))}}}});export{SpaAdapter,resolveClientDir};
|
|
12
12
|
//# sourceMappingURL=spa.mjs.map
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
|
|
2
|
-
import { a as RawUploadPart, c as buildFileTypeFilter, i as upload, l as resolveMimeTypes, n as buildUploadMiddleware, o as UploadedFileLike, r as cleanupFiles, s as applyUploadConfig, t as UploadOptions } from "../../upload-
|
|
2
|
+
import { a as RawUploadPart, c as buildFileTypeFilter, i as upload, l as resolveMimeTypes, n as buildUploadMiddleware, o as UploadedFileLike, r as cleanupFiles, s as applyUploadConfig, t as UploadOptions } from "../../upload-CbJOK-j9.mjs";
|
|
3
3
|
export { type RawUploadPart, UploadOptions, type UploadedFileLike, applyUploadConfig, buildFileTypeFilter, buildUploadMiddleware, cleanupFiles, resolveMimeTypes, upload };
|
|
@@ -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,6 +8,6 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{n as buildFileTypeFilter,r as resolveMimeTypes,t as applyUploadConfig}from"../../upload-config-
|
|
12
|
-
Install it as a peer dependency: pnpm add multer (or npm i multer / yarn add multer).`)}}function createMulter(options){let limits={fileSize:options.maxSize??5*1024*1024},fileFilter;if(options.allowedTypes){let typeAllowed=buildFileTypeFilter(options.allowedTypes,options.customMimeMap);fileFilter=(_req,file,cb)=>{typeAllowed(file.mimetype,file.originalname)?cb(null,!0):cb(
|
|
11
|
+
import{HttpException,HttpStatus}from"../../core/errors.mjs";import{n as buildFileTypeFilter,r as resolveMimeTypes,t as applyUploadConfig}from"../../upload-config-DtnX4uVR.mjs";import{createRequire}from"node:module";import{unlink}from"node:fs/promises";const requireFromHere=createRequire(import.meta.url);let _multer;function loadMulter(){if(_multer)return _multer;try{return _multer=requireFromHere(`multer`),_multer}catch{throw Error(`@forinda/kickjs: file uploads require the 'multer' package, which is not installed.
|
|
12
|
+
Install it as a peer dependency: pnpm add multer (or npm i multer / yarn add multer).`)}}function translateMulterErrors(mw,options){let maxSize=options.maxSize??5*1024*1024;return(req,res,next)=>{mw(req,res,err=>{if(err&&err.code===`LIMIT_FILE_SIZE`){let name=err.field??`file`;return next(new HttpException(HttpStatus.PAYLOAD_TOO_LARGE,`File ${name} exceeds the ${maxSize}-byte limit`))}next(err)})}}function createMulter(options){let limits={fileSize:options.maxSize??5*1024*1024},fileFilter;if(options.allowedTypes){let typeAllowed=buildFileTypeFilter(options.allowedTypes,options.customMimeMap);fileFilter=(_req,file,cb)=>{typeAllowed(file.mimetype,file.originalname)?cb(null,!0):cb(new HttpException(HttpStatus.UNSUPPORTED_MEDIA_TYPE,`File type ${file.mimetype} is not allowed`))}}let multerOptions={limits,...fileFilter?{fileFilter}:{},...options.storage?{storage:options.storage}:{},...options.dest?{dest:options.dest}:{}};return loadMulter()(multerOptions)}function single(fieldName,options={}){return translateMulterErrors(createMulter(options).single(fieldName),options)}function array(fieldName,maxCount=10,options={}){return translateMulterErrors(createMulter(options).array(fieldName,maxCount),options)}function none(options={}){return translateMulterErrors(createMulter(options).none(),options)}function cleanupFiles(){return(req,res,next)=>{res.on(`finish`,async()=>{let files=[];if(req.file?.path&&files.push(req.file),Array.isArray(req.files))for(let f of req.files)f?.path&&files.push(f);for(let file of files)try{await unlink(file.path)}catch{}}),next()}}function buildUploadMiddleware(config){let options={};config.maxSize&&(options.maxSize=config.maxSize),config.allowedTypes&&(options.allowedTypes=config.allowedTypes),config.customMimeMap&&(options.customMimeMap=config.customMimeMap);let fieldName=config.fieldName??`file`;switch(config.mode){case`single`:return single(fieldName,options);case`array`:return array(fieldName,config.maxCount??10,options);case`none`:return none(options)}}const upload={single,array,none};export{applyUploadConfig,buildFileTypeFilter,buildUploadMiddleware,cleanupFiles,resolveMimeTypes,upload};
|
|
13
13
|
//# sourceMappingURL=upload.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload.mjs","names":[],"sources":["../../../src/http/middleware/upload.ts"],"sourcesContent":["import { unlink } from 'node:fs/promises'\nimport { createRequire } from 'node:module'\nimport type { Request, Response, NextFunction, RequestHandler } from 'express'\nimport type { Options as MulterOptions } from 'multer'\nimport type { BaseUploadOptions, FileUploadConfig } from '../../core'\n\n// `multer` is an optional peer dependency. We load it lazily via\n// `createRequire` so importing this module never touches `multer` —\n// only constructing an upload middleware does. Adopters who never\n// call `upload.single/array/none()` or use `@FileUpload` don't need\n// `multer` installed at all.\n//\n// multer uses CJS `export = multer`, so the module is itself the\n// callable factory — no `.default` indirection on either the type\n// or the runtime side.\nconst requireFromHere = createRequire(import.meta.url)\ntype MulterFactory = typeof import('multer')\nlet _multer: MulterFactory | undefined\n\nfunction loadMulter(): MulterFactory {\n if (_multer) return _multer\n try {\n _multer = requireFromHere('multer') as MulterFactory\n return _multer\n } catch {\n throw new Error(\n \"@forinda/kickjs: file uploads require the 'multer' package, which is not installed.\\n\" +\n 'Install it as a peer dependency: pnpm add multer (or npm i multer / yarn add multer).',\n )\n }\n}\n\n// Pure upload core (MIME map, type filter, applyUploadConfig) moved to\n// `./upload-config` so the edge-safe web entry can use it without this\n// module's node-only surface. Re-exported here for compatibility.\nexport {\n buildFileTypeFilter,\n resolveMimeTypes,\n applyUploadConfig,\n type UploadedFileLike,\n type RawUploadPart,\n} from './upload-config'\nimport { buildFileTypeFilter } from './upload-config'\n\n/**\n * Upload options for the middleware.\n * Extends BaseUploadOptions from core (shared with @FileUpload decorator)\n * and adds Multer-specific storage options.\n */\nexport interface UploadOptions extends BaseUploadOptions {\n /** Multer storage config (default: memory storage) */\n storage?: MulterOptions['storage']\n /** Multer dest for disk storage shorthand */\n dest?: string\n}\n\nfunction createMulter(options: UploadOptions) {\n const limits: MulterOptions['limits'] = {\n fileSize: options.maxSize ?? 5 * 1024 * 1024,\n }\n\n let fileFilter: MulterOptions['fileFilter'] | undefined\n if (options.allowedTypes) {\n const typeAllowed = buildFileTypeFilter(options.allowedTypes, options.customMimeMap)\n fileFilter = (_req, file, cb) => {\n if (typeAllowed(file.mimetype, file.originalname)) {\n cb(null, true)\n } else {\n cb(new
|
|
1
|
+
{"version":3,"file":"upload.mjs","names":[],"sources":["../../../src/http/middleware/upload.ts"],"sourcesContent":["import { HttpException, HttpStatus } from '../../core/errors'\nimport { unlink } from 'node:fs/promises'\nimport { createRequire } from 'node:module'\nimport type { Request, Response, NextFunction, RequestHandler } from 'express'\nimport type { Options as MulterOptions } from 'multer'\nimport type { BaseUploadOptions, FileUploadConfig } from '../../core'\n\n// `multer` is an optional peer dependency. We load it lazily via\n// `createRequire` so importing this module never touches `multer` —\n// only constructing an upload middleware does. Adopters who never\n// call `upload.single/array/none()` or use `@FileUpload` don't need\n// `multer` installed at all.\n//\n// multer uses CJS `export = multer`, so the module is itself the\n// callable factory — no `.default` indirection on either the type\n// or the runtime side.\nconst requireFromHere = createRequire(import.meta.url)\ntype MulterFactory = typeof import('multer')\nlet _multer: MulterFactory | undefined\n\nfunction loadMulter(): MulterFactory {\n if (_multer) return _multer\n try {\n _multer = requireFromHere('multer') as MulterFactory\n return _multer\n } catch {\n throw new Error(\n \"@forinda/kickjs: file uploads require the 'multer' package, which is not installed.\\n\" +\n 'Install it as a peer dependency: pnpm add multer (or npm i multer / yarn add multer).',\n )\n }\n}\n\n// Pure upload core (MIME map, type filter, applyUploadConfig) moved to\n// `./upload-config` so the edge-safe web entry can use it without this\n// module's node-only surface. Re-exported here for compatibility.\nexport {\n buildFileTypeFilter,\n resolveMimeTypes,\n applyUploadConfig,\n type UploadedFileLike,\n type RawUploadPart,\n} from './upload-config'\nimport { buildFileTypeFilter } from './upload-config'\n\n/**\n * Upload options for the middleware.\n * Extends BaseUploadOptions from core (shared with @FileUpload decorator)\n * and adds Multer-specific storage options.\n */\nexport interface UploadOptions extends BaseUploadOptions {\n /** Multer storage config (default: memory storage) */\n storage?: MulterOptions['storage']\n /** Multer dest for disk storage shorthand */\n dest?: string\n}\n\n/**\n * Turn multer's own errors into the exceptions the other runtimes raise.\n *\n * multer reports a file over the limit as `MulterError: File too large`, a\n * plain error the handler maps to 500 — so an oversized upload was reported as\n * a server fault on Express while Fastify and h3 answered 413. The message is\n * matched to `applyUploadConfig` so the response does not depend on the engine.\n */\nfunction translateMulterErrors(mw: RequestHandler, options: UploadOptions): RequestHandler {\n const maxSize = options.maxSize ?? 5 * 1024 * 1024\n return (req, res, next) => {\n mw(req, res, (err?: unknown) => {\n if (err && (err as { code?: string }).code === 'LIMIT_FILE_SIZE') {\n const name = (err as { field?: string }).field ?? 'file'\n return next(\n new HttpException(\n HttpStatus.PAYLOAD_TOO_LARGE,\n `File ${name} exceeds the ${maxSize}-byte limit`,\n ),\n )\n }\n next(err as never)\n })\n }\n}\n\nfunction createMulter(options: UploadOptions) {\n const limits: MulterOptions['limits'] = {\n fileSize: options.maxSize ?? 5 * 1024 * 1024,\n }\n\n let fileFilter: MulterOptions['fileFilter'] | undefined\n if (options.allowedTypes) {\n const typeAllowed = buildFileTypeFilter(options.allowedTypes, options.customMimeMap)\n fileFilter = (_req, file, cb) => {\n if (typeAllowed(file.mimetype, file.originalname)) {\n cb(null, true)\n } else {\n // Same exception the Fastify and h3 path raises from\n // `applyUploadConfig`. A plain Error here reached the handler as a 500,\n // so Express blamed itself for the client's file — and leaked a multer\n // stack doing it.\n cb(\n new HttpException(\n HttpStatus.UNSUPPORTED_MEDIA_TYPE,\n `File type ${file.mimetype} is not allowed`,\n ),\n )\n }\n }\n }\n\n const multerOptions: MulterOptions = {\n limits,\n ...(fileFilter ? { fileFilter } : {}),\n ...(options.storage ? { storage: options.storage } : {}),\n ...(options.dest ? { dest: options.dest } : {}),\n }\n\n return loadMulter()(multerOptions)\n}\n\n/**\n * Single file upload middleware. Attaches the file to `req.file`.\n *\n * @example\n * ```ts\n * @Post('/avatar')\n * @Middleware(upload.single('avatar', { maxSize: 2 * 1024 * 1024, allowedTypes: ['jpg', 'png'] }))\n * async uploadAvatar(ctx: RequestContext) {\n * ctx.json({ filename: ctx.file.originalname })\n * }\n * ```\n */\nfunction single(fieldName: string, options: UploadOptions = {}): RequestHandler {\n const m = createMulter(options)\n return translateMulterErrors(m.single(fieldName) as RequestHandler, options)\n}\n\n/**\n * Multiple file upload middleware. Attaches files to `req.files`.\n */\nfunction array(fieldName: string, maxCount = 10, options: UploadOptions = {}): RequestHandler {\n const m = createMulter(options)\n return translateMulterErrors(m.array(fieldName, maxCount) as RequestHandler, options)\n}\n\n/**\n * No file upload — just parse multipart form data without file fields.\n */\nfunction none(options: UploadOptions = {}): RequestHandler {\n const m = createMulter(options)\n return translateMulterErrors(m.none() as RequestHandler, options)\n}\n\n/**\n * Removes temporary files from disk after the response is sent.\n * Attach this as Express middleware after the upload middleware.\n * Works with both `req.file` (single) and `req.files` (array).\n *\n * @example\n * ```ts\n * @Post('/process')\n * @Middleware(upload.single('document', { dest: '/tmp/uploads' }), cleanupFiles())\n * async processDocument(ctx: RequestContext) {\n * ctx.json({ ok: true })\n * }\n * ```\n */\nexport function cleanupFiles() {\n return (req: Request, res: Response, next: NextFunction) => {\n res.on('finish', async () => {\n const files: any[] = []\n\n if ((req as any).file?.path) {\n files.push((req as any).file)\n }\n if (Array.isArray((req as any).files)) {\n for (const f of (req as any).files) {\n if (f?.path) files.push(f)\n }\n }\n\n for (const file of files) {\n try {\n await unlink(file.path)\n } catch {\n // File may already be moved/deleted by the handler — ignore\n }\n }\n })\n\n next()\n }\n}\n\n/**\n * Build upload middleware from a @FileUpload decorator config.\n * Used internally by the router builder when it detects FILE_UPLOAD metadata.\n * Accepts the same FileUploadConfig interface used by the @FileUpload decorator.\n */\nexport function buildUploadMiddleware(config: FileUploadConfig): RequestHandler {\n const options: UploadOptions = {}\n if (config.maxSize) options.maxSize = config.maxSize\n if (config.allowedTypes) options.allowedTypes = config.allowedTypes\n if (config.customMimeMap) options.customMimeMap = config.customMimeMap\n\n const fieldName = config.fieldName ?? 'file'\n\n switch (config.mode) {\n case 'single':\n return single(fieldName, options)\n case 'array':\n return array(fieldName, config.maxCount ?? 10, options)\n case 'none':\n return none(options)\n }\n}\n\n/** Upload middleware factory with `.single()`, `.array()`, `.none()` methods */\nexport const upload = { single, array, none }\n"],"mappings":";;;;;;;;;;4PAgBA,MAAM,gBAAkB,cAAc,OAAO,KAAK,GAAG,EAErD,IAAI,QAEJ,SAAS,YAA4B,CACnC,GAAI,QAAS,OAAO,QACpB,GAAI,CAEF,MADA,SAAU,gBAAgB,QAAQ,EAC3B,OACT,MAAQ,CACN,MAAU,MACR;sFAEF,CACF,CACF,CAkCA,SAAS,sBAAsB,GAAoB,QAAwC,CACzF,IAAM,QAAU,QAAQ,SAAW,EAAI,KAAO,KAC9C,OAAQ,IAAK,IAAK,OAAS,CACzB,GAAG,IAAK,IAAM,KAAkB,CAC9B,GAAI,KAAQ,IAA0B,OAAS,kBAAmB,CAChE,IAAM,KAAQ,IAA2B,OAAS,OAClD,OAAO,KACL,IAAI,cACF,WAAW,kBACX,QAAQ,KAAK,eAAe,QAAQ,YACtC,CACF,CACF,CACA,KAAK,GAAY,CACnB,CAAC,CACH,CACF,CAEA,SAAS,aAAa,QAAwB,CAC5C,IAAM,OAAkC,CACtC,SAAU,QAAQ,SAAW,EAAI,KAAO,IAC1C,EAEI,WACJ,GAAI,QAAQ,aAAc,CACxB,IAAM,YAAc,oBAAoB,QAAQ,aAAc,QAAQ,aAAa,EACnF,YAAc,KAAM,KAAM,KAAO,CAC3B,YAAY,KAAK,SAAU,KAAK,YAAY,EAC9C,GAAG,KAAM,EAAI,EAMb,GACE,IAAI,cACF,WAAW,uBACX,aAAa,KAAK,SAAS,gBAC7B,CACF,CAEJ,CACF,CAEA,IAAM,cAA+B,CACnC,OACA,GAAI,WAAa,CAAE,UAAW,EAAI,CAAC,EACnC,GAAI,QAAQ,QAAU,CAAE,QAAS,QAAQ,OAAQ,EAAI,CAAC,EACtD,GAAI,QAAQ,KAAO,CAAE,KAAM,QAAQ,IAAK,EAAI,CAAC,CAC/C,EAEA,OAAO,WAAW,CAAC,CAAC,aAAa,CACnC,CAcA,SAAS,OAAO,UAAmB,QAAyB,CAAC,EAAmB,CAE9E,OAAO,sBADG,aAAa,OACM,CAAC,CAAC,OAAO,SAAS,EAAqB,OAAO,CAC7E,CAKA,SAAS,MAAM,UAAmB,SAAW,GAAI,QAAyB,CAAC,EAAmB,CAE5F,OAAO,sBADG,aAAa,OACM,CAAC,CAAC,MAAM,UAAW,QAAQ,EAAqB,OAAO,CACtF,CAKA,SAAS,KAAK,QAAyB,CAAC,EAAmB,CAEzD,OAAO,sBADG,aAAa,OACM,CAAC,CAAC,KAAK,EAAqB,OAAO,CAClE,CAgBA,SAAgB,cAAe,CAC7B,OAAQ,IAAc,IAAe,OAAuB,CAC1D,IAAI,GAAG,SAAU,SAAY,CAC3B,IAAM,MAAe,CAAC,EAKtB,GAHK,IAAY,MAAM,MACrB,MAAM,KAAM,IAAY,IAAI,EAE1B,MAAM,QAAS,IAAY,KAAK,EAC7B,IAAA,IAAM,KAAM,IAAY,MACvB,GAAG,MAAM,MAAM,KAAK,CAAC,EAI7B,IAAK,IAAM,QAAQ,MACjB,GAAI,CACF,MAAM,OAAO,KAAK,IAAI,CACxB,MAAQ,CAER,CAEJ,CAAC,EAED,KAAK,CACP,CACF,CAOA,SAAgB,sBAAsB,OAA0C,CAC9E,IAAM,QAAyB,CAAC,EAC5B,OAAO,UAAS,QAAQ,QAAU,OAAO,SACzC,OAAO,eAAc,QAAQ,aAAe,OAAO,cACnD,OAAO,gBAAe,QAAQ,cAAgB,OAAO,eAEzD,IAAM,UAAY,OAAO,WAAa,OAEtC,OAAQ,OAAO,KAAf,CACE,IAAK,SACH,OAAO,OAAO,UAAW,OAAO,EAClC,IAAK,QACH,OAAO,MAAM,UAAW,OAAO,UAAY,GAAI,OAAO,EACxD,IAAK,OACH,OAAO,KAAK,OAAO,CACvB,CACF,CAGA,MAAa,OAAS,CAAE,OAAQ,MAAO,IAAK"}
|
|
@@ -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{Logger}from"../../core/logger.mjs";import"../../core-
|
|
11
|
+
import{Logger}from"../../core/logger.mjs";import"../../core-DVbWxSyr.mjs";import{t as defineAdapter}from"../../define-adapter-BIwBwOK_.mjs";import{resolve}from"node:path";const log=Logger.for(`ViewEngine`),ViewAdapter=defineAdapter({name:`ViewAdapter`,build:options=>({beforeMount({app}){let{engine,ext,viewsDir=`src/views`}=options;engine.__express?app.engine(ext,engine.__express):typeof engine.renderFile==`function`?app.engine(ext,(path,opts,callback)=>{engine.renderFile(path,opts,callback)}):(typeof engine==`function`||log.warn(`Engine for .${ext} does not have __express or renderFile. Trying as-is.`),app.engine(ext,engine)),app.set(`view engine`,ext),app.set(`views`,resolve(viewsDir)),log.debug(`View engine: ${ext} (${resolve(viewsDir)})`)}})});export{ViewAdapter};
|
|
12
12
|
//# sourceMappingURL=views.mjs.map
|
|
@@ -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,4 +8,4 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{a as parseQuery,c as resetQueryParsingDefaults,i as parsePagination,l as setQueryParsingDefaults,n as getQueryParsingDefaults,o as parseSearchQuery,r as parseFilters,s as parseSort,t as buildQueryParams,u as FILTER_OPERATORS}from"../../query-
|
|
11
|
+
import{a as parseQuery,c as resetQueryParsingDefaults,i as parsePagination,l as setQueryParsingDefaults,n as getQueryParsingDefaults,o as parseSearchQuery,r as parseFilters,s as parseSort,t as buildQueryParams,u as FILTER_OPERATORS}from"../../query-BzyWJrd6.mjs";export{FILTER_OPERATORS,buildQueryParams,getQueryParsingDefaults,parseFilters,parsePagination,parseQuery,parseSearchQuery,parseSort,resetQueryParsingDefaults,setQueryParsingDefaults};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
|
|
2
|
-
import { i as buildRouteTable, n as _setExternalContributorSources, r as assertRouteUnique, t as BuildRoutesOptions } from "../router-builder-
|
|
2
|
+
import { i as buildRouteTable, n as _setExternalContributorSources, r as assertRouteUnique, t as BuildRoutesOptions } from "../router-builder-DptndWhd.mjs";
|
|
3
3
|
export { BuildRoutesOptions, _setExternalContributorSources, assertRouteUnique, buildRouteTable };
|
|
@@ -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,4 +8,4 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{n as assertRouteUnique,r as buildRouteTable,t as _setExternalContributorSources}from"../router-builder-
|
|
11
|
+
import{n as assertRouteUnique,r as buildRouteTable,t as _setExternalContributorSources}from"../router-builder-FukfDDLt.mjs";export{_setExternalContributorSources,assertRouteUnique,buildRouteTable};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { f as HttpRuntime } from "../../adapter-
|
|
2
|
+
import { f as HttpRuntime } from "../../adapter-BuOTeSQy.mjs";
|
|
3
3
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
4
4
|
//#region src/http/runtimes/fastify.d.ts
|
|
5
5
|
interface FastifyReplyLike {
|
|
@@ -16,6 +16,8 @@ interface FastifyRequestLike {
|
|
|
16
16
|
body?: unknown;
|
|
17
17
|
params?: unknown;
|
|
18
18
|
query?: unknown;
|
|
19
|
+
/** Request headers, as Fastify normalises them (lowercased keys). */
|
|
20
|
+
headers?: Record<string, string | string[] | undefined>;
|
|
19
21
|
/** Present once @fastify/multipart is registered — async iterator of parts. */
|
|
20
22
|
parts?: () => AsyncIterableIterator<MultipartPart>;
|
|
21
23
|
isMultipart?: () => boolean;
|
|
@@ -33,7 +35,17 @@ interface FastifyAppLike {
|
|
|
33
35
|
setErrorHandler(handler: (err: unknown, req: FastifyRequestLike, reply: FastifyReplyLike) => void): unknown;
|
|
34
36
|
ready(): Promise<void>;
|
|
35
37
|
routing(req: IncomingMessage, res: ServerResponse): void;
|
|
38
|
+
/**
|
|
39
|
+
* Fastify's content-type parser registry. Typed here rather than reached for
|
|
40
|
+
* with a cast so the callback shape is checked at the call site — these
|
|
41
|
+
* parsers decide what `ctx.body` is on this runtime.
|
|
42
|
+
*/
|
|
43
|
+
addContentTypeParser?(matcher: string | RegExp | string[], opts: {
|
|
44
|
+
parseAs: 'string' | 'buffer';
|
|
45
|
+
}, parser: FastifyBodyParser): unknown;
|
|
36
46
|
}
|
|
47
|
+
/** `done`-style callback Fastify hands a content-type parser. */
|
|
48
|
+
type FastifyBodyParser = (req: FastifyRequestLike, body: string | Buffer, done: (err: Error | null, body?: unknown) => void) => void;
|
|
37
49
|
interface MultipartPart {
|
|
38
50
|
type: 'file' | 'field';
|
|
39
51
|
fieldname: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastify.d.mts","names":[],"sources":["../../../src/http/runtimes/fastify.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"fastify.d.mts","names":[],"sources":["../../../src/http/runtimes/fastify.ts"],"mappings":";;;;UA+CU;EACR,KAAK,qBAAqB;EAC1B,KAAK,oBAAoB;EACzB,OAAO,cAAc,iBAAiB;EACtC,KAAK,sBAAsB;EAC3B;WACS;WACA,KAAK;;UAEN;EACR,KAAK;EACL;EACA;EACA;;EAEA,UAAU;;EAEV,cAAc,sBAAsB;EACpC;;KAEG,kBAAkB,SAAS,oBAAoB,OAAO,4BAA4B;UAC7E;EACR,SAAS,iBAAiB,iBAAiB,gBAAgB;EAC3D,IAAI,eAAe;EACnB,MAAM;IAAQ;IAAgB;IAAa,SAAS;;EACpD,mBAAmB,SAAS;EAC5B,gBACE,UAAU,cAAc,KAAK,oBAAoB,OAAO;EAE1D,SAAS;EACT,QAAQ,KAAK,iBAAiB,KAAK;;;;;;EAMnC,sBACE,kBAAkB,mBAClB;IAAQ;KACR,QAAQ;;;KAQP,qBACH,KAAK,oBACL,eAAe,QACf,OAAO,KAAK,cAAc;UAYlB;EACR;EACA;EACA;EACA;EACA;EACA,iBAAiB,QAAQ;;;;;;;iBAsLX,kBAAkB,YAAY;;;;;;;;UA6M7B;EACf,2BAA2B;EAC3B,4BAA4B;EAC5B,uBAAuB"}
|
|
@@ -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,6 +8,6 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{i as
|
|
12
|
-
Install it as a peer dependency: pnpm add @fastify/multipart.`);let rawParts=[],fields={};for await(let part of request.parts())part.type===`file`&&part.toBuffer?rawParts.push({fieldname:part.fieldname,filename:part.filename??``,mimetype:part.mimetype??`application/octet-stream`,buffer:await part.toBuffer()}):fields[part.fieldname]=part.value;let{file,files}=applyUploadConfig(rawParts,entry.meta.upload);raw.file=file,raw.files=files,raw.body={...raw.body,...fields}}let headerId=raw.headers[`x-request-id`],store=createRequestStore((Array.isArray(headerId)?headerId[0]:headerId)||raw.requestId);raw.requestId=store.requestId,reply.raw.once(`close`,()=>disposeRequestStore(store)),await requestStore.run(store,async()=>{let ctx=new RequestContext(raw,reply,NOOP_NEXT,replyDriver(reply));validator&&await new Promise((resolve,reject)=>{validator(raw,void 0,err=>err?reject(err):resolve())});for(let mw of entry.middlewares){let advanced=!1;if(await new Promise((resolve,reject)=>{Promise.resolve(mw(ctx,err=>{advanced=!0,err?reject(err):resolve()})).catch(reject)}),!advanced||reply.sent)return}if(entry.contributorRunner&&await entry.contributorRunner(ctx),reply.sent)return;let result=await entry.handler(ctx);reply.sent||applyHandlerResult(ctx,result)})}}function fastifyRuntime(){return{name:`fastify`,createApp(options={}){let app=loadPeer(`fastify`)({logger:!1,routerOptions:{ignoreTrailingSlash:!0},trustProxy:typeof options.trustProxy!=`function`&&options.trustProxy});
|
|
11
|
+
import{HttpException}from"../../core/errors.mjs";import{a as disposeRequestStore,d as unsupportedMediaTypeError,i as createRequestStore,t as applyHandlerResult}from"../../reply-CdVb6ipw.mjs";import{i as requestStore,t as RequestContext}from"../../context-BnIvtMUl.mjs";import{validate}from"../middleware/validate.mjs";import{t as applyUploadConfig}from"../../upload-config-DtnX4uVR.mjs";import"../middleware/upload.mjs";import{createRequire}from"node:module";const peerRequire=createRequire(import.meta.url),loadPeer=name=>{let mod=peerRequire(name);return mod&&mod.__esModule&&mod.default?mod.default:mod.default??mod},QUEUE=Symbol(`kickjs.fastify.connectQueue`),READY=Symbol(`kickjs.fastify.ready`),NEXT=Symbol(`kickjs.fastify.next`),NEEDS_MULTIPART=Symbol(`kickjs.fastify.needsMultipart`),NOOP_NEXT=()=>{};var FastifyReplyDriver=class{reply;constructor(reply){this.reply=reply}status(code){return this.reply.code(code),this}json(data){return this.reply.send(data),this}send(data){return this.reply.send(data),this}type(contentType){return this.reply.type(contentType),this}setHeader(name,value){return this.reply.header(name,value),this}render(){throw Error(`ctx.render() is not supported on the Fastify runtime (no view engine).`)}writeHead(statusCode,headers){return this.reply.hijack(),this.reply.raw.writeHead(statusCode,headers),this}flushHeaders(){return this.reply.raw.flushHeaders?.(),this}write(chunk){return this.reply.raw.write(chunk)}end(data){return this.reply.raw.end(data),this}once(event,listener){return this.reply.raw.once(event,listener),this}get headersSent(){return this.reply.sent||this.reply.raw.headersSent}};function replyDriver(reply){return new FastifyReplyDriver(reply)}function routeHandler(entry){let validator=entry.meta.validation?validate(entry.meta.validation):void 0;return async(request,reply)=>{let raw=request.raw;if(raw.body=request.body,raw.params=request.params,raw.query=request.query,entry.meta.upload&&entry.meta.upload.mode!==`none`){if(typeof request.parts!=`function`)throw Error(`@forinda/kickjs: @FileUpload on the Fastify runtime requires '@fastify/multipart'.
|
|
12
|
+
Install it as a peer dependency: pnpm add @fastify/multipart.`);let rawParts=[],fields={};for await(let part of request.parts())part.type===`file`&&part.toBuffer?rawParts.push({fieldname:part.fieldname,filename:part.filename??``,mimetype:part.mimetype??`application/octet-stream`,buffer:await part.toBuffer()}):fields[part.fieldname]=part.value;let{file,files}=applyUploadConfig(rawParts,entry.meta.upload);raw.file=file,raw.files=files,raw.body={...raw.body,...fields}}let headerId=raw.headers[`x-request-id`],store=createRequestStore((Array.isArray(headerId)?headerId[0]:headerId)||raw.requestId);raw.requestId=store.requestId,reply.raw.once(`close`,()=>disposeRequestStore(store)),await requestStore.run(store,async()=>{let ctx=new RequestContext(raw,reply,NOOP_NEXT,replyDriver(reply));validator&&await new Promise((resolve,reject)=>{validator(raw,void 0,err=>err?reject(err):resolve())});for(let mw of entry.middlewares){let advanced=!1;if(await new Promise((resolve,reject)=>{Promise.resolve(mw(ctx,err=>{advanced=!0,err?reject(err):resolve()})).catch(reject)}),!advanced||reply.sent)return}if(entry.contributorRunner&&await entry.contributorRunner(ctx),reply.sent)return;let result=await entry.handler(ctx);reply.sent||applyHandlerResult(ctx,result)})}}function fastifyRuntime(){return{name:`fastify`,createApp(options={}){let app=loadPeer(`fastify`)({logger:!1,routerOptions:{ignoreTrailingSlash:!0},trustProxy:typeof options.trustProxy!=`function`&&options.trustProxy});app[QUEUE]=[];let addParser=app.addContentTypeParser?.bind(app);return addParser&&(addParser(/^application\/[\w.+-]+\+json$/,{parseAs:`string`},(_req,body,done)=>{if(body===``)return done(null,void 0);try{done(null,JSON.parse(body))}catch(err){done(HttpException.badRequest(err instanceof Error?err.message:`Request body could not be parsed`),void 0)}}),addParser(`application/x-www-form-urlencoded`,{parseAs:`string`},(_req,body,done)=>{done(null,Object.fromEntries(new URLSearchParams(body)))}),addParser(/^text\//,{parseAs:`string`},(_req,body,done)=>{done(null,body)}),addParser(`*`,{parseAs:`string`},(req,body,done)=>{if(!body)return done(null,void 0);done(unsupportedMediaTypeError(req.headers?.[`content-type`]),void 0)})),app},nodeHandler(app){let state=app;state[READY]||(state[READY]=(async()=>{if(state[NEEDS_MULTIPART]){let multipart=loadPeer(`@fastify/multipart`);await app.register(multipart,{limits:{fileSize:2**53-1}})}let middie=loadPeer(`@fastify/middie`);await app.register(middie);for(let{mw,opts}of state[QUEUE]??[])opts?.path===void 0?app.use(mw):app.use(opts.path,mw);await app.ready()})());let readyP=state[READY];return(req,res,next)=>{next&&(req[NEXT]=next),readyP.then(()=>app.routing(req,res)).catch(err=>{next&&next(err)})}},mountRoutes(app,table){for(let{mountPath,routes}of table)for(let entry of routes){entry.meta.upload&&(app[NEEDS_MULTIPART]=!0);let url=joinPath(mountPath,entry.path);app.route({method:entry.method,url,handler:routeHandler(entry)})}},useConnect(app,mw,opts){app[QUEUE]?.push({mw,opts})},serveStatic(app,prefix,dir){let serveStatic=loadPeer(`serve-static`);this.useConnect(app,serveStatic(dir),{path:prefix})},setNotFound(app,mw){app.setNotFoundHandler((request,reply)=>{let next=request.raw[NEXT];if(next){reply.hijack(),next();return}mw(request.raw,replyDriver(reply),NOOP_NEXT)})},setErrorHandler(app,mw){app.setErrorHandler((err,request,reply)=>{mw(err,request.raw,replyDriver(reply),NOOP_NEXT)})},capabilities:{render:!1,uploads:!0,connectMiddleware:!0,nativeBodyParsing:!0}}}function joinPath(mountPath,path){let a=mountPath.endsWith(`/`)?mountPath.slice(0,-1):mountPath;return path===`/`||path===``?a===``?`/`:a:`${a}${path.startsWith(`/`)?path:`/${path}`}`}export{fastifyRuntime};
|
|
13
13
|
//# sourceMappingURL=fastify.mjs.map
|