@forinda/kickjs 9.1.0 → 9.2.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 (138) hide show
  1. package/dist/{adapter-DhjoIYVI.d.mts → adapter-DjfRMob8.d.mts} +3 -13
  2. package/dist/{adapter-DhjoIYVI.d.mts.map → adapter-DjfRMob8.d.mts.map} +1 -1
  3. package/dist/application-BSJpaiSY.mjs +12 -0
  4. package/dist/{application-nDj-76EX.mjs.map → application-BSJpaiSY.mjs.map} +1 -1
  5. package/dist/{application-DqD58rhF.d.mts → application-CGxZCYl1.d.mts} +8 -8
  6. package/dist/{application-DqD58rhF.d.mts.map → application-CGxZCYl1.d.mts.map} +1 -1
  7. package/dist/{bootstrap-BqcT1h9H.mjs → bootstrap-CbTF6xvv.mjs} +3 -3
  8. package/dist/{bootstrap-BqcT1h9H.mjs.map → bootstrap-CbTF6xvv.mjs.map} +1 -1
  9. package/dist/{client-ip-DMKc-Trg.mjs → client-ip-pcINkkPr.mjs} +2 -2
  10. package/dist/{client-ip-DMKc-Trg.mjs.map → client-ip-pcINkkPr.mjs.map} +1 -1
  11. package/dist/config/index.d.mts +1 -1
  12. package/dist/config/index.mjs +2 -2
  13. package/dist/{config-DrgIbebd.mjs → config-DmGiAIuH.mjs} +3 -3
  14. package/dist/{config-DrgIbebd.mjs.map → config-DmGiAIuH.mjs.map} +1 -1
  15. package/dist/{container-ZnfelY_a.mjs → container-CAPN7pBG.mjs} +3 -3
  16. package/dist/{container-ZnfelY_a.mjs.map → container-CAPN7pBG.mjs.map} +1 -1
  17. package/dist/{context-errors-Cgtrrqfs.mjs → context-errors-DYJjhalQ.mjs} +2 -2
  18. package/dist/{context-errors-Cgtrrqfs.mjs.map → context-errors-DYJjhalQ.mjs.map} +1 -1
  19. package/dist/context-gpvcMlG5.mjs +12 -0
  20. package/dist/context-gpvcMlG5.mjs.map +1 -0
  21. package/dist/{contributor-runner-BSpshfPN.mjs → contributor-runner-vk9XlOED.mjs} +3 -3
  22. package/dist/{contributor-runner-BSpshfPN.mjs.map → contributor-runner-vk9XlOED.mjs.map} +1 -1
  23. package/dist/core/adapter.d.mts +1 -1
  24. package/dist/core/adapter.mjs +1 -1
  25. package/dist/core/app-module.mjs +1 -1
  26. package/dist/core/cache.mjs +1 -1
  27. package/dist/core/container.mjs +2 -2
  28. package/dist/core/cron.mjs +2 -2
  29. package/dist/core/decorators.d.mts +1 -1
  30. package/dist/core/decorators.mjs +2 -2
  31. package/dist/core/decorators.mjs.map +1 -1
  32. package/dist/core/errors.mjs +1 -1
  33. package/dist/core/interfaces.mjs +1 -1
  34. package/dist/core/logger.mjs +1 -1
  35. package/dist/core/path.mjs +1 -1
  36. package/dist/core/plugin.d.mts +1 -1
  37. package/dist/core/plugin.mjs +1 -1
  38. package/dist/core/reactivity.mjs +1 -1
  39. package/dist/{core-_WNyghl_.mjs → core-DKrKVLfj.mjs} +3 -3
  40. package/dist/{core-_WNyghl_.mjs.map → core-DKrKVLfj.mjs.map} +1 -1
  41. package/dist/{csrf-COFjRrUz.d.mts → csrf-C73UkRgc.d.mts} +2 -2
  42. package/dist/{csrf-COFjRrUz.d.mts.map → csrf-C73UkRgc.d.mts.map} +1 -1
  43. package/dist/{csrf-BcxqzCZW.mjs → csrf-CxJItZVa.mjs} +3 -3
  44. package/dist/{csrf-BcxqzCZW.mjs.map → csrf-CxJItZVa.mjs.map} +1 -1
  45. package/dist/{decorate-BBwu7--P.mjs → decorate-DfH1NIqV.mjs} +1 -1
  46. package/dist/{decorators-DOcWsXci.d.mts → decorators-BxNSz9Qt.d.mts} +4 -4
  47. package/dist/{decorators-DOcWsXci.d.mts.map → decorators-BxNSz9Qt.d.mts.map} +1 -1
  48. package/dist/{define-adapter-CajLeMzM.mjs → define-adapter-C6lFhCL-.mjs} +2 -2
  49. package/dist/{define-adapter-CajLeMzM.mjs.map → define-adapter-C6lFhCL-.mjs.map} +1 -1
  50. package/dist/{disposables-DIXt8ze-.mjs → disposables-NlW6Mibk.mjs} +2 -2
  51. package/dist/{disposables-DIXt8ze-.mjs.map → disposables-NlW6Mibk.mjs.map} +1 -1
  52. package/dist/{env-DtJ-6gyL.mjs → env-__fzPsHD.mjs} +3 -3
  53. package/dist/{env-DtJ-6gyL.mjs.map → env-__fzPsHD.mjs.map} +1 -1
  54. package/dist/{error-handler-DiueQHes.d.mts → error-handler-itG65lj0.d.mts} +2 -2
  55. package/dist/{error-handler-DiueQHes.d.mts.map → error-handler-itG65lj0.d.mts.map} +1 -1
  56. package/dist/handler-DnZbcleS.mjs +13 -0
  57. package/dist/handler-DnZbcleS.mjs.map +1 -0
  58. package/dist/http/application.d.mts +1 -1
  59. package/dist/http/application.mjs +2 -2
  60. package/dist/http/bootstrap.d.mts +1 -1
  61. package/dist/http/bootstrap.mjs +2 -2
  62. package/dist/http/context.d.mts +1 -1
  63. package/dist/http/context.mjs +2 -2
  64. package/dist/http/middleware/cors.mjs +1 -1
  65. package/dist/http/middleware/csrf.d.mts +1 -1
  66. package/dist/http/middleware/csrf.mjs +2 -2
  67. package/dist/http/middleware/error-handler.d.mts +1 -1
  68. package/dist/http/middleware/error-handler.mjs +2 -2
  69. package/dist/http/middleware/helmet.mjs +1 -1
  70. package/dist/http/middleware/rate-limit.mjs +2 -2
  71. package/dist/http/middleware/request-id.mjs +1 -1
  72. package/dist/http/middleware/request-logger.mjs +2 -2
  73. package/dist/http/middleware/session.mjs +2 -2
  74. package/dist/http/middleware/spa.d.mts +1 -1
  75. package/dist/http/middleware/spa.mjs +2 -2
  76. package/dist/http/middleware/upload.d.mts +1 -1
  77. package/dist/http/middleware/upload.mjs +2 -2
  78. package/dist/http/middleware/validate.mjs +1 -1
  79. package/dist/http/middleware/views.d.mts +1 -1
  80. package/dist/http/middleware/views.mjs +2 -2
  81. package/dist/http/query/index.mjs +2 -2
  82. package/dist/http/router-builder.d.mts +1 -1
  83. package/dist/http/router-builder.mjs +2 -2
  84. package/dist/http/runtimes/fastify.d.mts +3 -1
  85. package/dist/http/runtimes/fastify.d.mts.map +1 -1
  86. package/dist/http/runtimes/fastify.mjs +2 -2
  87. package/dist/http/runtimes/fastify.mjs.map +1 -1
  88. package/dist/http/runtimes/h3-web.d.mts +1 -1
  89. package/dist/http/runtimes/h3-web.mjs +2 -2
  90. package/dist/http/runtimes/h3.d.mts +1 -1
  91. package/dist/http/runtimes/h3.mjs +2 -2
  92. package/dist/http/runtimes/h3.mjs.map +1 -1
  93. package/dist/{index-CDn-p3tq.d.mts → index-CNHSbZTE.d.mts} +4 -4
  94. package/dist/{index-CDn-p3tq.d.mts.map → index-CNHSbZTE.d.mts.map} +1 -1
  95. package/dist/index.d.mts +9 -9
  96. package/dist/index.mjs +2 -2
  97. package/dist/index.mjs.map +1 -1
  98. package/dist/{jobs-dx-MTUUb.mjs → jobs-BmpeFvi0.mjs} +3 -3
  99. package/dist/{jobs-dx-MTUUb.mjs.map → jobs-BmpeFvi0.mjs.map} +1 -1
  100. package/dist/{kv-stores-CetFF9N_.d.mts → kv-stores-BZTe23Tw.d.mts} +3 -3
  101. package/dist/{kv-stores-CetFF9N_.d.mts.map → kv-stores-BZTe23Tw.d.mts.map} +1 -1
  102. package/dist/{kv-stores-Ba5_rcz-.mjs → kv-stores-DJwxntyw.mjs} +3 -3
  103. package/dist/{kv-stores-Ba5_rcz-.mjs.map → kv-stores-DJwxntyw.mjs.map} +1 -1
  104. package/dist/{metadata-CW4I35nN.mjs → metadata-CzzRUnAx.mjs} +2 -2
  105. package/dist/{metadata-CW4I35nN.mjs.map → metadata-CzzRUnAx.mjs.map} +1 -1
  106. package/dist/{module-registry-kqozOSo0.mjs → module-registry-BnmRNy-3.mjs} +2 -2
  107. package/dist/{module-registry-kqozOSo0.mjs.map → module-registry-BnmRNy-3.mjs.map} +1 -1
  108. package/dist/{observers-CC3sO1BM.mjs → observers-COlQfinD.mjs} +2 -2
  109. package/dist/{observers-CC3sO1BM.mjs.map → observers-COlQfinD.mjs.map} +1 -1
  110. package/dist/{query-DPlkWOw6.mjs → query-B5DTq_4W.mjs} +2 -2
  111. package/dist/{query-DPlkWOw6.mjs.map → query-B5DTq_4W.mjs.map} +1 -1
  112. package/dist/reply-CbTYIj_e.mjs +12 -0
  113. package/dist/reply-CbTYIj_e.mjs.map +1 -0
  114. package/dist/{respond-DjxNwGnN.mjs → respond-wbMSqHYo.mjs} +2 -2
  115. package/dist/{respond-DjxNwGnN.mjs.map → respond-wbMSqHYo.mjs.map} +1 -1
  116. package/dist/{route-flag-sIYXay3q.mjs → route-flag-Bb7n9Mnp.mjs} +3 -3
  117. package/dist/{route-flag-sIYXay3q.mjs.map → route-flag-Bb7n9Mnp.mjs.map} +1 -1
  118. package/dist/{route-policy-B6_toGJW.mjs → route-policy-C_Wsc-b1.mjs} +2 -2
  119. package/dist/{route-policy-B6_toGJW.mjs.map → route-policy-C_Wsc-b1.mjs.map} +1 -1
  120. package/dist/{router-builder-BSBSYgYW.d.mts → router-builder-CDkW88KQ.d.mts} +2 -2
  121. package/dist/{router-builder-BSBSYgYW.d.mts.map → router-builder-CDkW88KQ.d.mts.map} +1 -1
  122. package/dist/{router-builder-CzVjPs5y.mjs → router-builder-DawnRzlp.mjs} +3 -3
  123. package/dist/{router-builder-CzVjPs5y.mjs.map → router-builder-DawnRzlp.mjs.map} +1 -1
  124. package/dist/{upload-DjhnyHHm.d.mts → upload-Cy7qQy7M.d.mts} +3 -3
  125. package/dist/{upload-DjhnyHHm.d.mts.map → upload-Cy7qQy7M.d.mts.map} +1 -1
  126. package/dist/{upload-config-d0pisVsK.mjs → upload-config-D01Ty40f.mjs} +2 -2
  127. package/dist/{upload-config-d0pisVsK.mjs.map → upload-config-D01Ty40f.mjs.map} +1 -1
  128. package/dist/web.d.mts +3 -3
  129. package/dist/web.mjs +2 -2
  130. package/dist/web.mjs.map +1 -1
  131. package/package.json +2 -2
  132. package/dist/application-nDj-76EX.mjs +0 -12
  133. package/dist/context-BGPof8bs.mjs +0 -12
  134. package/dist/context-BGPof8bs.mjs.map +0 -1
  135. package/dist/handler-2OebsoPr.mjs +0 -13
  136. package/dist/handler-2OebsoPr.mjs.map +0 -1
  137. package/dist/runtime-CDz5ayBt.mjs +0 -12
  138. package/dist/runtime-CDz5ayBt.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v9.1.0
