@forinda/kickjs 10.0.0 → 10.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/{adapter-CvEm8Gpp.d.mts → adapter-ClEh-Oos.d.mts} +2 -2
  2. package/dist/{adapter-CvEm8Gpp.d.mts.map → adapter-ClEh-Oos.d.mts.map} +1 -1
  3. package/dist/{application-D21ZVAWG.mjs → application-Dv514EeR.mjs} +3 -3
  4. package/dist/{application-D21ZVAWG.mjs.map → application-Dv514EeR.mjs.map} +1 -1
  5. package/dist/{application-1Hln746x.d.mts → application-oIPzgjfk.d.mts} +20 -9
  6. package/dist/application-oIPzgjfk.d.mts.map +1 -0
  7. package/dist/bootstrap-BdyBlBMe.mjs +12 -0
  8. package/dist/bootstrap-BdyBlBMe.mjs.map +1 -0
  9. package/dist/{client-ip-BkLX8wXf.mjs → client-ip-BbLDp03S.mjs} +2 -2
  10. package/dist/{client-ip-BkLX8wXf.mjs.map → client-ip-BbLDp03S.mjs.map} +1 -1
  11. package/dist/config/index.mjs +2 -2
  12. package/dist/{config-rXsEm1rn.mjs → config-CJiGeOca.mjs} +3 -3
  13. package/dist/{config-rXsEm1rn.mjs.map → config-CJiGeOca.mjs.map} +1 -1
  14. package/dist/{container-CsCdem73.mjs → container-CS9v9UFU.mjs} +3 -3
  15. package/dist/{container-CsCdem73.mjs.map → container-CS9v9UFU.mjs.map} +1 -1
  16. package/dist/{context-Bd02q5Sm.mjs → context-CtL9RxXp.mjs} +3 -3
  17. package/dist/{context-Bd02q5Sm.mjs.map → context-CtL9RxXp.mjs.map} +1 -1
  18. package/dist/{context-errors-BUdMBVWO.mjs → context-errors-7C5TBYn7.mjs} +2 -2
  19. package/dist/{context-errors-BUdMBVWO.mjs.map → context-errors-7C5TBYn7.mjs.map} +1 -1
  20. package/dist/{contributor-runner-CZTAFvla.mjs → contributor-runner-DBpRR5dl.mjs} +3 -3
  21. package/dist/{contributor-runner-CZTAFvla.mjs.map → contributor-runner-DBpRR5dl.mjs.map} +1 -1
  22. package/dist/core/adapter.d.mts +1 -1
  23. package/dist/core/adapter.mjs +1 -1
  24. package/dist/core/app-module.mjs +1 -1
  25. package/dist/core/cache.mjs +1 -1
  26. package/dist/core/container.mjs +2 -2
  27. package/dist/core/cron.mjs +2 -2
  28. package/dist/core/decorators.mjs +2 -2
  29. package/dist/core/errors.mjs +1 -1
  30. package/dist/core/interfaces.mjs +1 -1
  31. package/dist/core/logger.mjs +1 -1
  32. package/dist/core/path.mjs +1 -1
  33. package/dist/core/plugin.d.mts +1 -1
  34. package/dist/core/plugin.mjs +1 -1
  35. package/dist/core/reactivity.mjs +1 -1
  36. package/dist/{core-B0mErNE1.mjs → core-B0JmdBvX.mjs} +3 -3
  37. package/dist/{core-B0mErNE1.mjs.map → core-B0JmdBvX.mjs.map} +1 -1
  38. package/dist/{csrf-BnetlmWY.mjs → csrf--1t4qDov.mjs} +3 -3
  39. package/dist/{csrf-BnetlmWY.mjs.map → csrf--1t4qDov.mjs.map} +1 -1
  40. package/dist/{decorate-B7-z930n.mjs → decorate-BHkdkV4l.mjs} +1 -1
  41. package/dist/{define-adapter-B4D_Rci1.mjs → define-adapter-BLKlUbSL.mjs} +2 -2
  42. package/dist/{define-adapter-B4D_Rci1.mjs.map → define-adapter-BLKlUbSL.mjs.map} +1 -1
  43. package/dist/{disposables-CgQqGmPL.mjs → disposables-CuY2S3bw.mjs} +2 -2
  44. package/dist/{disposables-CgQqGmPL.mjs.map → disposables-CuY2S3bw.mjs.map} +1 -1
  45. package/dist/{env-ngWmBB9d.mjs → env-BwzF9MkZ.mjs} +3 -3
  46. package/dist/{env-ngWmBB9d.mjs.map → env-BwzF9MkZ.mjs.map} +1 -1
  47. package/dist/{error-handler-Chw07m-M.d.mts → error-handler-BB9wAElm.d.mts} +2 -2
  48. package/dist/{error-handler-Chw07m-M.d.mts.map → error-handler-BB9wAElm.d.mts.map} +1 -1
  49. package/dist/{handler-CyRoAqNt.mjs → handler-zKfIgNo4.mjs} +3 -3
  50. package/dist/{handler-CyRoAqNt.mjs.map → handler-zKfIgNo4.mjs.map} +1 -1
  51. package/dist/http/application.d.mts +1 -1
  52. package/dist/http/application.mjs +2 -2
  53. package/dist/http/bootstrap.d.mts +1 -1
  54. package/dist/http/bootstrap.mjs +2 -2
  55. package/dist/http/context.d.mts +1 -1
  56. package/dist/http/context.mjs +2 -2
  57. package/dist/http/middleware/cors.mjs +1 -1
  58. package/dist/http/middleware/csrf.mjs +2 -2
  59. package/dist/http/middleware/error-handler.d.mts +1 -1
  60. package/dist/http/middleware/error-handler.mjs +2 -2
  61. package/dist/http/middleware/helmet.mjs +1 -1
  62. package/dist/http/middleware/rate-limit.mjs +2 -2
  63. package/dist/http/middleware/request-id.mjs +1 -1
  64. package/dist/http/middleware/request-logger.mjs +2 -2
  65. package/dist/http/middleware/session.d.mts +1 -1
  66. package/dist/http/middleware/session.mjs +2 -2
  67. package/dist/http/middleware/session.mjs.map +1 -1
  68. package/dist/http/middleware/spa.d.mts +1 -1
  69. package/dist/http/middleware/spa.mjs +2 -2
  70. package/dist/http/middleware/upload.d.mts +1 -1
  71. package/dist/http/middleware/upload.mjs +2 -2
  72. package/dist/http/middleware/validate.mjs +1 -1
  73. package/dist/http/middleware/views.d.mts +1 -1
  74. package/dist/http/middleware/views.mjs +2 -2
  75. package/dist/http/query/index.mjs +2 -2
  76. package/dist/http/router-builder.d.mts +1 -1
  77. package/dist/http/router-builder.mjs +2 -2
  78. package/dist/http/runtimes/fastify.d.mts +1 -1
  79. package/dist/http/runtimes/fastify.mjs +2 -2
  80. package/dist/http/runtimes/h3-web.mjs +1 -1
  81. package/dist/http/runtimes/h3.d.mts +1 -1
  82. package/dist/http/runtimes/h3.d.mts.map +1 -1
  83. package/dist/http/runtimes/h3.mjs +2 -2
  84. package/dist/http/runtimes/h3.mjs.map +1 -1
  85. package/dist/index.d.mts +7 -7
  86. package/dist/index.mjs +2 -2
  87. package/dist/index.mjs.map +1 -1
  88. package/dist/{jobs-Ccs6PkmC.mjs → jobs-BQ4t3TAb.mjs} +3 -3
  89. package/dist/{jobs-Ccs6PkmC.mjs.map → jobs-BQ4t3TAb.mjs.map} +1 -1
  90. package/dist/{kv-stores-BYeO77zK.d.mts → kv-stores-Co5k2PYA.d.mts} +3 -3
  91. package/dist/{kv-stores-BYeO77zK.d.mts.map → kv-stores-Co5k2PYA.d.mts.map} +1 -1
  92. package/dist/{kv-stores-9IkW7Rjj.mjs → kv-stores-DsfVy2fk.mjs} +3 -3
  93. package/dist/{kv-stores-9IkW7Rjj.mjs.map → kv-stores-DsfVy2fk.mjs.map} +1 -1
  94. package/dist/{metadata-DCWKG472.mjs → metadata-zvd6AWkE.mjs} +2 -2
  95. package/dist/{metadata-DCWKG472.mjs.map → metadata-zvd6AWkE.mjs.map} +1 -1
  96. package/dist/{module-registry-Cl12vSJd.mjs → module-registry-Daf_P2oD.mjs} +2 -2
  97. package/dist/{module-registry-Cl12vSJd.mjs.map → module-registry-Daf_P2oD.mjs.map} +1 -1
  98. package/dist/{observers-9FRE8VCB.mjs → observers-DZgs48UM.mjs} +2 -2
  99. package/dist/{observers-9FRE8VCB.mjs.map → observers-DZgs48UM.mjs.map} +1 -1
  100. package/dist/{query-DUkboS0j.mjs → query-B_k9qn0R.mjs} +2 -2
  101. package/dist/{query-DUkboS0j.mjs.map → query-B_k9qn0R.mjs.map} +1 -1
  102. package/dist/{reply-DElkObMy.mjs → reply-BDK9xt7Z.mjs} +3 -3
  103. package/dist/{reply-DElkObMy.mjs.map → reply-BDK9xt7Z.mjs.map} +1 -1
  104. package/dist/{respond-Dcj1NF_X.mjs → respond-DFRj2-7q.mjs} +3 -3
  105. package/dist/{respond-Dcj1NF_X.mjs.map → respond-DFRj2-7q.mjs.map} +1 -1
  106. package/dist/{route-flag-jd8PDm20.mjs → route-flag-BWuvJsGZ.mjs} +3 -3
  107. package/dist/{route-flag-jd8PDm20.mjs.map → route-flag-BWuvJsGZ.mjs.map} +1 -1
  108. package/dist/{route-policy-DlunIjGc.mjs → route-policy-B3vM6bSf.mjs} +2 -2
  109. package/dist/{route-policy-DlunIjGc.mjs.map → route-policy-B3vM6bSf.mjs.map} +1 -1
  110. package/dist/{router-builder-DZsCjpeV.mjs → router-builder-JvMTp50k.mjs} +3 -3
  111. package/dist/{router-builder-DZsCjpeV.mjs.map → router-builder-JvMTp50k.mjs.map} +1 -1
  112. package/dist/{router-builder-D_SkWTfE.d.mts → router-builder-zWuVIOmd.d.mts} +2 -2
  113. package/dist/{router-builder-D_SkWTfE.d.mts.map → router-builder-zWuVIOmd.d.mts.map} +1 -1
  114. package/dist/{session-4Tb6q0Ny.d.mts → session-B4hOZj90.d.mts} +13 -4
  115. package/dist/session-B4hOZj90.d.mts.map +1 -0
  116. package/dist/{upload-DL_jrBGk.d.mts → upload-CiMtdXWD.d.mts} +2 -2
  117. package/dist/{upload-DL_jrBGk.d.mts.map → upload-CiMtdXWD.d.mts.map} +1 -1
  118. package/dist/{upload-config-BiL1QXkT.mjs → upload-config-Dz2INCaa.mjs} +2 -2
  119. package/dist/{upload-config-BiL1QXkT.mjs.map → upload-config-Dz2INCaa.mjs.map} +1 -1
  120. package/dist/web.d.mts +2 -2
  121. package/dist/web.mjs +2 -2
  122. package/package.json +1 -1
  123. package/dist/application-1Hln746x.d.mts.map +0 -1
  124. package/dist/bootstrap-2hi7Zhs8.mjs +0 -12
  125. package/dist/bootstrap-2hi7Zhs8.mjs.map +0 -1
  126. package/dist/session-4Tb6q0Ny.d.mts.map +0 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v10.0.0