2
+ * @forinda/kickjs v9.2.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{l as __require}from"./env-DtJ-6gyL.mjs";import{METADATA}from"./core/interfaces.mjs";import{i as createToken,p as _setAssetResolver}from"./container-ZnfelY_a.mjs";import"./core/logger.mjs";import{c as pushClassMeta,l as pushMethodMeta}from"./metadata-CW4I35nN.mjs";import"./core/decorators.mjs";import"./core/cron.mjs";import"./jobs-dx-MTUUb.mjs";import"./core/cache.mjs";import{n as assertFlagTest}from"./route-flag-sIYXay3q.mjs";import"./contributor-runner-BSpshfPN.mjs";import"reflect-metadata";import{existsSync,readFileSync,readdirSync}from"node:fs";import{isAbsolute,join,resolve,sep}from"node:path";function defineContextDecoratorImpl(spec){if(typeof spec!=`object`||!spec)throw TypeError(`defineContextDecorator: spec must be an object literal. See architecture.md §20.4 for the spec shape.`);if(typeof spec.key!=`string`||spec.key.length===0){let got=typeof spec.key==`string`?`""`:typeof spec.key;throw TypeError(`defineContextDecorator: spec.key must be a non-empty string (got ${got}). Pick a name that matches a key declared on \`ContextMeta\` (or one of the framework keys).`)}if(typeof spec.resolve!=`function`)throw TypeError(`defineContextDecorator(${spec.key}): spec.resolve is required and must be a function (got ${typeof spec.resolve}). Each contributor needs a resolver that produces the value to write into ctx.set(${JSON.stringify(spec.key)}, …).`);if(spec.onError!==void 0&&typeof spec.onError!=`function`)throw TypeError(`defineContextDecorator(${spec.key}): spec.onError must be a function when provided (got ${typeof spec.onError}).`);if(spec.dependsOn!==void 0&&!Array.isArray(spec.dependsOn))throw TypeError(`defineContextDecorator(${spec.key}): spec.dependsOn must be an array when provided (got ${typeof spec.dependsOn}).`);if(spec.requiredParams!==void 0&&!Array.isArray(spec.requiredParams))throw TypeError(`defineContextDecorator(${spec.key}): spec.requiredParams must be an array when provided (got ${typeof spec.requiredParams}).`);if(Array.isArray(spec.requiredParams)&&spec.requiredParams.some(p=>typeof p!=`string`||p.length===0))throw TypeError(`defineContextDecorator(${spec.key}): spec.requiredParams entries must be non-empty strings.`);if(Array.isArray(spec.dependsOn)&&spec.dependsOn.some(dep=>typeof dep!=`string`||dep.length===0))throw TypeError(`defineContextDecorator(${spec.key}): spec.dependsOn entries must be non-empty strings.`);let definedAt=Error().stack,defaults=Object.freeze({...spec.paramDefaults??{}}),sharedDeps=Object.freeze({...spec.deps??{}}),sharedDependsOn=Object.freeze([...spec.dependsOn??[]]),sharedOptional=spec.optional??!1,buildRegistration=params=>{let frozenParams=Object.freeze({...params}),onError=spec.onError?(err,ctx)=>spec.onError(err,ctx,frozenParams):void 0;return spec.skipWhen!==void 0&&assertFlagTest(spec.skipWhen,`contributor '${spec.key}' skipWhen`),spec.onlyWhen!==void 0&&assertFlagTest(spec.onlyWhen,`contributor '${spec.key}' onlyWhen`),Object.freeze({key:spec.key,deps:sharedDeps,dependsOn:sharedDependsOn,optional:sharedOptional,skipWhen:spec.skipWhen,onlyWhen:spec.onlyWhen,beforeValidation:spec.beforeValidation===!0,onError,resolve:(ctx,deps)=>spec.resolve(ctx,deps,frozenParams),definedAt})},defaultRegistration=buildRegistration({...defaults}),isDecoratorCall=args=>args.length===0?!1:args.length>=2||typeof args[0]==`function`,applyAsDecorator=(registration,target,propertyKey)=>{if(propertyKey===void 0)pushClassMeta(METADATA.CLASS_CONTRIBUTORS,target,registration);else{let constructor=target.constructor;pushMethodMeta(METADATA.METHOD_CONTRIBUTORS,constructor,String(propertyKey),registration)}},isPlainObject=value=>{let proto=Object.getPrototypeOf(value);return proto===Object.prototype||proto===null},mergeParams=override=>{if(override===void 0)return{...defaults};if(typeof override!=`object`||!override||Array.isArray(override)||!isPlainObject(override)){let got=override===null?`null`:Array.isArray(override)?`array`:typeof override==`object`?`instance of ${override.constructor?.name??`unknown class`}`:typeof override;throw TypeError(`defineContextDecorator(${spec.key}): factory call requires a plain object literal, got ${got}. Class instances and built-ins (Map, Date, etc) silently spread to empty objects — call the decorator with a spread object literal instead, e.g. \`MyDecorator({ ...someInstance })\`.`)}return{...defaults,...override}},sharedRequiredParams=Object.freeze([...spec.requiredParams??[]]),assertRequiredParams=(params,usage)=>{if(sharedRequiredParams.length===0)return;let missing=sharedRequiredParams.filter(name=>params[name]===void 0);if(missing.length!==0)throw TypeError(`defineContextDecorator(${spec.key}): ${usage} is missing required param(s) ${missing.map(m=>`'${m}'`).join(`, `)}. Supply them at the call site — e.g. \`@${spec.key}({ ${missing[0]}: … })\` — or add a default in \`paramDefaults\` if one is genuinely correct for every route.`)};function decoratorOrFactory(...args){if(isDecoratorCall(args)){let[target,propertyKey]=args;assertRequiredParams({...defaults},"bare `@decorator` usage"),applyAsDecorator(defaultRegistration,target,propertyKey);return}let params=mergeParams(args[0]);assertRequiredParams(params,`factory call`);let registration=buildRegistration(params);return(target,propertyKey)=>{applyAsDecorator(registration,target,propertyKey)}}Object.defineProperty(decoratorOrFactory,"name",{value:`ContextDecorator(${spec.key})`,writable:!1,enumerable:!1,configurable:!0});let decorator=Object.assign(decoratorOrFactory,{with:params=>{let merged=mergeParams(params);return assertRequiredParams(merged,"`.with()`"),{registration:buildRegistration(merged)}}});return Object.defineProperty(decorator,"registration",{get(){return assertRequiredParams({...defaults},"`.registration`"),defaultRegistration},enumerable:!0,configurable:!1}),Object.freeze(decorator)}const defineContextDecorator=Object.freeze(Object.assign(defineContextDecoratorImpl,{withParams:()=>spec=>defineContextDecoratorImpl(spec)}));function groupAssetKeys(namespace,paths,opts={}){let strategy=opts.strategy??`auto`;if(strategy===`with-extension`)return{pairs:paths.map(rel=>({rel,key:`${namespace}/${rel}`})),collisionGroupsResolved:0};if(strategy===`strip`)return{pairs:paths.map(rel=>({rel,key:`${namespace}/${stripExt(rel)}`})),collisionGroupsResolved:0};let groups=new Map;for(let rel of paths){let strippedKey=`${namespace}/${stripExt(rel)}`,arr=groups.get(strippedKey);arr?arr.push(rel):groups.set(strippedKey,[rel])}let collisionGroupsResolved=0,keyByRel=new Map;for(let[strippedKey,rels]of groups)if(rels.length===1)keyByRel.set(rels[0],strippedKey);else{collisionGroupsResolved+=1;for(let rel of rels)keyByRel.set(rel,`${namespace}/${rel}`)}return{pairs:paths.map(rel=>({rel,key:keyByRel.get(rel)??`${namespace}/${rel}`})),collisionGroupsResolved}}function stripExt(path){let lastSlash=path.lastIndexOf(`/`),filename=lastSlash>=0?path.slice(lastSlash+1):path,lastDot=filename.lastIndexOf(`.`);return lastDot<=0?path:(lastSlash>=0?path.slice(0,lastSlash+1):``)+filename.slice(0,lastDot)}const ASSET_MANIFEST_VERSION=1,ASSETS=createToken(`kick/assets/Map`);var UnknownAssetError=class extends Error{namespace;key;constructor(namespace,key){super(`Unknown asset '${namespace}/${key}'. Make sure the namespace exists in kick.config.ts assetMap and that the file is present in the source directory.`),this.name=`UnknownAssetError`,this.namespace=namespace,this.key=key}};let manifestCache;function toPosixPath(p){return p.replaceAll(`\\`,`/`)}function resolveAsset(namespace,key){let resolved=loadManifest();if(!resolved)throw new UnknownAssetError(namespace,key);let entry=resolved.manifest.entries[`${namespace}/${key}`];if(!entry)throw new UnknownAssetError(namespace,key);return toPosixPath(isAbsolute(entry)?entry:resolve(resolved.root,entry))}function clearAssetCache(){manifestCache=void 0}function loadManifest(){return process.env.NODE_ENV===`production`?(manifestCache===void 0&&(manifestCache=discoverManifest()),manifestCache):discoverManifest()}function discoverManifest(){let envRoot=process.env.KICK_ASSETS_ROOT;if(envRoot){let fromEnv=readBuiltManifest(envRoot);if(fromEnv)return fromEnv}let cwd=process.cwd(),config=loadConfigSync(cwd);if(process.env.NODE_ENV!==`production`){let fresh=synthesiseDevManifest(cwd,config);if(fresh)return fresh}let configOutDir=config?.build?.outDir;if(configOutDir&&!STANDARD_OUT_DIRS.includes(configOutDir)){let found=readBuiltManifest(resolve(cwd,configOutDir));if(found)return found}for(let candidate of STANDARD_OUT_DIRS){let found=readBuiltManifest(join(cwd,candidate));if(found)return found}return synthesiseDevManifest(cwd,config)}const STANDARD_OUT_DIRS=[`dist`,`build`,`out`];function readBuiltManifest(dir){let path=join(dir,`.kickjs-assets.json`);if(!existsSync(path))return null;try{let raw=readFileSync(path,`utf-8`),parsed=JSON.parse(raw);return parsed.version!==1||!parsed.entries||typeof parsed.entries!=`object`?null:{manifest:parsed,root:resolve(dir)}}catch{return null}}function synthesiseDevManifest(cwd,preloadedConfig){let config=preloadedConfig===void 0?loadConfigSync(cwd):preloadedConfig;if(!config?.assetMap)return null;let entries={};for(let[namespace,entry]of Object.entries(config.assetMap)){if(!entry||typeof entry.src!=`string`)continue;let srcAbs=resolve(cwd,entry.src);if(!existsSync(srcAbs))continue;let collected=[];walkSync(srcAbs,srcAbs,(relPath,absPath)=>{(!entry.glob||matchesGlobLite(relPath,entry.glob,namespace))&&collected.push({rel:relPath,abs:absPath})}),collected.sort((a,b)=>a.rel.localeCompare(b.rel));let{pairs,collisionGroupsResolved}=groupAssetKeys(namespace,collected.map(c=>c.rel),{strategy:entry.keys??`auto`}),absByRel=new Map(collected.map(c=>[c.rel,c.abs]));for(let{rel,key}of pairs){let abs=absByRel.get(rel);abs&&(entries[key]=abs)}collisionGroupsResolved>0&&process.env.NODE_ENV!==`production`&&console.log(`[kickjs/assets] '${namespace}': ${collisionGroupsResolved} basename collision(s) auto-resolved by keeping extensions (set assetMap.${namespace}.keys to 'strip' or 'with-extension' to override).`)}return{manifest:{version:1,entries},root:cwd}}const warnedGlobs=new Set;function matchesGlobLite(relPath,pattern,namespace){if(pattern===`**/*`||pattern===`**`)return!0;let singleExt=/^\*\*\/\*\.(\w+)$/.exec(pattern);if(singleExt)return relPath.endsWith(`.${singleExt[1]}`);let braceExt=/^\*\*\/\*\.\{([^}]+)\}$/.exec(pattern);if(braceExt)return braceExt[1].split(`,`).map(e=>e.trim()).some(ext=>relPath.endsWith(`.${ext}`));let tag=`${namespace}::${pattern}`;return warnedGlobs.has(tag)||(warnedGlobs.add(tag),console.warn(`[kickjs/assets] Dev-mode glob '${pattern}' on '${namespace}' isn't recognised by the lite matcher. Falling back to '**/*'. Run 'kick build:assets' to use the full glob engine.`)),!0}function walkSync(root,dir,onFile){let entries;try{entries=readdirSync(dir,{withFileTypes:!0})}catch{return}for(let entry of entries){if(entry.name===`node_modules`||entry.name.startsWith(`.`))continue;let full=join(dir,entry.name);entry.isDirectory()?walkSync(root,full,onFile):entry.isFile()&&onFile(full.slice(root.length+1).split(sep).join(`/`),full)}}function loadConfigSync(cwd){let jsonPath=join(cwd,`kick.config.json`);if(existsSync(jsonPath))try{return JSON.parse(readFileSync(jsonPath,`utf-8`))}catch{return null}for(let filename of[`kick.config.cjs`,`kick.config.js`]){let path=join(cwd,filename);if(existsSync(path))try{let{createRequire}=__require(`node:module`),mod=createRequire(join(cwd,`package.json`))(path);return mod.default??mod??null}catch{continue}}let snapshotPath=join(cwd,`.kickjs`,`kick.config.json`);if(existsSync(snapshotPath))try{return JSON.parse(readFileSync(snapshotPath,`utf-8`))}catch{return null}return null}const PROMISE_DETECTION_KEYS=new Set([`then`]),PASSTHROUGH_KEYS=new Set([`toString`,`valueOf`,`constructor`,`prototype`,`name`,`length`,`asymmetricMatch`,`nodeType`,Symbol.toPrimitive,Symbol.toStringTag,Symbol.iterator,Symbol.asyncIterator]);function createNamespaceProxy(parts){return new Proxy(function(){},{apply(){if(parts.length<2)throw new UnknownAssetError(parts[0]??``,``);let[namespace,...rest]=parts;return resolveAsset(namespace,rest.join(`/`))},get(t,prop){if(!PROMISE_DETECTION_KEYS.has(prop)){if(PASSTHROUGH_KEYS.has(prop))return Reflect.get(t,prop);if(typeof prop!=`symbol`)return createNamespaceProxy([...parts,prop])}}})}function createAssetProxy(){return new Proxy({},{get(t,prop){if(!PROMISE_DETECTION_KEYS.has(prop)){if(PASSTHROUGH_KEYS.has(prop))return Reflect.get(t,prop);if(typeof prop!=`symbol`)return createNamespaceProxy([prop])}}})}const assets=createAssetProxy();function useAssets(){return assets}_setAssetResolver(resolveAsset);export{clearAssetCache as a,groupAssetKeys as c,assets as i,stripExt as l,ASSET_MANIFEST_VERSION as n,resolveAsset as o,UnknownAssetError as r,useAssets as s,ASSETS as t,defineContextDecorator as u};
12
- //# sourceMappingURL=core-_WNyghl_.mjs.map
11
+ import{l as __require}from"./env-__fzPsHD.mjs";import{METADATA}from"./core/interfaces.mjs";import{i as createToken,p as _setAssetResolver}from"./container-CAPN7pBG.mjs";import"./core/logger.mjs";import{c as pushClassMeta,l as pushMethodMeta}from"./metadata-CzzRUnAx.mjs";import"./core/decorators.mjs";import"./core/cron.mjs";import"./jobs-BmpeFvi0.mjs";import"./core/cache.mjs";import{n as assertFlagTest}from"./route-flag-Bb7n9Mnp.mjs";import"./contributor-runner-vk9XlOED.mjs";import"reflect-metadata";import{existsSync,readFileSync,readdirSync}from"node:fs";import{isAbsolute,join,resolve,sep}from"node:path";function defineContextDecoratorImpl(spec){if(typeof spec!=`object`||!spec)throw TypeError(`defineContextDecorator: spec must be an object literal. See architecture.md §20.4 for the spec shape.`);if(typeof spec.key!=`string`||spec.key.length===0){let got=typeof spec.key==`string`?`""`:typeof spec.key;throw TypeError(`defineContextDecorator: spec.key must be a non-empty string (got ${got}). Pick a name that matches a key declared on \`ContextMeta\` (or one of the framework keys).`)}if(typeof spec.resolve!=`function`)throw TypeError(`defineContextDecorator(${spec.key}): spec.resolve is required and must be a function (got ${typeof spec.resolve}). Each contributor needs a resolver that produces the value to write into ctx.set(${JSON.stringify(spec.key)}, …).`);if(spec.onError!==void 0&&typeof spec.onError!=`function`)throw TypeError(`defineContextDecorator(${spec.key}): spec.onError must be a function when provided (got ${typeof spec.onError}).`);if(spec.dependsOn!==void 0&&!Array.isArray(spec.dependsOn))throw TypeError(`defineContextDecorator(${spec.key}): spec.dependsOn must be an array when provided (got ${typeof spec.dependsOn}).`);if(spec.requiredParams!==void 0&&!Array.isArray(spec.requiredParams))throw TypeError(`defineContextDecorator(${spec.key}): spec.requiredParams must be an array when provided (got ${typeof spec.requiredParams}).`);if(Array.isArray(spec.requiredParams)&&spec.requiredParams.some(p=>typeof p!=`string`||p.length===0))throw TypeError(`defineContextDecorator(${spec.key}): spec.requiredParams entries must be non-empty strings.`);if(Array.isArray(spec.dependsOn)&&spec.dependsOn.some(dep=>typeof dep!=`string`||dep.length===0))throw TypeError(`defineContextDecorator(${spec.key}): spec.dependsOn entries must be non-empty strings.`);let definedAt=Error().stack,defaults=Object.freeze({...spec.paramDefaults??{}}),sharedDeps=Object.freeze({...spec.deps??{}}),sharedDependsOn=Object.freeze([...spec.dependsOn??[]]),sharedOptional=spec.optional??!1,buildRegistration=params=>{let frozenParams=Object.freeze({...params}),onError=spec.onError?(err,ctx)=>spec.onError(err,ctx,frozenParams):void 0;return spec.skipWhen!==void 0&&assertFlagTest(spec.skipWhen,`contributor '${spec.key}' skipWhen`),spec.onlyWhen!==void 0&&assertFlagTest(spec.onlyWhen,`contributor '${spec.key}' onlyWhen`),Object.freeze({key:spec.key,deps:sharedDeps,dependsOn:sharedDependsOn,optional:sharedOptional,skipWhen:spec.skipWhen,onlyWhen:spec.onlyWhen,beforeValidation:spec.beforeValidation===!0,onError,resolve:(ctx,deps)=>spec.resolve(ctx,deps,frozenParams),definedAt})},defaultRegistration=buildRegistration({...defaults}),isDecoratorCall=args=>args.length===0?!1:args.length>=2||typeof args[0]==`function`,applyAsDecorator=(registration,target,propertyKey)=>{if(propertyKey===void 0)pushClassMeta(METADATA.CLASS_CONTRIBUTORS,target,registration);else{let constructor=target.constructor;pushMethodMeta(METADATA.METHOD_CONTRIBUTORS,constructor,String(propertyKey),registration)}},isPlainObject=value=>{let proto=Object.getPrototypeOf(value);return proto===Object.prototype||proto===null},mergeParams=override=>{if(override===void 0)return{...defaults};if(typeof override!=`object`||!override||Array.isArray(override)||!isPlainObject(override)){let got=override===null?`null`:Array.isArray(override)?`array`:typeof override==`object`?`instance of ${override.constructor?.name??`unknown class`}`:typeof override;throw TypeError(`defineContextDecorator(${spec.key}): factory call requires a plain object literal, got ${got}. Class instances and built-ins (Map, Date, etc) silently spread to empty objects — call the decorator with a spread object literal instead, e.g. \`MyDecorator({ ...someInstance })\`.`)}return{...defaults,...override}},sharedRequiredParams=Object.freeze([...spec.requiredParams??[]]),assertRequiredParams=(params,usage)=>{if(sharedRequiredParams.length===0)return;let missing=sharedRequiredParams.filter(name=>params[name]===void 0);if(missing.length!==0)throw TypeError(`defineContextDecorator(${spec.key}): ${usage} is missing required param(s) ${missing.map(m=>`'${m}'`).join(`, `)}. Supply them at the call site — e.g. \`@${spec.key}({ ${missing[0]}: … })\` — or add a default in \`paramDefaults\` if one is genuinely correct for every route.`)};function decoratorOrFactory(...args){if(isDecoratorCall(args)){let[target,propertyKey]=args;assertRequiredParams({...defaults},"bare `@decorator` usage"),applyAsDecorator(defaultRegistration,target,propertyKey);return}let params=mergeParams(args[0]);assertRequiredParams(params,`factory call`);let registration=buildRegistration(params);return(target,propertyKey)=>{applyAsDecorator(registration,target,propertyKey)}}Object.defineProperty(decoratorOrFactory,"name",{value:`ContextDecorator(${spec.key})`,writable:!1,enumerable:!1,configurable:!0});let decorator=Object.assign(decoratorOrFactory,{with:params=>{let merged=mergeParams(params);return assertRequiredParams(merged,"`.with()`"),{registration:buildRegistration(merged)}}});return Object.defineProperty(decorator,"registration",{get(){return assertRequiredParams({...defaults},"`.registration`"),defaultRegistration},enumerable:!0,configurable:!1}),Object.freeze(decorator)}const defineContextDecorator=Object.freeze(Object.assign(defineContextDecoratorImpl,{withParams:()=>spec=>defineContextDecoratorImpl(spec)}));function groupAssetKeys(namespace,paths,opts={}){let strategy=opts.strategy??`auto`;if(strategy===`with-extension`)return{pairs:paths.map(rel=>({rel,key:`${namespace}/${rel}`})),collisionGroupsResolved:0};if(strategy===`strip`)return{pairs:paths.map(rel=>({rel,key:`${namespace}/${stripExt(rel)}`})),collisionGroupsResolved:0};let groups=new Map;for(let rel of paths){let strippedKey=`${namespace}/${stripExt(rel)}`,arr=groups.get(strippedKey);arr?arr.push(rel):groups.set(strippedKey,[rel])}let collisionGroupsResolved=0,keyByRel=new Map;for(let[strippedKey,rels]of groups)if(rels.length===1)keyByRel.set(rels[0],strippedKey);else{collisionGroupsResolved+=1;for(let rel of rels)keyByRel.set(rel,`${namespace}/${rel}`)}return{pairs:paths.map(rel=>({rel,key:keyByRel.get(rel)??`${namespace}/${rel}`})),collisionGroupsResolved}}function stripExt(path){let lastSlash=path.lastIndexOf(`/`),filename=lastSlash>=0?path.slice(lastSlash+1):path,lastDot=filename.lastIndexOf(`.`);return lastDot<=0?path:(lastSlash>=0?path.slice(0,lastSlash+1):``)+filename.slice(0,lastDot)}const ASSET_MANIFEST_VERSION=1,ASSETS=createToken(`kick/assets/Map`);var UnknownAssetError=class extends Error{namespace;key;constructor(namespace,key){super(`Unknown asset '${namespace}/${key}'. Make sure the namespace exists in kick.config.ts assetMap and that the file is present in the source directory.`),this.name=`UnknownAssetError`,this.namespace=namespace,this.key=key}};let manifestCache;function toPosixPath(p){return p.replaceAll(`\\`,`/`)}function resolveAsset(namespace,key){let resolved=loadManifest();if(!resolved)throw new UnknownAssetError(namespace,key);let entry=resolved.manifest.entries[`${namespace}/${key}`];if(!entry)throw new UnknownAssetError(namespace,key);return toPosixPath(isAbsolute(entry)?entry:resolve(resolved.root,entry))}function clearAssetCache(){manifestCache=void 0}function loadManifest(){return process.env.NODE_ENV===`production`?(manifestCache===void 0&&(manifestCache=discoverManifest()),manifestCache):discoverManifest()}function discoverManifest(){let envRoot=process.env.KICK_ASSETS_ROOT;if(envRoot){let fromEnv=readBuiltManifest(envRoot);if(fromEnv)return fromEnv}let cwd=process.cwd(),config=loadConfigSync(cwd);if(process.env.NODE_ENV!==`production`){let fresh=synthesiseDevManifest(cwd,config);if(fresh)return fresh}let configOutDir=config?.build?.outDir;if(configOutDir&&!STANDARD_OUT_DIRS.includes(configOutDir)){let found=readBuiltManifest(resolve(cwd,configOutDir));if(found)return found}for(let candidate of STANDARD_OUT_DIRS){let found=readBuiltManifest(join(cwd,candidate));if(found)return found}return synthesiseDevManifest(cwd,config)}const STANDARD_OUT_DIRS=[`dist`,`build`,`out`];function readBuiltManifest(dir){let path=join(dir,`.kickjs-assets.json`);if(!existsSync(path))return null;try{let raw=readFileSync(path,`utf-8`),parsed=JSON.parse(raw);return parsed.version!==1||!parsed.entries||typeof parsed.entries!=`object`?null:{manifest:parsed,root:resolve(dir)}}catch{return null}}function synthesiseDevManifest(cwd,preloadedConfig){let config=preloadedConfig===void 0?loadConfigSync(cwd):preloadedConfig;if(!config?.assetMap)return null;let entries={};for(let[namespace,entry]of Object.entries(config.assetMap)){if(!entry||typeof entry.src!=`string`)continue;let srcAbs=resolve(cwd,entry.src);if(!existsSync(srcAbs))continue;let collected=[];walkSync(srcAbs,srcAbs,(relPath,absPath)=>{(!entry.glob||matchesGlobLite(relPath,entry.glob,namespace))&&collected.push({rel:relPath,abs:absPath})}),collected.sort((a,b)=>a.rel.localeCompare(b.rel));let{pairs,collisionGroupsResolved}=groupAssetKeys(namespace,collected.map(c=>c.rel),{strategy:entry.keys??`auto`}),absByRel=new Map(collected.map(c=>[c.rel,c.abs]));for(let{rel,key}of pairs){let abs=absByRel.get(rel);abs&&(entries[key]=abs)}collisionGroupsResolved>0&&process.env.NODE_ENV!==`production`&&console.log(`[kickjs/assets] '${namespace}': ${collisionGroupsResolved} basename collision(s) auto-resolved by keeping extensions (set assetMap.${namespace}.keys to 'strip' or 'with-extension' to override).`)}return{manifest:{version:1,entries},root:cwd}}const warnedGlobs=new Set;function matchesGlobLite(relPath,pattern,namespace){if(pattern===`**/*`||pattern===`**`)return!0;let singleExt=/^\*\*\/\*\.(\w+)$/.exec(pattern);if(singleExt)return relPath.endsWith(`.${singleExt[1]}`);let braceExt=/^\*\*\/\*\.\{([^}]+)\}$/.exec(pattern);if(braceExt)return braceExt[1].split(`,`).map(e=>e.trim()).some(ext=>relPath.endsWith(`.${ext}`));let tag=`${namespace}::${pattern}`;return warnedGlobs.has(tag)||(warnedGlobs.add(tag),console.warn(`[kickjs/assets] Dev-mode glob '${pattern}' on '${namespace}' isn't recognised by the lite matcher. Falling back to '**/*'. Run 'kick build:assets' to use the full glob engine.`)),!0}function walkSync(root,dir,onFile){let entries;try{entries=readdirSync(dir,{withFileTypes:!0})}catch{return}for(let entry of entries){if(entry.name===`node_modules`||entry.name.startsWith(`.`))continue;let full=join(dir,entry.name);entry.isDirectory()?walkSync(root,full,onFile):entry.isFile()&&onFile(full.slice(root.length+1).split(sep).join(`/`),full)}}function loadConfigSync(cwd){let jsonPath=join(cwd,`kick.config.json`);if(existsSync(jsonPath))try{return JSON.parse(readFileSync(jsonPath,`utf-8`))}catch{return null}for(let filename of[`kick.config.cjs`,`kick.config.js`]){let path=join(cwd,filename);if(existsSync(path))try{let{createRequire}=__require(`node:module`),mod=createRequire(join(cwd,`package.json`))(path);return mod.default??mod??null}catch{continue}}let snapshotPath=join(cwd,`.kickjs`,`kick.config.json`);if(existsSync(snapshotPath))try{return JSON.parse(readFileSync(snapshotPath,`utf-8`))}catch{return null}return null}const PROMISE_DETECTION_KEYS=new Set([`then`]),PASSTHROUGH_KEYS=new Set([`toString`,`valueOf`,`constructor`,`prototype`,`name`,`length`,`asymmetricMatch`,`nodeType`,Symbol.toPrimitive,Symbol.toStringTag,Symbol.iterator,Symbol.asyncIterator]);function createNamespaceProxy(parts){return new Proxy(function(){},{apply(){if(parts.length<2)throw new UnknownAssetError(parts[0]??``,``);let[namespace,...rest]=parts;return resolveAsset(namespace,rest.join(`/`))},get(t,prop){if(!PROMISE_DETECTION_KEYS.has(prop)){if(PASSTHROUGH_KEYS.has(prop))return Reflect.get(t,prop);if(typeof prop!=`symbol`)return createNamespaceProxy([...parts,prop])}}})}function createAssetProxy(){return new Proxy({},{get(t,prop){if(!PROMISE_DETECTION_KEYS.has(prop)){if(PASSTHROUGH_KEYS.has(prop))return Reflect.get(t,prop);if(typeof prop!=`symbol`)return createNamespaceProxy([prop])}}})}const assets=createAssetProxy();function useAssets(){return assets}_setAssetResolver(resolveAsset);export{clearAssetCache as a,groupAssetKeys as c,assets as i,stripExt as l,ASSET_MANIFEST_VERSION as n,resolveAsset as o,UnknownAssetError as r,useAssets as s,ASSETS as t,defineContextDecorator as u};
12
+ //# sourceMappingURL=core-DKrKVLfj.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"core-_WNyghl_.mjs","names":[],"sources":["../src/core/context-decorator.ts","../src/core/asset-keys.ts","../src/core/assets.ts"],"sourcesContent":["import { assertFlagTest, type RouteFlagTest } from './route-flag'\nimport 'reflect-metadata'\nimport { METADATA, type Constructor, type MaybePromise } from './interfaces'\nimport { pushClassMeta, pushMethodMeta } from './metadata'\nimport type { InjectionToken } from './token'\nimport type { ContextMetaKey, ExecutionContext, MetaValue } from './execution-context'\n\n/**\n * String-literal union of every known context key — the union of the\n * keys declared on {@link ContextKeys} (key-only registry) and\n * {@link ContextMeta} (value-type registry). Resolves to `string` when\n * the project hasn't augmented either yet (so a first-day project keeps\n * compiling), and narrows to the concrete keys once the\n * `declare module '@forinda/kickjs' { interface ContextKeys/ContextMeta { ... } }`\n * blocks land.\n *\n * Used as the element type of `dependsOn`, so a typo like\n * `dependsOn: ['tenent']` becomes a TS error instead of a boot-time\n * `MissingContributorError`.\n *\n * Unioning BOTH registries is deliberate: it means adding a value type\n * via `ContextMeta` automatically makes that key a valid `dependsOn`\n * target, and you can register a dependsOn-able key in `ContextKeys`\n * without being forced to give it a value type. (Before the split,\n * `dependsOn` was keyed off `ContextMeta` alone, so augmenting\n * `ContextMeta` for some keys broke `dependsOn` for every key you\n * hadn't added there.)\n */\n// Moved to `execution-context` (beside the registries it reads) so that\n// module can constrain `get`/`set` without a circular import. Re-exported\n// here so existing `from '@forinda/kickjs'` imports keep resolving.\nexport type { ContextMetaKey } from './execution-context'\n\n/**\n * What a single `deps` entry is allowed to be — the runtime calls\n * `container.resolve(value)`, which accepts an injection token brand\n * or a constructor (decorated class). Anything else (a string literal,\n * an array, a plain object) silently fails at boot when the runner\n * tries to resolve it; constraining at the type level surfaces the\n * mistake in the editor instead.\n */\nexport type DepValue = InjectionToken<unknown> | Constructor<unknown>\n\n/**\n * Maps a `deps` declaration object to its resolved-instance shape.\n *\n * `deps: { db: DB_TOKEN, cache: CacheService }` produces\n * `{ db: Db, cache: CacheService }` — the runner resolves each value\n * against the DI container and hands the result to `resolve()`.\n */\nexport type ResolvedDeps<D extends Record<string, DepValue>> = {\n [K in keyof D]: D[K] extends InjectionToken<infer T>\n ? T\n : D[K] extends Constructor<infer T>\n ? T\n : never\n}\n\n/**\n * Keys of `T` that are declared required (i.e. not `?`-optional).\n */\ntype RequiredParamKeys<T> = {\n // eslint-disable-next-line @typescript-eslint/ban-types\n [K in keyof T]-?: {} extends Pick<T, K> ? never : K\n}[keyof T]\n\n/**\n * Params a call site MUST supply: the required keys of `P` that\n * `paramDefaults` (type `PD`) doesn't already provide.\n *\n * The `string extends keyof P` guard short-circuits the index-signature\n * case — `P` defaults to `Record<string, never>` for the overwhelmingly\n * common zero-params contributor, and a mapped type over `string` would\n * otherwise report \"every key is required\" and break `@Foo` for every\n * existing decorator in the wild.\n */\nexport type MissingParamKeys<P, PD> = string extends keyof P\n ? never\n : Exclude<RequiredParamKeys<P>, keyof PD>\n\n/**\n * The argument type for a call site (`@Foo({...})`, `.with({...})`).\n *\n * `Partial<P>` when every required field has a default, so adopters keep\n * overriding just the fields they care about. Otherwise `Partial<P>`\n * intersected with the undefaulted required fields, making those\n * mandatory at each call site.\n */\nexport type CallSiteParams<P, PD> = [MissingParamKeys<P, PD>] extends [never]\n ? Partial<P>\n : Partial<P> & Pick<P, MissingParamKeys<P, PD> & keyof P>\n\n/**\n * Spec passed to {@link defineContextDecorator}. Describes one entry in\n * the per-request contributor pipeline.\n *\n * @typeParam K - The {@link ContextMeta} key this contributor populates.\n * @typeParam D - DI dependency declaration shape (see {@link ResolvedDeps}).\n * @typeParam Ctx - Concrete execution-context type. Defaults to the abstract\n * {@link ExecutionContext}; HTTP contributors typically\n * default to `RequestContext` at the call site.\n * @typeParam PD - Inferred from `paramDefaults`. Drives which params each\n * call site is required to supply — see {@link CallSiteParams}.\n * Never spell this by hand.\n */\nexport interface ContextDecoratorSpec<\n K extends ContextMetaKey = ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n PD extends Partial<P> = Partial<P>,\n> {\n /** ContextMeta key the resolved value is written to. */\n key: K\n /**\n * Skip this contributor on routes carrying the named route flag\n * (`defineRouteFlag`). See {@link ContributorRegistration.skipWhen}.\n */\n skipWhen?: RouteFlagTest\n /**\n * Run this contributor only on routes carrying the named route flag, or\n * matching the predicate.\n */\n onlyWhen?: RouteFlagTest\n /**\n * DI dependencies resolved by the runner before `resolve()` runs.\n * Map entry keys become property names on the resolved-deps argument\n * passed to `resolve()`.\n */\n deps?: D\n /**\n * Other contributor keys that must populate before this one runs.\n * Enforced via topo-sort at startup; missing deps and cycles fail boot.\n *\n * Typed against {@link ContextMetaKey} — the union of `keyof ContextMeta`\n * and `keyof ContextKeys` — once the project augments either, so\n * `dependsOn: ['tenent']` (typo) is a TS error, not a boot-time\n * `MissingContributorError`. Falls back to plain `string[]` when both\n * registries are empty (no augmentation yet) so first-day projects keep\n * compiling. Cast escape hatch: `dependsOn: ['custom-key' as ContextMetaKey]`.\n */\n dependsOn?: readonly ContextMetaKey[]\n /**\n * If `true`, a thrown `resolve` skips the contributor instead of\n * forwarding to the request error handler. The key is left unset\n * and downstream consumers see `ctx.get(key) === undefined`.\n */\n optional?: boolean\n /**\n * HTTP routes only: run this contributor before request validation and\n * `@Middleware()` handlers, instead of after them.\n *\n * For contributors that must answer before a request's shape is checked —\n * authentication above all. Without it, a request with no credentials and a\n * malformed body is rejected by validation (422) before the auth contributor\n * can reject it (401), telling an anonymous caller what the schema expects.\n *\n * `ctx.body` / `ctx.query` / `ctx.params` are **unvalidated** at this point,\n * and file uploads may not be parsed yet — read credentials (headers,\n * cookies), not the payload. It may only `dependsOn` contributors that also\n * set `beforeValidation`; anything else fails boot.\n */\n beforeValidation?: boolean\n /**\n * Default per-call params merged with the call-site params; call-site\n * wins. When omitted, params default to `{}` and the resolver\n * receives an empty object — back-compat with today's zero-arg\n * decorators.\n *\n * Param shape is intentionally unconstrained: plain data, functions,\n * and closures are all valid. Adopters who want runtime validation\n * should perform it inside `resolve()` — keeps the framework\n * validator-agnostic so projects can plug Zod / Valibot / hand-rolled\n * checks without the framework caring.\n *\n * **Required fields are enforced at the call site.** A required field\n * of `P` that has no entry here must be supplied wherever the\n * decorator is applied — `@Foo({ action: 'audit:read' })`. The bare\n * `@Foo` form and `.registration` stop type-checking for such a\n * decorator, because neither can supply the value.\n *\n * This exists so nobody has to invent a meaningless default just to\n * satisfy the type (`action: 'settings:read'` on a permission\n * contributor every call site overrides anyway). A default that is\n * never correct is worse than no default: forget the argument at one\n * call site and the route silently gates on the placeholder instead\n * of failing to compile.\n *\n * Omit this entirely for the common zero-params case — `P` defaults\n * to `Record<string, never>`, which requires nothing.\n */\n paramDefaults?: PD\n /**\n * Runtime mirror of the compile-time requirement above: param names\n * that must be present after merging call-site params over\n * `paramDefaults`. A missing one throws `TypeError` at the point of\n * use (decoration time for `@Foo`, call time for `@Foo({...})` /\n * `.with({...})`), naming the decorator and the field.\n *\n * The type-level check already covers TypeScript call sites, so this\n * is for the paths types can't reach: plain-JS adopters, `as any`\n * escapes, and params assembled dynamically. Optional — list only\n * fields whose absence is a correctness bug rather than a default.\n */\n requiredParams?: readonly (keyof P & string)[]\n /**\n * Hook invoked when `resolve()` throws and `optional` is `false`.\n * Returning a value writes it to `ctx.set(key, …)`; returning\n * `undefined` / `void` skips. Throwing inside the hook forwards\n * the new error to the request error handler.\n *\n * Async-permitted by design — adopters frequently want to\n * `await auditService.log(err)` or `await cache.fallback(...)`.\n *\n * The third `params` argument carries the resolved per-call params\n * (call-site overrides merged on top of `paramDefaults`).\n */\n onError?: (err: unknown, ctx: Ctx, params: P) => MaybePromise<MetaValue<K> | undefined | void>\n /**\n * Compute and return the value to write into `ctx.set(key, …)`.\n *\n * The third `params` argument carries the resolved per-call params\n * (call-site overrides merged on top of `paramDefaults`). When the\n * decorator is applied zero-arg (`@Foo`), `params` equals\n * `paramDefaults` (or `{}` if defaults are omitted). When applied as\n * a factory (`@Foo({ ... })`), the call-site object is merged on top.\n */\n resolve: (ctx: Ctx, deps: ResolvedDeps<D>, params: P) => MaybePromise<MetaValue<K>>\n}\n\n/**\n * Internal, normalised registration produced by {@link defineContextDecorator}.\n *\n * Frozen and shared by every use site of the decorator (method, class,\n * module, adapter, global). The pipeline builder dedupes by `key` using\n * the precedence rule defined in §20.4 of `architecture.md`.\n */\nexport interface ContributorRegistration<\n K extends ContextMetaKey = ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n> {\n readonly key: K\n readonly deps: D\n readonly dependsOn: readonly ContextMetaKey[]\n readonly optional: boolean\n /**\n * Per-call params from the spec are baked into the closure at\n * decorator-construction time, so the runner-facing `onError`\n * exposes only `(err, ctx)`.\n */\n readonly onError?: (err: unknown, ctx: Ctx) => MaybePromise<MetaValue<K> | undefined | void>\n /**\n * Per-call params from the spec are baked into the closure at\n * decorator-construction time, so the runner-facing `resolve`\n * exposes only `(ctx, deps)`. Adopters who write their own\n * registrations (without `defineContextDecorator`) match this\n * shape directly — params are an implementation detail of the\n * decorator factory, not of the runtime pipeline.\n */\n readonly resolve: (ctx: Ctx, deps: ResolvedDeps<D>) => MaybePromise<MetaValue<K>>\n /**\n * Stack snapshot captured the moment the decorator was defined.\n * Used by the contributor-pipeline builder to point boot-time\n * errors (`MissingContributorError`, `ContributorCycleError`,\n * `DuplicateContributorError`) at the adopter file that declared\n * the broken contributor — instead of forcing them to grep for\n * the key string.\n *\n * Optional because adopters who hand-roll a `ContributorRegistration`\n * (without `defineContextDecorator`) may not have a useful stack to\n * attach. Format is the raw `Error.stack` string; consumers slice\n * the first non-framework frame for display.\n */\n readonly definedAt?: string\n /** Runs before request validation on HTTP routes — see {@link ContextDecoratorSpec.beforeValidation}. */\n readonly beforeValidation?: boolean\n /**\n * Skip this contributor on routes carrying the named route flag.\n *\n * The composable way to say \"not here\". Exempting a contributor otherwise\n * means registering a permissive twin under the same key, which only its\n * author can do — a flag is declared on the route, so an adopter can exempt\n * a plugin's contributor without owning its key or forking it.\n *\n * ```ts\n * defineHttpContextDecorator({ key: 'user', skipWhen: 'auth.public', resolve })\n * ```\n */\n readonly skipWhen?: RouteFlagTest\n /**\n * Run this contributor **only** on routes carrying the named flag. The\n * inverse of {@link skipWhen}, and the case with no expression today short\n * of an `if` inside every resolver.\n */\n readonly onlyWhen?: RouteFlagTest\n}\n\n/**\n * Type-erased registration shape used in the public collection slots\n * (`AppModule.contributors?()`, `AppAdapter.contributors?()`,\n * `KickPlugin.contributors?()`, `bootstrap({ contributors })`).\n *\n * The narrow `ContributorRegistration` carries a `Ctx` parameter so\n * `resolve(ctx, deps)` is typed at the *definition* site, but `Ctx`\n * sits in a contravariant position on `resolve` — meaning a\n * `ContributorRegistration<…, RequestContext>` is *not* assignable to\n * `ContributorRegistration<…, ExecutionContext>` even though every\n * `RequestContext` is-a `ExecutionContext`. That's TS-sound but\n * useless: the runner only ever calls a contributor with the same\n * concrete ctx the route mounts under, so an HTTP-typed contributor\n * never sees a non-HTTP ctx. Erasing `Ctx` to `any` in the collection\n * type lets adopters store contributors typed against `RequestContext`\n * (or any future transport-specific ctx) in the same array without\n * casting.\n */\nexport type AnyContributorRegistration = ContributorRegistration<\n ContextMetaKey,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any\n>\n\n/** Convenience alias for `AnyContributorRegistration[]` collections. */\nexport type ContributorRegistrations =\n | AnyContributorRegistration[]\n | readonly AnyContributorRegistration[]\n\n/**\n * The decorator function returned by either a zero-arg\n * {@link ContextDecorator} application or a factory call. Overloaded\n * to satisfy both class and method decorator shapes — adopters apply\n * the same decorator to either site without TS rejecting the call.\n */\nexport interface ContextDecoratorTarget {\n /** Class decorator usage. */\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n (target: Function): void\n /** Method decorator usage. */\n (target: object, propertyKey: string | symbol, descriptor?: PropertyDescriptor): void\n}\n\n/**\n * The value returned by {@link defineContextDecorator}.\n *\n * Resolves to one of two shapes depending on whether every required\n * field of `P` has a `paramDefaults` entry:\n *\n * - {@link ContextDecoratorWithDefaults} — the classic shape. Usable\n * bare (`@Foo`), as a factory (`@Foo({...})`), via `.with(params)`,\n * and via `.registration`.\n * - {@link ContextDecoratorRequiringParams} — when `P` has required\n * fields `paramDefaults` doesn't cover. Only the factory and\n * `.with(params)` forms exist, because the others have no way to\n * supply the missing values. Applying such a decorator bare is a\n * compile error rather than a silent `undefined` at request time.\n *\n * `PD` is inferred from the spec's `paramDefaults`; the default here\n * (`Partial<P>`) keeps hand-written `ContextDecorator<K, D, P>`\n * annotations resolving to the permissive shape as before.\n */\nexport type ContextDecorator<\n K extends ContextMetaKey = ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n PD = Partial<P>,\n> = [MissingParamKeys<P, PD>] extends [never]\n ? ContextDecoratorWithDefaults<K, D, P, Ctx>\n : ContextDecoratorRequiringParams<K, D, P, Ctx, PD>\n\n/**\n * Decorator whose params are fully defaulted (or which takes none).\n * Callable as a class or method decorator directly, as a factory, or\n * via `.with(params)` / `.registration` at non-decorator registration\n * sites.\n */\nexport interface ContextDecoratorWithDefaults<\n K extends ContextMetaKey = ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n> {\n /**\n * Class decorator usage with `paramDefaults` — `@Foo class C {}`.\n * The class constructor is a `Function`, so this overload only\n * matches actual classes (not plain object literals — which\n * fall through to the factory overload below).\n */\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n (target: Function): void\n /**\n * Method decorator usage with `paramDefaults` — `@Foo` over a\n * method. The required `propertyKey` distinguishes this from the\n * factory overload's single-object-arg call shape.\n */\n (target: object, propertyKey: string | symbol, descriptor?: PropertyDescriptor): void\n /**\n * Zero-arg factory usage — `@Foo()` returns a decorator with\n * `paramDefaults` applied. Equivalent in behaviour to the bare\n * `@Foo` form, exposed here so adopters who prefer \"always call the\n * factory\" don't need an `as any` cast.\n */\n (): ContextDecoratorTarget\n /**\n * Factory usage — `@Foo({ ... })` returns the actual decorator with\n * call-site params merged onto `paramDefaults`. Matches a single\n * params object; plain objects don't satisfy the `Function` /\n * 2-arg-minimum constraints above, so TS picks this overload.\n *\n * Accepts `Partial<P>` so adopters can override only the fields\n * they care about — anything missing falls back to `paramDefaults`\n * at runtime. Inside `resolve()`, `params` is the fully merged\n * `P`, never a `Partial<P>`.\n *\n * The returned decorator is overloaded for class AND method usage —\n * `@Foo({...}) class C {}` and `@Foo({...}) m() {}` both type-check.\n */\n (params: Partial<P>): ContextDecoratorTarget\n /**\n * Bare registration — uses `paramDefaults` only. Use this at non-\n * decorator registration sites (module / adapter / bootstrap) when\n * the call site doesn't need to override params.\n */\n readonly registration: ContributorRegistration<K, D, Ctx>\n /**\n * Build a registration with call-site params merged onto\n * `paramDefaults`. Use this at non-decorator registration sites\n * when the call site DOES want to override params:\n *\n * ```ts\n * bootstrap({\n * contributors: [LoadTenant.with({ source: 'subdomain' }).registration],\n * })\n * ```\n *\n * `Partial<P>` for the same reason as the factory overload — the\n * runtime merges with `paramDefaults`, so adopters only specify\n * fields they want to override.\n */\n with(params: Partial<P>): { readonly registration: ContributorRegistration<K, D, Ctx> }\n}\n\n/**\n * Decorator with at least one required param that `paramDefaults`\n * doesn't cover. Deliberately narrower than\n * {@link ContextDecoratorWithDefaults}: the bare `@Foo` form, the\n * zero-arg `@Foo()` form, and the `.registration` accessor are all\n * absent, because none of them can supply the missing value.\n *\n * ```ts\n * const OperatorPerm = defineContextDecorator.withParams<{ action: string }>()({\n * key: 'operatorPerm',\n * resolve: (ctx, _deps, { action }) => check(ctx, action),\n * })\n *\n * class Ops {\n * @OperatorPerm // ✗ compile error — `action` missing\n * @Get('/a')\n * a(ctx: RequestContext) {}\n *\n * @OperatorPerm({ action: 'read' }) // ✓\n * @Get('/b')\n * b(ctx: RequestContext) {}\n * }\n * ```\n *\n * If you want the bare form back, give `action` a default in\n * `paramDefaults` — but only when the default is genuinely correct for\n * an undecorated route, which for a permission string it usually isn't.\n */\nexport interface ContextDecoratorRequiringParams<\n K extends ContextMetaKey = ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n PD = Partial<P>,\n> {\n /**\n * Factory usage — the only decorator form available here. The\n * argument type carries every required field `paramDefaults` didn't\n * provide, so omitting one is a compile error at this call site.\n */\n (params: CallSiteParams<P, PD>): ContextDecoratorTarget\n /**\n * Build a registration for non-decorator registration sites. Same\n * required-field rule as the factory form.\n */\n with(params: CallSiteParams<P, PD>): {\n readonly registration: ContributorRegistration<K, D, Ctx>\n }\n}\n\n/**\n * Curried params-first helper for {@link defineContextDecorator}.\n *\n * Solves the partial-inference problem: TS generics are positional, so\n * the moment an adopter wants to specify the per-call params shape\n * `P`, they're forced to spell `K` and `D` by hand — losing the\n * automatic `deps` inference that drives the `(ctx, deps, params) =>`\n * resolver signature:\n *\n * ```ts\n * // ❌ Positional generics — must repeat the deps type, and `D` no\n * // longer flows from `spec.deps` into `resolve(ctx, deps, ...)`.\n * defineContextDecorator<'tenant', { repo: typeof TENANT_REPO }, MyParams>({\n * key: 'tenant',\n * deps: { repo: TENANT_REPO },\n * resolve: (ctx, { repo }, params) => ...,\n * })\n *\n * // ✅ Curried form — only `P` is spelled; `K`, `D`, `Ctx` infer.\n * defineContextDecorator.withParams<MyParams>()({\n * key: 'tenant',\n * deps: { repo: TENANT_REPO }, // D inferred\n * resolve: (ctx, { repo }, params) => ..., // deps + params both typed\n * })\n * ```\n *\n * Use this whenever the contributor takes call-site params; use the\n * positional form when there are no params (the common case).\n */\nexport interface DefineContextDecoratorWithParams<P extends Record<string, unknown>> {\n <\n K extends ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n // Inferred from `spec.paramDefaults`. The `Record<never, never>`\n // default (NOT `Partial<P>`) is what makes an omitted `paramDefaults`\n // mean \"nothing is defaulted\" — so every required field of `P` lands\n // on the call site. `Partial<P>` here would make `keyof PD` cover all\n // of `P` and silently defeat the whole check.\n PD extends Partial<P> = Record<never, never>,\n >(\n spec: ContextDecoratorSpec<K, D, P, Ctx, PD>,\n ): ContextDecorator<K, D, P, Ctx, PD>\n}\n\n/**\n * The public shape of {@link defineContextDecorator}. Combines the\n * original positional-generic call signature with the curried\n * `.withParams<P>()` helper that fixes partial-inference for the\n * parameterised case.\n */\nexport interface DefineContextDecoratorFn {\n <\n K extends ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n PD extends Partial<P> = Record<never, never>,\n >(\n spec: ContextDecoratorSpec<K, D, P, Ctx, PD>,\n ): ContextDecorator<K, D, P, Ctx, PD>\n\n /**\n * Curried entry point. Spell only the per-call params shape `P`;\n * `K`, `D`, and `Ctx` are inferred from the spec passed to the\n * returned function. See {@link DefineContextDecoratorWithParams}\n * for the rationale.\n */\n withParams<P extends Record<string, unknown>>(): DefineContextDecoratorWithParams<P>\n}\n\n/**\n * Define a typed context contributor.\n *\n * The returned function works as a method or class decorator:\n *\n * ```ts\n * const LoadTenant = defineContextDecorator({\n * key: 'tenant',\n * deps: { tenants: TenantsRepo },\n * resolve: (ctx, { tenants }) => tenants.findByHost(ctx.requestId),\n * })\n *\n * @LoadTenant\n * @Get('/me')\n * me(ctx: RequestContext) {\n * ctx.get('tenant') // typed via ContextMeta\n * }\n * ```\n *\n * For non-decorator registration (module/adapter/bootstrap), pass\n * `LoadTenant.registration` into the appropriate contributors list.\n *\n * Boot-time validation: a missing `resolve`, an empty `key`, or a\n * non-array `dependsOn` throws `TypeError` here at definition time\n * (typically module-load) rather than waiting for the first request.\n * See `architecture.md` §20 for the full pipeline plan.\n *\n * **Parameterised contributors:** TS generics are positional, so to\n * specify `P` (the per-call params shape) with this signature you\n * also have to spell `K` and `D` — which loses automatic `deps`\n * inference. Use {@link DefineContextDecoratorFn.withParams} instead:\n * `defineContextDecorator.withParams<MyParams>()(spec)`.\n */\nfunction defineContextDecoratorImpl<\n K extends ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n PD extends Partial<P> = Record<never, never>,\n>(spec: ContextDecoratorSpec<K, D, P, Ctx, PD>): ContextDecorator<K, D, P, Ctx, PD> {\n // ---- Boot-time validation --------------------------------------\n // Surface mistakes the moment `defineContextDecorator` is called\n // (typically module-load time) instead of letting them ride to\n // request time as cryptic ContextMeta misses or container failures.\n // Cheap, ~15 lines, catches a whole class of definition-time bugs.\n if (spec === null || typeof spec !== 'object') {\n throw new TypeError(\n 'defineContextDecorator: spec must be an object literal. ' +\n 'See architecture.md §20.4 for the spec shape.',\n )\n }\n if (typeof spec.key !== 'string' || spec.key.length === 0) {\n const got = typeof spec.key === 'string' ? '\"\"' : typeof spec.key\n throw new TypeError(\n `defineContextDecorator: spec.key must be a non-empty string (got ${got}). ` +\n 'Pick a name that matches a key declared on `ContextMeta` (or one of the framework keys).',\n )\n }\n if (typeof spec.resolve !== 'function') {\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.resolve is required and must be a function ` +\n `(got ${typeof spec.resolve}). Each contributor needs a resolver that produces ` +\n `the value to write into ctx.set(${JSON.stringify(spec.key)}, …).`,\n )\n }\n if (spec.onError !== undefined && typeof spec.onError !== 'function') {\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.onError must be a function when provided ` +\n `(got ${typeof spec.onError}).`,\n )\n }\n if (spec.dependsOn !== undefined && !Array.isArray(spec.dependsOn)) {\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.dependsOn must be an array when provided ` +\n `(got ${typeof spec.dependsOn}).`,\n )\n }\n if (spec.requiredParams !== undefined && !Array.isArray(spec.requiredParams)) {\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.requiredParams must be an array when provided ` +\n `(got ${typeof spec.requiredParams}).`,\n )\n }\n if (\n Array.isArray(spec.requiredParams) &&\n spec.requiredParams.some((p) => typeof p !== 'string' || p.length === 0)\n ) {\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.requiredParams entries must be non-empty strings.`,\n )\n }\n if (\n Array.isArray(spec.dependsOn) &&\n spec.dependsOn.some((dep) => typeof dep !== 'string' || dep.length === 0)\n ) {\n // TS narrows dependsOn to `readonly ContextMetaKey[]` for typed\n // adopters, but JS callers (and TS callers who erase via `as any`)\n // can still slip through with `[42]` or `['']`. Surface it at\n // definition time as a TypeError instead of riding to first\n // request as a `MissingContributorError` on a phantom key.\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.dependsOn entries must be non-empty strings.`,\n )\n }\n\n // Source-location capture. `new Error().stack` here records the\n // adopter's call site so boot-time errors (cycles, missing deps,\n // duplicate keys) can point at the file that declared the broken\n // decorator. Bad boot errors are why frameworks get abandoned —\n // a single stack frame turns \"what's a 'tenent'?\" into \"edit\n // src/contributors/tenant.ts line 42\".\n const definedAt = new Error().stack\n\n // Snapshot + freeze paramDefaults so callers can't mutate the spec\n // object post-definition and shift the merged params under our feet.\n // Same immutability boundary applied to `deps` and `dependsOn`.\n const defaults = Object.freeze({\n ...(spec.paramDefaults ?? ({} as Partial<P>)),\n }) as Readonly<Partial<P>>\n\n // Shared deps + dependsOn — frozen once and reused across every\n // registration produced by this decorator (one per call site).\n // `Object.freeze` is shallow — the values inside (DI tokens,\n // constructors) are intentionally untouched; freezing them would\n // prevent the container from doing legitimate per-instance\n // bookkeeping.\n //\n // The `({} as D)` cast: `spec.deps` is optional (`deps?: D`), so\n // the fallback to `{}` is required. When `D` defaults to\n // `Record<string, never>`, the cast is sound (`{}` matches). When\n // `D` is non-empty AND adopter omitted `deps`, the cast is unsound\n // — but the runner errors loudly the moment it tries to resolve a\n // missing dep, so the mistake surfaces at first request rather\n // than producing wrong-but-silent behaviour. The alternative\n // (forcing `deps` non-optional in the spec) would break ergonomics\n // for the common zero-deps case.\n const sharedDeps = Object.freeze({ ...(spec.deps ?? ({} as D)) }) as D\n const sharedDependsOn = Object.freeze([...(spec.dependsOn ?? [])])\n const sharedOptional = spec.optional ?? false\n\n /**\n * Build a frozen registration that closes over a specific\n * resolved-params object. The runner sees a normal\n * `(ctx, deps) => value` registration — params are baked into the\n * closure, invisible to the pipeline.\n */\n const buildRegistration = (params: P): ContributorRegistration<K, D, Ctx> => {\n // Shallow-freeze the captured params so a misbehaving `resolve()` /\n // `onError()` can't mutate the closure and bleed into later\n // requests through the same registration. Adopters who really need\n // mutable per-request state should write to `ctx.set(...)` from\n // inside the resolver instead of mutating params.\n const frozenParams = Object.freeze({ ...params }) as P\n const onError: ContributorRegistration<K, D, Ctx>['onError'] = spec.onError\n ? (err: unknown, ctx: Ctx) => spec.onError!(err, ctx, frozenParams)\n : undefined\n const resolve: ContributorRegistration<K, D, Ctx>['resolve'] = (\n ctx: Ctx,\n deps: ResolvedDeps<D>,\n ) => spec.resolve(ctx, deps, frozenParams)\n if (spec.skipWhen !== undefined)\n assertFlagTest(spec.skipWhen, `contributor '${spec.key}' skipWhen`)\n if (spec.onlyWhen !== undefined)\n assertFlagTest(spec.onlyWhen, `contributor '${spec.key}' onlyWhen`)\n return Object.freeze({\n key: spec.key,\n deps: sharedDeps,\n dependsOn: sharedDependsOn,\n optional: sharedOptional,\n skipWhen: spec.skipWhen,\n onlyWhen: spec.onlyWhen,\n beforeValidation: spec.beforeValidation === true,\n onError,\n resolve,\n definedAt,\n })\n }\n\n // Default-params registration — used for zero-arg decorator\n // applications and for the `.registration` accessor. Cloning\n // `defaults` so the closure can't be mutated through the spec.\n const defaultRegistration = buildRegistration({ ...defaults } as P)\n\n /**\n * Decide whether the decorator was called as a decorator\n * (`@Foo` / `@Foo(target, key)`) or as a factory\n * (`@Foo()` / `@Foo({...})`).\n *\n * - Class decorator: `(target)` where target is a constructor function.\n * - Method decorator: `(target, propertyKey, descriptor?)` where\n * target is a prototype object and propertyKey is a string/symbol.\n * - Factory (defaults): `()` — adopter wrote `@Foo()` (rare; usually\n * they write the bare `@Foo`). Returns a decorator using\n * `paramDefaults`.\n * - Factory (params): `(params)` where params is a plain object.\n *\n * The legacy decorators that ship with KickJS (`@Get('/path')`,\n * `@Cron('* * * *')`, etc.) use the same heuristic, so adopters\n * already know to pass plain object literals as factory args.\n */\n const isDecoratorCall = (args: unknown[]): boolean => {\n // Zero args is a factory call (`@Foo()`), not a direct decorator\n // application — TS never invokes a decorator with zero args.\n if (args.length === 0) return false\n if (args.length >= 2) return true\n const first = args[0]\n // Functions are class constructors at decoration time.\n if (typeof first === 'function') return true\n // Anything else (plain object, undefined, primitive) → factory call.\n return false\n }\n\n const applyAsDecorator = (\n registration: ContributorRegistration<K, D, Ctx>,\n target: object,\n propertyKey?: string | symbol,\n ): void => {\n if (propertyKey === undefined) {\n // Class decorator — `target` is the constructor.\n pushClassMeta(METADATA.CLASS_CONTRIBUTORS, target, registration)\n } else {\n // Method decorator — `target` is the prototype; we write to its\n // constructor so reads can use the controller class as the\n // lookup key, matching the convention used by @Middleware and\n // consumed by router-builder.ts.\n const constructor = (target as { constructor: object }).constructor\n pushMethodMeta(METADATA.METHOD_CONTRIBUTORS, constructor, String(propertyKey), registration)\n }\n }\n\n /**\n * `true` for `{...}` and `Object.create(null)`. Rejects every\n * built-in subclass (Map / Set / Date / Error / RegExp / Promise),\n * adopter classes, and arrays.\n */\n const isPlainObject = (value: object): boolean => {\n const proto = Object.getPrototypeOf(value)\n return proto === Object.prototype || proto === null\n }\n\n /**\n * Merge call-site `Partial<P>` over `paramDefaults`. Strict guard:\n * accepts only **plain object literals** (or `Object.create(null)`).\n * Reject everything else with a descriptive `TypeError`.\n *\n * Why strict? Object spread copies enumerable own properties only.\n * `{ ...new Date() }` and `{ ...new Map([...])}` both produce `{}`\n * — the params silently drop. Catching this at the boundary\n * surfaces the mistake instead of routing the request with empty\n * config. Adopters who genuinely need a class-instance shape can\n * destructure it themselves: `Foo({ ...new MyParams() })`.\n */\n const mergeParams = (override: unknown): P => {\n if (override === undefined) return { ...defaults } as P\n if (\n override === null ||\n typeof override !== 'object' ||\n Array.isArray(override) ||\n !isPlainObject(override)\n ) {\n const got =\n override === null\n ? 'null'\n : Array.isArray(override)\n ? 'array'\n : typeof override === 'object'\n ? `instance of ${(override as object).constructor?.name ?? 'unknown class'}`\n : typeof override\n throw new TypeError(\n `defineContextDecorator(${spec.key}): factory call requires a plain object literal, got ${got}. ` +\n `Class instances and built-ins (Map, Date, etc) silently spread to empty objects — ` +\n `call the decorator with a spread object literal instead, e.g. ` +\n `\\`MyDecorator({ ...someInstance })\\`.`,\n )\n }\n return { ...defaults, ...(override as Partial<P>) } as P\n }\n\n const sharedRequiredParams = Object.freeze([...(spec.requiredParams ?? [])])\n\n /**\n * Runtime half of the required-params guarantee. The type system\n * already stops TS call sites from omitting a required field, so this\n * catches what types can't see: plain-JS adopters, `as any`, and\n * params built dynamically.\n *\n * `usage` names the form that failed so the message points at the fix\n * — a bare `@Foo` on a decorator with required params needs to become\n * `@Foo({ ... })`, which is a different edit from a factory call that\n * merely forgot one field.\n */\n const assertRequiredParams = (params: P, usage: string): void => {\n if (sharedRequiredParams.length === 0) return\n const missing = sharedRequiredParams.filter(\n (name) => (params as Record<string, unknown>)[name] === undefined,\n )\n if (missing.length === 0) return\n throw new TypeError(\n `defineContextDecorator(${spec.key}): ${usage} is missing required param(s) ` +\n `${missing.map((m) => `'${m}'`).join(', ')}. ` +\n `Supply them at the call site — e.g. \\`@${spec.key}({ ${missing[0]}: … })\\` — or ` +\n `add a default in \\`paramDefaults\\` if one is genuinely correct for every route.`,\n )\n }\n\n // Overloaded function signatures so `decoratorOrFactory`'s type\n // matches `ContextDecorator`'s call shapes directly — no\n // `as unknown as` double-cast needed. The implementation signature\n // (`...args: unknown[]`) is hidden from callers; only the four\n // declared overloads are reachable.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n function decoratorOrFactory(target: Function): void\n function decoratorOrFactory(\n target: object,\n propertyKey: string | symbol,\n descriptor?: PropertyDescriptor,\n ): void\n function decoratorOrFactory(): ContextDecoratorTarget\n function decoratorOrFactory(params: Partial<P>): ContextDecoratorTarget\n function decoratorOrFactory(...args: unknown[]): unknown {\n if (isDecoratorCall(args)) {\n const [target, propertyKey] = args as [\n object,\n string | symbol | undefined,\n PropertyDescriptor?,\n ]\n // Bare `@Foo` — nothing can supply params here, so a required one\n // that isn't defaulted is a decoration-time (module-load) failure\n // rather than an undefined riding into the resolver.\n assertRequiredParams({ ...defaults } as P, 'bare `@decorator` usage')\n applyAsDecorator(defaultRegistration, target, propertyKey)\n return undefined\n }\n // Factory call — capture merged params and return a decorator.\n const params = mergeParams(args[0])\n assertRequiredParams(params, 'factory call')\n const registration = buildRegistration(params)\n return (target: object, propertyKey?: string | symbol) => {\n applyAsDecorator(registration, target, propertyKey)\n }\n }\n\n // Set a meaningful `.name` so `console.log(LoadTenant)` and stack\n // traces show `[Function: ContextDecorator(tenant)]` instead of\n // `[Function: decoratorOrFactory]`. Function.name is configurable\n // by default, so this works on the existing function reference.\n Object.defineProperty(decoratorOrFactory, 'name', {\n value: `ContextDecorator(${spec.key})`,\n writable: false,\n enumerable: false,\n configurable: true,\n })\n\n // `Object.assign` returns the intersection type, so the decorator\n // gets `.registration` + `.with` properties typed without the\n // previous `as unknown as ContextDecorator<...>` double cast.\n // Properties go on via assign (writable + enumerable defaults),\n // then `Object.freeze` locks the shape so adopters can't mutate\n // `decorator.registration = …` post-construction.\n const decorator = Object.assign(decoratorOrFactory, {\n with: (params: Partial<P>) => {\n const merged = mergeParams(params)\n assertRequiredParams(merged, '`.with()`')\n return { registration: buildRegistration(merged) }\n },\n })\n\n // `.registration` is a getter, not a plain property, so that reading it\n // on a decorator with undefaulted required params throws instead of\n // handing back a registration whose resolver will see `undefined`.\n // (`Object.assign` would have copied the value and lost the check.)\n Object.defineProperty(decorator, 'registration', {\n get() {\n assertRequiredParams({ ...defaults } as P, '`.registration`')\n return defaultRegistration\n },\n enumerable: true,\n configurable: false,\n })\n\n // `ContextDecorator` is a conditional type (permissive vs required-params\n // shape), and TS can't evaluate a conditional whose input generics are\n // still unresolved — so the assignment can't be checked structurally\n // here regardless of how `decorator` is built. The cast is the price of\n // the call-site guarantee; the two branches differ only in which call\n // forms they expose, and every one of them is implemented above.\n return Object.freeze(decorator) as unknown as ContextDecorator<K, D, P, Ctx, PD>\n}\n\n/**\n * Public export — the positional-generic implementation plus the\n * curried `.withParams<P>()` helper attached as a method. Adopters\n * pick whichever form fits the call site:\n *\n * - **Positional** — `defineContextDecorator(spec)` when there are\n * no per-call params (the common case). `K`, `D`, `Ctx` infer.\n * - **Curried** — `defineContextDecorator.withParams<P>()(spec)`\n * when the contributor takes params; spell only `P`, the rest\n * still infer.\n *\n * Frozen so adopters can't reassign `.withParams` at runtime.\n */\nexport const defineContextDecorator: DefineContextDecoratorFn = Object.freeze(\n Object.assign(defineContextDecoratorImpl, {\n withParams: <P extends Record<string, unknown>>(): DefineContextDecoratorWithParams<P> => {\n return <\n K extends ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n PD extends Partial<P> = Record<never, never>,\n >(\n spec: ContextDecoratorSpec<K, D, P, Ctx, PD>,\n ): ContextDecorator<K, D, P, Ctx, PD> => defineContextDecoratorImpl<K, D, P, Ctx, PD>(spec)\n },\n }),\n) as DefineContextDecoratorFn\n","// Shared asset-key strategy — used by the build pipeline, the\n// runtime dev resolver, and the typegen emitter so all three layers\n// agree on what the manifest looks like.\n//\n// Three strategies:\n//\n// - `'strip'` — drop the final extension. `pages/index.pug` →\n// `pages/index`. Best for namespaces where every basename is\n// unique (mail templates, pdf invoices). Shortest autocomplete.\n//\n// - `'with-extension'` — keep the extension. `pages/index.pug` →\n// `pages/index.pug`. Required when extension siblings coexist.\n//\n// - `'auto'` (default) — strip when safe, keep extensions when not.\n// The grouping pass collects files by stripped basename; groups\n// of one keep the stripped key, groups of more than one keep the\n// full path. Backward compatible for projects with no\n// extension-siblings; lossless for projects that have them.\n//\n// The function returns the path / key pairs in walk order (input\n// order preserved) so callers can iterate without re-sorting. Sort\n// in the caller if a stable manifest order is needed.\n\nexport type AssetKeyStrategy = 'auto' | 'strip' | 'with-extension'\n\nexport interface AssetKeyPair {\n /** Source-relative path the file was discovered at. */\n rel: string\n /** Final logical key for the manifest, including the namespace prefix. */\n key: string\n}\n\nexport interface GroupAssetKeysOptions {\n /**\n * Strategy applied to this namespace. Default `'auto'`.\n *\n * Adopters override per-namespace via `assetMap.<ns>.keys` in\n * kick.config.ts when they want uniform behaviour (e.g. always\n * include the extension regardless of collisions).\n */\n strategy?: AssetKeyStrategy\n}\n\nexport interface GroupAssetKeysResult {\n pairs: AssetKeyPair[]\n /**\n * Number of collision groups resolved by keeping extensions.\n * Surfaces as an informational log (\"auto-resolved N collisions\")\n * so adopters know whether they're relying on auto-mode or not.\n * Always 0 for `'strip'` (overwrites) and `'with-extension'`\n * (extensions always preserved, no collision possible).\n */\n collisionGroupsResolved: number\n}\n\n/**\n * Compute final manifest keys for a namespace's discovered paths,\n * given the chosen strategy. Pure function — no fs / config / globals\n * involved.\n *\n * @param namespace assetMap key — e.g. `'mails'`, `'pages'`.\n * @param paths source-relative file paths (POSIX separators) discovered\n * under the namespace's `src` directory.\n * @param opts grouping options.\n */\nexport function groupAssetKeys(\n namespace: string,\n paths: readonly string[],\n opts: GroupAssetKeysOptions = {},\n): GroupAssetKeysResult {\n const strategy = opts.strategy ?? 'auto'\n\n if (strategy === 'with-extension') {\n return {\n pairs: paths.map((rel) => ({ rel, key: `${namespace}/${rel}` })),\n collisionGroupsResolved: 0,\n }\n }\n\n if (strategy === 'strip') {\n // Strict strip — last-walk-order wins on collision; no\n // collision tracking here because the docs document this as the\n // adopter's choice. The caller (build / runtime / typegen) can\n // still warn if it wants; this function just shapes keys.\n return {\n pairs: paths.map((rel) => ({ rel, key: `${namespace}/${stripExt(rel)}` })),\n collisionGroupsResolved: 0,\n }\n }\n\n // strategy === 'auto' — group by stripped basename, keep\n // extensions only on collision groups.\n const groups = new Map<string, string[]>()\n for (const rel of paths) {\n const strippedKey = `${namespace}/${stripExt(rel)}`\n const arr = groups.get(strippedKey)\n if (arr) {\n arr.push(rel)\n } else {\n groups.set(strippedKey, [rel])\n }\n }\n\n let collisionGroupsResolved = 0\n // Build a per-rel decision table so we preserve input ordering in\n // the output (manifest writers rely on stable ordering).\n const keyByRel = new Map<string, string>()\n for (const [strippedKey, rels] of groups) {\n if (rels.length === 1) {\n keyByRel.set(rels[0], strippedKey)\n } else {\n collisionGroupsResolved += 1\n for (const rel of rels) {\n keyByRel.set(rel, `${namespace}/${rel}`)\n }\n }\n }\n\n return {\n pairs: paths.map((rel) => ({\n rel,\n key: keyByRel.get(rel) ?? `${namespace}/${rel}`,\n })),\n collisionGroupsResolved,\n }\n}\n\n/** Strip the final extension from a path. `'mails/welcome.ejs'` → `'mails/welcome'`. */\nexport function stripExt(path: string): string {\n // Inline the logic instead of importing extname so this module\n // stays usable in pure-browser contexts. Same algorithm: find the\n // last dot AFTER the last separator, slice up to it.\n const lastSlash = path.lastIndexOf('/')\n const filename = lastSlash >= 0 ? path.slice(lastSlash + 1) : path\n const lastDot = filename.lastIndexOf('.')\n if (lastDot <= 0) return path // no ext, or hidden file like `.env`\n const dirPart = lastSlash >= 0 ? path.slice(0, lastSlash + 1) : ''\n return dirPart + filename.slice(0, lastDot)\n}\n","/**\n * Asset manager runtime.\n *\n * Three accessor surfaces over one resolver engine:\n *\n * - `assets.x.y()` — Proxy ambient, autocomplete-friendly default.\n * - `useAssets()` — hook returning the same Proxy; mockable per-call.\n * - `resolveAsset(ns, k)` — string resolver for dynamic dispatch.\n *\n * Resolution path:\n *\n * 1. Load the build-time manifest (`<outDir>/.kickjs-assets.json`)\n * when present. Manifest existence = \"we're running from a built\n * dist\". Fast path; mapped paths returned verbatim.\n * 2. Otherwise (dev / test) walk the assetMap src directories from\n * `kick.config.{json,js,cjs}` and synthesise the same manifest\n * shape in memory. First call pays the walk cost; subsequent\n * calls hit the cache.\n * 3. `KICK_ASSETS_ROOT` env var overrides everything — the resolver\n * treats the path as the manifest directory and skips discovery.\n *\n * @module @forinda/kickjs/core/assets\n */\n\nimport { existsSync, readFileSync, readdirSync } from 'node:fs'\nimport { isAbsolute, join, resolve, sep } from 'node:path'\nimport { createToken, type InjectionToken } from './token'\nimport { groupAssetKeys } from './asset-keys'\n\nexport const ASSET_MANIFEST_VERSION = 1 as const\n\ninterface AssetManifest {\n version: typeof ASSET_MANIFEST_VERSION\n entries: Record<string, string>\n}\n\ninterface ResolvedManifest {\n manifest: AssetManifest\n root: string\n}\n\n/**\n * Augmentable interface — `kick typegen` emits\n * a `declare global` augmentation listing every namespace + key for\n * the project's `assetMap`. Adopters who haven't run typegen yet\n * still get a runtime-correct Proxy; only typed autocomplete waits\n * on the augmentation.\n */\nexport interface KickAssets {}\n\n/**\n * DI token for the `assets` Proxy. Adopter code that wants to\n * receive assets via constructor injection registers via\n * `container.registerInstance(ASSETS, useAssets())` in `bootstrap()`\n * and injects with `@Inject(ASSETS)`.\n */\nexport const ASSETS: InjectionToken<KickAssets> = createToken<KickAssets>('kick/assets/Map')\n\nexport class UnknownAssetError extends Error {\n readonly namespace: string\n readonly key: string\n constructor(namespace: string, key: string) {\n super(\n `Unknown asset '${namespace}/${key}'. ` +\n `Make sure the namespace exists in kick.config.ts assetMap and that the file is present in the source directory.`,\n )\n this.name = 'UnknownAssetError'\n this.namespace = namespace\n this.key = key\n }\n}\n\nlet manifestCache: ResolvedManifest | null | undefined = undefined\n\n/**\n * Convert a filesystem path to its posix (forward-slash) form. The\n * asset manifest and the public `resolveAsset` / `assets.x.y()` /\n * `useAssets()` contract returns posix paths on every platform so\n * adopters can:\n *\n * - pass the result to `express.static` / `res.sendFile` / `ejs.renderFile`\n * without worrying about Windows vs. posix separators\n * - splice the path into URLs (`href`, `src`, CDN keys) without\n * replacing `\\` with `/` by hand\n * - compare two paths from `resolveAsset` for equality on any host\n *\n * Node's `fs.*` and Express's path-handling APIs accept either\n * separator on Windows, so this normalisation is safe — the only\n * adopter code it could break is something explicitly parsing\n * Windows backslashes back out of the result, which would already be\n * brittle.\n */\nfunction toPosixPath(p: string): string {\n return p.replaceAll('\\\\', '/')\n}\n\nexport function resolveAsset(namespace: string, key: string): string {\n const resolved = loadManifest()\n if (!resolved) {\n throw new UnknownAssetError(namespace, key)\n }\n const entry = resolved.manifest.entries[`${namespace}/${key}`]\n if (!entry) {\n throw new UnknownAssetError(namespace, key)\n }\n const abs = isAbsolute(entry) ? entry : resolve(resolved.root, entry)\n return toPosixPath(abs)\n}\n\n/** Reset the manifest cache. Tests use this; production code shouldn't need it. */\nexport function clearAssetCache(): void {\n manifestCache = undefined\n}\n\nfunction loadManifest(): ResolvedManifest | null {\n // In development we deliberately skip the cache so adding or removing\n // a template file (e.g. dropping `welcome-back.ejs` into\n // `src/templates/mails/`) takes effect without a server restart. The\n // synthesise-dev-manifest path walks ~10 files; the cost is negligible\n // compared to the user-visible surprise of \"I added a file but\n // assets.x.y() throws\". Production keeps the cache because the built\n // manifest under `dist/.kickjs-assets.json` doesn't change at runtime.\n if (process.env.NODE_ENV !== 'production') {\n return discoverManifest()\n }\n if (manifestCache !== undefined) return manifestCache\n manifestCache = discoverManifest()\n return manifestCache\n}\n\nfunction discoverManifest(): ResolvedManifest | null {\n // `KICK_ASSETS_ROOT` is an explicit, deliberate override — it wins in\n // both dev and prod.\n const envRoot = process.env.KICK_ASSETS_ROOT\n if (envRoot) {\n const fromEnv = readBuiltManifest(envRoot)\n if (fromEnv) return fromEnv\n }\n\n const cwd = process.cwd()\n const config = loadConfigSync(cwd)\n\n // In dev, prefer a live walk of the source tree over any built\n // manifest left on disk by an earlier `kick build`. Otherwise a stale\n // `dist/.kickjs-assets.json` shadows the live `src/` tree: a freshly\n // added template file would resolve to the old map (or throw\n // UnknownAssetError) until the next rebuild — exactly the surprise the\n // cache-skip in `loadManifest` exists to avoid. `synthesiseDevManifest`\n // returns null only when there's no `assetMap` config at all, in which\n // case we fall through to the built-manifest probes below.\n if (process.env.NODE_ENV !== 'production') {\n const fresh = synthesiseDevManifest(cwd, config)\n if (fresh) return fresh\n }\n\n // Honour `kick.config.build.outDir` BEFORE the standard probe list\n // so adopters with non-default Vite outputs (e.g. `output/`,\n // `target/`, anything else) don't have to set KICK_ASSETS_ROOT\n // every time. Cached config load — same loader the dev fallback\n // uses. Skip when outDir matches one of the standard candidates;\n // it'd be probed redundantly otherwise.\n const configOutDir = config?.build?.outDir\n if (configOutDir && !(STANDARD_OUT_DIRS as readonly string[]).includes(configOutDir)) {\n const found = readBuiltManifest(resolve(cwd, configOutDir))\n if (found) return found\n }\n\n for (const candidate of STANDARD_OUT_DIRS) {\n const found = readBuiltManifest(join(cwd, candidate))\n if (found) return found\n }\n\n return synthesiseDevManifest(cwd, config)\n}\n\nconst STANDARD_OUT_DIRS = ['dist', 'build', 'out'] as const\n\nfunction readBuiltManifest(dir: string): ResolvedManifest | null {\n const path = join(dir, '.kickjs-assets.json')\n if (!existsSync(path)) return null\n try {\n const raw = readFileSync(path, 'utf-8')\n const parsed = JSON.parse(raw) as Partial<AssetManifest>\n if (parsed.version !== ASSET_MANIFEST_VERSION) return null\n if (!parsed.entries || typeof parsed.entries !== 'object') return null\n return { manifest: parsed as AssetManifest, root: resolve(dir) }\n } catch {\n return null\n }\n}\n\ninterface AssetMapConfigShape {\n assetMap?: Record<\n string,\n {\n src: string\n dest?: string\n glob?: string\n keys?: 'auto' | 'strip' | 'with-extension'\n }\n >\n build?: { outDir?: string }\n}\n\nfunction synthesiseDevManifest(\n cwd: string,\n preloadedConfig?: AssetMapConfigShape | null,\n): ResolvedManifest | null {\n const config = preloadedConfig === undefined ? loadConfigSync(cwd) : preloadedConfig\n if (!config?.assetMap) return null\n\n const entries: Record<string, string> = {}\n // Walk order is filesystem-dependent on Linux/macOS, so we collect\n // every match per-namespace first, then sort + feed `groupAssetKeys`\n // for stable manifest output. Build pipeline does the same — both\n // layers agree on the resulting key set.\n for (const [namespace, entry] of Object.entries(config.assetMap)) {\n if (!entry || typeof entry.src !== 'string') continue\n const srcAbs = resolve(cwd, entry.src)\n if (!existsSync(srcAbs)) continue\n\n const collected: Array<{ rel: string; abs: string }> = []\n walkSync(srcAbs, srcAbs, (relPath, absPath) => {\n if (entry.glob && !matchesGlobLite(relPath, entry.glob, namespace)) return\n collected.push({ rel: relPath, abs: absPath })\n })\n collected.sort((a, b) => a.rel.localeCompare(b.rel))\n\n const { pairs, collisionGroupsResolved } = groupAssetKeys(\n namespace,\n collected.map((c) => c.rel),\n { strategy: entry.keys ?? 'auto' },\n )\n const absByRel = new Map(collected.map((c) => [c.rel, c.abs]))\n for (const { rel, key } of pairs) {\n const abs = absByRel.get(rel)\n if (abs) entries[key] = abs\n }\n if (collisionGroupsResolved > 0 && process.env.NODE_ENV !== 'production') {\n // Dev-only nudge — production runs read the build manifest where\n // the collision was already resolved at build time.\n console.log(\n `[kickjs/assets] '${namespace}': ${collisionGroupsResolved} basename collision(s) auto-resolved by keeping extensions ` +\n `(set assetMap.${namespace}.keys to 'strip' or 'with-extension' to override).`,\n )\n }\n }\n\n return { manifest: { version: ASSET_MANIFEST_VERSION, entries }, root: cwd }\n}\n\nconst warnedGlobs = new Set<string>()\n\n/**\n * Tiny glob matcher used only by dev manifest synthesis. Covers the\n * common assetMap glob patterns (`**\\/*`, `**\\/*.ext`,\n * `**\\/*.{a,b}`). Anything exotic prints a one-time warning + falls\n * through to `**\\/*` semantics rather than silently dropping files.\n *\n * The build pipeline (`packages/cli/src/asset-manager/build.ts`) uses\n * the full `glob` package; adopters who hit the dev-mode warning\n * should run `kick build:assets` to produce a real manifest.\n */\nfunction matchesGlobLite(relPath: string, pattern: string, namespace: string): boolean {\n if (pattern === '**/*' || pattern === '**') return true\n const singleExt = /^\\*\\*\\/\\*\\.(\\w+)$/.exec(pattern)\n if (singleExt) return relPath.endsWith(`.${singleExt[1]}`)\n const braceExt = /^\\*\\*\\/\\*\\.\\{([^}]+)\\}$/.exec(pattern)\n if (braceExt) {\n const exts = braceExt[1].split(',').map((e) => e.trim())\n return exts.some((ext) => relPath.endsWith(`.${ext}`))\n }\n const tag = `${namespace}::${pattern}`\n if (!warnedGlobs.has(tag)) {\n warnedGlobs.add(tag)\n console.warn(\n `[kickjs/assets] Dev-mode glob '${pattern}' on '${namespace}' isn't recognised by the lite matcher. ` +\n `Falling back to '**/*'. Run 'kick build:assets' to use the full glob engine.`,\n )\n }\n return true\n}\n\nfunction walkSync(root: string, dir: string, onFile: (rel: string, abs: string) => void): void {\n let entries\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n } catch {\n return\n }\n for (const entry of entries) {\n if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue\n const full = join(dir, entry.name)\n if (entry.isDirectory()) {\n walkSync(root, full, onFile)\n } else if (entry.isFile()) {\n const rel = full\n .slice(root.length + 1)\n .split(sep)\n .join('/')\n onFile(rel, full)\n }\n }\n}\n\n/**\n * Sync `kick.config.{json,cjs,js}` loader for dev manifest\n * synthesis. Avoids dynamic `import()` (would force every assets\n * lookup to be async). TypeScript configs (`kick.config.ts`) are\n * skipped here — adopters who use them should run `kick build:assets`\n * to produce a real manifest the resolver can read.\n */\nfunction loadConfigSync(cwd: string): AssetMapConfigShape | null {\n const jsonPath = join(cwd, 'kick.config.json')\n if (existsSync(jsonPath)) {\n try {\n return JSON.parse(readFileSync(jsonPath, 'utf-8')) as AssetMapConfigShape\n } catch {\n return null\n }\n }\n for (const filename of ['kick.config.cjs', 'kick.config.js']) {\n const path = join(cwd, filename)\n if (!existsSync(path)) continue\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { createRequire } = require('node:module') as typeof import('node:module')\n const req = createRequire(join(cwd, 'package.json'))\n const mod = req(path) as { default?: AssetMapConfigShape } & AssetMapConfigShape\n return mod.default ?? mod ?? null\n } catch {\n continue\n }\n }\n\n // Fallback: the CLI snapshot of a transpiled `kick.config.ts`. We\n // can't transpile TS synchronously on the asset hot path, so the CLI\n // (`loadKickConfig`) mirrors the JSON-serialisable `assetMap` +\n // `build.outDir` into `.kickjs/kick.config.json` whenever it loads a\n // `.ts`/`.js` config. This is what makes `assets.x.y()` resolve in\n // dev for `.ts`-config projects before the first production build.\n const snapshotPath = join(cwd, '.kickjs', 'kick.config.json')\n if (existsSync(snapshotPath)) {\n try {\n return JSON.parse(readFileSync(snapshotPath, 'utf-8')) as AssetMapConfigShape\n } catch {\n return null\n }\n }\n return null\n}\n\n/**\n * Keys we MUST return `undefined` for: returning anything truthy\n * here causes Promise resolution to treat the Proxy as a thenable\n * and `await` it (resolving to whatever `then.call(...)` produces).\n * Two-element list deliberately — every other coercion-related key\n * goes through {@link PASSTHROUGH_KEYS} below so `String(assets)`,\n * template literals, and `util.inspect` keep working.\n */\nconst PROMISE_DETECTION_KEYS: ReadonlySet<string | symbol> = new Set<string | symbol>(['then'])\n\n/**\n * Keys we let fall through to the proxy target (a function-shaped\n * object). The default `Object.prototype` implementations of\n * `toString` / `valueOf` etc. are correct for these — returning\n * `undefined` here would break `String(assets)` and template\n * literals (`${assets}`) because `Symbol.toPrimitive` falling through\n * to a missing `toString` throws. Adopters that hit `assets.toString`\n * get `'function () {}'` back, which is fine — the contract is\n * \"asset access returns paths\"; everything else is incidental.\n */\nconst PASSTHROUGH_KEYS: ReadonlySet<string | symbol> = new Set<string | symbol>([\n 'toString',\n 'valueOf',\n 'constructor',\n 'prototype',\n 'name',\n 'length',\n 'asymmetricMatch',\n 'nodeType',\n Symbol.toPrimitive,\n Symbol.toStringTag,\n Symbol.iterator,\n Symbol.asyncIterator,\n])\n\nfunction createNamespaceProxy(parts: readonly string[]): unknown {\n const target = function () {} as unknown as Record<string, unknown>\n return new Proxy(target, {\n apply() {\n if (parts.length < 2) {\n throw new UnknownAssetError(parts[0] ?? '', '')\n }\n const [namespace, ...rest] = parts\n return resolveAsset(namespace, rest.join('/'))\n },\n get(t, prop) {\n if (PROMISE_DETECTION_KEYS.has(prop)) return undefined\n if (PASSTHROUGH_KEYS.has(prop)) return Reflect.get(t, prop)\n if (typeof prop === 'symbol') return undefined\n return createNamespaceProxy([...parts, prop])\n },\n })\n}\n\nfunction createAssetProxy(): KickAssets {\n return new Proxy({} as Record<string, unknown>, {\n get(t, prop) {\n if (PROMISE_DETECTION_KEYS.has(prop)) return undefined\n if (PASSTHROUGH_KEYS.has(prop)) return Reflect.get(t, prop)\n if (typeof prop === 'symbol') return undefined\n return createNamespaceProxy([prop])\n },\n }) as unknown as KickAssets\n}\n\n/**\n * Variant A — the ambient Proxy. Default accessor for static call\n * sites where the namespace + key are literals known at compile time.\n *\n * @example\n * ```ts\n * import { assets } from '@forinda/kickjs'\n * const path = assets.mails.welcome()\n * ```\n */\nexport const assets: KickAssets = createAssetProxy()\n\n/**\n * Variant B — the hook accessor. Returns the same Proxy as `assets`;\n * factory shape exists for testability + DI. Mock via\n * `vi.mock('@forinda/kickjs', () => ({ useAssets: () => fakeAssets }))`.\n *\n * @example\n * ```ts\n * import { useAssets } from '@forinda/kickjs'\n * class MailService {\n * private assets = useAssets()\n * send(name: string) {\n * return ejs.renderFile(this.assets.mails[name](), {})\n * }\n * }\n * ```\n */\nexport function useAssets(): KickAssets {\n return assets\n}\n\n// Install this module's resolver into the container's asset slot — the\n// container deliberately avoids importing this file (edge purity; see\n// asset-resolver-slot.ts). Runs at module scope so any node app that loads\n// the asset manager gets full @Asset injection.\nimport { _setAssetResolver } from './asset-resolver-slot'\n_setAssetResolver(resolveAsset)\n"],"mappings":"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"}
1
+ {"version":3,"file":"core-DKrKVLfj.mjs","names":[],"sources":["../src/core/context-decorator.ts","../src/core/asset-keys.ts","../src/core/assets.ts"],"sourcesContent":["import { assertFlagTest, type RouteFlagTest } from './route-flag'\nimport 'reflect-metadata'\nimport { METADATA, type Constructor, type MaybePromise } from './interfaces'\nimport { pushClassMeta, pushMethodMeta } from './metadata'\nimport type { InjectionToken } from './token'\nimport type { ContextMetaKey, ExecutionContext, MetaValue } from './execution-context'\n\n/**\n * String-literal union of every known context key — the union of the\n * keys declared on {@link ContextKeys} (key-only registry) and\n * {@link ContextMeta} (value-type registry). Resolves to `string` when\n * the project hasn't augmented either yet (so a first-day project keeps\n * compiling), and narrows to the concrete keys once the\n * `declare module '@forinda/kickjs' { interface ContextKeys/ContextMeta { ... } }`\n * blocks land.\n *\n * Used as the element type of `dependsOn`, so a typo like\n * `dependsOn: ['tenent']` becomes a TS error instead of a boot-time\n * `MissingContributorError`.\n *\n * Unioning BOTH registries is deliberate: it means adding a value type\n * via `ContextMeta` automatically makes that key a valid `dependsOn`\n * target, and you can register a dependsOn-able key in `ContextKeys`\n * without being forced to give it a value type. (Before the split,\n * `dependsOn` was keyed off `ContextMeta` alone, so augmenting\n * `ContextMeta` for some keys broke `dependsOn` for every key you\n * hadn't added there.)\n */\n// Moved to `execution-context` (beside the registries it reads) so that\n// module can constrain `get`/`set` without a circular import. Re-exported\n// here so existing `from '@forinda/kickjs'` imports keep resolving.\nexport type { ContextMetaKey } from './execution-context'\n\n/**\n * What a single `deps` entry is allowed to be — the runtime calls\n * `container.resolve(value)`, which accepts an injection token brand\n * or a constructor (decorated class). Anything else (a string literal,\n * an array, a plain object) silently fails at boot when the runner\n * tries to resolve it; constraining at the type level surfaces the\n * mistake in the editor instead.\n */\nexport type DepValue = InjectionToken<unknown> | Constructor<unknown>\n\n/**\n * Maps a `deps` declaration object to its resolved-instance shape.\n *\n * `deps: { db: DB_TOKEN, cache: CacheService }` produces\n * `{ db: Db, cache: CacheService }` — the runner resolves each value\n * against the DI container and hands the result to `resolve()`.\n */\nexport type ResolvedDeps<D extends Record<string, DepValue>> = {\n [K in keyof D]: D[K] extends InjectionToken<infer T>\n ? T\n : D[K] extends Constructor<infer T>\n ? T\n : never\n}\n\n/**\n * Keys of `T` that are declared required (i.e. not `?`-optional).\n */\ntype RequiredParamKeys<T> = {\n // eslint-disable-next-line @typescript-eslint/ban-types\n [K in keyof T]-?: {} extends Pick<T, K> ? never : K\n}[keyof T]\n\n/**\n * Params a call site MUST supply: the required keys of `P` that\n * `paramDefaults` (type `PD`) doesn't already provide.\n *\n * The `string extends keyof P` guard short-circuits the index-signature\n * case — `P` defaults to `Record<string, never>` for the overwhelmingly\n * common zero-params contributor, and a mapped type over `string` would\n * otherwise report \"every key is required\" and break `@Foo` for every\n * existing decorator in the wild.\n */\nexport type MissingParamKeys<P, PD> = string extends keyof P\n ? never\n : Exclude<RequiredParamKeys<P>, keyof PD>\n\n/**\n * The argument type for a call site (`@Foo({...})`, `.with({...})`).\n *\n * `Partial<P>` when every required field has a default, so adopters keep\n * overriding just the fields they care about. Otherwise `Partial<P>`\n * intersected with the undefaulted required fields, making those\n * mandatory at each call site.\n */\nexport type CallSiteParams<P, PD> = [MissingParamKeys<P, PD>] extends [never]\n ? Partial<P>\n : Partial<P> & Pick<P, MissingParamKeys<P, PD> & keyof P>\n\n/**\n * Spec passed to {@link defineContextDecorator}. Describes one entry in\n * the per-request contributor pipeline.\n *\n * @typeParam K - The {@link ContextMeta} key this contributor populates.\n * @typeParam D - DI dependency declaration shape (see {@link ResolvedDeps}).\n * @typeParam Ctx - Concrete execution-context type. Defaults to the abstract\n * {@link ExecutionContext}; HTTP contributors typically\n * default to `RequestContext` at the call site.\n * @typeParam PD - Inferred from `paramDefaults`. Drives which params each\n * call site is required to supply — see {@link CallSiteParams}.\n * Never spell this by hand.\n */\nexport interface ContextDecoratorSpec<\n K extends ContextMetaKey = ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n PD extends Partial<P> = Partial<P>,\n> {\n /** ContextMeta key the resolved value is written to. */\n key: K\n /**\n * Skip this contributor on routes carrying the named route flag\n * (`defineRouteFlag`). See {@link ContributorRegistration.skipWhen}.\n */\n skipWhen?: RouteFlagTest\n /**\n * Run this contributor only on routes carrying the named route flag, or\n * matching the predicate.\n */\n onlyWhen?: RouteFlagTest\n /**\n * DI dependencies resolved by the runner before `resolve()` runs.\n * Map entry keys become property names on the resolved-deps argument\n * passed to `resolve()`.\n */\n deps?: D\n /**\n * Other contributor keys that must populate before this one runs.\n * Enforced via topo-sort at startup; missing deps and cycles fail boot.\n *\n * Typed against {@link ContextMetaKey} — the union of `keyof ContextMeta`\n * and `keyof ContextKeys` — once the project augments either, so\n * `dependsOn: ['tenent']` (typo) is a TS error, not a boot-time\n * `MissingContributorError`. Falls back to plain `string[]` when both\n * registries are empty (no augmentation yet) so first-day projects keep\n * compiling. Cast escape hatch: `dependsOn: ['custom-key' as ContextMetaKey]`.\n */\n dependsOn?: readonly ContextMetaKey[]\n /**\n * If `true`, a thrown `resolve` skips the contributor instead of\n * forwarding to the request error handler. The key is left unset\n * and downstream consumers see `ctx.get(key) === undefined`.\n */\n optional?: boolean\n /**\n * HTTP routes only: run this contributor before request validation and\n * `@Middleware()` handlers, instead of after them.\n *\n * For contributors that must answer before a request's shape is checked —\n * authentication above all. Without it, a request with no credentials and a\n * malformed body is rejected by validation (422) before the auth contributor\n * can reject it (401), telling an anonymous caller what the schema expects.\n *\n * `ctx.body` / `ctx.query` / `ctx.params` are **unvalidated** at this point,\n * and file uploads may not be parsed yet — read credentials (headers,\n * cookies), not the payload. It may only `dependsOn` contributors that also\n * set `beforeValidation`; anything else fails boot.\n */\n beforeValidation?: boolean\n /**\n * Default per-call params merged with the call-site params; call-site\n * wins. When omitted, params default to `{}` and the resolver\n * receives an empty object — back-compat with today's zero-arg\n * decorators.\n *\n * Param shape is intentionally unconstrained: plain data, functions,\n * and closures are all valid. Adopters who want runtime validation\n * should perform it inside `resolve()` — keeps the framework\n * validator-agnostic so projects can plug Zod / Valibot / hand-rolled\n * checks without the framework caring.\n *\n * **Required fields are enforced at the call site.** A required field\n * of `P` that has no entry here must be supplied wherever the\n * decorator is applied — `@Foo({ action: 'audit:read' })`. The bare\n * `@Foo` form and `.registration` stop type-checking for such a\n * decorator, because neither can supply the value.\n *\n * This exists so nobody has to invent a meaningless default just to\n * satisfy the type (`action: 'settings:read'` on a permission\n * contributor every call site overrides anyway). A default that is\n * never correct is worse than no default: forget the argument at one\n * call site and the route silently gates on the placeholder instead\n * of failing to compile.\n *\n * Omit this entirely for the common zero-params case — `P` defaults\n * to `Record<string, never>`, which requires nothing.\n */\n paramDefaults?: PD\n /**\n * Runtime mirror of the compile-time requirement above: param names\n * that must be present after merging call-site params over\n * `paramDefaults`. A missing one throws `TypeError` at the point of\n * use (decoration time for `@Foo`, call time for `@Foo({...})` /\n * `.with({...})`), naming the decorator and the field.\n *\n * The type-level check already covers TypeScript call sites, so this\n * is for the paths types can't reach: plain-JS adopters, `as any`\n * escapes, and params assembled dynamically. Optional — list only\n * fields whose absence is a correctness bug rather than a default.\n */\n requiredParams?: readonly (keyof P & string)[]\n /**\n * Hook invoked when `resolve()` throws and `optional` is `false`.\n * Returning a value writes it to `ctx.set(key, …)`; returning\n * `undefined` / `void` skips. Throwing inside the hook forwards\n * the new error to the request error handler.\n *\n * Async-permitted by design — adopters frequently want to\n * `await auditService.log(err)` or `await cache.fallback(...)`.\n *\n * The third `params` argument carries the resolved per-call params\n * (call-site overrides merged on top of `paramDefaults`).\n */\n onError?: (err: unknown, ctx: Ctx, params: P) => MaybePromise<MetaValue<K> | undefined | void>\n /**\n * Compute and return the value to write into `ctx.set(key, …)`.\n *\n * The third `params` argument carries the resolved per-call params\n * (call-site overrides merged on top of `paramDefaults`). When the\n * decorator is applied zero-arg (`@Foo`), `params` equals\n * `paramDefaults` (or `{}` if defaults are omitted). When applied as\n * a factory (`@Foo({ ... })`), the call-site object is merged on top.\n */\n resolve: (ctx: Ctx, deps: ResolvedDeps<D>, params: P) => MaybePromise<MetaValue<K>>\n}\n\n/**\n * Internal, normalised registration produced by {@link defineContextDecorator}.\n *\n * Frozen and shared by every use site of the decorator (method, class,\n * module, adapter, global). The pipeline builder dedupes by `key` using\n * the precedence rule defined in §20.4 of `architecture.md`.\n */\nexport interface ContributorRegistration<\n K extends ContextMetaKey = ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n> {\n readonly key: K\n readonly deps: D\n readonly dependsOn: readonly ContextMetaKey[]\n readonly optional: boolean\n /**\n * Per-call params from the spec are baked into the closure at\n * decorator-construction time, so the runner-facing `onError`\n * exposes only `(err, ctx)`.\n */\n readonly onError?: (err: unknown, ctx: Ctx) => MaybePromise<MetaValue<K> | undefined | void>\n /**\n * Per-call params from the spec are baked into the closure at\n * decorator-construction time, so the runner-facing `resolve`\n * exposes only `(ctx, deps)`. Adopters who write their own\n * registrations (without `defineContextDecorator`) match this\n * shape directly — params are an implementation detail of the\n * decorator factory, not of the runtime pipeline.\n */\n readonly resolve: (ctx: Ctx, deps: ResolvedDeps<D>) => MaybePromise<MetaValue<K>>\n /**\n * Stack snapshot captured the moment the decorator was defined.\n * Used by the contributor-pipeline builder to point boot-time\n * errors (`MissingContributorError`, `ContributorCycleError`,\n * `DuplicateContributorError`) at the adopter file that declared\n * the broken contributor — instead of forcing them to grep for\n * the key string.\n *\n * Optional because adopters who hand-roll a `ContributorRegistration`\n * (without `defineContextDecorator`) may not have a useful stack to\n * attach. Format is the raw `Error.stack` string; consumers slice\n * the first non-framework frame for display.\n */\n readonly definedAt?: string\n /** Runs before request validation on HTTP routes — see {@link ContextDecoratorSpec.beforeValidation}. */\n readonly beforeValidation?: boolean\n /**\n * Skip this contributor on routes carrying the named route flag.\n *\n * The composable way to say \"not here\". Exempting a contributor otherwise\n * means registering a permissive twin under the same key, which only its\n * author can do — a flag is declared on the route, so an adopter can exempt\n * a plugin's contributor without owning its key or forking it.\n *\n * ```ts\n * defineHttpContextDecorator({ key: 'user', skipWhen: 'auth.public', resolve })\n * ```\n */\n readonly skipWhen?: RouteFlagTest\n /**\n * Run this contributor **only** on routes carrying the named flag. The\n * inverse of {@link skipWhen}, and the case with no expression today short\n * of an `if` inside every resolver.\n */\n readonly onlyWhen?: RouteFlagTest\n}\n\n/**\n * Type-erased registration shape used in the public collection slots\n * (`AppModule.contributors?()`, `AppAdapter.contributors?()`,\n * `KickPlugin.contributors?()`, `bootstrap({ contributors })`).\n *\n * The narrow `ContributorRegistration` carries a `Ctx` parameter so\n * `resolve(ctx, deps)` is typed at the *definition* site, but `Ctx`\n * sits in a contravariant position on `resolve` — meaning a\n * `ContributorRegistration<…, RequestContext>` is *not* assignable to\n * `ContributorRegistration<…, ExecutionContext>` even though every\n * `RequestContext` is-a `ExecutionContext`. That's TS-sound but\n * useless: the runner only ever calls a contributor with the same\n * concrete ctx the route mounts under, so an HTTP-typed contributor\n * never sees a non-HTTP ctx. Erasing `Ctx` to `any` in the collection\n * type lets adopters store contributors typed against `RequestContext`\n * (or any future transport-specific ctx) in the same array without\n * casting.\n */\nexport type AnyContributorRegistration = ContributorRegistration<\n ContextMetaKey,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any\n>\n\n/** Convenience alias for `AnyContributorRegistration[]` collections. */\nexport type ContributorRegistrations =\n | AnyContributorRegistration[]\n | readonly AnyContributorRegistration[]\n\n/**\n * The decorator function returned by either a zero-arg\n * {@link ContextDecorator} application or a factory call. Overloaded\n * to satisfy both class and method decorator shapes — adopters apply\n * the same decorator to either site without TS rejecting the call.\n */\nexport interface ContextDecoratorTarget {\n /** Class decorator usage. */\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n (target: Function): void\n /** Method decorator usage. */\n (target: object, propertyKey: string | symbol, descriptor?: PropertyDescriptor): void\n}\n\n/**\n * The value returned by {@link defineContextDecorator}.\n *\n * Resolves to one of two shapes depending on whether every required\n * field of `P` has a `paramDefaults` entry:\n *\n * - {@link ContextDecoratorWithDefaults} — the classic shape. Usable\n * bare (`@Foo`), as a factory (`@Foo({...})`), via `.with(params)`,\n * and via `.registration`.\n * - {@link ContextDecoratorRequiringParams} — when `P` has required\n * fields `paramDefaults` doesn't cover. Only the factory and\n * `.with(params)` forms exist, because the others have no way to\n * supply the missing values. Applying such a decorator bare is a\n * compile error rather than a silent `undefined` at request time.\n *\n * `PD` is inferred from the spec's `paramDefaults`; the default here\n * (`Partial<P>`) keeps hand-written `ContextDecorator<K, D, P>`\n * annotations resolving to the permissive shape as before.\n */\nexport type ContextDecorator<\n K extends ContextMetaKey = ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n PD = Partial<P>,\n> = [MissingParamKeys<P, PD>] extends [never]\n ? ContextDecoratorWithDefaults<K, D, P, Ctx>\n : ContextDecoratorRequiringParams<K, D, P, Ctx, PD>\n\n/**\n * Decorator whose params are fully defaulted (or which takes none).\n * Callable as a class or method decorator directly, as a factory, or\n * via `.with(params)` / `.registration` at non-decorator registration\n * sites.\n */\nexport interface ContextDecoratorWithDefaults<\n K extends ContextMetaKey = ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n> {\n /**\n * Class decorator usage with `paramDefaults` — `@Foo class C {}`.\n * The class constructor is a `Function`, so this overload only\n * matches actual classes (not plain object literals — which\n * fall through to the factory overload below).\n */\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n (target: Function): void\n /**\n * Method decorator usage with `paramDefaults` — `@Foo` over a\n * method. The required `propertyKey` distinguishes this from the\n * factory overload's single-object-arg call shape.\n */\n (target: object, propertyKey: string | symbol, descriptor?: PropertyDescriptor): void\n /**\n * Zero-arg factory usage — `@Foo()` returns a decorator with\n * `paramDefaults` applied. Equivalent in behaviour to the bare\n * `@Foo` form, exposed here so adopters who prefer \"always call the\n * factory\" don't need an `as any` cast.\n */\n (): ContextDecoratorTarget\n /**\n * Factory usage — `@Foo({ ... })` returns the actual decorator with\n * call-site params merged onto `paramDefaults`. Matches a single\n * params object; plain objects don't satisfy the `Function` /\n * 2-arg-minimum constraints above, so TS picks this overload.\n *\n * Accepts `Partial<P>` so adopters can override only the fields\n * they care about — anything missing falls back to `paramDefaults`\n * at runtime. Inside `resolve()`, `params` is the fully merged\n * `P`, never a `Partial<P>`.\n *\n * The returned decorator is overloaded for class AND method usage —\n * `@Foo({...}) class C {}` and `@Foo({...}) m() {}` both type-check.\n */\n (params: Partial<P>): ContextDecoratorTarget\n /**\n * Bare registration — uses `paramDefaults` only. Use this at non-\n * decorator registration sites (module / adapter / bootstrap) when\n * the call site doesn't need to override params.\n */\n readonly registration: ContributorRegistration<K, D, Ctx>\n /**\n * Build a registration with call-site params merged onto\n * `paramDefaults`. Use this at non-decorator registration sites\n * when the call site DOES want to override params:\n *\n * ```ts\n * bootstrap({\n * contributors: [LoadTenant.with({ source: 'subdomain' }).registration],\n * })\n * ```\n *\n * `Partial<P>` for the same reason as the factory overload — the\n * runtime merges with `paramDefaults`, so adopters only specify\n * fields they want to override.\n */\n with(params: Partial<P>): { readonly registration: ContributorRegistration<K, D, Ctx> }\n}\n\n/**\n * Decorator with at least one required param that `paramDefaults`\n * doesn't cover. Deliberately narrower than\n * {@link ContextDecoratorWithDefaults}: the bare `@Foo` form, the\n * zero-arg `@Foo()` form, and the `.registration` accessor are all\n * absent, because none of them can supply the missing value.\n *\n * ```ts\n * const OperatorPerm = defineContextDecorator.withParams<{ action: string }>()({\n * key: 'operatorPerm',\n * resolve: (ctx, _deps, { action }) => check(ctx, action),\n * })\n *\n * class Ops {\n * @OperatorPerm // ✗ compile error — `action` missing\n * @Get('/a')\n * a(ctx: RequestContext) {}\n *\n * @OperatorPerm({ action: 'read' }) // ✓\n * @Get('/b')\n * b(ctx: RequestContext) {}\n * }\n * ```\n *\n * If you want the bare form back, give `action` a default in\n * `paramDefaults` — but only when the default is genuinely correct for\n * an undecorated route, which for a permission string it usually isn't.\n */\nexport interface ContextDecoratorRequiringParams<\n K extends ContextMetaKey = ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n PD = Partial<P>,\n> {\n /**\n * Factory usage — the only decorator form available here. The\n * argument type carries every required field `paramDefaults` didn't\n * provide, so omitting one is a compile error at this call site.\n */\n (params: CallSiteParams<P, PD>): ContextDecoratorTarget\n /**\n * Build a registration for non-decorator registration sites. Same\n * required-field rule as the factory form.\n */\n with(params: CallSiteParams<P, PD>): {\n readonly registration: ContributorRegistration<K, D, Ctx>\n }\n}\n\n/**\n * Curried params-first helper for {@link defineContextDecorator}.\n *\n * Solves the partial-inference problem: TS generics are positional, so\n * the moment an adopter wants to specify the per-call params shape\n * `P`, they're forced to spell `K` and `D` by hand — losing the\n * automatic `deps` inference that drives the `(ctx, deps, params) =>`\n * resolver signature:\n *\n * ```ts\n * // ❌ Positional generics — must repeat the deps type, and `D` no\n * // longer flows from `spec.deps` into `resolve(ctx, deps, ...)`.\n * defineContextDecorator<'tenant', { repo: typeof TENANT_REPO }, MyParams>({\n * key: 'tenant',\n * deps: { repo: TENANT_REPO },\n * resolve: (ctx, { repo }, params) => ...,\n * })\n *\n * // ✅ Curried form — only `P` is spelled; `K`, `D`, `Ctx` infer.\n * defineContextDecorator.withParams<MyParams>()({\n * key: 'tenant',\n * deps: { repo: TENANT_REPO }, // D inferred\n * resolve: (ctx, { repo }, params) => ..., // deps + params both typed\n * })\n * ```\n *\n * Use this whenever the contributor takes call-site params; use the\n * positional form when there are no params (the common case).\n */\nexport interface DefineContextDecoratorWithParams<P extends Record<string, unknown>> {\n <\n K extends ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n // Inferred from `spec.paramDefaults`. The `Record<never, never>`\n // default (NOT `Partial<P>`) is what makes an omitted `paramDefaults`\n // mean \"nothing is defaulted\" — so every required field of `P` lands\n // on the call site. `Partial<P>` here would make `keyof PD` cover all\n // of `P` and silently defeat the whole check.\n PD extends Partial<P> = Record<never, never>,\n >(\n spec: ContextDecoratorSpec<K, D, P, Ctx, PD>,\n ): ContextDecorator<K, D, P, Ctx, PD>\n}\n\n/**\n * The public shape of {@link defineContextDecorator}. Combines the\n * original positional-generic call signature with the curried\n * `.withParams<P>()` helper that fixes partial-inference for the\n * parameterised case.\n */\nexport interface DefineContextDecoratorFn {\n <\n K extends ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n PD extends Partial<P> = Record<never, never>,\n >(\n spec: ContextDecoratorSpec<K, D, P, Ctx, PD>,\n ): ContextDecorator<K, D, P, Ctx, PD>\n\n /**\n * Curried entry point. Spell only the per-call params shape `P`;\n * `K`, `D`, and `Ctx` are inferred from the spec passed to the\n * returned function. See {@link DefineContextDecoratorWithParams}\n * for the rationale.\n */\n withParams<P extends Record<string, unknown>>(): DefineContextDecoratorWithParams<P>\n}\n\n/**\n * Define a typed context contributor.\n *\n * The returned function works as a method or class decorator:\n *\n * ```ts\n * const LoadTenant = defineContextDecorator({\n * key: 'tenant',\n * deps: { tenants: TenantsRepo },\n * resolve: (ctx, { tenants }) => tenants.findByHost(ctx.requestId),\n * })\n *\n * @LoadTenant\n * @Get('/me')\n * me(ctx: RequestContext) {\n * ctx.get('tenant') // typed via ContextMeta\n * }\n * ```\n *\n * For non-decorator registration (module/adapter/bootstrap), pass\n * `LoadTenant.registration` into the appropriate contributors list.\n *\n * Boot-time validation: a missing `resolve`, an empty `key`, or a\n * non-array `dependsOn` throws `TypeError` here at definition time\n * (typically module-load) rather than waiting for the first request.\n * See `architecture.md` §20 for the full pipeline plan.\n *\n * **Parameterised contributors:** TS generics are positional, so to\n * specify `P` (the per-call params shape) with this signature you\n * also have to spell `K` and `D` — which loses automatic `deps`\n * inference. Use {@link DefineContextDecoratorFn.withParams} instead:\n * `defineContextDecorator.withParams<MyParams>()(spec)`.\n */\nfunction defineContextDecoratorImpl<\n K extends ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n PD extends Partial<P> = Record<never, never>,\n>(spec: ContextDecoratorSpec<K, D, P, Ctx, PD>): ContextDecorator<K, D, P, Ctx, PD> {\n // ---- Boot-time validation --------------------------------------\n // Surface mistakes the moment `defineContextDecorator` is called\n // (typically module-load time) instead of letting them ride to\n // request time as cryptic ContextMeta misses or container failures.\n // Cheap, ~15 lines, catches a whole class of definition-time bugs.\n if (spec === null || typeof spec !== 'object') {\n throw new TypeError(\n 'defineContextDecorator: spec must be an object literal. ' +\n 'See architecture.md §20.4 for the spec shape.',\n )\n }\n if (typeof spec.key !== 'string' || spec.key.length === 0) {\n const got = typeof spec.key === 'string' ? '\"\"' : typeof spec.key\n throw new TypeError(\n `defineContextDecorator: spec.key must be a non-empty string (got ${got}). ` +\n 'Pick a name that matches a key declared on `ContextMeta` (or one of the framework keys).',\n )\n }\n if (typeof spec.resolve !== 'function') {\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.resolve is required and must be a function ` +\n `(got ${typeof spec.resolve}). Each contributor needs a resolver that produces ` +\n `the value to write into ctx.set(${JSON.stringify(spec.key)}, …).`,\n )\n }\n if (spec.onError !== undefined && typeof spec.onError !== 'function') {\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.onError must be a function when provided ` +\n `(got ${typeof spec.onError}).`,\n )\n }\n if (spec.dependsOn !== undefined && !Array.isArray(spec.dependsOn)) {\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.dependsOn must be an array when provided ` +\n `(got ${typeof spec.dependsOn}).`,\n )\n }\n if (spec.requiredParams !== undefined && !Array.isArray(spec.requiredParams)) {\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.requiredParams must be an array when provided ` +\n `(got ${typeof spec.requiredParams}).`,\n )\n }\n if (\n Array.isArray(spec.requiredParams) &&\n spec.requiredParams.some((p) => typeof p !== 'string' || p.length === 0)\n ) {\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.requiredParams entries must be non-empty strings.`,\n )\n }\n if (\n Array.isArray(spec.dependsOn) &&\n spec.dependsOn.some((dep) => typeof dep !== 'string' || dep.length === 0)\n ) {\n // TS narrows dependsOn to `readonly ContextMetaKey[]` for typed\n // adopters, but JS callers (and TS callers who erase via `as any`)\n // can still slip through with `[42]` or `['']`. Surface it at\n // definition time as a TypeError instead of riding to first\n // request as a `MissingContributorError` on a phantom key.\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.dependsOn entries must be non-empty strings.`,\n )\n }\n\n // Source-location capture. `new Error().stack` here records the\n // adopter's call site so boot-time errors (cycles, missing deps,\n // duplicate keys) can point at the file that declared the broken\n // decorator. Bad boot errors are why frameworks get abandoned —\n // a single stack frame turns \"what's a 'tenent'?\" into \"edit\n // src/contributors/tenant.ts line 42\".\n const definedAt = new Error().stack\n\n // Snapshot + freeze paramDefaults so callers can't mutate the spec\n // object post-definition and shift the merged params under our feet.\n // Same immutability boundary applied to `deps` and `dependsOn`.\n const defaults = Object.freeze({\n ...(spec.paramDefaults ?? ({} as Partial<P>)),\n }) as Readonly<Partial<P>>\n\n // Shared deps + dependsOn — frozen once and reused across every\n // registration produced by this decorator (one per call site).\n // `Object.freeze` is shallow — the values inside (DI tokens,\n // constructors) are intentionally untouched; freezing them would\n // prevent the container from doing legitimate per-instance\n // bookkeeping.\n //\n // The `({} as D)` cast: `spec.deps` is optional (`deps?: D`), so\n // the fallback to `{}` is required. When `D` defaults to\n // `Record<string, never>`, the cast is sound (`{}` matches). When\n // `D` is non-empty AND adopter omitted `deps`, the cast is unsound\n // — but the runner errors loudly the moment it tries to resolve a\n // missing dep, so the mistake surfaces at first request rather\n // than producing wrong-but-silent behaviour. The alternative\n // (forcing `deps` non-optional in the spec) would break ergonomics\n // for the common zero-deps case.\n const sharedDeps = Object.freeze({ ...(spec.deps ?? ({} as D)) }) as D\n const sharedDependsOn = Object.freeze([...(spec.dependsOn ?? [])])\n const sharedOptional = spec.optional ?? false\n\n /**\n * Build a frozen registration that closes over a specific\n * resolved-params object. The runner sees a normal\n * `(ctx, deps) => value` registration — params are baked into the\n * closure, invisible to the pipeline.\n */\n const buildRegistration = (params: P): ContributorRegistration<K, D, Ctx> => {\n // Shallow-freeze the captured params so a misbehaving `resolve()` /\n // `onError()` can't mutate the closure and bleed into later\n // requests through the same registration. Adopters who really need\n // mutable per-request state should write to `ctx.set(...)` from\n // inside the resolver instead of mutating params.\n const frozenParams = Object.freeze({ ...params }) as P\n const onError: ContributorRegistration<K, D, Ctx>['onError'] = spec.onError\n ? (err: unknown, ctx: Ctx) => spec.onError!(err, ctx, frozenParams)\n : undefined\n const resolve: ContributorRegistration<K, D, Ctx>['resolve'] = (\n ctx: Ctx,\n deps: ResolvedDeps<D>,\n ) => spec.resolve(ctx, deps, frozenParams)\n if (spec.skipWhen !== undefined)\n assertFlagTest(spec.skipWhen, `contributor '${spec.key}' skipWhen`)\n if (spec.onlyWhen !== undefined)\n assertFlagTest(spec.onlyWhen, `contributor '${spec.key}' onlyWhen`)\n return Object.freeze({\n key: spec.key,\n deps: sharedDeps,\n dependsOn: sharedDependsOn,\n optional: sharedOptional,\n skipWhen: spec.skipWhen,\n onlyWhen: spec.onlyWhen,\n beforeValidation: spec.beforeValidation === true,\n onError,\n resolve,\n definedAt,\n })\n }\n\n // Default-params registration — used for zero-arg decorator\n // applications and for the `.registration` accessor. Cloning\n // `defaults` so the closure can't be mutated through the spec.\n const defaultRegistration = buildRegistration({ ...defaults } as P)\n\n /**\n * Decide whether the decorator was called as a decorator\n * (`@Foo` / `@Foo(target, key)`) or as a factory\n * (`@Foo()` / `@Foo({...})`).\n *\n * - Class decorator: `(target)` where target is a constructor function.\n * - Method decorator: `(target, propertyKey, descriptor?)` where\n * target is a prototype object and propertyKey is a string/symbol.\n * - Factory (defaults): `()` — adopter wrote `@Foo()` (rare; usually\n * they write the bare `@Foo`). Returns a decorator using\n * `paramDefaults`.\n * - Factory (params): `(params)` where params is a plain object.\n *\n * The legacy decorators that ship with KickJS (`@Get('/path')`,\n * `@Cron('* * * *')`, etc.) use the same heuristic, so adopters\n * already know to pass plain object literals as factory args.\n */\n const isDecoratorCall = (args: unknown[]): boolean => {\n // Zero args is a factory call (`@Foo()`), not a direct decorator\n // application — TS never invokes a decorator with zero args.\n if (args.length === 0) return false\n if (args.length >= 2) return true\n const first = args[0]\n // Functions are class constructors at decoration time.\n if (typeof first === 'function') return true\n // Anything else (plain object, undefined, primitive) → factory call.\n return false\n }\n\n const applyAsDecorator = (\n registration: ContributorRegistration<K, D, Ctx>,\n target: object,\n propertyKey?: string | symbol,\n ): void => {\n if (propertyKey === undefined) {\n // Class decorator — `target` is the constructor.\n pushClassMeta(METADATA.CLASS_CONTRIBUTORS, target, registration)\n } else {\n // Method decorator — `target` is the prototype; we write to its\n // constructor so reads can use the controller class as the\n // lookup key, matching the convention used by @Middleware and\n // consumed by router-builder.ts.\n const constructor = (target as { constructor: object }).constructor\n pushMethodMeta(METADATA.METHOD_CONTRIBUTORS, constructor, String(propertyKey), registration)\n }\n }\n\n /**\n * `true` for `{...}` and `Object.create(null)`. Rejects every\n * built-in subclass (Map / Set / Date / Error / RegExp / Promise),\n * adopter classes, and arrays.\n */\n const isPlainObject = (value: object): boolean => {\n const proto = Object.getPrototypeOf(value)\n return proto === Object.prototype || proto === null\n }\n\n /**\n * Merge call-site `Partial<P>` over `paramDefaults`. Strict guard:\n * accepts only **plain object literals** (or `Object.create(null)`).\n * Reject everything else with a descriptive `TypeError`.\n *\n * Why strict? Object spread copies enumerable own properties only.\n * `{ ...new Date() }` and `{ ...new Map([...])}` both produce `{}`\n * — the params silently drop. Catching this at the boundary\n * surfaces the mistake instead of routing the request with empty\n * config. Adopters who genuinely need a class-instance shape can\n * destructure it themselves: `Foo({ ...new MyParams() })`.\n */\n const mergeParams = (override: unknown): P => {\n if (override === undefined) return { ...defaults } as P\n if (\n override === null ||\n typeof override !== 'object' ||\n Array.isArray(override) ||\n !isPlainObject(override)\n ) {\n const got =\n override === null\n ? 'null'\n : Array.isArray(override)\n ? 'array'\n : typeof override === 'object'\n ? `instance of ${(override as object).constructor?.name ?? 'unknown class'}`\n : typeof override\n throw new TypeError(\n `defineContextDecorator(${spec.key}): factory call requires a plain object literal, got ${got}. ` +\n `Class instances and built-ins (Map, Date, etc) silently spread to empty objects — ` +\n `call the decorator with a spread object literal instead, e.g. ` +\n `\\`MyDecorator({ ...someInstance })\\`.`,\n )\n }\n return { ...defaults, ...(override as Partial<P>) } as P\n }\n\n const sharedRequiredParams = Object.freeze([...(spec.requiredParams ?? [])])\n\n /**\n * Runtime half of the required-params guarantee. The type system\n * already stops TS call sites from omitting a required field, so this\n * catches what types can't see: plain-JS adopters, `as any`, and\n * params built dynamically.\n *\n * `usage` names the form that failed so the message points at the fix\n * — a bare `@Foo` on a decorator with required params needs to become\n * `@Foo({ ... })`, which is a different edit from a factory call that\n * merely forgot one field.\n */\n const assertRequiredParams = (params: P, usage: string): void => {\n if (sharedRequiredParams.length === 0) return\n const missing = sharedRequiredParams.filter(\n (name) => (params as Record<string, unknown>)[name] === undefined,\n )\n if (missing.length === 0) return\n throw new TypeError(\n `defineContextDecorator(${spec.key}): ${usage} is missing required param(s) ` +\n `${missing.map((m) => `'${m}'`).join(', ')}. ` +\n `Supply them at the call site — e.g. \\`@${spec.key}({ ${missing[0]}: … })\\` — or ` +\n `add a default in \\`paramDefaults\\` if one is genuinely correct for every route.`,\n )\n }\n\n // Overloaded function signatures so `decoratorOrFactory`'s type\n // matches `ContextDecorator`'s call shapes directly — no\n // `as unknown as` double-cast needed. The implementation signature\n // (`...args: unknown[]`) is hidden from callers; only the four\n // declared overloads are reachable.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n function decoratorOrFactory(target: Function): void\n function decoratorOrFactory(\n target: object,\n propertyKey: string | symbol,\n descriptor?: PropertyDescriptor,\n ): void\n function decoratorOrFactory(): ContextDecoratorTarget\n function decoratorOrFactory(params: Partial<P>): ContextDecoratorTarget\n function decoratorOrFactory(...args: unknown[]): unknown {\n if (isDecoratorCall(args)) {\n const [target, propertyKey] = args as [\n object,\n string | symbol | undefined,\n PropertyDescriptor?,\n ]\n // Bare `@Foo` — nothing can supply params here, so a required one\n // that isn't defaulted is a decoration-time (module-load) failure\n // rather than an undefined riding into the resolver.\n assertRequiredParams({ ...defaults } as P, 'bare `@decorator` usage')\n applyAsDecorator(defaultRegistration, target, propertyKey)\n return undefined\n }\n // Factory call — capture merged params and return a decorator.\n const params = mergeParams(args[0])\n assertRequiredParams(params, 'factory call')\n const registration = buildRegistration(params)\n return (target: object, propertyKey?: string | symbol) => {\n applyAsDecorator(registration, target, propertyKey)\n }\n }\n\n // Set a meaningful `.name` so `console.log(LoadTenant)` and stack\n // traces show `[Function: ContextDecorator(tenant)]` instead of\n // `[Function: decoratorOrFactory]`. Function.name is configurable\n // by default, so this works on the existing function reference.\n Object.defineProperty(decoratorOrFactory, 'name', {\n value: `ContextDecorator(${spec.key})`,\n writable: false,\n enumerable: false,\n configurable: true,\n })\n\n // `Object.assign` returns the intersection type, so the decorator\n // gets `.registration` + `.with` properties typed without the\n // previous `as unknown as ContextDecorator<...>` double cast.\n // Properties go on via assign (writable + enumerable defaults),\n // then `Object.freeze` locks the shape so adopters can't mutate\n // `decorator.registration = …` post-construction.\n const decorator = Object.assign(decoratorOrFactory, {\n with: (params: Partial<P>) => {\n const merged = mergeParams(params)\n assertRequiredParams(merged, '`.with()`')\n return { registration: buildRegistration(merged) }\n },\n })\n\n // `.registration` is a getter, not a plain property, so that reading it\n // on a decorator with undefaulted required params throws instead of\n // handing back a registration whose resolver will see `undefined`.\n // (`Object.assign` would have copied the value and lost the check.)\n Object.defineProperty(decorator, 'registration', {\n get() {\n assertRequiredParams({ ...defaults } as P, '`.registration`')\n return defaultRegistration\n },\n enumerable: true,\n configurable: false,\n })\n\n // `ContextDecorator` is a conditional type (permissive vs required-params\n // shape), and TS can't evaluate a conditional whose input generics are\n // still unresolved — so the assignment can't be checked structurally\n // here regardless of how `decorator` is built. The cast is the price of\n // the call-site guarantee; the two branches differ only in which call\n // forms they expose, and every one of them is implemented above.\n return Object.freeze(decorator) as unknown as ContextDecorator<K, D, P, Ctx, PD>\n}\n\n/**\n * Public export — the positional-generic implementation plus the\n * curried `.withParams<P>()` helper attached as a method. Adopters\n * pick whichever form fits the call site:\n *\n * - **Positional** — `defineContextDecorator(spec)` when there are\n * no per-call params (the common case). `K`, `D`, `Ctx` infer.\n * - **Curried** — `defineContextDecorator.withParams<P>()(spec)`\n * when the contributor takes params; spell only `P`, the rest\n * still infer.\n *\n * Frozen so adopters can't reassign `.withParams` at runtime.\n */\nexport const defineContextDecorator: DefineContextDecoratorFn = Object.freeze(\n Object.assign(defineContextDecoratorImpl, {\n withParams: <P extends Record<string, unknown>>(): DefineContextDecoratorWithParams<P> => {\n return <\n K extends ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n PD extends Partial<P> = Record<never, never>,\n >(\n spec: ContextDecoratorSpec<K, D, P, Ctx, PD>,\n ): ContextDecorator<K, D, P, Ctx, PD> => defineContextDecoratorImpl<K, D, P, Ctx, PD>(spec)\n },\n }),\n) as DefineContextDecoratorFn\n","// Shared asset-key strategy — used by the build pipeline, the\n// runtime dev resolver, and the typegen emitter so all three layers\n// agree on what the manifest looks like.\n//\n// Three strategies:\n//\n// - `'strip'` — drop the final extension. `pages/index.pug` →\n// `pages/index`. Best for namespaces where every basename is\n// unique (mail templates, pdf invoices). Shortest autocomplete.\n//\n// - `'with-extension'` — keep the extension. `pages/index.pug` →\n// `pages/index.pug`. Required when extension siblings coexist.\n//\n// - `'auto'` (default) — strip when safe, keep extensions when not.\n// The grouping pass collects files by stripped basename; groups\n// of one keep the stripped key, groups of more than one keep the\n// full path. Backward compatible for projects with no\n// extension-siblings; lossless for projects that have them.\n//\n// The function returns the path / key pairs in walk order (input\n// order preserved) so callers can iterate without re-sorting. Sort\n// in the caller if a stable manifest order is needed.\n\nexport type AssetKeyStrategy = 'auto' | 'strip' | 'with-extension'\n\nexport interface AssetKeyPair {\n /** Source-relative path the file was discovered at. */\n rel: string\n /** Final logical key for the manifest, including the namespace prefix. */\n key: string\n}\n\nexport interface GroupAssetKeysOptions {\n /**\n * Strategy applied to this namespace. Default `'auto'`.\n *\n * Adopters override per-namespace via `assetMap.<ns>.keys` in\n * kick.config.ts when they want uniform behaviour (e.g. always\n * include the extension regardless of collisions).\n */\n strategy?: AssetKeyStrategy\n}\n\nexport interface GroupAssetKeysResult {\n pairs: AssetKeyPair[]\n /**\n * Number of collision groups resolved by keeping extensions.\n * Surfaces as an informational log (\"auto-resolved N collisions\")\n * so adopters know whether they're relying on auto-mode or not.\n * Always 0 for `'strip'` (overwrites) and `'with-extension'`\n * (extensions always preserved, no collision possible).\n */\n collisionGroupsResolved: number\n}\n\n/**\n * Compute final manifest keys for a namespace's discovered paths,\n * given the chosen strategy. Pure function — no fs / config / globals\n * involved.\n *\n * @param namespace assetMap key — e.g. `'mails'`, `'pages'`.\n * @param paths source-relative file paths (POSIX separators) discovered\n * under the namespace's `src` directory.\n * @param opts grouping options.\n */\nexport function groupAssetKeys(\n namespace: string,\n paths: readonly string[],\n opts: GroupAssetKeysOptions = {},\n): GroupAssetKeysResult {\n const strategy = opts.strategy ?? 'auto'\n\n if (strategy === 'with-extension') {\n return {\n pairs: paths.map((rel) => ({ rel, key: `${namespace}/${rel}` })),\n collisionGroupsResolved: 0,\n }\n }\n\n if (strategy === 'strip') {\n // Strict strip — last-walk-order wins on collision; no\n // collision tracking here because the docs document this as the\n // adopter's choice. The caller (build / runtime / typegen) can\n // still warn if it wants; this function just shapes keys.\n return {\n pairs: paths.map((rel) => ({ rel, key: `${namespace}/${stripExt(rel)}` })),\n collisionGroupsResolved: 0,\n }\n }\n\n // strategy === 'auto' — group by stripped basename, keep\n // extensions only on collision groups.\n const groups = new Map<string, string[]>()\n for (const rel of paths) {\n const strippedKey = `${namespace}/${stripExt(rel)}`\n const arr = groups.get(strippedKey)\n if (arr) {\n arr.push(rel)\n } else {\n groups.set(strippedKey, [rel])\n }\n }\n\n let collisionGroupsResolved = 0\n // Build a per-rel decision table so we preserve input ordering in\n // the output (manifest writers rely on stable ordering).\n const keyByRel = new Map<string, string>()\n for (const [strippedKey, rels] of groups) {\n if (rels.length === 1) {\n keyByRel.set(rels[0], strippedKey)\n } else {\n collisionGroupsResolved += 1\n for (const rel of rels) {\n keyByRel.set(rel, `${namespace}/${rel}`)\n }\n }\n }\n\n return {\n pairs: paths.map((rel) => ({\n rel,\n key: keyByRel.get(rel) ?? `${namespace}/${rel}`,\n })),\n collisionGroupsResolved,\n }\n}\n\n/** Strip the final extension from a path. `'mails/welcome.ejs'` → `'mails/welcome'`. */\nexport function stripExt(path: string): string {\n // Inline the logic instead of importing extname so this module\n // stays usable in pure-browser contexts. Same algorithm: find the\n // last dot AFTER the last separator, slice up to it.\n const lastSlash = path.lastIndexOf('/')\n const filename = lastSlash >= 0 ? path.slice(lastSlash + 1) : path\n const lastDot = filename.lastIndexOf('.')\n if (lastDot <= 0) return path // no ext, or hidden file like `.env`\n const dirPart = lastSlash >= 0 ? path.slice(0, lastSlash + 1) : ''\n return dirPart + filename.slice(0, lastDot)\n}\n","/**\n * Asset manager runtime.\n *\n * Three accessor surfaces over one resolver engine:\n *\n * - `assets.x.y()` — Proxy ambient, autocomplete-friendly default.\n * - `useAssets()` — hook returning the same Proxy; mockable per-call.\n * - `resolveAsset(ns, k)` — string resolver for dynamic dispatch.\n *\n * Resolution path:\n *\n * 1. Load the build-time manifest (`<outDir>/.kickjs-assets.json`)\n * when present. Manifest existence = \"we're running from a built\n * dist\". Fast path; mapped paths returned verbatim.\n * 2. Otherwise (dev / test) walk the assetMap src directories from\n * `kick.config.{json,js,cjs}` and synthesise the same manifest\n * shape in memory. First call pays the walk cost; subsequent\n * calls hit the cache.\n * 3. `KICK_ASSETS_ROOT` env var overrides everything — the resolver\n * treats the path as the manifest directory and skips discovery.\n *\n * @module @forinda/kickjs/core/assets\n */\n\nimport { existsSync, readFileSync, readdirSync } from 'node:fs'\nimport { isAbsolute, join, resolve, sep } from 'node:path'\nimport { createToken, type InjectionToken } from './token'\nimport { groupAssetKeys } from './asset-keys'\n\nexport const ASSET_MANIFEST_VERSION = 1 as const\n\ninterface AssetManifest {\n version: typeof ASSET_MANIFEST_VERSION\n entries: Record<string, string>\n}\n\ninterface ResolvedManifest {\n manifest: AssetManifest\n root: string\n}\n\n/**\n * Augmentable interface — `kick typegen` emits\n * a `declare global` augmentation listing every namespace + key for\n * the project's `assetMap`. Adopters who haven't run typegen yet\n * still get a runtime-correct Proxy; only typed autocomplete waits\n * on the augmentation.\n */\nexport interface KickAssets {}\n\n/**\n * DI token for the `assets` Proxy. Adopter code that wants to\n * receive assets via constructor injection registers via\n * `container.registerInstance(ASSETS, useAssets())` in `bootstrap()`\n * and injects with `@Inject(ASSETS)`.\n */\nexport const ASSETS: InjectionToken<KickAssets> = createToken<KickAssets>('kick/assets/Map')\n\nexport class UnknownAssetError extends Error {\n readonly namespace: string\n readonly key: string\n constructor(namespace: string, key: string) {\n super(\n `Unknown asset '${namespace}/${key}'. ` +\n `Make sure the namespace exists in kick.config.ts assetMap and that the file is present in the source directory.`,\n )\n this.name = 'UnknownAssetError'\n this.namespace = namespace\n this.key = key\n }\n}\n\nlet manifestCache: ResolvedManifest | null | undefined = undefined\n\n/**\n * Convert a filesystem path to its posix (forward-slash) form. The\n * asset manifest and the public `resolveAsset` / `assets.x.y()` /\n * `useAssets()` contract returns posix paths on every platform so\n * adopters can:\n *\n * - pass the result to `express.static` / `res.sendFile` / `ejs.renderFile`\n * without worrying about Windows vs. posix separators\n * - splice the path into URLs (`href`, `src`, CDN keys) without\n * replacing `\\` with `/` by hand\n * - compare two paths from `resolveAsset` for equality on any host\n *\n * Node's `fs.*` and Express's path-handling APIs accept either\n * separator on Windows, so this normalisation is safe — the only\n * adopter code it could break is something explicitly parsing\n * Windows backslashes back out of the result, which would already be\n * brittle.\n */\nfunction toPosixPath(p: string): string {\n return p.replaceAll('\\\\', '/')\n}\n\nexport function resolveAsset(namespace: string, key: string): string {\n const resolved = loadManifest()\n if (!resolved) {\n throw new UnknownAssetError(namespace, key)\n }\n const entry = resolved.manifest.entries[`${namespace}/${key}`]\n if (!entry) {\n throw new UnknownAssetError(namespace, key)\n }\n const abs = isAbsolute(entry) ? entry : resolve(resolved.root, entry)\n return toPosixPath(abs)\n}\n\n/** Reset the manifest cache. Tests use this; production code shouldn't need it. */\nexport function clearAssetCache(): void {\n manifestCache = undefined\n}\n\nfunction loadManifest(): ResolvedManifest | null {\n // In development we deliberately skip the cache so adding or removing\n // a template file (e.g. dropping `welcome-back.ejs` into\n // `src/templates/mails/`) takes effect without a server restart. The\n // synthesise-dev-manifest path walks ~10 files; the cost is negligible\n // compared to the user-visible surprise of \"I added a file but\n // assets.x.y() throws\". Production keeps the cache because the built\n // manifest under `dist/.kickjs-assets.json` doesn't change at runtime.\n if (process.env.NODE_ENV !== 'production') {\n return discoverManifest()\n }\n if (manifestCache !== undefined) return manifestCache\n manifestCache = discoverManifest()\n return manifestCache\n}\n\nfunction discoverManifest(): ResolvedManifest | null {\n // `KICK_ASSETS_ROOT` is an explicit, deliberate override — it wins in\n // both dev and prod.\n const envRoot = process.env.KICK_ASSETS_ROOT\n if (envRoot) {\n const fromEnv = readBuiltManifest(envRoot)\n if (fromEnv) return fromEnv\n }\n\n const cwd = process.cwd()\n const config = loadConfigSync(cwd)\n\n // In dev, prefer a live walk of the source tree over any built\n // manifest left on disk by an earlier `kick build`. Otherwise a stale\n // `dist/.kickjs-assets.json` shadows the live `src/` tree: a freshly\n // added template file would resolve to the old map (or throw\n // UnknownAssetError) until the next rebuild — exactly the surprise the\n // cache-skip in `loadManifest` exists to avoid. `synthesiseDevManifest`\n // returns null only when there's no `assetMap` config at all, in which\n // case we fall through to the built-manifest probes below.\n if (process.env.NODE_ENV !== 'production') {\n const fresh = synthesiseDevManifest(cwd, config)\n if (fresh) return fresh\n }\n\n // Honour `kick.config.build.outDir` BEFORE the standard probe list\n // so adopters with non-default Vite outputs (e.g. `output/`,\n // `target/`, anything else) don't have to set KICK_ASSETS_ROOT\n // every time. Cached config load — same loader the dev fallback\n // uses. Skip when outDir matches one of the standard candidates;\n // it'd be probed redundantly otherwise.\n const configOutDir = config?.build?.outDir\n if (configOutDir && !(STANDARD_OUT_DIRS as readonly string[]).includes(configOutDir)) {\n const found = readBuiltManifest(resolve(cwd, configOutDir))\n if (found) return found\n }\n\n for (const candidate of STANDARD_OUT_DIRS) {\n const found = readBuiltManifest(join(cwd, candidate))\n if (found) return found\n }\n\n return synthesiseDevManifest(cwd, config)\n}\n\nconst STANDARD_OUT_DIRS = ['dist', 'build', 'out'] as const\n\nfunction readBuiltManifest(dir: string): ResolvedManifest | null {\n const path = join(dir, '.kickjs-assets.json')\n if (!existsSync(path)) return null\n try {\n const raw = readFileSync(path, 'utf-8')\n const parsed = JSON.parse(raw) as Partial<AssetManifest>\n if (parsed.version !== ASSET_MANIFEST_VERSION) return null\n if (!parsed.entries || typeof parsed.entries !== 'object') return null\n return { manifest: parsed as AssetManifest, root: resolve(dir) }\n } catch {\n return null\n }\n}\n\ninterface AssetMapConfigShape {\n assetMap?: Record<\n string,\n {\n src: string\n dest?: string\n glob?: string\n keys?: 'auto' | 'strip' | 'with-extension'\n }\n >\n build?: { outDir?: string }\n}\n\nfunction synthesiseDevManifest(\n cwd: string,\n preloadedConfig?: AssetMapConfigShape | null,\n): ResolvedManifest | null {\n const config = preloadedConfig === undefined ? loadConfigSync(cwd) : preloadedConfig\n if (!config?.assetMap) return null\n\n const entries: Record<string, string> = {}\n // Walk order is filesystem-dependent on Linux/macOS, so we collect\n // every match per-namespace first, then sort + feed `groupAssetKeys`\n // for stable manifest output. Build pipeline does the same — both\n // layers agree on the resulting key set.\n for (const [namespace, entry] of Object.entries(config.assetMap)) {\n if (!entry || typeof entry.src !== 'string') continue\n const srcAbs = resolve(cwd, entry.src)\n if (!existsSync(srcAbs)) continue\n\n const collected: Array<{ rel: string; abs: string }> = []\n walkSync(srcAbs, srcAbs, (relPath, absPath) => {\n if (entry.glob && !matchesGlobLite(relPath, entry.glob, namespace)) return\n collected.push({ rel: relPath, abs: absPath })\n })\n collected.sort((a, b) => a.rel.localeCompare(b.rel))\n\n const { pairs, collisionGroupsResolved } = groupAssetKeys(\n namespace,\n collected.map((c) => c.rel),\n { strategy: entry.keys ?? 'auto' },\n )\n const absByRel = new Map(collected.map((c) => [c.rel, c.abs]))\n for (const { rel, key } of pairs) {\n const abs = absByRel.get(rel)\n if (abs) entries[key] = abs\n }\n if (collisionGroupsResolved > 0 && process.env.NODE_ENV !== 'production') {\n // Dev-only nudge — production runs read the build manifest where\n // the collision was already resolved at build time.\n console.log(\n `[kickjs/assets] '${namespace}': ${collisionGroupsResolved} basename collision(s) auto-resolved by keeping extensions ` +\n `(set assetMap.${namespace}.keys to 'strip' or 'with-extension' to override).`,\n )\n }\n }\n\n return { manifest: { version: ASSET_MANIFEST_VERSION, entries }, root: cwd }\n}\n\nconst warnedGlobs = new Set<string>()\n\n/**\n * Tiny glob matcher used only by dev manifest synthesis. Covers the\n * common assetMap glob patterns (`**\\/*`, `**\\/*.ext`,\n * `**\\/*.{a,b}`). Anything exotic prints a one-time warning + falls\n * through to `**\\/*` semantics rather than silently dropping files.\n *\n * The build pipeline (`packages/cli/src/asset-manager/build.ts`) uses\n * the full `glob` package; adopters who hit the dev-mode warning\n * should run `kick build:assets` to produce a real manifest.\n */\nfunction matchesGlobLite(relPath: string, pattern: string, namespace: string): boolean {\n if (pattern === '**/*' || pattern === '**') return true\n const singleExt = /^\\*\\*\\/\\*\\.(\\w+)$/.exec(pattern)\n if (singleExt) return relPath.endsWith(`.${singleExt[1]}`)\n const braceExt = /^\\*\\*\\/\\*\\.\\{([^}]+)\\}$/.exec(pattern)\n if (braceExt) {\n const exts = braceExt[1].split(',').map((e) => e.trim())\n return exts.some((ext) => relPath.endsWith(`.${ext}`))\n }\n const tag = `${namespace}::${pattern}`\n if (!warnedGlobs.has(tag)) {\n warnedGlobs.add(tag)\n console.warn(\n `[kickjs/assets] Dev-mode glob '${pattern}' on '${namespace}' isn't recognised by the lite matcher. ` +\n `Falling back to '**/*'. Run 'kick build:assets' to use the full glob engine.`,\n )\n }\n return true\n}\n\nfunction walkSync(root: string, dir: string, onFile: (rel: string, abs: string) => void): void {\n let entries\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n } catch {\n return\n }\n for (const entry of entries) {\n if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue\n const full = join(dir, entry.name)\n if (entry.isDirectory()) {\n walkSync(root, full, onFile)\n } else if (entry.isFile()) {\n const rel = full\n .slice(root.length + 1)\n .split(sep)\n .join('/')\n onFile(rel, full)\n }\n }\n}\n\n/**\n * Sync `kick.config.{json,cjs,js}` loader for dev manifest\n * synthesis. Avoids dynamic `import()` (would force every assets\n * lookup to be async). TypeScript configs (`kick.config.ts`) are\n * skipped here — adopters who use them should run `kick build:assets`\n * to produce a real manifest the resolver can read.\n */\nfunction loadConfigSync(cwd: string): AssetMapConfigShape | null {\n const jsonPath = join(cwd, 'kick.config.json')\n if (existsSync(jsonPath)) {\n try {\n return JSON.parse(readFileSync(jsonPath, 'utf-8')) as AssetMapConfigShape\n } catch {\n return null\n }\n }\n for (const filename of ['kick.config.cjs', 'kick.config.js']) {\n const path = join(cwd, filename)\n if (!existsSync(path)) continue\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { createRequire } = require('node:module') as typeof import('node:module')\n const req = createRequire(join(cwd, 'package.json'))\n const mod = req(path) as { default?: AssetMapConfigShape } & AssetMapConfigShape\n return mod.default ?? mod ?? null\n } catch {\n continue\n }\n }\n\n // Fallback: the CLI snapshot of a transpiled `kick.config.ts`. We\n // can't transpile TS synchronously on the asset hot path, so the CLI\n // (`loadKickConfig`) mirrors the JSON-serialisable `assetMap` +\n // `build.outDir` into `.kickjs/kick.config.json` whenever it loads a\n // `.ts`/`.js` config. This is what makes `assets.x.y()` resolve in\n // dev for `.ts`-config projects before the first production build.\n const snapshotPath = join(cwd, '.kickjs', 'kick.config.json')\n if (existsSync(snapshotPath)) {\n try {\n return JSON.parse(readFileSync(snapshotPath, 'utf-8')) as AssetMapConfigShape\n } catch {\n return null\n }\n }\n return null\n}\n\n/**\n * Keys we MUST return `undefined` for: returning anything truthy\n * here causes Promise resolution to treat the Proxy as a thenable\n * and `await` it (resolving to whatever `then.call(...)` produces).\n * Two-element list deliberately — every other coercion-related key\n * goes through {@link PASSTHROUGH_KEYS} below so `String(assets)`,\n * template literals, and `util.inspect` keep working.\n */\nconst PROMISE_DETECTION_KEYS: ReadonlySet<string | symbol> = new Set<string | symbol>(['then'])\n\n/**\n * Keys we let fall through to the proxy target (a function-shaped\n * object). The default `Object.prototype` implementations of\n * `toString` / `valueOf` etc. are correct for these — returning\n * `undefined` here would break `String(assets)` and template\n * literals (`${assets}`) because `Symbol.toPrimitive` falling through\n * to a missing `toString` throws. Adopters that hit `assets.toString`\n * get `'function () {}'` back, which is fine — the contract is\n * \"asset access returns paths\"; everything else is incidental.\n */\nconst PASSTHROUGH_KEYS: ReadonlySet<string | symbol> = new Set<string | symbol>([\n 'toString',\n 'valueOf',\n 'constructor',\n 'prototype',\n 'name',\n 'length',\n 'asymmetricMatch',\n 'nodeType',\n Symbol.toPrimitive,\n Symbol.toStringTag,\n Symbol.iterator,\n Symbol.asyncIterator,\n])\n\nfunction createNamespaceProxy(parts: readonly string[]): unknown {\n const target = function () {} as unknown as Record<string, unknown>\n return new Proxy(target, {\n apply() {\n if (parts.length < 2) {\n throw new UnknownAssetError(parts[0] ?? '', '')\n }\n const [namespace, ...rest] = parts\n return resolveAsset(namespace, rest.join('/'))\n },\n get(t, prop) {\n if (PROMISE_DETECTION_KEYS.has(prop)) return undefined\n if (PASSTHROUGH_KEYS.has(prop)) return Reflect.get(t, prop)\n if (typeof prop === 'symbol') return undefined\n return createNamespaceProxy([...parts, prop])\n },\n })\n}\n\nfunction createAssetProxy(): KickAssets {\n return new Proxy({} as Record<string, unknown>, {\n get(t, prop) {\n if (PROMISE_DETECTION_KEYS.has(prop)) return undefined\n if (PASSTHROUGH_KEYS.has(prop)) return Reflect.get(t, prop)\n if (typeof prop === 'symbol') return undefined\n return createNamespaceProxy([prop])\n },\n }) as unknown as KickAssets\n}\n\n/**\n * Variant A — the ambient Proxy. Default accessor for static call\n * sites where the namespace + key are literals known at compile time.\n *\n * @example\n * ```ts\n * import { assets } from '@forinda/kickjs'\n * const path = assets.mails.welcome()\n * ```\n */\nexport const assets: KickAssets = createAssetProxy()\n\n/**\n * Variant B — the hook accessor. Returns the same Proxy as `assets`;\n * factory shape exists for testability + DI. Mock via\n * `vi.mock('@forinda/kickjs', () => ({ useAssets: () => fakeAssets }))`.\n *\n * @example\n * ```ts\n * import { useAssets } from '@forinda/kickjs'\n * class MailService {\n * private assets = useAssets()\n * send(name: string) {\n * return ejs.renderFile(this.assets.mails[name](), {})\n * }\n * }\n * ```\n */\nexport function useAssets(): KickAssets {\n return assets\n}\n\n// Install this module's resolver into the container's asset slot — the\n// container deliberately avoids importing this file (edge purity; see\n// asset-resolver-slot.ts). Runs at module scope so any node app that loads\n// the asset manager gets full @Asset injection.\nimport { _setAssetResolver } from './asset-resolver-slot'\n_setAssetResolver(resolveAsset)\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
 
2
- import { x as MiddlewareHandler } from "./decorators-DOcWsXci.mjs";
2
+ import { x as MiddlewareHandler } from "./decorators-BxNSz9Qt.mjs";
3
3
  import { d as RouteFlagTest } from "./route-flag-B1lQht6v.mjs";
4
4
  import { NextFunction, Request, Response } from "express";
5
5
  //#region src/http/middleware/csrf.d.ts
@@ -99,4 +99,4 @@ interface CsrfGuardOptions extends CsrfOptions {
99
99
  declare function csrfGuard(options?: CsrfGuardOptions): MiddlewareHandler;
100
100
  //#endregion
101
101
  export { csrfGuard as i, CsrfOptions as n, csrf as r, CsrfGuardOptions as t };
102
- //# sourceMappingURL=csrf-COFjRrUz.d.mts.map
102
+ //# sourceMappingURL=csrf-C73UkRgc.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"csrf-COFjRrUz.d.mts","names":[],"sources":["../src/http/middleware/csrf.ts"],"mappings":";;;;;UASiB;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;IAME;IACA;IACA;IACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6BY,KAAK,UAAS,eAAgB,KAuB/B,SAAO,KAAO,UAAQ,MAAQ;UAqC5B,yBAAyB;;;;;;;;;;;;;;;;;;;;;EAqBxC,aAAa;;;;;;;;;;;;;;;;;;;;;;iBAuBC,UAAU,UAAS,mBAAwB"}
1
+ {"version":3,"file":"csrf-C73UkRgc.d.mts","names":[],"sources":["../src/http/middleware/csrf.ts"],"mappings":";;;;;UASiB;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;IAME;IACA;IACA;IACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6BY,KAAK,UAAS,eAAgB,KAuB/B,SAAO,KAAO,UAAQ,MAAQ;UAqC5B,yBAAyB;;;;;;;;;;;;;;;;;;;;;EAqBxC,aAAa;;;;;;;;;;;;;;;;;;;;;;iBAuBC,UAAU,UAAS,mBAAwB"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v9.1.0
2
+ * @forinda/kickjs v9.2.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{n as assertFlagTest,s as matchesFlagTest}from"./route-flag-sIYXay3q.mjs";import{n as resolvePathname}from"./client-ip-DMKc-Trg.mjs";import{i as setCookie,n as readCookies,r as sendJson}from"./respond-DjxNwGnN.mjs";function randomHex(bytes){let buf=crypto.getRandomValues(new Uint8Array(bytes));return Array.from(buf,b=>b.toString(16).padStart(2,`0`)).join(``)}function csrf(options={}){let cookieName=options.cookie??`_csrf`,headerName=options.header??`x-csrf-token`,protectedMethods=new Set((options.methods??[`POST`,`PUT`,`PATCH`,`DELETE`]).map(m=>m.toUpperCase())),ignorePaths=new Set(options.ignorePaths??[]),tokenLength=options.tokenLength??32,cookieOpts={httpOnly:!1,sameSite:`strict`,secure:process.env.NODE_ENV===`production`,path:`/`,...options.cookieOptions};return(req,res,next)=>{let token=readCookies(req)[cookieName];if(token||(token=randomHex(tokenLength),setCookie(res,cookieName,token,cookieOpts)),!protectedMethods.has(req.method.toUpperCase())||ignorePaths.has(resolvePathname(req)))return next();let headerToken=req.headers[headerName];if(!headerToken||headerToken!==token)return sendJson(res,403,{message:`CSRF token mismatch`});next()}}function csrfGuard(options={}){let cookieName=options.cookie??`_csrf`,headerName=options.header??`x-csrf-token`,protectedMethods=new Set((options.methods??[`POST`,`PUT`,`PATCH`,`DELETE`]).map(m=>m.toUpperCase())),ignorePaths=new Set(options.ignorePaths??[]),tokenLength=options.tokenLength??32,exemptWhen=options.exemptWhen;exemptWhen!==void 0&&assertFlagTest(exemptWhen,`csrfGuard({ exemptWhen })`);let cookieOpts={httpOnly:!1,sameSite:`strict`,secure:process.env.NODE_ENV===`production`,path:`/`,...options.cookieOptions};return(ctx,next)=>{let token=readCookies(ctx.req)[cookieName];token||(token=randomHex(tokenLength),setCookie(ctx.res,cookieName,token,cookieOpts));let method=ctx.route?.method??ctx.req.method??`GET`;if(!protectedMethods.has(method.toUpperCase())||exemptWhen!==void 0&&matchesFlagTest(exemptWhen,ctx.route?.flags,ctx.route)||ignorePaths.has(resolvePathname(ctx.req)))return next();let headerToken=ctx.headers[headerName];if(!headerToken||headerToken!==token){ctx.json({message:`CSRF token mismatch`},403);return}next()}}export{csrfGuard as n,randomHex as r,csrf as t};
12
- //# sourceMappingURL=csrf-BcxqzCZW.mjs.map
11
+ import{n as assertFlagTest,s as matchesFlagTest}from"./route-flag-Bb7n9Mnp.mjs";import{n as resolvePathname}from"./client-ip-pcINkkPr.mjs";import{i as setCookie,n as readCookies,r as sendJson}from"./respond-wbMSqHYo.mjs";function randomHex(bytes){let buf=crypto.getRandomValues(new Uint8Array(bytes));return Array.from(buf,b=>b.toString(16).padStart(2,`0`)).join(``)}function csrf(options={}){let cookieName=options.cookie??`_csrf`,headerName=options.header??`x-csrf-token`,protectedMethods=new Set((options.methods??[`POST`,`PUT`,`PATCH`,`DELETE`]).map(m=>m.toUpperCase())),ignorePaths=new Set(options.ignorePaths??[]),tokenLength=options.tokenLength??32,cookieOpts={httpOnly:!1,sameSite:`strict`,secure:process.env.NODE_ENV===`production`,path:`/`,...options.cookieOptions};return(req,res,next)=>{let token=readCookies(req)[cookieName];if(token||(token=randomHex(tokenLength),setCookie(res,cookieName,token,cookieOpts)),!protectedMethods.has(req.method.toUpperCase())||ignorePaths.has(resolvePathname(req)))return next();let headerToken=req.headers[headerName];if(!headerToken||headerToken!==token)return sendJson(res,403,{message:`CSRF token mismatch`});next()}}function csrfGuard(options={}){let cookieName=options.cookie??`_csrf`,headerName=options.header??`x-csrf-token`,protectedMethods=new Set((options.methods??[`POST`,`PUT`,`PATCH`,`DELETE`]).map(m=>m.toUpperCase())),ignorePaths=new Set(options.ignorePaths??[]),tokenLength=options.tokenLength??32,exemptWhen=options.exemptWhen;exemptWhen!==void 0&&assertFlagTest(exemptWhen,`csrfGuard({ exemptWhen })`);let cookieOpts={httpOnly:!1,sameSite:`strict`,secure:process.env.NODE_ENV===`production`,path:`/`,...options.cookieOptions};return(ctx,next)=>{let token=readCookies(ctx.req)[cookieName];token||(token=randomHex(tokenLength),setCookie(ctx.res,cookieName,token,cookieOpts));let method=ctx.route?.method??ctx.req.method??`GET`;if(!protectedMethods.has(method.toUpperCase())||exemptWhen!==void 0&&matchesFlagTest(exemptWhen,ctx.route?.flags,ctx.route)||ignorePaths.has(resolvePathname(ctx.req)))return next();let headerToken=ctx.headers[headerName];if(!headerToken||headerToken!==token){ctx.json({message:`CSRF token mismatch`},403);return}next()}}export{csrfGuard as n,randomHex as r,csrf as t};
12
+ //# sourceMappingURL=csrf-CxJItZVa.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"csrf-BcxqzCZW.mjs","names":[],"sources":["../src/core/web-crypto.ts","../src/http/middleware/csrf.ts"],"sourcesContent":["/**\n * Web Crypto helpers — portable across node/bun/deno/workers via\n * `globalThis.crypto`. The Web Crypto migration lives here so the request\n * path never imports `node:crypto` (edge-runtime portability).\n */\n\n/** Random hex string — replacement for `randomBytes(n).toString('hex')`. */\nexport function randomHex(bytes: number): string {\n const buf = crypto.getRandomValues(new Uint8Array(bytes))\n return Array.from(buf, (b) => b.toString(16).padStart(2, '0')).join('')\n}\n","import { readCookies, sendJson, setCookie } from './respond'\nimport type { Request, Response, NextFunction } from 'express'\nimport { resolvePathname, type ClientRequestLike } from '../client-ip'\n\nimport { randomHex } from '../../core/web-crypto'\nimport { assertFlagTest, matchesFlagTest, type RouteFlagTest } from '../../core/route-flag'\nimport type { MiddlewareHandler } from '../../core/decorators'\nimport type { RequestContext } from '../context'\n\nexport interface CsrfOptions {\n /** Cookie name for the CSRF token (default: '_csrf') */\n cookie?: string\n /** Header name to check for the token (default: 'x-csrf-token') */\n header?: string\n /** HTTP methods that require CSRF validation (default: POST, PUT, PATCH, DELETE) */\n methods?: string[]\n /** Paths to exclude from CSRF checks (e.g. webhooks) */\n ignorePaths?: string[]\n /** Token byte length before hex encoding (default: 32 = 64 hex chars) */\n tokenLength?: number\n /** Cookie options */\n cookieOptions?: {\n /**\n * Default `false`, because double-submit CSRF needs the page to read the\n * token and echo it in a header. Set `true` only if the client receives\n * the token another way — server-rendered, or from your own endpoint.\n */\n httpOnly?: boolean\n sameSite?: 'strict' | 'lax' | 'none'\n secure?: boolean\n path?: string\n }\n}\n\n/**\n * Double-submit cookie CSRF protection middleware.\n *\n * On every request, sets a CSRF token cookie. For state-changing methods\n * (POST, PUT, PATCH, DELETE), validates that the request header matches\n * the cookie value.\n *\n * @example\n * ```ts\n * import { csrf } from '@forinda/kickjs'\n *\n * bootstrap({\n * modules,\n * middlewares: [\n * cookieParser(),\n * csrf(),\n * // ... other middleware\n * ],\n * })\n * ```\n *\n * Client usage:\n * 1. Read the `_csrf` cookie value\n * 2. Send it in the `x-csrf-token` header on every mutating request\n */\nexport function csrf(options: CsrfOptions = {}) {\n const cookieName = options.cookie ?? '_csrf'\n const headerName = options.header ?? 'x-csrf-token'\n const protectedMethods = new Set(\n (options.methods ?? ['POST', 'PUT', 'PATCH', 'DELETE']).map((m) => m.toUpperCase()),\n )\n const ignorePaths = new Set(options.ignorePaths ?? [])\n const tokenLength = options.tokenLength ?? 32\n const cookieOpts = {\n // `false`, not `true`: double-submit CSRF requires the PAGE to read this\n // cookie and echo it in a header. Under `httpOnly: true` `document.cookie`\n // returns nothing, no header is sent, and every mutating request answers\n // 403 — the documented client flow could not work. The token is not a\n // credential; a token an attacker cannot read is also one your own page\n // cannot send. Set `cookieOptions: { httpOnly: true }` only when the token\n // reaches the client some other way (server-rendered, or your own endpoint).\n httpOnly: false,\n sameSite: 'strict' as const,\n secure: process.env.NODE_ENV === 'production',\n path: '/',\n ...options.cookieOptions,\n }\n\n return (req: Request, res: Response, next: NextFunction) => {\n // Generate or reuse CSRF token\n // NOT `req.cookies` alone: that is populated only by an upstream parser,\n // which Fastify and h3 never have and Express only has if the app mounts\n // one. Without this the middleware never saw the token it had just issued.\n const cookies = readCookies(req)\n let token = cookies[cookieName]\n\n if (!token) {\n token = randomHex(tokenLength)\n setCookie(res, cookieName, token, cookieOpts)\n }\n\n // Skip validation for safe methods and ignored paths\n if (!protectedMethods.has(req.method.toUpperCase())) {\n return next()\n }\n\n // `req.path` is Express-only, so `has(undefined)` never matched and\n // configured exemptions were silently ignored on Fastify / h3.\n if (ignorePaths.has(resolvePathname(req as ClientRequestLike))) {\n return next()\n }\n\n // Validate: header token must match cookie token\n const headerToken = req.headers[headerName] as string | undefined\n\n if (!headerToken || headerToken !== token) {\n return sendJson(res, 403, {\n message: 'CSRF token mismatch',\n })\n }\n\n next()\n }\n}\n\nexport interface CsrfGuardOptions extends CsrfOptions {\n /**\n * Skip the token check on routes carrying a route flag — a name, a list of\n * names (any-of), or a predicate.\n *\n * This is the reason the guard exists. `csrf()` runs before route matching,\n * so its only handle on \"not this endpoint\" is {@link CsrfOptions.ignorePaths}\n * — an exact pathname string that cannot express `/webhooks/:provider` and\n * keeps parsing after an `apiPrefix` change that voids it. A flag is declared\n * on the route itself:\n *\n * ```ts\n * const CsrfExempt = defineRouteFlag('csrf.exempt')\n *\n * @CsrfExempt\n * @Controller()\n * class WebhooksController {\n * @Post('/:provider') receive(ctx: RequestContext) {}\n * }\n * ```\n */\n exemptWhen?: RouteFlagTest\n}\n\n/**\n * Double-submit cookie CSRF protection as a `(ctx, next)` middleware.\n *\n * The ctx-style counterpart of {@link csrf}: it runs inside the matched route,\n * so it can read `ctx.route.flags` and be exempted per route, and it works on\n * every runtime including the `@forinda/kickjs/web` fetch entry.\n *\n * ```ts\n * // per controller — the flag-aware form\n * @Middleware(csrfGuard({ exemptWhen: 'csrf.exempt' }))\n * @Controller()\n * class BillingController {}\n *\n * // edge, app-wide\n * createWebApp({ h3, modules, middleware: [csrfGuard()] })\n * ```\n *\n * Mount `csrf()` instead when you want one app-wide connect middleware that\n * also covers requests matching no route — nothing to exempt, nothing to read.\n */\nexport function csrfGuard(options: CsrfGuardOptions = {}): MiddlewareHandler {\n const cookieName = options.cookie ?? '_csrf'\n const headerName = options.header ?? 'x-csrf-token'\n const protectedMethods = new Set(\n (options.methods ?? ['POST', 'PUT', 'PATCH', 'DELETE']).map((m) => m.toUpperCase()),\n )\n const ignorePaths = new Set(options.ignorePaths ?? [])\n const tokenLength = options.tokenLength ?? 32\n const exemptWhen = options.exemptWhen\n if (exemptWhen !== undefined) assertFlagTest(exemptWhen, 'csrfGuard({ exemptWhen })')\n const cookieOpts = {\n // Same default as `csrf()` above, and for the same reason.\n httpOnly: false,\n sameSite: 'strict' as const,\n secure: process.env.NODE_ENV === 'production',\n path: '/',\n ...options.cookieOptions,\n }\n\n return (ctx: RequestContext, next: () => void): void => {\n const cookies = readCookies(ctx.req)\n let token = cookies[cookieName]\n\n if (!token) {\n token = randomHex(tokenLength)\n setCookie(ctx.res, cookieName, token, cookieOpts)\n }\n\n // Safe methods only ever issue the token.\n const method = ctx.route?.method ?? (ctx.req as { method?: string }).method ?? 'GET'\n if (!protectedMethods.has(method.toUpperCase())) return next()\n\n // Flag first: it is the declared exemption, and cheaper than the path set.\n if (exemptWhen !== undefined && matchesFlagTest(exemptWhen, ctx.route?.flags, ctx.route)) {\n return next()\n }\n\n if (ignorePaths.has(resolvePathname(ctx.req as unknown as ClientRequestLike))) return next()\n\n const headerToken = ctx.headers[headerName] as string | undefined\n if (!headerToken || headerToken !== token) {\n ctx.json({ message: 'CSRF token mismatch' }, 403)\n return\n }\n\n next()\n }\n}\n"],"mappings":";;;;;;;;;;6NAOA,SAAgB,UAAU,MAAuB,CAC/C,IAAM,IAAM,OAAO,gBAAgB,IAAI,WAAW,KAAK,CAAC,EACxD,OAAO,MAAM,KAAK,IAAM,GAAM,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CACxE,CCiDA,SAAgB,KAAK,QAAuB,CAAC,EAAG,CAC9C,IAAM,WAAa,QAAQ,QAAU,QAC/B,WAAa,QAAQ,QAAU,eAC/B,iBAAmB,IAAI,KAC1B,QAAQ,SAAW,CAAC,OAAQ,MAAO,QAAS,QAAQ,EAAA,CAAG,IAAK,GAAM,EAAE,YAAY,CAAC,CACpF,EACM,YAAc,IAAI,IAAI,QAAQ,aAAe,CAAC,CAAC,EAC/C,YAAc,QAAQ,aAAe,GACrC,WAAa,CAQjB,SAAU,GACV,SAAU,SACV,OAAQ,QAAQ,IAAI,WAAa,aACjC,KAAM,IACN,GAAG,QAAQ,aACb,EAEA,OAAQ,IAAc,IAAe,OAAuB,CAM1D,IAAI,MADY,YAAY,GACV,CAAC,CAAC,YAcpB,GAZK,QACH,MAAQ,UAAU,WAAW,EAC7B,UAAU,IAAK,WAAY,MAAO,UAAU,GAI1C,CAAC,iBAAiB,IAAI,IAAI,OAAO,YAAY,CAAC,GAM9C,YAAY,IAAI,gBAAgB,GAAwB,CAAC,EAC3D,OAAO,KAAK,EAId,IAAM,YAAc,IAAI,QAAQ,YAEhC,GAAI,CAAC,aAAe,cAAgB,MAClC,OAAO,SAAS,IAAK,IAAK,CACxB,QAAS,qBACX,CAAC,EAGH,KAAK,CACP,CACF,CA8CA,SAAgB,UAAU,QAA4B,CAAC,EAAsB,CAC3E,IAAM,WAAa,QAAQ,QAAU,QAC/B,WAAa,QAAQ,QAAU,eAC/B,iBAAmB,IAAI,KAC1B,QAAQ,SAAW,CAAC,OAAQ,MAAO,QAAS,QAAQ,EAAA,CAAG,IAAK,GAAM,EAAE,YAAY,CAAC,CACpF,EACM,YAAc,IAAI,IAAI,QAAQ,aAAe,CAAC,CAAC,EAC/C,YAAc,QAAQ,aAAe,GACrC,WAAa,QAAQ,WACvB,aAAe,IAAA,IAAW,eAAe,WAAY,2BAA2B,EACpF,IAAM,WAAa,CAEjB,SAAU,GACV,SAAU,SACV,OAAQ,QAAQ,IAAI,WAAa,aACjC,KAAM,IACN,GAAG,QAAQ,aACb,EAEA,OAAQ,IAAqB,OAA2B,CAEtD,IAAI,MADY,YAAY,IAAI,GACd,CAAC,CAAC,YAEf,QACH,MAAQ,UAAU,WAAW,EAC7B,UAAU,IAAI,IAAK,WAAY,MAAO,UAAU,GAIlD,IAAM,OAAS,IAAI,OAAO,QAAW,IAAI,IAA4B,QAAU,MAQ/E,GAPI,CAAC,iBAAiB,IAAI,OAAO,YAAY,CAAC,GAG1C,aAAe,IAAA,IAAa,gBAAgB,WAAY,IAAI,OAAO,MAAO,IAAI,KAAK,GAInF,YAAY,IAAI,gBAAgB,IAAI,GAAmC,CAAC,EAAG,OAAO,KAAK,EAE3F,IAAM,YAAc,IAAI,QAAQ,YAChC,GAAI,CAAC,aAAe,cAAgB,MAAO,CACzC,IAAI,KAAK,CAAE,QAAS,qBAAsB,EAAG,GAAG,EAChD,MACF,CAEA,KAAK,CACP,CACF"}
1
+ {"version":3,"file":"csrf-CxJItZVa.mjs","names":[],"sources":["../src/core/web-crypto.ts","../src/http/middleware/csrf.ts"],"sourcesContent":["/**\n * Web Crypto helpers — portable across node/bun/deno/workers via\n * `globalThis.crypto`. The Web Crypto migration lives here so the request\n * path never imports `node:crypto` (edge-runtime portability).\n */\n\n/** Random hex string — replacement for `randomBytes(n).toString('hex')`. */\nexport function randomHex(bytes: number): string {\n const buf = crypto.getRandomValues(new Uint8Array(bytes))\n return Array.from(buf, (b) => b.toString(16).padStart(2, '0')).join('')\n}\n","import { readCookies, sendJson, setCookie } from './respond'\nimport type { Request, Response, NextFunction } from 'express'\nimport { resolvePathname, type ClientRequestLike } from '../client-ip'\n\nimport { randomHex } from '../../core/web-crypto'\nimport { assertFlagTest, matchesFlagTest, type RouteFlagTest } from '../../core/route-flag'\nimport type { MiddlewareHandler } from '../../core/decorators'\nimport type { RequestContext } from '../context'\n\nexport interface CsrfOptions {\n /** Cookie name for the CSRF token (default: '_csrf') */\n cookie?: string\n /** Header name to check for the token (default: 'x-csrf-token') */\n header?: string\n /** HTTP methods that require CSRF validation (default: POST, PUT, PATCH, DELETE) */\n methods?: string[]\n /** Paths to exclude from CSRF checks (e.g. webhooks) */\n ignorePaths?: string[]\n /** Token byte length before hex encoding (default: 32 = 64 hex chars) */\n tokenLength?: number\n /** Cookie options */\n cookieOptions?: {\n /**\n * Default `false`, because double-submit CSRF needs the page to read the\n * token and echo it in a header. Set `true` only if the client receives\n * the token another way — server-rendered, or from your own endpoint.\n */\n httpOnly?: boolean\n sameSite?: 'strict' | 'lax' | 'none'\n secure?: boolean\n path?: string\n }\n}\n\n/**\n * Double-submit cookie CSRF protection middleware.\n *\n * On every request, sets a CSRF token cookie. For state-changing methods\n * (POST, PUT, PATCH, DELETE), validates that the request header matches\n * the cookie value.\n *\n * @example\n * ```ts\n * import { csrf } from '@forinda/kickjs'\n *\n * bootstrap({\n * modules,\n * middlewares: [\n * cookieParser(),\n * csrf(),\n * // ... other middleware\n * ],\n * })\n * ```\n *\n * Client usage:\n * 1. Read the `_csrf` cookie value\n * 2. Send it in the `x-csrf-token` header on every mutating request\n */\nexport function csrf(options: CsrfOptions = {}) {\n const cookieName = options.cookie ?? '_csrf'\n const headerName = options.header ?? 'x-csrf-token'\n const protectedMethods = new Set(\n (options.methods ?? ['POST', 'PUT', 'PATCH', 'DELETE']).map((m) => m.toUpperCase()),\n )\n const ignorePaths = new Set(options.ignorePaths ?? [])\n const tokenLength = options.tokenLength ?? 32\n const cookieOpts = {\n // `false`, not `true`: double-submit CSRF requires the PAGE to read this\n // cookie and echo it in a header. Under `httpOnly: true` `document.cookie`\n // returns nothing, no header is sent, and every mutating request answers\n // 403 — the documented client flow could not work. The token is not a\n // credential; a token an attacker cannot read is also one your own page\n // cannot send. Set `cookieOptions: { httpOnly: true }` only when the token\n // reaches the client some other way (server-rendered, or your own endpoint).\n httpOnly: false,\n sameSite: 'strict' as const,\n secure: process.env.NODE_ENV === 'production',\n path: '/',\n ...options.cookieOptions,\n }\n\n return (req: Request, res: Response, next: NextFunction) => {\n // Generate or reuse CSRF token\n // NOT `req.cookies` alone: that is populated only by an upstream parser,\n // which Fastify and h3 never have and Express only has if the app mounts\n // one. Without this the middleware never saw the token it had just issued.\n const cookies = readCookies(req)\n let token = cookies[cookieName]\n\n if (!token) {\n token = randomHex(tokenLength)\n setCookie(res, cookieName, token, cookieOpts)\n }\n\n // Skip validation for safe methods and ignored paths\n if (!protectedMethods.has(req.method.toUpperCase())) {\n return next()\n }\n\n // `req.path` is Express-only, so `has(undefined)` never matched and\n // configured exemptions were silently ignored on Fastify / h3.\n if (ignorePaths.has(resolvePathname(req as ClientRequestLike))) {\n return next()\n }\n\n // Validate: header token must match cookie token\n const headerToken = req.headers[headerName] as string | undefined\n\n if (!headerToken || headerToken !== token) {\n return sendJson(res, 403, {\n message: 'CSRF token mismatch',\n })\n }\n\n next()\n }\n}\n\nexport interface CsrfGuardOptions extends CsrfOptions {\n /**\n * Skip the token check on routes carrying a route flag — a name, a list of\n * names (any-of), or a predicate.\n *\n * This is the reason the guard exists. `csrf()` runs before route matching,\n * so its only handle on \"not this endpoint\" is {@link CsrfOptions.ignorePaths}\n * — an exact pathname string that cannot express `/webhooks/:provider` and\n * keeps parsing after an `apiPrefix` change that voids it. A flag is declared\n * on the route itself:\n *\n * ```ts\n * const CsrfExempt = defineRouteFlag('csrf.exempt')\n *\n * @CsrfExempt\n * @Controller()\n * class WebhooksController {\n * @Post('/:provider') receive(ctx: RequestContext) {}\n * }\n * ```\n */\n exemptWhen?: RouteFlagTest\n}\n\n/**\n * Double-submit cookie CSRF protection as a `(ctx, next)` middleware.\n *\n * The ctx-style counterpart of {@link csrf}: it runs inside the matched route,\n * so it can read `ctx.route.flags` and be exempted per route, and it works on\n * every runtime including the `@forinda/kickjs/web` fetch entry.\n *\n * ```ts\n * // per controller — the flag-aware form\n * @Middleware(csrfGuard({ exemptWhen: 'csrf.exempt' }))\n * @Controller()\n * class BillingController {}\n *\n * // edge, app-wide\n * createWebApp({ h3, modules, middleware: [csrfGuard()] })\n * ```\n *\n * Mount `csrf()` instead when you want one app-wide connect middleware that\n * also covers requests matching no route — nothing to exempt, nothing to read.\n */\nexport function csrfGuard(options: CsrfGuardOptions = {}): MiddlewareHandler {\n const cookieName = options.cookie ?? '_csrf'\n const headerName = options.header ?? 'x-csrf-token'\n const protectedMethods = new Set(\n (options.methods ?? ['POST', 'PUT', 'PATCH', 'DELETE']).map((m) => m.toUpperCase()),\n )\n const ignorePaths = new Set(options.ignorePaths ?? [])\n const tokenLength = options.tokenLength ?? 32\n const exemptWhen = options.exemptWhen\n if (exemptWhen !== undefined) assertFlagTest(exemptWhen, 'csrfGuard({ exemptWhen })')\n const cookieOpts = {\n // Same default as `csrf()` above, and for the same reason.\n httpOnly: false,\n sameSite: 'strict' as const,\n secure: process.env.NODE_ENV === 'production',\n path: '/',\n ...options.cookieOptions,\n }\n\n return (ctx: RequestContext, next: () => void): void => {\n const cookies = readCookies(ctx.req)\n let token = cookies[cookieName]\n\n if (!token) {\n token = randomHex(tokenLength)\n setCookie(ctx.res, cookieName, token, cookieOpts)\n }\n\n // Safe methods only ever issue the token.\n const method = ctx.route?.method ?? (ctx.req as { method?: string }).method ?? 'GET'\n if (!protectedMethods.has(method.toUpperCase())) return next()\n\n // Flag first: it is the declared exemption, and cheaper than the path set.\n if (exemptWhen !== undefined && matchesFlagTest(exemptWhen, ctx.route?.flags, ctx.route)) {\n return next()\n }\n\n if (ignorePaths.has(resolvePathname(ctx.req as unknown as ClientRequestLike))) return next()\n\n const headerToken = ctx.headers[headerName] as string | undefined\n if (!headerToken || headerToken !== token) {\n ctx.json({ message: 'CSRF token mismatch' }, 403)\n return\n }\n\n next()\n }\n}\n"],"mappings":";;;;;;;;;;6NAOA,SAAgB,UAAU,MAAuB,CAC/C,IAAM,IAAM,OAAO,gBAAgB,IAAI,WAAW,KAAK,CAAC,EACxD,OAAO,MAAM,KAAK,IAAM,GAAM,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CACxE,CCiDA,SAAgB,KAAK,QAAuB,CAAC,EAAG,CAC9C,IAAM,WAAa,QAAQ,QAAU,QAC/B,WAAa,QAAQ,QAAU,eAC/B,iBAAmB,IAAI,KAC1B,QAAQ,SAAW,CAAC,OAAQ,MAAO,QAAS,QAAQ,EAAA,CAAG,IAAK,GAAM,EAAE,YAAY,CAAC,CACpF,EACM,YAAc,IAAI,IAAI,QAAQ,aAAe,CAAC,CAAC,EAC/C,YAAc,QAAQ,aAAe,GACrC,WAAa,CAQjB,SAAU,GACV,SAAU,SACV,OAAQ,QAAQ,IAAI,WAAa,aACjC,KAAM,IACN,GAAG,QAAQ,aACb,EAEA,OAAQ,IAAc,IAAe,OAAuB,CAM1D,IAAI,MADY,YAAY,GACV,CAAC,CAAC,YAcpB,GAZK,QACH,MAAQ,UAAU,WAAW,EAC7B,UAAU,IAAK,WAAY,MAAO,UAAU,GAI1C,CAAC,iBAAiB,IAAI,IAAI,OAAO,YAAY,CAAC,GAM9C,YAAY,IAAI,gBAAgB,GAAwB,CAAC,EAC3D,OAAO,KAAK,EAId,IAAM,YAAc,IAAI,QAAQ,YAEhC,GAAI,CAAC,aAAe,cAAgB,MAClC,OAAO,SAAS,IAAK,IAAK,CACxB,QAAS,qBACX,CAAC,EAGH,KAAK,CACP,CACF,CA8CA,SAAgB,UAAU,QAA4B,CAAC,EAAsB,CAC3E,IAAM,WAAa,QAAQ,QAAU,QAC/B,WAAa,QAAQ,QAAU,eAC/B,iBAAmB,IAAI,KAC1B,QAAQ,SAAW,CAAC,OAAQ,MAAO,QAAS,QAAQ,EAAA,CAAG,IAAK,GAAM,EAAE,YAAY,CAAC,CACpF,EACM,YAAc,IAAI,IAAI,QAAQ,aAAe,CAAC,CAAC,EAC/C,YAAc,QAAQ,aAAe,GACrC,WAAa,QAAQ,WACvB,aAAe,IAAA,IAAW,eAAe,WAAY,2BAA2B,EACpF,IAAM,WAAa,CAEjB,SAAU,GACV,SAAU,SACV,OAAQ,QAAQ,IAAI,WAAa,aACjC,KAAM,IACN,GAAG,QAAQ,aACb,EAEA,OAAQ,IAAqB,OAA2B,CAEtD,IAAI,MADY,YAAY,IAAI,GACd,CAAC,CAAC,YAEf,QACH,MAAQ,UAAU,WAAW,EAC7B,UAAU,IAAI,IAAK,WAAY,MAAO,UAAU,GAIlD,IAAM,OAAS,IAAI,OAAO,QAAW,IAAI,IAA4B,QAAU,MAQ/E,GAPI,CAAC,iBAAiB,IAAI,OAAO,YAAY,CAAC,GAG1C,aAAe,IAAA,IAAa,gBAAgB,WAAY,IAAI,OAAO,MAAO,IAAI,KAAK,GAInF,YAAY,IAAI,gBAAgB,IAAI,GAAmC,CAAC,EAAG,OAAO,KAAK,EAE3F,IAAM,YAAc,IAAI,QAAQ,YAChC,GAAI,CAAC,aAAe,cAAgB,MAAO,CACzC,IAAI,KAAK,CAAE,QAAS,qBAAsB,EAAG,GAAG,EAChD,MACF,CAEA,KAAK,CACP,CACF"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v9.1.0
2
+ * @forinda/kickjs v9.2.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -281,11 +281,11 @@ interface RouteDefinition {
281
281
  path: string;
282
282
  handlerName: string;
283
283
  validation?: {
284
- /** JSON Schema object for validating the request body */
284
+ /** Schema validating the request body — Zod, Valibot, Yup, any Standard Schema or KickSchema. */
285
285
  body?: any;
286
- /** JSON Schema object for validating query parameters */
286
+ /** Schema validating query parameters — any library `detectSchema` accepts. */
287
287
  query?: any;
288
- /** JSON Schema object for validating URL params */
288
+ /** Schema validating URL params — any library `detectSchema` accepts. */
289
289
  params?: any;
290
290
  /**
291
291
  * DECLARED response schema — never validated at runtime. Feeds two
@@ -439,4 +439,4 @@ declare function withBuilder<C extends new (...args: any[]) => any>(ctor: C): C
439
439
  };
440
440
  //#endregion
441
441
  export { Service as A, resolveAsset as B, Post as C, Put as D, PropertyOrParameterDecorator as E, ASSET_MANIFEST_VERSION as F, KickAssets as I, UnknownAssetError as L, normalizeApiQueryParamsConfig as M, withBuilder as N, Repository as O, ASSETS as P, assets as R, Patch as S, PreDestroy as T, useAssets as V, Get as _, Autowired as a, Middleware as b, ColumnApiQueryParamsConfig as c, Delete as d, Env as f, FileUploadConfig as g, FileUpload as h, AssetKey as i, Value as j, RouteDefinition as k, Component as l, FileTypeFilter as m, ApiQueryParamsConfig as n, BaseUploadOptions as o, EnvKey as p, Asset as r, Builder as s, ApiQueryParams as t, Controller as u, Inject as v, PostConstruct as w, MiddlewareHandler as x, Injectable as y, clearAssetCache as z };
442
- //# sourceMappingURL=decorators-DOcWsXci.d.mts.map
442
+ //# sourceMappingURL=decorators-BxNSz9Qt.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"decorators-DOcWsXci.d.mts","names":[],"sources":["../src/core/assets.ts","../src/core/decorators.ts"],"mappings":";;;;;cA6Ba;;;;;;;;UAmBI;;;;;;;cAQJ,QAAQ,eAAe;cAEvB,0BAA0B;WAC5B;WACA;EACT,YAAY,mBAAmB;;iBAmCjB,aAAa,mBAAmB;;iBAchC;;;;;;;;;;;cA6TH,QAAQ;;;;;;;;;;;;;;;;;iBAkBL,aAAa;;;;iBCpTb,WAAW,UAAU,iBAAiB;;iBAQtC,QAAQ,UAAU,iBAAiB;;iBAQnC,UAAU,UAAU,iBAAiB;;iBAQrC,WAAW,UAAU,iBAAiB;;;;;;;;;;iBAgBtC,cAAc;;iBAUd,iBAAiB;;;;;;;;;;;;;;;;;;iBAuBjB,cAAc;;;;;;;;;UAgBb;;GAEd,gBAAgB;;GAEhB,gBAAgB,0CAA0C;;;;;;;;;;;;;;;;;;;;iBA2C7C,UAAU,gBAAgB,yBACxC,OAAO,IACN;iBACa,UAAU,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyC5B,OAAO,gBAAgB,yBACrC,OAAO,IACN;iBACa,OAAO,kBAAkB;;;;;;;;;;;;QAsBjC;YAEI;;;KAIA,eAAe;;;;;;;;;;;;;;;;KAiBf,IAAI,UAAU,SAAS,UAAU,QAAQ;;;;;;;;;;;;KAahD,SAAS,qBAAqB,0BAA0B,IAAI,IAAI;;;;;;;;;;;;;;;;iBAiBrD,MAAM,kBACpB,QAAQ,SAAS,IACjB,eAAe,UAAU,SAAS,QAAQ,eACzC;;;;;;;;;;;;KAqBE,cAAc,GAAG,iCACnB,WAAW,aAAa,EAAE,+BACpB,SAAS,MACZ,EAAE,WAAW,0BACX,cAAc,EAAE,OAAO,SAAS,uBAEhC;;KAGI,WAAW,cAAc;;;;;;;KAQhC,YAAY,qBAAqB,4BAA4B,IAAI,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmC1D,MAAM,kBAAkB,UAAU,YAAY,KAAK;UAQlD;EACf;EACA;EACA;EACA;;IAEE;;IAEA;;IAEA;;;;;;;IAOA;;IAEA;;;cAiBS,MAAG,eAZO,aAAe,kCAAgC;cAazD,OAAI,eAbM,aAAe,kCAAgC;cAczD,MAAG,eAdO,aAAe,kCAAgC;cAezD,SAAM,eAfI,aAAe,kCAAgC;cAgBzD,QAAK,eAhBK,aAAe,kCAAgC;UAoBrD;;EAEf;;EAEA;;EAEA;;;;;;UAOe;EACf,SAAS;EACT,WAAW;EACX;GACC;;;;;;iBAOa,8BACd,QAAQ,uBAAuB,6BAC9B;;;;;;;;;;;;;;;;;;;;;iBAiCa,eACd,QAAQ,uBAAuB,6BAC9B;;;;;;;;;;;;;;;;KAwBS,kBAAkB,eAAe,KAAK,MAAM,4BAA4B;;iBAGpE,cAAc,UAAU,sBAAsB,iBAAiB;;;;;KAqBnE,8BAA8B,kBAAkB;;;;;UAM3C;;EAEf;;;;;;EAMA,eAAe;;EAEf,gBAAgB;;UAGD,yBAAyB;EACxC;;EAEA;;EAEA;;;iBAIc,WAAW,QAAQ,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;iBAoDtC,QAAQ;;;;;;;;;;;;;;;;;iBAoBR,YAAY,kBAAkB,qBAC5C,MAAM,IACL;EAAM,WAAW,UAAU,aAAa"}
1
+ {"version":3,"file":"decorators-BxNSz9Qt.d.mts","names":[],"sources":["../src/core/assets.ts","../src/core/decorators.ts"],"mappings":";;;;;cA6Ba;;;;;;;;UAmBI;;;;;;;cAQJ,QAAQ,eAAe;cAEvB,0BAA0B;WAC5B;WACA;EACT,YAAY,mBAAmB;;iBAmCjB,aAAa,mBAAmB;;iBAchC;;;;;;;;;;;cA6TH,QAAQ;;;;;;;;;;;;;;;;;iBAkBL,aAAa;;;;iBCpTb,WAAW,UAAU,iBAAiB;;iBAQtC,QAAQ,UAAU,iBAAiB;;iBAQnC,UAAU,UAAU,iBAAiB;;iBAQrC,WAAW,UAAU,iBAAiB;;;;;;;;;;iBAgBtC,cAAc;;iBAUd,iBAAiB;;;;;;;;;;;;;;;;;;iBAuBjB,cAAc;;;;;;;;;UAgBb;;GAEd,gBAAgB;;GAEhB,gBAAgB,0CAA0C;;;;;;;;;;;;;;;;;;;;iBA2C7C,UAAU,gBAAgB,yBACxC,OAAO,IACN;iBACa,UAAU,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyC5B,OAAO,gBAAgB,yBACrC,OAAO,IACN;iBACa,OAAO,kBAAkB;;;;;;;;;;;;QAsBjC;YAEI;;;KAIA,eAAe;;;;;;;;;;;;;;;;KAiBf,IAAI,UAAU,SAAS,UAAU,QAAQ;;;;;;;;;;;;KAahD,SAAS,qBAAqB,0BAA0B,IAAI,IAAI;;;;;;;;;;;;;;;;iBAiBrD,MAAM,kBACpB,QAAQ,SAAS,IACjB,eAAe,UAAU,SAAS,QAAQ,eACzC;;;;;;;;;;;;KAqBE,cAAc,GAAG,iCACnB,WAAW,aAAa,EAAE,+BACpB,SAAS,MACZ,EAAE,WAAW,0BACX,cAAc,EAAE,OAAO,SAAS,uBAEhC;;KAGI,WAAW,cAAc;;;;;;;KAQhC,YAAY,qBAAqB,4BAA4B,IAAI,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmC1D,MAAM,kBAAkB,UAAU,YAAY,KAAK;UAQlD;EACf;EACA;EACA;EACA;;IAEE;;IAEA;;IAEA;;;;;;;IAOA;;IAEA;;;cAiBS,MAAG,eAZO,aAAe,kCAAgC;cAazD,OAAI,eAbM,aAAe,kCAAgC;cAczD,MAAG,eAdO,aAAe,kCAAgC;cAezD,SAAM,eAfI,aAAe,kCAAgC;cAgBzD,QAAK,eAhBK,aAAe,kCAAgC;UAoBrD;;EAEf;;EAEA;;EAEA;;;;;;UAOe;EACf,SAAS;EACT,WAAW;EACX;GACC;;;;;;iBAOa,8BACd,QAAQ,uBAAuB,6BAC9B;;;;;;;;;;;;;;;;;;;;;iBAiCa,eACd,QAAQ,uBAAuB,6BAC9B;;;;;;;;;;;;;;;;KAwBS,kBAAkB,eAAe,KAAK,MAAM,4BAA4B;;iBAGpE,cAAc,UAAU,sBAAsB,iBAAiB;;;;;KAqBnE,8BAA8B,kBAAkB;;;;;UAM3C;;EAEf;;;;;;EAMA,eAAe;;EAEf,gBAAgB;;UAGD,yBAAyB;EACxC;;EAEA;;EAEA;;;iBAIc,WAAW,QAAQ,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;iBAoDtC,QAAQ;;;;;;;;;;;;;;;;;iBAoBR,YAAY,kBAAkB,qBAC5C,MAAM,IACL;EAAM,WAAW,UAAU,aAAa"}