2
+ * @forinda/kickjs v10.1.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{createLogger,describeError}from"../../core/logger.mjs";import{HttpException,ProblemException,normalizeProblem}from"../../core/errors.mjs";import"../../core-B0mErNE1.mjs";const log=createLogger(`ErrorHandler`);function patternToRegExp(pattern){let source=pattern.replace(/[.+?^${}()|[\]\\]/g,`\\$&`).replace(/\/:[^/]+/g,`/[^/]+`).replace(/\*/g,`.*`);return RegExp(`^${source}/?$`)}function pathOf(req){let url=req.originalUrl??req.url??`/`,cut=url.search(/[?#]/);return cut===-1?url:url.slice(0,cut)}function notFoundHandler(routes=[]){let matchers=routes.map(route=>({method:route.method.toUpperCase(),matches:patternToRegExp(route.path)}));return(req,res,_next)=>{let path=pathOf(req),method=(req.method??`GET`).toUpperCase(),allowed=[...new Set(matchers.filter(m=>m.matches.test(path)).map(m=>m.method))];if(allowed.length>0&&!allowed.includes(method)){let verbs=allowed.toSorted().join(`, `);return res.setHeader(`Allow`,verbs),res.setHeader(`Content-Type`,`application/problem+json`),res.status(405).json(normalizeProblem({status:405,detail:`${method} is not supported for this resource. Allowed: ${verbs}.`}))}return res.setHeader(`Content-Type`,`application/problem+json`),res.status(404).json(normalizeProblem({status:404}))}}function errorHandler(){let isProduction=process.env.NODE_ENV===`production`;return(err,req,res,_next)=>{if(res.headersSent){log.warn(`Error after headers sent: ${err?.message||`Unknown`}`);return}if(err instanceof ProblemException){if(err.problem.status>=500&&log.error(err,err.message),err.headers)for(let[k,v]of Object.entries(err.headers))res.setHeader(k,v);let body=normalizeProblem(err.problem);return res.setHeader(`Content-Type`,`application/problem+json`),res.status(body.status).json(body)}if(err?.name===`ZodError`){let firstIssue=err.issues?.[0];return res.status(422).json({message:firstIssue?.message||`Validation failed`,...isProduction?{}:{errors:err.issues}})}if(err instanceof HttpException){if(err.status>=500&&log.error(err,err.message),err.headers)for(let[k,v]of Object.entries(err.headers))res.setHeader(k,v);let exposeDetails=!isProduction&&err.details!==void 0;return res.setHeader(`Content-Type`,`application/problem+json`),res.status(err.status).json(normalizeProblem({status:err.status,detail:err.message,...exposeDetails?{errors:err.details}:{}}))}let requestId=req.requestId??req.headers[`x-request-id`],declared=Number(err.status??err.statusCode);if(Number.isInteger(declared)&&declared>=400&&declared<500)return log.warn(`${req.method} ${req.originalUrl??req.url} — ${declared} ${describeError(err)}`),res.setHeader(`Content-Type`,`application/problem+json`),res.status(declared).json(normalizeProblem({status:declared,detail:err.message||void 0,...requestId?{requestId}:{}}));let status=Number.isInteger(declared)&&declared>=500&&declared<600?declared:500;log.error(err,`${req.method} ${req.originalUrl??req.url} — ${describeError(err)}${requestId?` [${requestId}]`:``}`),res.status(status).json({message:`Internal Server Error`,...requestId?{requestId}:{},...isProduction?{}:{error:describeError(err),...typeof err?.stack==`string`?{stack:err.stack.split(`
11
+ import{createLogger,describeError}from"../../core/logger.mjs";import{HttpException,ProblemException,normalizeProblem}from"../../core/errors.mjs";import"../../core-B0JmdBvX.mjs";const log=createLogger(`ErrorHandler`);function patternToRegExp(pattern){let source=pattern.replace(/[.+?^${}()|[\]\\]/g,`\\$&`).replace(/\/:[^/]+/g,`/[^/]+`).replace(/\*/g,`.*`);return RegExp(`^${source}/?$`)}function pathOf(req){let url=req.originalUrl??req.url??`/`,cut=url.search(/[?#]/);return cut===-1?url:url.slice(0,cut)}function notFoundHandler(routes=[]){let matchers=routes.map(route=>({method:route.method.toUpperCase(),matches:patternToRegExp(route.path)}));return(req,res,_next)=>{let path=pathOf(req),method=(req.method??`GET`).toUpperCase(),allowed=[...new Set(matchers.filter(m=>m.matches.test(path)).map(m=>m.method))];if(allowed.length>0&&!allowed.includes(method)){let verbs=allowed.toSorted().join(`, `);return res.setHeader(`Allow`,verbs),res.setHeader(`Content-Type`,`application/problem+json`),res.status(405).json(normalizeProblem({status:405,detail:`${method} is not supported for this resource. Allowed: ${verbs}.`}))}return res.setHeader(`Content-Type`,`application/problem+json`),res.status(404).json(normalizeProblem({status:404}))}}function errorHandler(){let isProduction=process.env.NODE_ENV===`production`;return(err,req,res,_next)=>{if(res.headersSent){log.warn(`Error after headers sent: ${err?.message||`Unknown`}`);return}if(err instanceof ProblemException){if(err.problem.status>=500&&log.error(err,err.message),err.headers)for(let[k,v]of Object.entries(err.headers))res.setHeader(k,v);let body=normalizeProblem(err.problem);return res.setHeader(`Content-Type`,`application/problem+json`),res.status(body.status).json(body)}if(err?.name===`ZodError`){let firstIssue=err.issues?.[0];return res.status(422).json({message:firstIssue?.message||`Validation failed`,...isProduction?{}:{errors:err.issues}})}if(err instanceof HttpException){if(err.status>=500&&log.error(err,err.message),err.headers)for(let[k,v]of Object.entries(err.headers))res.setHeader(k,v);let exposeDetails=!isProduction&&err.details!==void 0;return res.setHeader(`Content-Type`,`application/problem+json`),res.status(err.status).json(normalizeProblem({status:err.status,detail:err.message,...exposeDetails?{errors:err.details}:{}}))}let requestId=req.requestId??req.headers[`x-request-id`],declared=Number(err.status??err.statusCode);if(Number.isInteger(declared)&&declared>=400&&declared<500)return log.warn(`${req.method} ${req.originalUrl??req.url} — ${declared} ${describeError(err)}`),res.setHeader(`Content-Type`,`application/problem+json`),res.status(declared).json(normalizeProblem({status:declared,detail:err.message||void 0,...requestId?{requestId}:{}}));let status=Number.isInteger(declared)&&declared>=500&&declared<600?declared:500;log.error(err,`${req.method} ${req.originalUrl??req.url} — ${describeError(err)}${requestId?` [${requestId}]`:``}`),res.status(status).json({message:`Internal Server Error`,...requestId?{requestId}:{},...isProduction?{}:{error:describeError(err),...typeof err?.stack==`string`?{stack:err.stack.split(`
12
12
  `)}:{}}})}}export{errorHandler,notFoundHandler};
13
13
  //# sourceMappingURL=error-handler.mjs.map
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v10.0.0
2
+ * @forinda/kickjs v10.1.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v10.0.0
2
+ * @forinda/kickjs v10.1.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-CgQqGmPL.mjs";import{n as assertFlagTest,s as matchesFlagTest}from"../../route-flag-jd8PDm20.mjs";import{n as bindRoutePolicy}from"../../route-policy-DlunIjGc.mjs";import{n as resolvePathname,t as resolveClientIp}from"../../client-ip-BkLX8wXf.mjs";import{r as sendJson}from"../../respond-Dcj1NF_X.mjs";var MemoryStore=class{windowMs;hits=/* @__PURE__ */ 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??[]),exemptWhen=options.exemptWhen;exemptWhen!==void 0&&assertFlagTest(exemptWhen,`rateLimit({ exemptWhen })`);let policy;return bindRoutePolicy(async(req,res,next)=>{let pathname=resolvePathname(req);if(skipPaths.has(pathname))return next();if(exemptWhen!==void 0&&policy){let{originalUrl}=req,routePath=originalUrl?resolvePathname({url:originalUrl}):pathname,flags=policy.lookup(String(req.method??`GET`),routePath);if(matchesFlagTest(exemptWhen,flags))return next()}if(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()},table=>{policy=table})}export{rateLimit};
11
+ import{r as registerDisposable}from"../../disposables-CuY2S3bw.mjs";import{n as assertFlagTest,s as matchesFlagTest}from"../../route-flag-BWuvJsGZ.mjs";import{n as bindRoutePolicy}from"../../route-policy-B3vM6bSf.mjs";import{n as resolvePathname,t as resolveClientIp}from"../../client-ip-BbLDp03S.mjs";import{i as sendJson}from"../../respond-DFRj2-7q.mjs";var MemoryStore=class{windowMs;hits=/* @__PURE__ */ 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??[]),exemptWhen=options.exemptWhen;exemptWhen!==void 0&&assertFlagTest(exemptWhen,`rateLimit({ exemptWhen })`);let policy;return bindRoutePolicy(async(req,res,next)=>{let pathname=resolvePathname(req);if(skipPaths.has(pathname))return next();if(exemptWhen!==void 0&&policy){let{originalUrl}=req,routePath=originalUrl?resolvePathname({url:originalUrl}):pathname,flags=policy.lookup(String(req.method??`GET`),routePath);if(matchesFlagTest(exemptWhen,flags))return next()}if(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()},table=>{policy=table})}export{rateLimit};
12
12
  //# sourceMappingURL=rate-limit.mjs.map
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v10.0.0
2
+ * @forinda/kickjs v10.1.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v10.0.0
2
+ * @forinda/kickjs v10.1.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-B0mErNE1.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.child({method:req.method,path,status,ms:duration,requestId})[level](`${req.method} ${path} ${status} ${duration}ms ${requestId}`)}),next()}}export{requestLogger};
11
+ import{createLogger}from"../../core/logger.mjs";import"../../core-B0JmdBvX.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.child({method:req.method,path,status,ms:duration,requestId})[level](`${req.method} ${path} ${status} ${duration}ms ${requestId}`)}),next()}}export{requestLogger};
12
12
  //# sourceMappingURL=request-logger.mjs.map
@@ -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-4Tb6q0Ny.mjs";
2
+ import { a as session, i as SessionStore, n as SessionData, r as SessionOptions, t as Session } from "../../session-B4hOZj90.mjs";
3
3
  export { Session, SessionData, SessionOptions, SessionStore, session };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v10.0.0
2
+ * @forinda/kickjs v10.1.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-CgQqGmPL.mjs";import{i as setCookie,t as parseCookieHeader}from"../../respond-Dcj1NF_X.mjs";import{createHmac,randomUUID,timingSafeEqual}from"node:crypto";var MemoryStore=class{sessions=/* @__PURE__ */ 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,setCookie(res,cookieName,``,{...cookieDefaults,maxAge:0,expires:/* @__PURE__ */ new Date(0)})},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};
11
+ import{createLogger}from"../../core/logger.mjs";import{r as registerDisposable}from"../../disposables-CuY2S3bw.mjs";import{a as setCookie,r as replaceCookie,t as parseCookieHeader}from"../../respond-DFRj2-7q.mjs";import{createCipheriv,createDecipheriv,createHmac,hkdfSync,randomBytes,randomUUID,timingSafeEqual}from"node:crypto";var MemoryStore=class{sessions=/* @__PURE__ */ 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){if(options.store===`cookie`)return cookieSession(options);let{secret,cookieName=`kick.sid`,maxAge=864e5,rolling=!1,saveUninitialized=!0,cookie:cookieOpts={}}=options,store=options.store??new MemoryStore,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,setCookie(res,cookieName,``,{...cookieDefaults,maxAge:0,expires:/* @__PURE__ */ new Date(0)})},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()}}const cookieLog=createLogger(`Session`);function sealer(secret,cookieName){let key=Buffer.from(hkdfSync(`sha256`,secret,`kick.session`,`cookie-store-v1`,32)),aad=Buffer.from(cookieName);return{seal(payload){let iv=randomBytes(12),cipher=createCipheriv(`aes-256-gcm`,key,iv).setAAD(aad),body=Buffer.concat([cipher.update(JSON.stringify(payload),`utf8`),cipher.final()]);return`v1.${Buffer.concat([iv,cipher.getAuthTag(),body]).toString(`base64url`)}`},open(value){if(!value.startsWith(`v1.`))return null;try{let raw=Buffer.from(value.slice(3),`base64url`);if(raw.length<29)return null;let decipher=createDecipheriv(`aes-256-gcm`,key,raw.subarray(0,12)).setAAD(aad).setAuthTag(raw.subarray(12,28)),json=Buffer.concat([decipher.update(raw.subarray(28)),decipher.final()]),payload=JSON.parse(json.toString(`utf8`));return typeof payload?.i!=`string`||typeof payload.e!=`number`||!payload.d||typeof payload.d!=`object`?null:payload.e>Date.now()?payload:null}catch{return null}}}}function cookieSession(options){let{secret,cookieName=`kick.sid`,maxAge=864e5,rolling=!1,saveUninitialized=!0,cookie:cookieOpts={}}=options,{seal,open}=sealer(secret,cookieName),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}:{}},warnedSize=!1;return(req,res,next)=>{let cookies=req.cookies;if(!cookies){let header=req.headers.cookie;cookies=header?parseCookieHeader(header):{}}let existing=cookies[cookieName]?open(cookies[cookieName]):null,isNew=!existing,id=existing?.i??randomUUID(),destroyed=!1,written=existing?JSON.stringify(existing.d):void 0,write=(force=!1)=>{if(res.headersSent)return;if(destroyed){replaceCookie(res,cookieName,``,{...cookieDefaults,maxAge:0,expires:/* @__PURE__ */ new Date(0)});return}let json=JSON.stringify(sessionObj.data);if(json===written&&!force&&(!rolling||isNew)||written===void 0&&json===`{}`&&!saveUninitialized&&!force)return;let value=seal({i:id,d:sessionObj.data,e:Date.now()+maxAge});value.length+cookieName.length>4e3&&!warnedSize&&(warnedSize=!0,cookieLog.warn(`session cookie is ${value.length} bytes; browsers drop cookies over 4 KB, so this session will not be kept. Store less in it, or use a shared store.`)),replaceCookie(res,cookieName,value,{...cookieDefaults,maxAge}),written=json};for(let method of[`writeHead`,`flushHeaders`,`write`,`end`]){let original=res[method];typeof original==`function`&&(res[method]=function(...args){return write(),original.apply(this,args)})}res.on?.(`finish`,()=>write());let sessionObj={get id(){return id},data:existing?{...existing.d}:{},async regenerate(){id=randomUUID(),sessionObj.data={},written=void 0,write(!0)},async destroy(){destroyed=!0,sessionObj.data={},write()},async save(){write()}};req.session=sessionObj,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 { 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\n/**\n * What a session holds. Augment it to type your keys — undeclared keys stay\n * readable as `unknown`:\n *\n * ```ts\n * declare module '@forinda/kickjs' {\n * interface SessionData { userId?: string; cart?: string[] }\n * }\n * ```\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 // Expire the cookie the same way it was set — `res.clearCookie` is\n // Express-only and threw under Fastify and h3.\n setCookie(res, cookieName, '', { ...cookieDefaults, maxAge: 0, expires: new Date(0) })\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":";;;;;;;;;;iNA4DA,IAAM,YAAN,KAA0C,CACxC,yBAA4B,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,YAGlB,UAAU,IAAK,WAAY,GAAI,CAAE,GAAG,eAAgB,OAAQ,EAAG,wBAAS,IAAI,KAAK,CAAC,CAAE,CAAC,CACvF,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,YACA,QAAU,mBAAqB,OAAO,KAAK,WAAW,CAAC,CAAC,SAAW,KAEvE,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
+ {"version":3,"file":"session.mjs","names":[],"sources":["../../../src/http/middleware/session.ts"],"sourcesContent":["import { parseCookieHeader, replaceCookie, setCookie } from './respond'\nimport {\n createCipheriv,\n createDecipheriv,\n createHmac,\n hkdfSync,\n randomBytes,\n randomUUID,\n timingSafeEqual,\n} from 'node:crypto'\nimport { createLogger } from '../../core/logger'\nimport type { Request, Response, NextFunction } from 'express'\n\nimport { registerDisposable } from '../../core/disposables'\n\n/**\n * What a session holds. Augment it to type your keys — undeclared keys stay\n * readable as `unknown`:\n *\n * ```ts\n * declare module '@forinda/kickjs' {\n * interface SessionData { userId?: string; cart?: string[] }\n * }\n * ```\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 /**\n * Where session data lives. Default: in memory, in this process.\n *\n * `'cookie'` keeps it in the session cookie itself, encrypted with\n * AES-256-GCM under a key derived from `secret`: every instance reads the\n * same session with nothing shared between them — serverless functions,\n * several containers. The data has to fit a cookie (about 4 KB).\n *\n * Or pass a store shared between instances (`KvSessionStore`, Redis).\n */\n store?: SessionStore | 'cookie'\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 if (options.store === 'cookie') return cookieSession(options)\n const {\n secret,\n cookieName = 'kick.sid',\n maxAge = 86_400_000,\n rolling = false,\n saveUninitialized = true,\n cookie: cookieOpts = {},\n } = options\n const store = (options.store as SessionStore | undefined) ?? new MemoryStore()\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 // Expire the cookie the same way it was set — `res.clearCookie` is\n // Express-only and threw under Fastify and h3.\n setCookie(res, cookieName, '', { ...cookieDefaults, maxAge: 0, expires: new Date(0) })\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\n// ── Cookie store ──────────────────────────────────────────────────────\n\nconst cookieLog = createLogger('Session')\n\n/** Browsers drop a cookie larger than 4096 bytes, name and attributes included. */\nconst COOKIE_LIMIT = 4000\n\ninterface CookiePayload {\n /** Session id. */\n i: string\n d: SessionData\n /** Expiry, ms since epoch. */\n e: number\n}\n\n/** AES-256-GCM, with the cookie name as associated data: `v1.<iv|tag|ciphertext>`. */\nfunction sealer(secret: string, cookieName: string) {\n const key = Buffer.from(hkdfSync('sha256', secret, 'kick.session', 'cookie-store-v1', 32))\n const aad = Buffer.from(cookieName)\n return {\n seal(payload: CookiePayload): string {\n const iv = randomBytes(12)\n const cipher = createCipheriv('aes-256-gcm', key, iv).setAAD(aad)\n const body = Buffer.concat([cipher.update(JSON.stringify(payload), 'utf8'), cipher.final()])\n return `v1.${Buffer.concat([iv, cipher.getAuthTag(), body]).toString('base64url')}`\n },\n /** The payload, or null for anything forged, altered, expired or malformed. */\n open(value: string): CookiePayload | null {\n if (!value.startsWith('v1.')) return null\n try {\n const raw = Buffer.from(value.slice(3), 'base64url')\n if (raw.length < 29) return null\n const decipher = createDecipheriv('aes-256-gcm', key, raw.subarray(0, 12))\n .setAAD(aad)\n .setAuthTag(raw.subarray(12, 28))\n const json = Buffer.concat([decipher.update(raw.subarray(28)), decipher.final()])\n const payload = JSON.parse(json.toString('utf8')) as CookiePayload\n if (typeof payload?.i !== 'string' || typeof payload.e !== 'number') return null\n if (!payload.d || typeof payload.d !== 'object') return null\n return payload.e > Date.now() ? payload : null\n } catch {\n return null\n }\n },\n }\n}\n\ntype HeaderWriter = (...args: unknown[]) => unknown\n\n/**\n * `session({ store: 'cookie' })`. The cookie is written as late as possible —\n * just before the response's headers go out — so every change made while\n * handling the request is in it, nested ones included. `save()` writes it\n * straight away, for code that redirects right after.\n */\nfunction cookieSession(options: SessionOptions) {\n const {\n secret,\n cookieName = 'kick.sid',\n maxAge = 86_400_000,\n rolling = false,\n saveUninitialized = true,\n cookie: cookieOpts = {},\n } = options\n const { seal, open } = sealer(secret, cookieName)\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 }\n let warnedSize = false\n\n return (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 existing = cookies[cookieName] ? open(cookies[cookieName]) : null\n const isNew = !existing\n\n let id = existing?.i ?? randomUUID()\n let destroyed = false\n // What the browser already holds, to skip rewriting an unchanged cookie.\n let written = existing ? JSON.stringify(existing.d) : undefined\n\n const write = (force = false) => {\n if (res.headersSent) return\n if (destroyed) {\n replaceCookie(res, cookieName, '', { ...cookieDefaults, maxAge: 0, expires: new Date(0) })\n return\n }\n const json = JSON.stringify(sessionObj.data)\n // Unchanged: leave the browser's copy, unless `rolling` renews it.\n if (json === written && !force && !(rolling && !isNew)) return\n // A new, empty session gets a cookie only with saveUninitialized.\n if (written === undefined && json === '{}' && !saveUninitialized && !force) return\n const value = seal({ i: id, d: sessionObj.data, e: Date.now() + maxAge })\n if (value.length + cookieName.length > COOKIE_LIMIT && !warnedSize) {\n warnedSize = true\n cookieLog.warn(\n `session cookie is ${value.length} bytes; browsers drop cookies over 4 KB, ` +\n 'so this session will not be kept. Store less in it, or use a shared store.',\n )\n }\n replaceCookie(res, cookieName, value, { ...cookieDefaults, maxAge })\n written = json\n }\n\n // Just before the headers go out, on whichever call sends them first —\n // Node responses go through writeHead; the web driver's buffered end()\n // does not.\n for (const method of ['writeHead', 'flushHeaders', 'write', 'end'] as const) {\n const original = (res as unknown as Record<string, HeaderWriter | undefined>)[method]\n if (typeof original !== 'function') continue\n ;(res as unknown as Record<string, HeaderWriter>)[method] = function (\n this: unknown,\n ...args: unknown[]\n ) {\n write()\n return original.apply(this, args)\n }\n }\n\n // h3 called through fetch hands middleware a stand-in response whose\n // headers join the route's response after `finish` — the last chance there.\n // On a Node response `finish` comes after the headers, so this is a no-op.\n ;(res as unknown as { on?: (event: string, fn: () => void) => unknown }).on?.('finish', () =>\n write(),\n )\n\n const sessionObj: Session = {\n get id() {\n return id\n },\n data: existing ? { ...existing.d } : {},\n async regenerate() {\n id = randomUUID()\n sessionObj.data = {}\n written = undefined\n write(true)\n },\n async destroy() {\n destroyed = true\n sessionObj.data = {}\n write()\n },\n async save() {\n write()\n },\n }\n ;(req as any).session = sessionObj\n next()\n }\n}\n"],"mappings":";;;;;;;;;;yUA8EA,IAAM,YAAN,KAA0C,CACxC,yBAA4B,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,GAAI,QAAQ,QAAU,SAAU,OAAO,cAAc,OAAO,EAC5D,GAAM,CACJ,OACA,WAAa,WACb,OAAS,MACT,QAAU,GACV,kBAAoB,GACpB,OAAQ,WAAa,CAAC,GACpB,QACE,MAAS,QAAQ,OAAsC,IAAI,YAE3D,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,YAGlB,UAAU,IAAK,WAAY,GAAI,CAAE,GAAG,eAAgB,OAAQ,EAAG,wBAAS,IAAI,KAAK,CAAC,CAAE,CAAC,CACvF,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,YACA,QAAU,mBAAqB,OAAO,KAAK,WAAW,CAAC,CAAC,SAAW,KAEvE,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,CAIA,MAAM,UAAY,aAAa,SAAS,EAcxC,SAAS,OAAO,OAAgB,WAAoB,CAClD,IAAM,IAAM,OAAO,KAAK,SAAS,SAAU,OAAQ,eAAgB,kBAAmB,EAAE,CAAC,EACnF,IAAM,OAAO,KAAK,UAAU,EAClC,MAAO,CACL,KAAK,QAAgC,CACnC,IAAM,GAAK,YAAY,EAAE,EACnB,OAAS,eAAe,cAAe,IAAK,EAAE,CAAC,CAAC,OAAO,GAAG,EAC1D,KAAO,OAAO,OAAO,CAAC,OAAO,OAAO,KAAK,UAAU,OAAO,EAAG,MAAM,EAAG,OAAO,MAAM,CAAC,CAAC,EAC3F,MAAO,MAAM,OAAO,OAAO,CAAC,GAAI,OAAO,WAAW,EAAG,IAAI,CAAC,CAAC,CAAC,SAAS,WAAW,GAClF,EAEA,KAAK,MAAqC,CACxC,GAAI,CAAC,MAAM,WAAW,KAAK,EAAG,OAAO,KACrC,GAAI,CACF,IAAM,IAAM,OAAO,KAAK,MAAM,MAAM,CAAC,EAAG,WAAW,EACnD,GAAI,IAAI,OAAS,GAAI,OAAO,KAC5B,IAAM,SAAW,iBAAiB,cAAe,IAAK,IAAI,SAAS,EAAG,EAAE,CAAC,CAAC,CACvE,OAAO,GAAG,CAAC,CACX,WAAW,IAAI,SAAS,GAAI,EAAE,CAAC,EAC5B,KAAO,OAAO,OAAO,CAAC,SAAS,OAAO,IAAI,SAAS,EAAE,CAAC,EAAG,SAAS,MAAM,CAAC,CAAC,EAC1E,QAAU,KAAK,MAAM,KAAK,SAAS,MAAM,CAAC,EAGhD,OAFI,OAAO,SAAS,GAAM,UAAY,OAAO,QAAQ,GAAM,UACvD,CAAC,QAAQ,GAAK,OAAO,QAAQ,GAAM,SAAiB,KACjD,QAAQ,EAAI,KAAK,IAAI,EAAI,QAAU,IAC5C,MAAQ,CACN,OAAO,IACT,CACF,CACF,CACF,CAUA,SAAS,cAAc,QAAyB,CAC9C,GAAM,CACJ,OACA,WAAa,WACb,OAAS,MACT,QAAU,GACV,kBAAoB,GACpB,OAAQ,WAAa,CAAC,GACpB,QACE,CAAE,KAAM,MAAS,OAAO,OAAQ,UAAU,EAC1C,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,CAC3D,EACI,WAAa,GAEjB,OAAQ,IAAc,IAAe,OAAuB,CAC1D,IAAI,QAAW,IAAY,QAC3B,GAAI,CAAC,QAAS,CACZ,IAAM,OAAS,IAAI,QAAQ,OAC3B,QAAU,OAAS,kBAAkB,MAAM,EAAI,CAAC,CAClD,CACA,IAAM,SAAW,QAAQ,YAAc,KAAK,QAAQ,WAAW,EAAI,KAC7D,MAAQ,CAAC,SAEX,GAAK,UAAU,GAAK,WAAW,EAC/B,UAAY,GAEZ,QAAU,SAAW,KAAK,UAAU,SAAS,CAAC,EAAI,IAAA,GAEhD,OAAS,MAAQ,KAAU,CAC/B,GAAI,IAAI,YAAa,OACrB,GAAI,UAAW,CACb,cAAc,IAAK,WAAY,GAAI,CAAE,GAAG,eAAgB,OAAQ,EAAG,wBAAS,IAAI,KAAK,CAAC,CAAE,CAAC,EACzF,MACF,CACA,IAAM,KAAO,KAAK,UAAU,WAAW,IAAI,EAI3C,GAFI,OAAS,SAAW,CAAC,QAAW,UAAY,QAE5C,UAAY,IAAA,IAAa,OAAS,MAAQ,CAAC,mBAAqB,CAAC,MAAO,OAC5E,IAAM,MAAQ,KAAK,CAAE,EAAG,GAAI,EAAG,WAAW,KAAM,EAAG,KAAK,IAAI,EAAI,MAAO,CAAC,EACpE,MAAM,OAAS,WAAW,OAAS,KAAgB,CAAC,aACtD,WAAa,GACb,UAAU,KACR,qBAAqB,MAAM,OAAO,oHAEpC,GAEF,cAAc,IAAK,WAAY,MAAO,CAAE,GAAG,eAAgB,MAAO,CAAC,EACnE,QAAU,IACZ,EAKA,IAAK,IAAM,SAAU,CAAC,YAAa,eAAgB,QAAS,KAAK,EAAY,CAC3E,IAAM,SAAY,IAA4D,QAC1E,OAAO,UAAa,aACvB,IAAiD,QAAU,SAE1D,GAAG,KACH,CAEA,OADA,MAAM,EACC,SAAS,MAAM,KAAM,IAAI,CAClC,EACF,CAKC,IAAwE,KAAK,aAC5E,MAAM,CACR,EAEA,IAAM,WAAsB,CAC1B,IAAI,IAAK,CACP,OAAO,EACT,EACA,KAAM,SAAW,CAAE,GAAG,SAAS,CAAE,EAAI,CAAC,EACtC,MAAM,YAAa,CACjB,GAAK,WAAW,EAChB,WAAW,KAAO,CAAC,EACnB,QAAU,IAAA,GACV,MAAM,EAAI,CACZ,EACA,MAAM,SAAU,CACd,UAAY,GACZ,WAAW,KAAO,CAAC,EACnB,MAAM,CACR,EACA,MAAM,MAAO,CACX,MAAM,CACR,CACF,EACC,IAAa,QAAU,WACxB,KAAK,CACP,CACF"}
@@ -1,3 +1,3 @@
1
1
 
2
- import { f as SpaAdapter, m as resolveClientDir, p as SpaAdapterOptions } from "../../application-1Hln746x.mjs";
2
+ import { f as SpaAdapter, m as resolveClientDir, p as SpaAdapterOptions } from "../../application-oIPzgjfk.mjs";
3
3
  export { SpaAdapter, SpaAdapterOptions, resolveClientDir };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v10.0.0
2
+ * @forinda/kickjs v10.1.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-B0mErNE1.mjs";import{t as defineAdapter}from"../../define-adapter-B4D_Rci1.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=/* @__PURE__ */ new Set;function snapshotClientDir(){let paths=/* @__PURE__ */ 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/* @__PURE__ */ 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`);existsSync(indexPath)?(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}`)):log.warn(`index.html not found in ${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};
11
+ import{Logger}from"../../core/logger.mjs";import"../../core-B0JmdBvX.mjs";import{t as defineAdapter}from"../../define-adapter-BLKlUbSL.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=/* @__PURE__ */ new Set;function snapshotClientDir(){let paths=/* @__PURE__ */ 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/* @__PURE__ */ 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`);existsSync(indexPath)?(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}`)):log.warn(`index.html not found in ${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-DL_jrBGk.mjs";
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-CiMtdXWD.mjs";
3
3
  export { type RawUploadPart, UploadOptions, type UploadedFileLike, applyUploadConfig, buildFileTypeFilter, buildUploadMiddleware, cleanupFiles, resolveMimeTypes, upload };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v10.0.0
2
+ * @forinda/kickjs v10.1.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{HttpException,HttpStatus}from"../../core/errors.mjs";import{n as buildFileTypeFilter,r as resolveMimeTypes,t as applyUploadConfig}from"../../upload-config-BiL1QXkT.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.
11
+ import{HttpException,HttpStatus}from"../../core/errors.mjs";import{n as buildFileTypeFilter,r as resolveMimeTypes,t as applyUploadConfig}from"../../upload-config-Dz2INCaa.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
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??5242880;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??5242880},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 bothConventions(handler){return((first,second,third)=>third===void 0&&typeof second==`function`&&typeof first==`object`&&first&&`req`in first&&`res`in first?new Promise((resolve,reject)=>{handler(first.req,first.res,err=>{if(err)return reject(err);resolve(),second()})}):handler(first,second,third))}function single(fieldName,options={}){return bothConventions(translateMulterErrors(createMulter(options).single(fieldName),options))}function array(fieldName,maxCount=10,options={}){return bothConventions(translateMulterErrors(createMulter(options).array(fieldName,maxCount),options))}function none(options={}){return bothConventions(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,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v10.0.0
2
+ * @forinda/kickjs v10.1.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -1,3 +1,3 @@
1
1
 
2
- import { g as ViewAdapterOptions, h as ViewAdapter } from "../../application-1Hln746x.mjs";
2
+ import { g as ViewAdapterOptions, h as ViewAdapter } from "../../application-oIPzgjfk.mjs";
3
3
  export { ViewAdapter, ViewAdapterOptions };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v10.0.0
2
+ * @forinda/kickjs v10.1.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-B0mErNE1.mjs";import{t as defineAdapter}from"../../define-adapter-B4D_Rci1.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};
11
+ import{Logger}from"../../core/logger.mjs";import"../../core-B0JmdBvX.mjs";import{t as defineAdapter}from"../../define-adapter-BLKlUbSL.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 v10.0.0
2
+ * @forinda/kickjs v10.1.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,4 +8,4 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- export{u as FILTER_OPERATORS,t as buildQueryParams,n as getQueryParsingDefaults,r as parseFilters,i as parsePagination,a as parseQuery,o as parseSearchQuery,s as parseSort,c as resetQueryParsingDefaults,l as setQueryParsingDefaults}from"../../query-DUkboS0j.mjs";
11
+ export{u as FILTER_OPERATORS,t as buildQueryParams,n as getQueryParsingDefaults,r as parseFilters,i as parsePagination,a as parseQuery,o as parseSearchQuery,s as parseSort,c as resetQueryParsingDefaults,l as setQueryParsingDefaults}from"../../query-B_k9qn0R.mjs";
@@ -1,3 +1,3 @@
1
1
 
2
- import { i as buildRouteTable, n as _setExternalContributorSources, r as assertRouteUnique, t as BuildRoutesOptions } from "../router-builder-D_SkWTfE.mjs";
2
+ import { i as buildRouteTable, n as _setExternalContributorSources, r as assertRouteUnique, t as BuildRoutesOptions } from "../router-builder-zWuVIOmd.mjs";
3
3
  export { BuildRoutesOptions, _setExternalContributorSources, assertRouteUnique, buildRouteTable };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v10.0.0
2
+ * @forinda/kickjs v10.1.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,4 +8,4 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- export{t as _setExternalContributorSources,n as assertRouteUnique,r as buildRouteTable}from"../router-builder-DZsCjpeV.mjs";
11
+ export{t as _setExternalContributorSources,n as assertRouteUnique,r as buildRouteTable}from"../router-builder-JvMTp50k.mjs";
@@ -1,5 +1,5 @@
1
1
 
2
- import { p as HttpRuntime } from "../../adapter-CvEm8Gpp.mjs";
2
+ import { p as HttpRuntime } from "../../adapter-ClEh-Oos.mjs";
3
3
  import { IncomingMessage, ServerResponse } from "node:http";
4
4
  //#region src/http/runtimes/fastify.d.ts
5
5
  interface FastifyReplyLike {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v10.0.0
2
+ * @forinda/kickjs v10.1.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{HttpException}from"../../core/errors.mjs";import{n as applyHandlerResult,o as createRequestStore,p as unsupportedMediaTypeError,s as disposeRequestStore}from"../../reply-DElkObMy.mjs";import{d as requestStore,i as stringifyJson,r as publishMatchedRoute,t as RequestContext}from"../../context-Bd02q5Sm.mjs";import{validate}from"../middleware/validate.mjs";import{t as applyUploadConfig}from"../../upload-config-BiL1QXkT.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=()=>{};function settleOrFail(reply,returned){typeof returned?.then==`function`&&Promise.resolve(returned).then(void 0,()=>{!reply.sent&&!reply.raw.headersSent&&reply.code(500).send()})}const NO_FALLTHROUGH=Symbol(`kickjs.fastify.noFallthrough`);var FastifyReplyDriver=class{reply;constructor(reply){this.reply=reply}status(code){return this.reply.code(code),this}json(data){return this.reply.getHeader(`content-type`)||this.reply.type(`application/json; charset=utf-8`),this.reply.serializer(stringifyJson).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.headersSent?this.reply.raw.end(data):this.reply.send(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'.
11
+ import{HttpException}from"../../core/errors.mjs";import{n as applyHandlerResult,o as createRequestStore,p as unsupportedMediaTypeError,s as disposeRequestStore}from"../../reply-BDK9xt7Z.mjs";import{d as requestStore,i as stringifyJson,r as publishMatchedRoute,t as RequestContext}from"../../context-CtL9RxXp.mjs";import{validate}from"../middleware/validate.mjs";import{t as applyUploadConfig}from"../../upload-config-Dz2INCaa.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=()=>{};function settleOrFail(reply,returned){typeof returned?.then==`function`&&Promise.resolve(returned).then(void 0,()=>{!reply.sent&&!reply.raw.headersSent&&reply.code(500).send()})}const NO_FALLTHROUGH=Symbol(`kickjs.fastify.noFallthrough`);var FastifyReplyDriver=class{reply;constructor(reply){this.reply=reply}status(code){return this.reply.code(code),this}json(data){return this.reply.getHeader(`content-type`)||this.reply.type(`application/json; charset=utf-8`),this.reply.serializer(stringifyJson).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.headersSent?this.reply.raw.end(data):this.reply.send(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
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`],requestId=(Array.isArray(headerId)?headerId[0]:headerId)||raw.requestId,store=createRequestStore(requestId);raw.requestId=store.requestId,reply.raw.once(`close`,()=>disposeRequestStore(store)),await requestStore.run(store,async()=>{publishMatchedRoute(raw,entry);let ctx=new RequestContext(raw,reply,NOOP_NEXT,replyDriver(reply));if(entry.earlyContributorRunner&&(await entry.earlyContributorRunner(ctx),reply.sent))return;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||await 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)})),options.notFoundFallthrough===!1&&(app[NO_FALLTHROUGH]=!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];next&&!app[NO_FALLTHROUGH]?(reply.hijack(),next()):settleOrFail(reply,mw(request.raw,replyDriver(reply),NOOP_NEXT))})},setErrorHandler(app,mw){app.setErrorHandler((err,request,reply)=>{settleOrFail(reply,mw(err,request.raw,replyDriver(reply),NOOP_NEXT))})},capabilities:{render:!1,uploads:!0,connectMiddleware:!0,nativeBodyParsing:!0,http2:!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
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v10.0.0
2
+ * @forinda/kickjs v10.1.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -1,5 +1,5 @@
1
1
 
2
- import { p as HttpRuntime } from "../../adapter-CvEm8Gpp.mjs";
2
+ import { p as HttpRuntime } from "../../adapter-ClEh-Oos.mjs";
3
3
  import { IncomingMessage, ServerResponse } from "node:http";
4
4
  //#region src/http/runtimes/h3.d.ts
5
5
  interface H3AppLike {
@@ -1 +1 @@
1
- {"version":3,"file":"h3.d.mts","names":[],"sources":["../../../src/http/runtimes/h3.ts"],"mappings":";;;;UA+CU;EACR,GAAG,gBAAgB,cAAc,UAAU,OAAO;EAClD,IAAI,cAAc,UAAU,OAAO;EACnC,OAAO;EACP,MAAM,SAAS,UAAU,QAAQ;;UAEzB;EACR,KAAK;IAAY;MAAY;QAAS,KAAK;QAAiB,KAAK;;;;EACjE,KAAK;EACL;IAAW,SAAS;;;iBASL;;;;;;;EAOf;;;;;;wBAyYc,UAAU,UAAS,mBAAwB,YAAY;;;;;iBA+JtD;EACf,SAAS;EACT,UAAU;EACV;IAAmC,YAAY,wBAAwB;MAAM;;;qBAYlE,qBAAY;;YAEb,sBAAsB;;YAEtB,oBAAoB"}
1
+ {"version":3,"file":"h3.d.mts","names":[],"sources":["../../../src/http/runtimes/h3.ts"],"mappings":";;;;UA+CU;EACR,GAAG,gBAAgB,cAAc,UAAU,OAAO;EAClD,IAAI,cAAc,UAAU,OAAO;EACnC,OAAO;EACP,MAAM,SAAS,UAAU,QAAQ;;UAEzB;EACR,KAAK;IAAY;MAAY;QAAS,KAAK;QAAiB,KAAK;;;;EACjE,KAAK;EACL;IAAW,SAAS;;;iBASL;;;;;;;EAOf;;;;;;wBA8Yc,UAAU,UAAS,mBAAwB,YAAY;;;;;iBA+JtD;EACf,SAAS;EACT,UAAU;EACV;IAAmC,YAAY,wBAAwB;MAAM;;;qBAYlE,qBAAY;;YAEb,sBAAsB;;YAEtB,oBAAoB"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v10.0.0
2
+ * @forinda/kickjs v10.1.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,describeError}from"../../core/logger.mjs";import{i as WebResponseDriver,n as readThrough,r as WebRequestShim,t as compileWebRoute}from"../../handler-CyRoAqNt.mjs";import{createRequire}from"node:module";const log=createLogger(`H3Runtime`),peerRequire=createRequire(import.meta.url),NOTFOUND_MW=Symbol(`kickjs.h3web.notFoundMw`),ERROR_MW=Symbol(`kickjs.h3web.errorMw`),ASSEMBLED=Symbol(`kickjs.h3web.assembled`),NOOP_NEXT=()=>{},NEXT=Symbol(`kickjs.h3web.next`),NO_FALLTHROUGH=Symbol(`kickjs.h3web.noFallthrough`),TRUST_PROXY=Symbol(`kickjs.h3.trustProxy`),H3_HANDLED=Symbol.for(`h3.handled`);function unwrapH3Module(mod){let m=mod;return m&&m.__esModule&&m.default?{...m.default,...m}:m}function loadH3v2(injected){let resolved=unwrapH3Module(injected??peerRequire(`h3`));if(typeof resolved.H3!=`function`)throw Error("@forinda/kickjs: h3Runtime() requires h3 v2, but the installed h3 has no H3 class — that is the v1 line, which this version of KickJS no longer supports. Upgrade: `pnpm add h3@^2`.");return resolved}async function runConnectTerminal(mw,event,err){let shim=new WebRequestShim(event.req,event.url),carry=event.req.runtime?.node?.req??FETCH_CONNECT.get(event)?.req,req=carry?readThrough(shim,carry):shim,driver=new WebResponseDriver(event.req.signal);if(mw){let returned=err===void 0?mw(req,driver,NOOP_NEXT):mw(err,req,driver,NOOP_NEXT);if(typeof returned?.then==`function`)try{await returned}catch(handlerErr){driver.settled||(log.error(handlerErr,`h3: the ${err===void 0?`not-found`:`error`} handler failed`),driver.status(500).json({error:`Internal Server Error`}))}}return driver.settled||(err===void 0?driver.status(404).json({error:`Not Found`}):(log.error(err,`h3: error handler did not settle the response — ${describeError(err)}`),driver.status(500).json({error:`Internal Server Error`,...typeof process<`u`&&process.env?.NODE_ENV===`production`?{}:{message:describeError(err)}}))),driver.ready}const FETCH_CONNECT=/* @__PURE__ */ new WeakMap;function fetchConnectState(event){let state=FETCH_CONNECT.get(event);return state||(state={req:new WebRequestShim(event.req,event.url),headers:new Headers},FETCH_CONNECT.set(event,state)),state}function withConnectHeaders(event,response){let state=FETCH_CONNECT.get(event);if(!state)return response;let missing=[...state.headers].filter(([name])=>name===`set-cookie`||!response.headers.has(name));if(state.headers=new Headers,missing.length===0)return response;try{for(let[name,value]of missing)response.headers.append(name,value);return response}catch{let headers=new Headers(response.headers);for(let[name,value]of missing)headers.append(name,value);return new Response(response.body,{status:response.status,statusText:response.statusText,headers})}}var FetchConnectResponse=class extends WebResponseDriver{prepared;code=200;listeners=[];constructor(prepared,signal){super(signal),this.prepared=prepared}get statusCode(){return this.code}set statusCode(code){this.status(code)}status(code){return this.code=code,super.status(code)}writeHead(code,headers){return this.code=code,super.writeHead(code,headers)}setHeader(name,value){if(Array.isArray(value)){this.prepared.delete(name);for(let v of value)this.prepared.append(name,String(v))}else this.prepared.set(name,String(value));return this}getHeader(name){return this.prepared.get(name)??void 0}hasHeader(name){return this.prepared.has(name)}removeHeader(name){this.prepared.delete(name)}once(event,listener){return this.listeners.push([event,listener]),this}on(event,listener){return this.once(event,listener)}removeListener(event,listener){return this.listeners=this.listeners.filter(([e,l])=>e!==event||l!==listener),this}off(event,listener){return this.removeListener(event,listener)}done(){for(let event of[`finish`,`close`]){for(let[e,listener]of this.listeners)e===event&&listener();this.listeners=this.listeners.filter(([e])=>e!==event)}}};function runConnectOnFetch(mw,event,next){let state=fetchConnectState(event),res=new FetchConnectResponse(state.headers,event.req.signal);return new Promise((resolve,reject)=>{let respond=response=>{res.done(),resolve(withConnectHeaders(event,response))},fail=err=>{res.done(),reject(err)};res.ready.then(respond);let connectNext=err=>{if(err)return fail(err);if(res.settled)return;let id=state.req.requestId;if(id&&event.req.headers.get(`x-request-id`)!==id)try{event.req.headers.set(`x-request-id`,id)}catch{}let downstream;try{downstream=next()}catch(e){return fail(e)}Promise.resolve(downstream).then(value=>{if(value instanceof Response)return respond(value);res.done(),resolve(value)},fail)};try{mw(state.req,res,connectNext)}catch(err){fail(err)}})}function assembleCatchAll(app){let state=app;state[ASSEMBLED]||(state[ASSEMBLED]=!0,app.all(`/**`,event=>{let node=event.req.runtime?.node,next=node?.req?.[NEXT];if(node&&next&&!state[NO_FALLTHROUGH])return new Promise(resolve=>{let done=()=>resolve(H3_HANDLED);node.res.once(`finish`,done),node.res.once(`close`,done),next()});let mw=state[NOTFOUND_MW];return runConnectTerminal(mw,event)}))}function proxyTrust(option){if(option!==void 0&&option!==!1){if(option===!0)return()=>!0;if(typeof option==`number`)return(_address,hop)=>hop<option;if(typeof option==`function`)return option;log.warn(`h3: trustProxy as an address or subnet list is not supported; client IPs use the socket address. Use a hop count or a function instead.`)}}function forwardedClientIp(req,trust){let socket=req.socket?.remoteAddress;if(!socket)return;let header=req.headers[`x-forwarded-for`],chain=[socket,...(Array.isArray(header)?header.join(`,`):header??``).split(`,`).map(s=>s.trim()).filter(Boolean).toReversed()];for(let hop=0;hop<chain.length-1;hop++)if(!trust(chain[hop],hop))return chain[hop];return chain[chain.length-1]}function stripMountPrefix(path,mw){let prefix=path.endsWith(`/`)?path.slice(0,-1):path;return prefix===``?mw:((req,res,next)=>{let url=req.url??`/`,rest=url.startsWith(prefix)?url.slice(prefix.length):null;return rest===null||rest!==``&&rest[0]!==`/`&&rest[0]!==`?`?mw(req,res,next):(req.originalUrl??=url,req.url=rest.startsWith(`/`)?rest:`/${rest}`,mw(req,res,err=>{req.url=url,next(err)}))})}function h3Runtime(options={}){let{H3,toNodeHandler,fromNodeHandler}=loadH3v2(options.h3);return{name:`h3`,createApp(options={}){let holder={},app=new H3({onError:(error,event)=>{let mw=holder.app?.[ERROR_MW];return runConnectTerminal(mw,event,error).then(response=>withConnectHeaders(event,response))}});return holder.app=app,app[NO_FALLTHROUGH]=options.notFoundFallthrough===!1,app[TRUST_PROXY]=proxyTrust(options.trustProxy),app},nodeHandler(app){assembleCatchAll(app);let listener=toNodeHandler(app),trust=app[TRUST_PROXY];return(req,res,next)=>{if(next&&(req[NEXT]=next),trust&&!req.ip){let ip=forwardedClientIp(req,trust);ip&&(req.ip=ip)}res.setMaxListeners(0);try{let result=listener(req,res);result&&typeof result.then==`function`&&result.catch(err=>next?.(err))}catch(err){next?.(err)}}},mountRoutes(app,table){for(let{mountPath,routes}of table)for(let entry of routes){let url=joinPath(mountPath,entry.path),run=compileWebRoute(entry,{onError:async(err,req,driver)=>{let mw=app[ERROR_MW];if(mw)try{await mw(err,req,driver,NOOP_NEXT)}catch(handlerErr){driver.settled||(log.error(handlerErr,`h3: the error handler failed`),driver.status(500).json({error:`Internal Server Error`}))}}});app.on(entry.method,url,event=>run({request:event.req,url:event.url,params:event.context.params??{},carry:event.req.runtime?.node?.req??FETCH_CONNECT.get(event)?.req}))}},useConnect(app,rawMw,opts){let path=opts?.path;if(Array.isArray(path)){for(let member of path)this.useConnect(app,rawMw,{path:member});return}let mw=typeof path==`string`?stripMountPrefix(path,rawMw):rawMw,viaNode=fromNodeHandler(mw),handler=(event,next)=>event.req.runtime?.node?.res?viaNode(event):runConnectOnFetch(mw,event,next);path instanceof RegExp?app.use((event,next)=>path.test(event.url.pathname)?handler(event,next):next()):typeof path==`string`?app.use(joinPath(path,`/**`),handler):app.use(handler)},serveStatic(app,prefix,dir){let serveStatic=peerRequire(`serve-static`);this.useConnect(app,serveStatic(dir),{path:prefix})},setNotFound(app,mw){app[NOTFOUND_MW]=mw,assembleCatchAll(app)},setErrorHandler(app,mw){app[ERROR_MW]=mw},capabilities:{render:!1,uploads:!0,connectMiddleware:!0,nativeBodyParsing:!0,http2:!0}}}function joinPath(mountPath,path){let a=mountPath.endsWith(`/`)?mountPath.slice(0,-1):mountPath;return path===`/`||path===``?a===``?`/`:a:`${a}${path.startsWith(`/`)?path:`/${path}`}`}const h3WebRuntime=h3Runtime;export{h3Runtime,h3WebRuntime};
11
+ import{createLogger,describeError}from"../../core/logger.mjs";import{i as WebResponseDriver,n as readThrough,r as WebRequestShim,t as compileWebRoute}from"../../handler-zKfIgNo4.mjs";import{createRequire}from"node:module";const log=createLogger(`H3Runtime`),peerRequire=createRequire(import.meta.url),NOTFOUND_MW=Symbol(`kickjs.h3web.notFoundMw`),ERROR_MW=Symbol(`kickjs.h3web.errorMw`),ASSEMBLED=Symbol(`kickjs.h3web.assembled`),NOOP_NEXT=()=>{},NEXT=Symbol(`kickjs.h3web.next`),NO_FALLTHROUGH=Symbol(`kickjs.h3web.noFallthrough`),TRUST_PROXY=Symbol(`kickjs.h3.trustProxy`),H3_HANDLED=Symbol.for(`h3.handled`);function unwrapH3Module(mod){let m=mod;return m&&m.__esModule&&m.default?{...m.default,...m}:m}function loadH3v2(injected){let resolved=unwrapH3Module(injected??peerRequire(`h3`));if(typeof resolved.H3!=`function`)throw Error("@forinda/kickjs: h3Runtime() requires h3 v2, but the installed h3 has no H3 class — that is the v1 line, which this version of KickJS no longer supports. Upgrade: `pnpm add h3@^2`.");return resolved}async function runConnectTerminal(mw,event,err){let shim=new WebRequestShim(event.req,event.url),carry=event.req.runtime?.node?.req??FETCH_CONNECT.get(event)?.req,req=carry?readThrough(shim,carry):shim,driver=new WebResponseDriver(event.req.signal);if(mw){let returned=err===void 0?mw(req,driver,NOOP_NEXT):mw(err,req,driver,NOOP_NEXT);if(typeof returned?.then==`function`)try{await returned}catch(handlerErr){driver.settled||(log.error(handlerErr,`h3: the ${err===void 0?`not-found`:`error`} handler failed`),driver.status(500).json({error:`Internal Server Error`}))}}return driver.settled||(err===void 0?driver.status(404).json({error:`Not Found`}):(log.error(err,`h3: error handler did not settle the response — ${describeError(err)}`),driver.status(500).json({error:`Internal Server Error`,...typeof process<`u`&&process.env?.NODE_ENV===`production`?{}:{message:describeError(err)}}))),driver.ready}const FETCH_CONNECT=/* @__PURE__ */ new WeakMap;function fetchConnectState(event){let state=FETCH_CONNECT.get(event);return state||(state={req:new WebRequestShim(event.req,event.url),headers:new Headers},FETCH_CONNECT.set(event,state)),state}function withConnectHeaders(event,response){let state=FETCH_CONNECT.get(event);if(!state)return response;let missing=[...state.headers].filter(([name])=>name===`set-cookie`||!response.headers.has(name));if(state.headers=new Headers,missing.length===0)return response;try{for(let[name,value]of missing)response.headers.append(name,value);return response}catch{let headers=new Headers(response.headers);for(let[name,value]of missing)headers.append(name,value);return new Response(response.body,{status:response.status,statusText:response.statusText,headers})}}var FetchConnectResponse=class extends WebResponseDriver{prepared;code=200;listeners=[];constructor(prepared,signal){super(signal),this.prepared=prepared}get statusCode(){return this.code}set statusCode(code){this.status(code)}status(code){return this.code=code,super.status(code)}writeHead(code,headers){return this.code=code,super.writeHead(code,headers)}setHeader(name,value){if(Array.isArray(value)){this.prepared.delete(name);for(let v of value)this.prepared.append(name,String(v))}else this.prepared.set(name,String(value));return this}getHeader(name){return this.prepared.get(name)??void 0}hasHeader(name){return this.prepared.has(name)}removeHeader(name){this.prepared.delete(name)}once(event,listener){return this.listeners.push([event,listener]),this}on(event,listener){return this.once(event,listener)}removeListener(event,listener){return this.listeners=this.listeners.filter(([e,l])=>e!==event||l!==listener),this}off(event,listener){return this.removeListener(event,listener)}done(){for(let event of[`finish`,`close`]){for(let[e,listener]of this.listeners)e===event&&listener();this.listeners=this.listeners.filter(([e])=>e!==event)}}};function runConnectOnFetch(mw,event,next){let state=fetchConnectState(event),res=new FetchConnectResponse(state.headers,event.req.signal);return new Promise((resolve,reject)=>{let respond=response=>{res.done(),resolve(withConnectHeaders(event,response))},fail=err=>{res.done(),reject(err)};res.ready.then(respond);let connectNext=err=>{if(err)return fail(err);if(res.settled)return;let id=state.req.requestId;if(id&&event.req.headers.get(`x-request-id`)!==id)try{event.req.headers.set(`x-request-id`,id)}catch{}let downstream;try{downstream=next()}catch(e){return fail(e)}Promise.resolve(downstream).then(value=>{if(value instanceof Response)return respond(value);res.done(),resolve(value)},fail)};try{mw(state.req,res,connectNext)}catch(err){fail(err)}})}function assembleCatchAll(app){let state=app;state[ASSEMBLED]||(state[ASSEMBLED]=!0,app.all(`/**`,event=>{let node=event.req.runtime?.node,next=node?.req?.[NEXT];if(node&&next&&!state[NO_FALLTHROUGH])return new Promise(resolve=>{let done=()=>resolve(H3_HANDLED);node.res.once(`finish`,done),node.res.once(`close`,done),next()});let mw=state[NOTFOUND_MW];return runConnectTerminal(mw,event)}))}function proxyTrust(option){if(option!==void 0&&option!==!1){if(option===!0)return()=>!0;if(typeof option==`number`)return(_address,hop)=>hop<option;if(typeof option==`function`)return option;log.warn(`h3: trustProxy as an address or subnet list is not supported; client IPs use the socket address. Use a hop count or a function instead.`)}}function forwardedClientIp(req,trust){let socket=req.socket?.remoteAddress;if(!socket)return;let header=req.headers[`x-forwarded-for`],chain=[socket,...(Array.isArray(header)?header.join(`,`):header??``).split(`,`).map(s=>s.trim()).filter(Boolean).toReversed()];for(let hop=0;hop<chain.length-1;hop++)if(!trust(chain[hop],hop))return chain[hop];return chain[chain.length-1]}function stripMountPrefix(path,mw){let prefix=path.endsWith(`/`)?path.slice(0,-1):path;return prefix===``?mw:((req,res,next)=>{let url=req.url??`/`,rest=url.startsWith(prefix)?url.slice(prefix.length):null;return rest===null||rest!==``&&rest[0]!==`/`&&rest[0]!==`?`?mw(req,res,next):(req.originalUrl??=url,req.url=rest.startsWith(`/`)?rest:`/${rest}`,mw(req,res,err=>{req.url=url,next(err)}))})}function h3Runtime(options={}){let{H3,toNodeHandler,fromNodeHandler}=loadH3v2(options.h3);return{name:`h3`,createApp(options={}){let holder={},app=new H3({onError:(error,event)=>{let mw=holder.app?.[ERROR_MW];return runConnectTerminal(mw,event,error).then(response=>withConnectHeaders(event,response))}});return holder.app=app,app[NO_FALLTHROUGH]=options.notFoundFallthrough===!1,app[TRUST_PROXY]=proxyTrust(options.trustProxy),app},nodeHandler(app){assembleCatchAll(app);let listener=toNodeHandler(app),trust=app[TRUST_PROXY];return(req,res,next)=>{if(next&&(req[NEXT]=next),trust&&!req.ip){let ip=forwardedClientIp(req,trust);ip&&(req.ip=ip)}res.setMaxListeners(0);try{let result=listener(req,res);result&&typeof result.then==`function`&&result.catch(err=>next?.(err))}catch(err){next?.(err)}}},mountRoutes(app,table){for(let{mountPath,routes}of table)for(let entry of routes){let url=joinPath(mountPath,entry.path),run=compileWebRoute(entry,{onError:async(err,req,driver)=>{let mw=app[ERROR_MW];if(mw)try{await mw(err,req,driver,NOOP_NEXT)}catch(handlerErr){driver.settled||(log.error(handlerErr,`h3: the error handler failed`),driver.status(500).json({error:`Internal Server Error`}))}}});app.on(entry.method,url,event=>run({request:event.req,url:event.url,params:event.context.params??{},carry:event.req.runtime?.node?.req??FETCH_CONNECT.get(event)?.req}))}},useConnect(app,rawMw,opts){let path=opts?.path;if(Array.isArray(path)){for(let member of path)this.useConnect(app,rawMw,{path:member});return}let mw=typeof path==`string`?stripMountPrefix(path,rawMw):rawMw,viaNode=fromNodeHandler(mw),handler=(event,next)=>event.req.runtime?.node?.res?viaNode(event):runConnectOnFetch(mw,event,next);path instanceof RegExp?app.use((event,next)=>path.test(event.url.pathname)?handler(event,next):next()):typeof path==`string`?app.use(joinPath(path,`/**`),handler):app.use(handler)},serveStatic(app,prefix,dir){let serveStatic=peerRequire(`serve-static`);this.useConnect(app,serveStatic(dir),{path:prefix})},setNotFound(app,mw){app[NOTFOUND_MW]=mw,assembleCatchAll(app)},setErrorHandler(app,mw){app[ERROR_MW]=mw},capabilities:{render:!1,uploads:!0,connectMiddleware:!0,nativeBodyParsing:!0,http2:!0}}}function joinPath(mountPath,path){let a=mountPath.endsWith(`/`)?mountPath.slice(0,-1):mountPath;return path===`/`||path===``?a===``?`/`:a:`${a}${path.startsWith(`/`)?path:`/${path}`}`}const h3WebRuntime=h3Runtime;export{h3Runtime,h3WebRuntime};
12
12
  //# sourceMappingURL=h3.mjs.map