@forinda/kickjs 9.0.2 → 9.0.3

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 (89) hide show
  1. package/dist/{application-BSDt9vOo.mjs → application-DPLzamTb.mjs} +3 -3
  2. package/dist/{application-BSDt9vOo.mjs.map → application-DPLzamTb.mjs.map} +1 -1
  3. package/dist/{bootstrap-CJ8ALvif.mjs → bootstrap-DUNvDJ1a.mjs} +3 -3
  4. package/dist/{bootstrap-CJ8ALvif.mjs.map → bootstrap-DUNvDJ1a.mjs.map} +1 -1
  5. package/dist/{client-ip-BHH-wDR7.mjs → client-ip-CTr33Gtu.mjs} +2 -2
  6. package/dist/{client-ip-BHH-wDR7.mjs.map → client-ip-CTr33Gtu.mjs.map} +1 -1
  7. package/dist/config/index.mjs +2 -2
  8. package/dist/{config-Dz5GgLKS.mjs → config-C-sAHakM.mjs} +3 -3
  9. package/dist/{config-Dz5GgLKS.mjs.map → config-C-sAHakM.mjs.map} +1 -1
  10. package/dist/{container-DM3gPzGt.mjs → container-ByH0uCpG.mjs} +3 -3
  11. package/dist/{container-DM3gPzGt.mjs.map → container-ByH0uCpG.mjs.map} +1 -1
  12. package/dist/{context-errors-Cb4jKwm4.mjs → context-errors-VjQBtxEc.mjs} +2 -2
  13. package/dist/{context-errors-Cb4jKwm4.mjs.map → context-errors-VjQBtxEc.mjs.map} +1 -1
  14. package/dist/{context-DEntnLmq.mjs → context-fth87C3G.mjs} +3 -3
  15. package/dist/{context-DEntnLmq.mjs.map → context-fth87C3G.mjs.map} +1 -1
  16. package/dist/{contributor-runner-Buxk3Ay_.mjs → contributor-runner-BEjgGaoG.mjs} +3 -3
  17. package/dist/{contributor-runner-Buxk3Ay_.mjs.map → contributor-runner-BEjgGaoG.mjs.map} +1 -1
  18. package/dist/core/adapter.mjs +1 -1
  19. package/dist/core/app-module.mjs +1 -1
  20. package/dist/core/cache.mjs +1 -1
  21. package/dist/core/container.mjs +2 -2
  22. package/dist/core/cron.mjs +2 -2
  23. package/dist/core/decorators.mjs +2 -2
  24. package/dist/core/errors.mjs +1 -1
  25. package/dist/core/interfaces.mjs +1 -1
  26. package/dist/core/logger.mjs +1 -1
  27. package/dist/core/path.mjs +1 -1
  28. package/dist/core/plugin.mjs +1 -1
  29. package/dist/core/reactivity.mjs +1 -1
  30. package/dist/{core-Cv1KjPCj.mjs → core-DrjVUTxG.mjs} +3 -3
  31. package/dist/{core-Cv1KjPCj.mjs.map → core-DrjVUTxG.mjs.map} +1 -1
  32. package/dist/{csrf-BgfnRJ_y.mjs → csrf-CbFJiyKW.mjs} +3 -3
  33. package/dist/{csrf-BgfnRJ_y.mjs.map → csrf-CbFJiyKW.mjs.map} +1 -1
  34. package/dist/{decorate-CnpOo_Rp.mjs → decorate-hW_esogS.mjs} +1 -1
  35. package/dist/{define-adapter-7WlOyHBR.mjs → define-adapter-B4Iu_TcU.mjs} +2 -2
  36. package/dist/{define-adapter-7WlOyHBR.mjs.map → define-adapter-B4Iu_TcU.mjs.map} +1 -1
  37. package/dist/{disposables-C2uznWks.mjs → disposables-CgCLyCoP.mjs} +2 -2
  38. package/dist/{disposables-C2uznWks.mjs.map → disposables-CgCLyCoP.mjs.map} +1 -1
  39. package/dist/{env-Cj-U1sin.mjs → env-w27RDkCQ.mjs} +3 -3
  40. package/dist/{env-Cj-U1sin.mjs.map → env-w27RDkCQ.mjs.map} +1 -1
  41. package/dist/{handler-CnjyOHSw.mjs → handler-eguNTfCv.mjs} +3 -3
  42. package/dist/{handler-CnjyOHSw.mjs.map → handler-eguNTfCv.mjs.map} +1 -1
  43. package/dist/http/application.mjs +2 -2
  44. package/dist/http/bootstrap.mjs +2 -2
  45. package/dist/http/context.mjs +2 -2
  46. package/dist/http/middleware/cors.mjs +1 -1
  47. package/dist/http/middleware/csrf.mjs +2 -2
  48. package/dist/http/middleware/error-handler.mjs +2 -2
  49. package/dist/http/middleware/helmet.mjs +1 -1
  50. package/dist/http/middleware/rate-limit.mjs +2 -2
  51. package/dist/http/middleware/request-id.mjs +1 -1
  52. package/dist/http/middleware/request-logger.mjs +2 -2
  53. package/dist/http/middleware/session.mjs +2 -2
  54. package/dist/http/middleware/spa.mjs +2 -2
  55. package/dist/http/middleware/upload.mjs +2 -2
  56. package/dist/http/middleware/validate.mjs +1 -1
  57. package/dist/http/middleware/views.mjs +2 -2
  58. package/dist/http/query/index.mjs +2 -2
  59. package/dist/http/router-builder.mjs +2 -2
  60. package/dist/http/runtimes/fastify.mjs +2 -2
  61. package/dist/http/runtimes/h3-web.mjs +2 -2
  62. package/dist/http/runtimes/h3.mjs +2 -2
  63. package/dist/index.mjs +2 -2
  64. package/dist/{jobs-DIEQILZ8.mjs → jobs-CeP4aLYR.mjs} +3 -3
  65. package/dist/{jobs-DIEQILZ8.mjs.map → jobs-CeP4aLYR.mjs.map} +1 -1
  66. package/dist/{kv-stores-D4dmpn6D.mjs → kv-stores-5ocdxfdh.mjs} +3 -3
  67. package/dist/{kv-stores-D4dmpn6D.mjs.map → kv-stores-5ocdxfdh.mjs.map} +1 -1
  68. package/dist/{metadata-DrCyWXgz.mjs → metadata-CIXErBTj.mjs} +2 -2
  69. package/dist/{metadata-DrCyWXgz.mjs.map → metadata-CIXErBTj.mjs.map} +1 -1
  70. package/dist/{module-registry-Bv3NMDX5.mjs → module-registry-IO8RfVjX.mjs} +2 -2
  71. package/dist/{module-registry-Bv3NMDX5.mjs.map → module-registry-IO8RfVjX.mjs.map} +1 -1
  72. package/dist/{observers-DzVW_zb7.mjs → observers-BtXkP5km.mjs} +2 -2
  73. package/dist/{observers-DzVW_zb7.mjs.map → observers-BtXkP5km.mjs.map} +1 -1
  74. package/dist/{query-BGQdAxkX.mjs → query-CNa0mxSK.mjs} +2 -2
  75. package/dist/{query-BGQdAxkX.mjs.map → query-CNa0mxSK.mjs.map} +1 -1
  76. package/dist/{respond--WlinyCt.mjs → respond-C4Vt5r46.mjs} +2 -2
  77. package/dist/{respond--WlinyCt.mjs.map → respond-C4Vt5r46.mjs.map} +1 -1
  78. package/dist/{route-flag-DEUYr5Br.mjs → route-flag-CpTa8y1n.mjs} +3 -3
  79. package/dist/{route-flag-DEUYr5Br.mjs.map → route-flag-CpTa8y1n.mjs.map} +1 -1
  80. package/dist/{route-policy-BmiX7NiW.mjs → route-policy-DPTLfer-.mjs} +2 -2
  81. package/dist/{route-policy-BmiX7NiW.mjs.map → route-policy-DPTLfer-.mjs.map} +1 -1
  82. package/dist/{router-builder-Cig7o4BH.mjs → router-builder-BxXKP8Wy.mjs} +3 -3
  83. package/dist/{router-builder-Cig7o4BH.mjs.map → router-builder-BxXKP8Wy.mjs.map} +1 -1
  84. package/dist/{runtime-C3izjAep.mjs → runtime-B4NQGN66.mjs} +3 -3
  85. package/dist/{runtime-C3izjAep.mjs.map → runtime-B4NQGN66.mjs.map} +1 -1
  86. package/dist/{upload-config-BhaBjCiB.mjs → upload-config-BmpmZZVK.mjs} +2 -2
  87. package/dist/{upload-config-BhaBjCiB.mjs.map → upload-config-BmpmZZVK.mjs.map} +1 -1
  88. package/dist/web.mjs +2 -2
  89. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v9.0.2
2
+ * @forinda/kickjs v9.0.3
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,6 +8,6 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{HttpException}from"../../core/errors.mjs";import{a as createRequestStore,f as unsupportedMediaTypeError,n as applyHandlerResult,o as disposeRequestStore,t as publishMatchedRoute}from"../../runtime-C3izjAep.mjs";import{l as requestStore,t as RequestContext}from"../../context-DEntnLmq.mjs";import{validate}from"../middleware/validate.mjs";import{t as applyUploadConfig}from"../../upload-config-BhaBjCiB.mjs";import"../middleware/upload.mjs";import{createRequire}from"node:module";const peerRequire=createRequire(import.meta.url),loadPeer=name=>{let mod=peerRequire(name);return mod&&mod.__esModule&&mod.default?mod.default:mod.default??mod},QUEUE=Symbol(`kickjs.fastify.connectQueue`),READY=Symbol(`kickjs.fastify.ready`),NEXT=Symbol(`kickjs.fastify.next`),NEEDS_MULTIPART=Symbol(`kickjs.fastify.needsMultipart`),NOOP_NEXT=()=>{};var FastifyReplyDriver=class{reply;constructor(reply){this.reply=reply}status(code){return this.reply.code(code),this}json(data){return this.reply.send(data),this}send(data){return this.reply.send(data),this}type(contentType){return this.reply.type(contentType),this}setHeader(name,value){return this.reply.header(name,value),this}render(){throw Error(`ctx.render() is not supported on the Fastify runtime (no view engine).`)}writeHead(statusCode,headers){return this.reply.hijack(),this.reply.raw.writeHead(statusCode,headers),this}flushHeaders(){return this.reply.raw.flushHeaders?.(),this}write(chunk){return this.reply.raw.write(chunk)}end(data){return this.reply.raw.end(data),this}once(event,listener){return this.reply.raw.once(event,listener),this}get headersSent(){return this.reply.sent||this.reply.raw.headersSent}};function replyDriver(reply){return new FastifyReplyDriver(reply)}function routeHandler(entry){let validator=entry.meta.validation?validate(entry.meta.validation):void 0;return async(request,reply)=>{let raw=request.raw;if(raw.body=request.body,raw.params=request.params,raw.query=request.query,entry.meta.upload&&entry.meta.upload.mode!==`none`){if(typeof request.parts!=`function`)throw Error(`@forinda/kickjs: @FileUpload on the Fastify runtime requires '@fastify/multipart'.
11
+ import{HttpException}from"../../core/errors.mjs";import{a as createRequestStore,f as unsupportedMediaTypeError,n as applyHandlerResult,o as disposeRequestStore,t as publishMatchedRoute}from"../../runtime-B4NQGN66.mjs";import{l as requestStore,t as RequestContext}from"../../context-fth87C3G.mjs";import{validate}from"../middleware/validate.mjs";import{t as applyUploadConfig}from"../../upload-config-BmpmZZVK.mjs";import"../middleware/upload.mjs";import{createRequire}from"node:module";const peerRequire=createRequire(import.meta.url),loadPeer=name=>{let mod=peerRequire(name);return mod&&mod.__esModule&&mod.default?mod.default:mod.default??mod},QUEUE=Symbol(`kickjs.fastify.connectQueue`),READY=Symbol(`kickjs.fastify.ready`),NEXT=Symbol(`kickjs.fastify.next`),NEEDS_MULTIPART=Symbol(`kickjs.fastify.needsMultipart`),NOOP_NEXT=()=>{};var FastifyReplyDriver=class{reply;constructor(reply){this.reply=reply}status(code){return this.reply.code(code),this}json(data){return this.reply.send(data),this}send(data){return this.reply.send(data),this}type(contentType){return this.reply.type(contentType),this}setHeader(name,value){return this.reply.header(name,value),this}render(){throw Error(`ctx.render() is not supported on the Fastify runtime (no view engine).`)}writeHead(statusCode,headers){return this.reply.hijack(),this.reply.raw.writeHead(statusCode,headers),this}flushHeaders(){return this.reply.raw.flushHeaders?.(),this}write(chunk){return this.reply.raw.write(chunk)}end(data){return this.reply.raw.end(data),this}once(event,listener){return this.reply.raw.once(event,listener),this}get headersSent(){return this.reply.sent||this.reply.raw.headersSent}};function replyDriver(reply){return new FastifyReplyDriver(reply)}function routeHandler(entry){let validator=entry.meta.validation?validate(entry.meta.validation):void 0;return async(request,reply)=>{let raw=request.raw;if(raw.body=request.body,raw.params=request.params,raw.query=request.query,entry.meta.upload&&entry.meta.upload.mode!==`none`){if(typeof request.parts!=`function`)throw Error(`@forinda/kickjs: @FileUpload on the Fastify runtime requires '@fastify/multipart'.
12
12
  Install it as a peer dependency: pnpm add @fastify/multipart.`);let rawParts=[],fields={};for await(let part of request.parts())part.type===`file`&&part.toBuffer?rawParts.push({fieldname:part.fieldname,filename:part.filename??``,mimetype:part.mimetype??`application/octet-stream`,buffer:await part.toBuffer()}):fields[part.fieldname]=part.value;let{file,files}=applyUploadConfig(rawParts,entry.meta.upload);raw.file=file,raw.files=files,raw.body={...raw.body,...fields}}let headerId=raw.headers[`x-request-id`],requestId=(Array.isArray(headerId)?headerId[0]:headerId)||raw.requestId,store=createRequestStore(requestId);raw.requestId=store.requestId,reply.raw.once(`close`,()=>disposeRequestStore(store)),await requestStore.run(store,async()=>{publishMatchedRoute(raw,entry);let ctx=new RequestContext(raw,reply,NOOP_NEXT,replyDriver(reply));if(entry.earlyContributorRunner&&(await entry.earlyContributorRunner(ctx),reply.sent))return;validator&&await new Promise((resolve,reject)=>{validator(raw,void 0,err=>err?reject(err):resolve())});for(let mw of entry.middlewares){let advanced=!1;if(await new Promise((resolve,reject)=>{Promise.resolve(mw(ctx,err=>{advanced=!0,err?reject(err):resolve()})).catch(reject)}),!advanced||reply.sent)return}if(entry.contributorRunner&&await entry.contributorRunner(ctx),reply.sent)return;let result=await entry.handler(ctx);reply.sent||applyHandlerResult(ctx,result)})}}function fastifyRuntime(){return{name:`fastify`,createApp(options={}){let app=loadPeer(`fastify`)({logger:!1,routerOptions:{ignoreTrailingSlash:!0},trustProxy:typeof options.trustProxy!=`function`&&options.trustProxy});app[QUEUE]=[];let addParser=app.addContentTypeParser?.bind(app);return addParser&&(addParser(/^application\/[\w.+-]+\+json$/,{parseAs:`string`},(_req,body,done)=>{if(body===``)return done(null,void 0);try{done(null,JSON.parse(body))}catch(err){done(HttpException.badRequest(err instanceof Error?err.message:`Request body could not be parsed`),void 0)}}),addParser(`application/x-www-form-urlencoded`,{parseAs:`string`},(_req,body,done)=>{done(null,Object.fromEntries(new URLSearchParams(body)))}),addParser(/^text\//,{parseAs:`string`},(_req,body,done)=>{done(null,body)}),addParser(`*`,{parseAs:`string`},(req,body,done)=>{if(!body)return done(null,void 0);done(unsupportedMediaTypeError(req.headers?.[`content-type`]),void 0)})),app},nodeHandler(app){let state=app;state[READY]||(state[READY]=(async()=>{if(state[NEEDS_MULTIPART]){let multipart=loadPeer(`@fastify/multipart`);await app.register(multipart,{limits:{fileSize:2**53-1}})}let middie=loadPeer(`@fastify/middie`);await app.register(middie);for(let{mw,opts}of state[QUEUE]??[])opts?.path===void 0?app.use(mw):app.use(opts.path,mw);await app.ready()})());let readyP=state[READY];return(req,res,next)=>{next&&(req[NEXT]=next),readyP.then(()=>app.routing(req,res)).catch(err=>{next&&next(err)})}},mountRoutes(app,table){for(let{mountPath,routes}of table)for(let entry of routes){entry.meta.upload&&(app[NEEDS_MULTIPART]=!0);let url=joinPath(mountPath,entry.path);app.route({method:entry.method,url,handler:routeHandler(entry)})}},useConnect(app,mw,opts){app[QUEUE]?.push({mw,opts})},serveStatic(app,prefix,dir){let serveStatic=loadPeer(`serve-static`);this.useConnect(app,serveStatic(dir),{path:prefix})},setNotFound(app,mw){app.setNotFoundHandler((request,reply)=>{let next=request.raw[NEXT];if(next){reply.hijack(),next();return}mw(request.raw,replyDriver(reply),NOOP_NEXT)})},setErrorHandler(app,mw){app.setErrorHandler((err,request,reply)=>{mw(err,request.raw,replyDriver(reply),NOOP_NEXT)})},capabilities:{render:!1,uploads:!0,connectMiddleware:!0,nativeBodyParsing:!0,http2:!0}}}function joinPath(mountPath,path){let a=mountPath.endsWith(`/`)?mountPath.slice(0,-1):mountPath;return path===`/`||path===``?a===``?`/`:a:`${a}${path.startsWith(`/`)?path:`/${path}`}`}export{fastifyRuntime};
13
13
  //# sourceMappingURL=fastify.mjs.map
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v9.0.2
2
+ * @forinda/kickjs v9.0.3
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{createLogger,describeError}from"../../core/logger.mjs";import{n as WebRequestShim,r as WebResponseDriver,t as compileWebRoute}from"../../handler-CnjyOHSw.mjs";import{createRequire}from"node:module";const log=createLogger(`H3WebRuntime`),peerRequire=createRequire(import.meta.url),NOTFOUND_MW=Symbol(`kickjs.h3web.notFoundMw`),ERROR_MW=Symbol(`kickjs.h3web.errorMw`),ASSEMBLED=Symbol(`kickjs.h3web.assembled`),NOOP_NEXT=()=>{};function loadH3v2(injected){let m=injected??peerRequire(`h3`),resolved=m&&m.__esModule&&m.default?{...m.default,...m}:m;if(typeof resolved.H3!=`function`)throw Error("@forinda/kickjs: h3WebRuntime() requires h3 v2 (the web-standards line, npm dist-tag 'latest'), but the installed h3 has no H3 class — that is the v1 line. Either `pnpm add h3@latest` for this runtime, or keep h3 v1 and use h3Runtime() from '@forinda/kickjs/h3'.");return resolved}async function runConnectTerminal(mw,event,err){let req=new WebRequestShim(event.req,event.url),driver=new WebResponseDriver(event.req.signal);return mw&&(err===void 0?mw(req,driver,NOOP_NEXT):mw(err,req,driver,NOOP_NEXT)),driver.settled||(err===void 0?driver.status(404).json({error:`Not Found`}):(log.error(err,`h3-web: error handler did not settle the response — ${describeError(err)}`),driver.status(500).json({error:`Internal Server Error`,...typeof process<`u`&&process.env?.NODE_ENV===`production`?{}:{message:describeError(err)}}))),driver.ready}const FETCH_CONNECT=new WeakMap;function fetchConnectState(event){let state=FETCH_CONNECT.get(event);return state||(state={req:new WebRequestShim(event.req,event.url),headers:new Headers},FETCH_CONNECT.set(event,state)),state}function withConnectHeaders(event,response){let state=FETCH_CONNECT.get(event);if(!state)return response;let missing=[...state.headers].filter(([name])=>name===`set-cookie`||!response.headers.has(name));if(state.headers=new Headers,missing.length===0)return response;try{for(let[name,value]of missing)response.headers.append(name,value);return response}catch{let headers=new Headers(response.headers);for(let[name,value]of missing)headers.append(name,value);return new Response(response.body,{status:response.status,statusText:response.statusText,headers})}}var FetchConnectResponse=class extends WebResponseDriver{prepared;code=200;listeners=[];constructor(prepared,signal){super(signal),this.prepared=prepared}get statusCode(){return this.code}set statusCode(code){this.status(code)}status(code){return this.code=code,super.status(code)}writeHead(code,headers){return this.code=code,super.writeHead(code,headers)}setHeader(name,value){if(Array.isArray(value)){this.prepared.delete(name);for(let v of value)this.prepared.append(name,String(v))}else this.prepared.set(name,String(value));return this}getHeader(name){return this.prepared.get(name)??void 0}hasHeader(name){return this.prepared.has(name)}removeHeader(name){this.prepared.delete(name)}once(event,listener){return this.listeners.push([event,listener]),this}on(event,listener){return this.once(event,listener)}removeListener(event,listener){return this.listeners=this.listeners.filter(([e,l])=>e!==event||l!==listener),this}off(event,listener){return this.removeListener(event,listener)}done(){for(let event of[`finish`,`close`]){for(let[e,listener]of this.listeners)e===event&&listener();this.listeners=this.listeners.filter(([e])=>e!==event)}}};function runConnectOnFetch(mw,event,next){let state=fetchConnectState(event),res=new FetchConnectResponse(state.headers,event.req.signal);return new Promise((resolve,reject)=>{let respond=response=>{res.done(),resolve(withConnectHeaders(event,response))},fail=err=>{res.done(),reject(err)};res.ready.then(respond);let connectNext=err=>{if(err)return fail(err);if(res.settled)return;let id=state.req.requestId;if(id&&event.req.headers.get(`x-request-id`)!==id)try{event.req.headers.set(`x-request-id`,id)}catch{}let downstream;try{downstream=next()}catch(e){return fail(e)}Promise.resolve(downstream).then(value=>{if(value instanceof Response)return respond(value);res.done(),resolve(value)},fail)};try{mw(state.req,res,connectNext)}catch(err){fail(err)}})}function assembleCatchAll(app){let state=app;state[ASSEMBLED]||(state[ASSEMBLED]=!0,app.all(`/**`,event=>{let mw=state[NOTFOUND_MW];return runConnectTerminal(mw,event)}))}function h3WebRuntime(options={}){let{H3,toNodeHandler,fromNodeHandler}=loadH3v2(options.h3);return{name:`h3-web`,createApp(_options={}){let holder={},app=new H3({onError:(error,event)=>{let mw=holder.app?.[ERROR_MW];return runConnectTerminal(mw,event,error).then(response=>withConnectHeaders(event,response))}});return holder.app=app,app},nodeHandler(app){assembleCatchAll(app);let listener=toNodeHandler(app);return(req,res,next)=>{try{let result=listener(req,res);result&&typeof result.then==`function`&&result.catch(err=>next?.(err))}catch(err){next?.(err)}}},mountRoutes(app,table){for(let{mountPath,routes}of table)for(let entry of routes){let url=joinPath(mountPath,entry.path),run=compileWebRoute(entry,{onError:async(err,req,driver)=>{let mw=app[ERROR_MW];mw&&mw(err,req,driver,NOOP_NEXT)}});app.on(entry.method,url,event=>run({request:event.req,url:event.url,params:event.context.params??{}}))}},useConnect(app,mw,opts){let viaNode=fromNodeHandler(mw),handler=(event,next)=>event.req.runtime?.node?.res?viaNode(event):runConnectOnFetch(mw,event,next);opts?.path===void 0?app.use(handler):app.use(joinPath(String(opts.path),`/**`),handler)},serveStatic(app,prefix,dir){let serveStatic=peerRequire(`serve-static`);this.useConnect(app,serveStatic(dir),{path:prefix})},setNotFound(app,mw){app[NOTFOUND_MW]=mw,assembleCatchAll(app)},setErrorHandler(app,mw){app[ERROR_MW]=mw},capabilities:{render:!1,uploads:!0,connectMiddleware:!0,nativeBodyParsing:!0}}}function joinPath(mountPath,path){let a=mountPath.endsWith(`/`)?mountPath.slice(0,-1):mountPath;return path===`/`||path===``?a===``?`/`:a:`${a}${path.startsWith(`/`)?path:`/${path}`}`}export{h3WebRuntime};
11
+ import{createLogger,describeError}from"../../core/logger.mjs";import{n as WebRequestShim,r as WebResponseDriver,t as compileWebRoute}from"../../handler-eguNTfCv.mjs";import{createRequire}from"node:module";const log=createLogger(`H3WebRuntime`),peerRequire=createRequire(import.meta.url),NOTFOUND_MW=Symbol(`kickjs.h3web.notFoundMw`),ERROR_MW=Symbol(`kickjs.h3web.errorMw`),ASSEMBLED=Symbol(`kickjs.h3web.assembled`),NOOP_NEXT=()=>{};function loadH3v2(injected){let m=injected??peerRequire(`h3`),resolved=m&&m.__esModule&&m.default?{...m.default,...m}:m;if(typeof resolved.H3!=`function`)throw Error("@forinda/kickjs: h3WebRuntime() requires h3 v2 (the web-standards line, npm dist-tag 'latest'), but the installed h3 has no H3 class — that is the v1 line. Either `pnpm add h3@latest` for this runtime, or keep h3 v1 and use h3Runtime() from '@forinda/kickjs/h3'.");return resolved}async function runConnectTerminal(mw,event,err){let req=new WebRequestShim(event.req,event.url),driver=new WebResponseDriver(event.req.signal);return mw&&(err===void 0?mw(req,driver,NOOP_NEXT):mw(err,req,driver,NOOP_NEXT)),driver.settled||(err===void 0?driver.status(404).json({error:`Not Found`}):(log.error(err,`h3-web: error handler did not settle the response — ${describeError(err)}`),driver.status(500).json({error:`Internal Server Error`,...typeof process<`u`&&process.env?.NODE_ENV===`production`?{}:{message:describeError(err)}}))),driver.ready}const FETCH_CONNECT=new WeakMap;function fetchConnectState(event){let state=FETCH_CONNECT.get(event);return state||(state={req:new WebRequestShim(event.req,event.url),headers:new Headers},FETCH_CONNECT.set(event,state)),state}function withConnectHeaders(event,response){let state=FETCH_CONNECT.get(event);if(!state)return response;let missing=[...state.headers].filter(([name])=>name===`set-cookie`||!response.headers.has(name));if(state.headers=new Headers,missing.length===0)return response;try{for(let[name,value]of missing)response.headers.append(name,value);return response}catch{let headers=new Headers(response.headers);for(let[name,value]of missing)headers.append(name,value);return new Response(response.body,{status:response.status,statusText:response.statusText,headers})}}var FetchConnectResponse=class extends WebResponseDriver{prepared;code=200;listeners=[];constructor(prepared,signal){super(signal),this.prepared=prepared}get statusCode(){return this.code}set statusCode(code){this.status(code)}status(code){return this.code=code,super.status(code)}writeHead(code,headers){return this.code=code,super.writeHead(code,headers)}setHeader(name,value){if(Array.isArray(value)){this.prepared.delete(name);for(let v of value)this.prepared.append(name,String(v))}else this.prepared.set(name,String(value));return this}getHeader(name){return this.prepared.get(name)??void 0}hasHeader(name){return this.prepared.has(name)}removeHeader(name){this.prepared.delete(name)}once(event,listener){return this.listeners.push([event,listener]),this}on(event,listener){return this.once(event,listener)}removeListener(event,listener){return this.listeners=this.listeners.filter(([e,l])=>e!==event||l!==listener),this}off(event,listener){return this.removeListener(event,listener)}done(){for(let event of[`finish`,`close`]){for(let[e,listener]of this.listeners)e===event&&listener();this.listeners=this.listeners.filter(([e])=>e!==event)}}};function runConnectOnFetch(mw,event,next){let state=fetchConnectState(event),res=new FetchConnectResponse(state.headers,event.req.signal);return new Promise((resolve,reject)=>{let respond=response=>{res.done(),resolve(withConnectHeaders(event,response))},fail=err=>{res.done(),reject(err)};res.ready.then(respond);let connectNext=err=>{if(err)return fail(err);if(res.settled)return;let id=state.req.requestId;if(id&&event.req.headers.get(`x-request-id`)!==id)try{event.req.headers.set(`x-request-id`,id)}catch{}let downstream;try{downstream=next()}catch(e){return fail(e)}Promise.resolve(downstream).then(value=>{if(value instanceof Response)return respond(value);res.done(),resolve(value)},fail)};try{mw(state.req,res,connectNext)}catch(err){fail(err)}})}function assembleCatchAll(app){let state=app;state[ASSEMBLED]||(state[ASSEMBLED]=!0,app.all(`/**`,event=>{let mw=state[NOTFOUND_MW];return runConnectTerminal(mw,event)}))}function h3WebRuntime(options={}){let{H3,toNodeHandler,fromNodeHandler}=loadH3v2(options.h3);return{name:`h3-web`,createApp(_options={}){let holder={},app=new H3({onError:(error,event)=>{let mw=holder.app?.[ERROR_MW];return runConnectTerminal(mw,event,error).then(response=>withConnectHeaders(event,response))}});return holder.app=app,app},nodeHandler(app){assembleCatchAll(app);let listener=toNodeHandler(app);return(req,res,next)=>{try{let result=listener(req,res);result&&typeof result.then==`function`&&result.catch(err=>next?.(err))}catch(err){next?.(err)}}},mountRoutes(app,table){for(let{mountPath,routes}of table)for(let entry of routes){let url=joinPath(mountPath,entry.path),run=compileWebRoute(entry,{onError:async(err,req,driver)=>{let mw=app[ERROR_MW];mw&&mw(err,req,driver,NOOP_NEXT)}});app.on(entry.method,url,event=>run({request:event.req,url:event.url,params:event.context.params??{}}))}},useConnect(app,mw,opts){let viaNode=fromNodeHandler(mw),handler=(event,next)=>event.req.runtime?.node?.res?viaNode(event):runConnectOnFetch(mw,event,next);opts?.path===void 0?app.use(handler):app.use(joinPath(String(opts.path),`/**`),handler)},serveStatic(app,prefix,dir){let serveStatic=peerRequire(`serve-static`);this.useConnect(app,serveStatic(dir),{path:prefix})},setNotFound(app,mw){app[NOTFOUND_MW]=mw,assembleCatchAll(app)},setErrorHandler(app,mw){app[ERROR_MW]=mw},capabilities:{render:!1,uploads:!0,connectMiddleware:!0,nativeBodyParsing:!0}}}function joinPath(mountPath,path){let a=mountPath.endsWith(`/`)?mountPath.slice(0,-1):mountPath;return path===`/`||path===``?a===``?`/`:a:`${a}${path.startsWith(`/`)?path:`/${path}`}`}export{h3WebRuntime};
12
12
  //# sourceMappingURL=h3-web.mjs.map
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v9.0.2
2
+ * @forinda/kickjs v9.0.3
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{HttpException}from"../../core/errors.mjs";import{a as createRequestStore,f as unsupportedMediaTypeError,n as applyHandlerResult,o as disposeRequestStore,t as publishMatchedRoute,u as classifyMediaType}from"../../runtime-C3izjAep.mjs";import{l as requestStore,t as RequestContext}from"../../context-DEntnLmq.mjs";import{validate}from"../middleware/validate.mjs";import{t as applyUploadConfig}from"../../upload-config-BhaBjCiB.mjs";import"../middleware/upload.mjs";import{createRequire}from"node:module";const peerRequire=createRequire(import.meta.url),h3=()=>peerRequire(`h3`),NEXT=Symbol(`kickjs.h3.next`),ERROR_MW=Symbol(`kickjs.h3.errorMw`),ROUTER=Symbol(`kickjs.h3.router`),NOTFOUND_MW=Symbol(`kickjs.h3.notFoundMw`),ASSEMBLED=Symbol(`kickjs.h3.assembled`),NOOP_NEXT=()=>{},BODY_METHODS=new Set([`POST`,`PUT`,`PATCH`,`DELETE`]);var NodeResDriver=class{res;constructor(res){this.res=res}status(code){return this.res.statusCode=code,this}json(data){return!this.res.headersSent&&!this.res.getHeader(`content-type`)&&this.res.setHeader(`content-type`,`application/json; charset=utf-8`),this.res.end(JSON.stringify(data)),this}send(data){return this.res.end(data),this}type(contentType){return this.res.setHeader(`content-type`,contentType),this}setHeader(name,value){return this.res.setHeader(name,value),this}render(){throw Error(`ctx.render() is not supported on the h3 runtime (no view engine).`)}writeHead(statusCode,headers){return this.res.writeHead(statusCode,headers),this}flushHeaders(){return this.res.flushHeaders(),this}write(chunk){return this.res.write(chunk)}end(data){return this.res.end(data),this}once(event,listener){return this.res.once(event,listener),this}get headersSent(){return this.res.headersSent}};function resDriver(res){return new NodeResDriver(res)}const TRUST_PROXY=Symbol(`kickjs.h3.trustProxy`);function makeEventHandler(entry,trustProxy){let{getRouterParams,getQuery,readRawBody,readMultipartFormData}=h3(),upload=entry.meta.upload,validator=entry.meta.validation?validate(entry.meta.validation):void 0;return async event=>{let req=event.node.req,res=event.node.res;if(trustProxy&&!req.ip){let forwarded=req.headers[`x-forwarded-for`],first=(Array.isArray(forwarded)?forwarded[0]:forwarded)?.split(`,`)[0]?.trim();first&&(req.ip=first)}if(req.params=getRouterParams(event)??{},req.query=getQuery(event)??{},req.httpVersionMajor===2&&req.headers[`content-length`]===void 0&&BODY_METHODS.has(req.method??`GET`)&&(req.rawBody=await readStreamBuffer(req)),upload&&upload.mode!==`none`){let form=await readMultipartFormData(event).catch(()=>void 0),rawParts=[],fields={};for(let part of form??[])part.filename?rawParts.push({fieldname:part.name??``,filename:part.filename,mimetype:part.type??`application/octet-stream`,buffer:part.data}):part.name&&(fields[part.name]=part.data.toString(`utf-8`));let{file,files}=applyUploadConfig(rawParts,upload);req.file=file,req.files=files,req.body=fields}else if(BODY_METHODS.has(req.method??`GET`)){let{"content-length":length,"transfer-encoding":encoding}=req.headers;if(encoding!==void 0||length!==void 0&&length!==`0`||req.httpVersionMajor===2&&length!==`0`){let kind=classifyMediaType(req.headers[`content-type`]);if(kind!==`multipart`){let raw=await readRawBody(event,`utf8`);if(raw){if(kind===`unsupported`)throw unsupportedMediaTypeError(req.headers[`content-type`]);if(kind===`json`)try{req.body=JSON.parse(raw)}catch(err){throw HttpException.badRequest(err instanceof Error?err.message:`Request body could not be parsed`)}else req.body=kind===`urlencoded`?Object.fromEntries(new URLSearchParams(raw)):raw}}}}let headerId=req.headers[`x-request-id`],requestId=(Array.isArray(headerId)?headerId[0]:headerId)||req.requestId,store=createRequestStore(requestId);req.requestId=store.requestId,res.once(`close`,()=>disposeRequestStore(store)),await requestStore.run(store,async()=>{publishMatchedRoute(req,entry);let ctx=new RequestContext(req,res,NOOP_NEXT,resDriver(res));if(entry.earlyContributorRunner&&(await entry.earlyContributorRunner(ctx),res.writableEnded))return;validator&&await new Promise((resolve,reject)=>{validator(req,void 0,err=>err?reject(err):resolve())});for(let mw of entry.middlewares){let advanced=!1;if(await new Promise((resolve,reject)=>{Promise.resolve(mw(ctx,err=>{advanced=!0,err?reject(err):resolve()})).catch(reject)}),!advanced||res.writableEnded)return}if(entry.contributorRunner&&await entry.contributorRunner(ctx),res.writableEnded)return;let result=await entry.handler(ctx);!res.writableEnded&&!res.headersSent&&applyHandlerResult(ctx,result)})}}function unwrapH3Error(error){if(!(error instanceof Error))return error;let{cause}=error;return cause instanceof Error?cause:error}async function readStreamBuffer(req){let chunks=[];for await(let chunk of req)chunks.push(typeof chunk==`string`?Buffer.from(chunk):chunk);return Buffer.concat(chunks)}function h3Runtime(){return{name:`h3`,createApp(options={}){let{createApp}=h3(),app=createApp({onError:(error,event)=>{let res=event.node.res;if(res.writableEnded)return;let state=app,status=error?.statusCode,thrownByHandler=error?.cause instanceof Error;if(status===404&&!thrownByHandler){let next=event.node.req[NEXT];if(next){next();return}let nf=state[NOTFOUND_MW];if(nf){nf(event.node.req,resDriver(res),NOOP_NEXT);return}}let mw=state[ERROR_MW];mw&&mw(unwrapH3Error(error),event.node.req,resDriver(res),NOOP_NEXT)}});return app[TRUST_PROXY]=options.trustProxy,app},nodeHandler(app){let{toNodeListener}=h3(),state=app;state[ASSEMBLED]||(state[ASSEMBLED]=!0,state[ROUTER]&&app.use(state[ROUTER]));let listener=toNodeListener(app);return(req,res,next)=>{next&&(req[NEXT]=next),Promise.resolve(listener(req,res)).catch(err=>{next&&next(err)})}},mountRoutes(app,table){let{createRouter,eventHandler}=h3(),trustProxy=!!app[TRUST_PROXY],state=app;state[ROUTER]||(state[ROUTER]=createRouter());let router=state[ROUTER];for(let{mountPath,routes}of table)for(let entry of routes){let url=joinPath(mountPath,entry.path);router[entry.method.toLowerCase()](url,eventHandler(makeEventHandler(entry,trustProxy)))}},useConnect(app,mw,opts){let{fromNodeMiddleware}=h3(),handler=fromNodeMiddleware(mw);opts?.path===void 0?app.use(handler):app.use(opts.path,handler)},serveStatic(app,prefix,dir){let serveStatic=peerRequire(`serve-static`);this.useConnect(app,serveStatic(dir),{path:prefix})},setNotFound(app,mw){app[NOTFOUND_MW]=mw},setErrorHandler(app,mw){app[ERROR_MW]=mw},capabilities:{render:!1,uploads:!0,connectMiddleware:!0,nativeBodyParsing:!0,http2:!0}}}function joinPath(mountPath,path){let a=mountPath.endsWith(`/`)?mountPath.slice(0,-1):mountPath;return path===`/`||path===``?a===``?`/`:a:`${a}${path.startsWith(`/`)?path:`/${path}`}`}export{h3Runtime};
11
+ import{HttpException}from"../../core/errors.mjs";import{a as createRequestStore,f as unsupportedMediaTypeError,n as applyHandlerResult,o as disposeRequestStore,t as publishMatchedRoute,u as classifyMediaType}from"../../runtime-B4NQGN66.mjs";import{l as requestStore,t as RequestContext}from"../../context-fth87C3G.mjs";import{validate}from"../middleware/validate.mjs";import{t as applyUploadConfig}from"../../upload-config-BmpmZZVK.mjs";import"../middleware/upload.mjs";import{createRequire}from"node:module";const peerRequire=createRequire(import.meta.url),h3=()=>peerRequire(`h3`),NEXT=Symbol(`kickjs.h3.next`),ERROR_MW=Symbol(`kickjs.h3.errorMw`),ROUTER=Symbol(`kickjs.h3.router`),NOTFOUND_MW=Symbol(`kickjs.h3.notFoundMw`),ASSEMBLED=Symbol(`kickjs.h3.assembled`),NOOP_NEXT=()=>{},BODY_METHODS=new Set([`POST`,`PUT`,`PATCH`,`DELETE`]);var NodeResDriver=class{res;constructor(res){this.res=res}status(code){return this.res.statusCode=code,this}json(data){return!this.res.headersSent&&!this.res.getHeader(`content-type`)&&this.res.setHeader(`content-type`,`application/json; charset=utf-8`),this.res.end(JSON.stringify(data)),this}send(data){return this.res.end(data),this}type(contentType){return this.res.setHeader(`content-type`,contentType),this}setHeader(name,value){return this.res.setHeader(name,value),this}render(){throw Error(`ctx.render() is not supported on the h3 runtime (no view engine).`)}writeHead(statusCode,headers){return this.res.writeHead(statusCode,headers),this}flushHeaders(){return this.res.flushHeaders(),this}write(chunk){return this.res.write(chunk)}end(data){return this.res.end(data),this}once(event,listener){return this.res.once(event,listener),this}get headersSent(){return this.res.headersSent}};function resDriver(res){return new NodeResDriver(res)}const TRUST_PROXY=Symbol(`kickjs.h3.trustProxy`);function makeEventHandler(entry,trustProxy){let{getRouterParams,getQuery,readRawBody,readMultipartFormData}=h3(),upload=entry.meta.upload,validator=entry.meta.validation?validate(entry.meta.validation):void 0;return async event=>{let req=event.node.req,res=event.node.res;if(trustProxy&&!req.ip){let forwarded=req.headers[`x-forwarded-for`],first=(Array.isArray(forwarded)?forwarded[0]:forwarded)?.split(`,`)[0]?.trim();first&&(req.ip=first)}if(req.params=getRouterParams(event)??{},req.query=getQuery(event)??{},req.httpVersionMajor===2&&req.headers[`content-length`]===void 0&&BODY_METHODS.has(req.method??`GET`)&&(req.rawBody=await readStreamBuffer(req)),upload&&upload.mode!==`none`){let form=await readMultipartFormData(event).catch(()=>void 0),rawParts=[],fields={};for(let part of form??[])part.filename?rawParts.push({fieldname:part.name??``,filename:part.filename,mimetype:part.type??`application/octet-stream`,buffer:part.data}):part.name&&(fields[part.name]=part.data.toString(`utf-8`));let{file,files}=applyUploadConfig(rawParts,upload);req.file=file,req.files=files,req.body=fields}else if(BODY_METHODS.has(req.method??`GET`)){let{"content-length":length,"transfer-encoding":encoding}=req.headers;if(encoding!==void 0||length!==void 0&&length!==`0`||req.httpVersionMajor===2&&length!==`0`){let kind=classifyMediaType(req.headers[`content-type`]);if(kind!==`multipart`){let raw=await readRawBody(event,`utf8`);if(raw){if(kind===`unsupported`)throw unsupportedMediaTypeError(req.headers[`content-type`]);if(kind===`json`)try{req.body=JSON.parse(raw)}catch(err){throw HttpException.badRequest(err instanceof Error?err.message:`Request body could not be parsed`)}else req.body=kind===`urlencoded`?Object.fromEntries(new URLSearchParams(raw)):raw}}}}let headerId=req.headers[`x-request-id`],requestId=(Array.isArray(headerId)?headerId[0]:headerId)||req.requestId,store=createRequestStore(requestId);req.requestId=store.requestId,res.once(`close`,()=>disposeRequestStore(store)),await requestStore.run(store,async()=>{publishMatchedRoute(req,entry);let ctx=new RequestContext(req,res,NOOP_NEXT,resDriver(res));if(entry.earlyContributorRunner&&(await entry.earlyContributorRunner(ctx),res.writableEnded))return;validator&&await new Promise((resolve,reject)=>{validator(req,void 0,err=>err?reject(err):resolve())});for(let mw of entry.middlewares){let advanced=!1;if(await new Promise((resolve,reject)=>{Promise.resolve(mw(ctx,err=>{advanced=!0,err?reject(err):resolve()})).catch(reject)}),!advanced||res.writableEnded)return}if(entry.contributorRunner&&await entry.contributorRunner(ctx),res.writableEnded)return;let result=await entry.handler(ctx);!res.writableEnded&&!res.headersSent&&applyHandlerResult(ctx,result)})}}function unwrapH3Error(error){if(!(error instanceof Error))return error;let{cause}=error;return cause instanceof Error?cause:error}async function readStreamBuffer(req){let chunks=[];for await(let chunk of req)chunks.push(typeof chunk==`string`?Buffer.from(chunk):chunk);return Buffer.concat(chunks)}function h3Runtime(){return{name:`h3`,createApp(options={}){let{createApp}=h3(),app=createApp({onError:(error,event)=>{let res=event.node.res;if(res.writableEnded)return;let state=app,status=error?.statusCode,thrownByHandler=error?.cause instanceof Error;if(status===404&&!thrownByHandler){let next=event.node.req[NEXT];if(next){next();return}let nf=state[NOTFOUND_MW];if(nf){nf(event.node.req,resDriver(res),NOOP_NEXT);return}}let mw=state[ERROR_MW];mw&&mw(unwrapH3Error(error),event.node.req,resDriver(res),NOOP_NEXT)}});return app[TRUST_PROXY]=options.trustProxy,app},nodeHandler(app){let{toNodeListener}=h3(),state=app;state[ASSEMBLED]||(state[ASSEMBLED]=!0,state[ROUTER]&&app.use(state[ROUTER]));let listener=toNodeListener(app);return(req,res,next)=>{next&&(req[NEXT]=next),Promise.resolve(listener(req,res)).catch(err=>{next&&next(err)})}},mountRoutes(app,table){let{createRouter,eventHandler}=h3(),trustProxy=!!app[TRUST_PROXY],state=app;state[ROUTER]||(state[ROUTER]=createRouter());let router=state[ROUTER];for(let{mountPath,routes}of table)for(let entry of routes){let url=joinPath(mountPath,entry.path);router[entry.method.toLowerCase()](url,eventHandler(makeEventHandler(entry,trustProxy)))}},useConnect(app,mw,opts){let{fromNodeMiddleware}=h3(),handler=fromNodeMiddleware(mw);opts?.path===void 0?app.use(handler):app.use(opts.path,handler)},serveStatic(app,prefix,dir){let serveStatic=peerRequire(`serve-static`);this.useConnect(app,serveStatic(dir),{path:prefix})},setNotFound(app,mw){app[NOTFOUND_MW]=mw},setErrorHandler(app,mw){app[ERROR_MW]=mw},capabilities:{render:!1,uploads:!0,connectMiddleware:!0,nativeBodyParsing:!0,http2:!0}}}function joinPath(mountPath,path){let a=mountPath.endsWith(`/`)?mountPath.slice(0,-1):mountPath;return path===`/`||path===``?a===``?`/`:a:`${a}${path.startsWith(`/`)?path:`/${path}`}`}export{h3Runtime};
12
12
  //# sourceMappingURL=h3.mjs.map
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v9.0.2
2
+ * @forinda/kickjs v9.0.3
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{a as loadEnvFromSchema,c as withEnv,i as loadEnv,n as defineEnv,o as reloadEnv,r as getEnv,s as resetEnvCache,t as baseEnvSchema}from"./env-Cj-U1sin.mjs";import{METADATA,Scope}from"./core/interfaces.mjs";import{a as isInjectionToken,d as KickError,f as formatKickError,i as createToken,n as tokenName,r as INJECTION_TOKEN,t as Container}from"./container-DM3gPzGt.mjs";import{ConsoleLoggerProvider,Logger,createLogger,describeError}from"./core/logger.mjs";import{a as getMethodMeta,c as pushClassMeta,d as setInMetaMap,f as setInMetaRecord,i as getMetaRecord,l as pushMethodMeta,n as getClassMetaOrUndefined,o as getMethodMetaOrUndefined,p as setMethodMeta,r as getMetaMap,s as hasClassMeta,t as getClassMeta,u as setClassMeta}from"./metadata-DrCyWXgz.mjs";import{ApiQueryParams,Asset,Autowired,Builder,Component,Controller,Delete,FileUpload,Get,Inject,Injectable,Middleware,Patch,Post,PostConstruct,PreDestroy,Put,Repository,Service,Value,normalizeApiQueryParamsConfig,withBuilder}from"./core/decorators.mjs";import{a as HEALTH_PROBE,c as ERROR_CHANNEL,d as DuplicateMountNameError,f as MissingMountDepError,h as defineModule,i as materializeRouter,l as HANDLER_CHANNEL,m as mountSort,n as buildRoutes,o as HealthController,p as MountCycleError,r as expressRuntime,s as healthModule,t as Application,u as RESPONSE_CHANNEL}from"./application-BSDt9vOo.mjs";import{t as MutableModuleRegistry}from"./module-registry-Bv3NMDX5.mjs";import{i as reportResponse,r as reportError}from"./observers-DzVW_zb7.mjs";import{CRON_META,Cron,cronScheduleId,getCronJobs,isCronJobEnabled,listCronJobs,runCronJob,runCronJobs}from"./core/cron.mjs";import{a as NoJobHandlerError,c as listJobQueues,d as stampJobContext,i as Job,l as registerJobContext,n as JOB_DISPATCHER,o as Process,r as JOB_META,s as listJobHandlers,t as JOB_CONTEXT_FIELD,u as runJob}from"./jobs-DIEQILZ8.mjs";import{CacheEvict,Cacheable,MemoryCacheProvider,getCacheProvider,setCacheProvider}from"./core/cache.mjs";import{r as registerDisposable,t as disposeAll}from"./disposables-C2uznWks.mjs";import{HttpException,HttpStatus,ProblemException,Problems,defaultProblemTitle,normalizeProblem}from"./core/errors.mjs";import{buildMountPath,joinPaths,normalizePath}from"./core/path.mjs";import{computed,isRef,reactive,ref,toRefs,unref,watch}from"./core/reactivity.mjs";import{l as resolveRouteFlags,n as assertFlagTest,o as getRouteFlags,r as defineRouteFlag,s as matchesFlagTest}from"./route-flag-DEUYr5Br.mjs";import{a as clearAssetCache,c as groupAssetKeys,i as assets,l as stripExt,n as ASSET_MANIFEST_VERSION,o as resolveAsset,r as UnknownAssetError,s as useAssets,t as ASSETS,u as defineContextDecorator}from"./core-Cv1KjPCj.mjs";import{i as MissingContributorError,n as DuplicateContributorError,r as MissingContextValueError,t as ContributorCycleError}from"./context-errors-Cb4jKwm4.mjs";import{i as buildPipeline,r as buildRouteTable}from"./router-builder-Cig7o4BH.mjs";import{t as runContributors}from"./contributor-runner-Buxk3Ay_.mjs";import{t as defineAdapter}from"./define-adapter-7WlOyHBR.mjs";import{n as bindRoutePolicy,r as offerRoutePolicy,t as RoutePolicyTable}from"./route-policy-BmiX7NiW.mjs";import{c as requestScopeMiddleware,i as reply,n as applyHandlerResult,r as isReply}from"./runtime-C3izjAep.mjs";import{REQUEST_ID_HEADER,requestId}from"./http/middleware/request-id.mjs";import{a as settleBackgroundWork,c as getRequestValue,l as requestStore,o as waitUntil,r as forwardBackgroundWork,s as getRequestStore,t as RequestContext}from"./context-DEntnLmq.mjs";import{errorHandler,notFoundHandler}from"./http/middleware/error-handler.mjs";import{a as parseQuery,c as resetQueryParsingDefaults,i as parsePagination,l as setQueryParsingDefaults,n as getQueryParsingDefaults,o as parseSearchQuery,r as parseFilters,s as parseSort,t as buildQueryParams,u as FILTER_OPERATORS}from"./query-BGQdAxkX.mjs";import{validate}from"./http/middleware/validate.mjs";import{r as resolveMimeTypes}from"./upload-config-BhaBjCiB.mjs";import{buildUploadMiddleware,cleanupFiles,upload}from"./http/middleware/upload.mjs";import{i as KickCronAdapter,n as isClusterPrimary,r as CRON_WORKER_ENV,t as bootstrap}from"./bootstrap-CJ8ALvif.mjs";import{n as csrfGuard,r as randomHex,t as csrf}from"./csrf-BgfnRJ_y.mjs";import{requestLogger}from"./http/middleware/request-logger.mjs";import{helmet}from"./http/middleware/helmet.mjs";import{cors}from"./http/middleware/cors.mjs";import{rateLimit}from"./http/middleware/rate-limit.mjs";import{n as KvSessionStore,r as rateLimitGuard,t as KvRateLimitStore}from"./kv-stores-D4dmpn6D.mjs";import{session}from"./http/middleware/session.mjs";import{ViewAdapter}from"./http/middleware/views.mjs";import{SpaAdapter}from"./http/middleware/spa.mjs";import{n as createConfigService,t as ConfigService}from"./config-Dz5GgLKS.mjs";var ModuleList=class extends Array{mount(module){return this.push(module),this}};function defineModules(...initial){let list=new ModuleList;for(let entry of initial)list.push(entry);return list}var CircuitOpenError=class extends Error{breakerName;constructor(breakerName){super(`Circuit breaker "${breakerName}" is open — request rejected`),this.breakerName=breakerName,this.name=`CircuitOpenError`}},CircuitBreaker=class{name;state=`closed`;failures=0;successes=0;halfOpenAttempts=0;lastFailure;openedAt;failureThreshold;resetTimeout;halfOpenMax;constructor(name,options){this.name=name,this.failureThreshold=options.failureThreshold,this.resetTimeout=options.resetTimeout,this.halfOpenMax=options.halfOpenMax??1}async execute(fn){if(this.maybeTransitionToHalfOpen(),this.state===`open`||this.state===`half_open`&&this.halfOpenAttempts>=this.halfOpenMax)throw new CircuitOpenError(this.name);this.state===`half_open`&&this.halfOpenAttempts++;try{let result=await fn();return this.onSuccess(),result}catch(error){throw this.onFailure(),error}}getState(){return this.maybeTransitionToHalfOpen(),this.state}getStats(){return this.maybeTransitionToHalfOpen(),{failures:this.failures,successes:this.successes,state:this.state,...this.lastFailure?{lastFailure:this.lastFailure}:{}}}reset(){this.state=`closed`,this.failures=0,this.successes=0,this.halfOpenAttempts=0,this.lastFailure=void 0,this.openedAt=void 0}onSuccess(){this.state===`half_open`&&(this.state=`closed`,this.failures=0,this.halfOpenAttempts=0,this.openedAt=void 0),this.successes++}onFailure(){this.failures++,this.lastFailure=new Date,this.state===`half_open`?(this.state=`open`,this.openedAt=Date.now(),this.halfOpenAttempts=0):this.state===`closed`&&this.failures>=this.failureThreshold&&(this.state=`open`,this.openedAt=Date.now())}maybeTransitionToHalfOpen(){this.state===`open`&&this.openedAt!=null&&Date.now()-this.openedAt>=this.resetTimeout&&(this.state=`half_open`,this.halfOpenAttempts=0)}};const mergeConfig=(defaults={},overrides={})=>({...defaults,...overrides}),composeName=(base,scope)=>`${base}:${scope}`;function definePlugin(options){let buildSync=(instanceName,scoped,overrides)=>{let config=mergeConfig(options.defaults,overrides),built=options.build(config,{name:instanceName,scoped});return built.name=instanceName,built},buildAsync=(instanceName,opts)=>{let inner,ensureInner=async container=>{if(inner)return inner;let deps=(opts.inject??[]).map(tok=>container.resolve(tok)),config=await opts.useFactory(...deps);return inner=buildSync(instanceName,!1,config),inner};return{name:instanceName,async onReady(container){let built=await ensureInner(container);built.register?.(container),await built.onReady?.(container)},async shutdown(){await inner?.shutdown?.()}}},factory=(config=>buildSync(options.name,!1,config));return factory.scoped=(scopeName,config)=>buildSync(composeName(options.name,scopeName),!0,config),factory.async=opts=>buildAsync(options.name,opts),Object.defineProperty(factory,"definition",{value:Object.freeze({...options})}),factory}function createHandler(options){let app,ready=()=>(app??=(async()=>{let instance=new Application(options);try{await instance.startWithoutServer()}catch(err){throw app=void 0,await instance.shutdown().catch(()=>{}),err}return instance})(),app);return{ready,async fetch(request,platform){let response=await(await ready()).fetch(request);return forwardBackgroundWork(platform),response},async node(req,res,platform){if((await ready()).handle(req,res),platform?.waitUntil){let sent=new Promise(resolve=>res.once(`close`,()=>resolve()));platform.waitUntil(sent.then(settleBackgroundWork))}},async close(){let current=app;app=void 0,current&&await(await current).shutdown()}}}function defineHttpContextDecoratorImpl(spec){return defineContextDecorator(spec)}const defineHttpContextDecorator=Object.freeze(Object.assign(defineHttpContextDecoratorImpl,{withParams:()=>spec=>defineHttpContextDecoratorImpl(spec)})),TRACEPARENT_HEADER=`traceparent`,TRACEPARENT_RE=/^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;function generateTraceId(){return randomHex(16)}function generateSpanId(){return randomHex(8)}function parseTraceparent(header){let match=TRACEPARENT_RE.exec(header.trim().toLowerCase());if(!match)return null;let version=match[1],traceId=match[2],parentSpanId=match[3],flags=match[4];return version===`ff`||traceId===`00000000000000000000000000000000`||parentSpanId===`0000000000000000`?null:{version,traceId,parentSpanId,flags}}function traceContext(options={}){return(req,res,next)=>{let store=requestStore.getStore(),traceparentHeader=req.headers[TRACEPARENT_HEADER],raw=Array.isArray(traceparentHeader)?traceparentHeader[0]:traceparentHeader,parsed=raw?parseTraceparent(raw):null,traceId=parsed?.traceId??generateTraceId(),spanId=generateSpanId();store&&(store.values.set(`traceId`,traceId),store.values.set(`spanId`,spanId),parsed&&(store.values.set(`parentSpanId`,parsed.parentSpanId),store.values.set(`traceFlags`,parsed.flags),store.values.set(`traceVersion`,parsed.version))),req.traceId=traceId,req.spanId=spanId,options.propagateResponse&&res.setHeader(`traceresponse`,traceId),next()}}export{ASSETS,ASSET_MANIFEST_VERSION,ApiQueryParams,Application,Asset,Autowired,Builder,CRON_META,CRON_WORKER_ENV,CacheEvict,Cacheable,CircuitBreaker,CircuitOpenError,Component,ConfigService,ConsoleLoggerProvider,Container,ContributorCycleError,Controller,Cron,Delete,DuplicateContributorError,DuplicateMountNameError,ERROR_CHANNEL,FILTER_OPERATORS,FileUpload,Get,HANDLER_CHANNEL,HEALTH_PROBE,HealthController,HttpException,HttpStatus,INJECTION_TOKEN,Inject,Injectable,JOB_CONTEXT_FIELD,JOB_DISPATCHER,JOB_META,Job,KickCronAdapter,KickError,KvRateLimitStore,KvSessionStore,Logger,METADATA,MemoryCacheProvider,Middleware,MissingContextValueError,MissingContributorError,MissingMountDepError,ModuleList,MountCycleError,MutableModuleRegistry,NoJobHandlerError,Patch,Post,PostConstruct,PreDestroy,ProblemException,Problems,Process,Put,REQUEST_ID_HEADER,RESPONSE_CHANNEL,Repository,RequestContext,RoutePolicyTable,Scope,Service,SpaAdapter,TRACEPARENT_HEADER,UnknownAssetError,Value,ViewAdapter,applyHandlerResult,assertFlagTest,assets,baseEnvSchema,bindRoutePolicy,bootstrap,buildMountPath,buildPipeline,buildQueryParams,buildRouteTable,buildRoutes,buildUploadMiddleware,cleanupFiles,clearAssetCache,computed,cors,createConfigService,createHandler,createLogger,createToken,cronScheduleId,csrf,csrfGuard,defaultProblemTitle,defineAdapter,defineContextDecorator,defineEnv,defineHttpContextDecorator,defineModule,defineModules,definePlugin,defineRouteFlag,describeError,disposeAll,errorHandler,expressRuntime,formatKickError,getCacheProvider,getClassMeta,getClassMetaOrUndefined,getCronJobs,getEnv,getMetaMap,getMetaRecord,getMethodMeta,getMethodMetaOrUndefined,getQueryParsingDefaults,getRequestStore,getRequestValue,getRouteFlags,groupAssetKeys,hasClassMeta,healthModule,helmet,isClusterPrimary,isCronJobEnabled,isInjectionToken,isRef,isReply,joinPaths,listCronJobs,listJobHandlers,listJobQueues,loadEnv,loadEnvFromSchema,matchesFlagTest,materializeRouter,mountSort,normalizeApiQueryParamsConfig,normalizePath,normalizeProblem,notFoundHandler,offerRoutePolicy,parseFilters,parsePagination,parseQuery,parseSearchQuery,parseSort,parseTraceparent,pushClassMeta,pushMethodMeta,rateLimit,rateLimitGuard,reactive,ref,registerDisposable,registerJobContext,reloadEnv,reply,reportError,reportResponse,requestId,requestLogger,requestScopeMiddleware,requestStore,resetEnvCache,resetQueryParsingDefaults,resolveAsset,resolveMimeTypes,resolveRouteFlags,runContributors,runCronJob,runCronJobs,runJob,session,setCacheProvider,setClassMeta,setInMetaMap,setInMetaRecord,setMethodMeta,setQueryParsingDefaults,settleBackgroundWork,stampJobContext,stripExt,toRefs,tokenName,traceContext,unref,upload,useAssets,validate,waitUntil,watch,withBuilder,withEnv};
11
+ import{a as loadEnvFromSchema,c as withEnv,i as loadEnv,n as defineEnv,o as reloadEnv,r as getEnv,s as resetEnvCache,t as baseEnvSchema}from"./env-w27RDkCQ.mjs";import{METADATA,Scope}from"./core/interfaces.mjs";import{a as isInjectionToken,d as KickError,f as formatKickError,i as createToken,n as tokenName,r as INJECTION_TOKEN,t as Container}from"./container-ByH0uCpG.mjs";import{ConsoleLoggerProvider,Logger,createLogger,describeError}from"./core/logger.mjs";import{a as getMethodMeta,c as pushClassMeta,d as setInMetaMap,f as setInMetaRecord,i as getMetaRecord,l as pushMethodMeta,n as getClassMetaOrUndefined,o as getMethodMetaOrUndefined,p as setMethodMeta,r as getMetaMap,s as hasClassMeta,t as getClassMeta,u as setClassMeta}from"./metadata-CIXErBTj.mjs";import{ApiQueryParams,Asset,Autowired,Builder,Component,Controller,Delete,FileUpload,Get,Inject,Injectable,Middleware,Patch,Post,PostConstruct,PreDestroy,Put,Repository,Service,Value,normalizeApiQueryParamsConfig,withBuilder}from"./core/decorators.mjs";import{a as HEALTH_PROBE,c as ERROR_CHANNEL,d as DuplicateMountNameError,f as MissingMountDepError,h as defineModule,i as materializeRouter,l as HANDLER_CHANNEL,m as mountSort,n as buildRoutes,o as HealthController,p as MountCycleError,r as expressRuntime,s as healthModule,t as Application,u as RESPONSE_CHANNEL}from"./application-DPLzamTb.mjs";import{t as MutableModuleRegistry}from"./module-registry-IO8RfVjX.mjs";import{i as reportResponse,r as reportError}from"./observers-BtXkP5km.mjs";import{CRON_META,Cron,cronScheduleId,getCronJobs,isCronJobEnabled,listCronJobs,runCronJob,runCronJobs}from"./core/cron.mjs";import{a as NoJobHandlerError,c as listJobQueues,d as stampJobContext,i as Job,l as registerJobContext,n as JOB_DISPATCHER,o as Process,r as JOB_META,s as listJobHandlers,t as JOB_CONTEXT_FIELD,u as runJob}from"./jobs-CeP4aLYR.mjs";import{CacheEvict,Cacheable,MemoryCacheProvider,getCacheProvider,setCacheProvider}from"./core/cache.mjs";import{r as registerDisposable,t as disposeAll}from"./disposables-CgCLyCoP.mjs";import{HttpException,HttpStatus,ProblemException,Problems,defaultProblemTitle,normalizeProblem}from"./core/errors.mjs";import{buildMountPath,joinPaths,normalizePath}from"./core/path.mjs";import{computed,isRef,reactive,ref,toRefs,unref,watch}from"./core/reactivity.mjs";import{l as resolveRouteFlags,n as assertFlagTest,o as getRouteFlags,r as defineRouteFlag,s as matchesFlagTest}from"./route-flag-CpTa8y1n.mjs";import{a as clearAssetCache,c as groupAssetKeys,i as assets,l as stripExt,n as ASSET_MANIFEST_VERSION,o as resolveAsset,r as UnknownAssetError,s as useAssets,t as ASSETS,u as defineContextDecorator}from"./core-DrjVUTxG.mjs";import{i as MissingContributorError,n as DuplicateContributorError,r as MissingContextValueError,t as ContributorCycleError}from"./context-errors-VjQBtxEc.mjs";import{i as buildPipeline,r as buildRouteTable}from"./router-builder-BxXKP8Wy.mjs";import{t as runContributors}from"./contributor-runner-BEjgGaoG.mjs";import{t as defineAdapter}from"./define-adapter-B4Iu_TcU.mjs";import{n as bindRoutePolicy,r as offerRoutePolicy,t as RoutePolicyTable}from"./route-policy-DPTLfer-.mjs";import{c as requestScopeMiddleware,i as reply,n as applyHandlerResult,r as isReply}from"./runtime-B4NQGN66.mjs";import{REQUEST_ID_HEADER,requestId}from"./http/middleware/request-id.mjs";import{a as settleBackgroundWork,c as getRequestValue,l as requestStore,o as waitUntil,r as forwardBackgroundWork,s as getRequestStore,t as RequestContext}from"./context-fth87C3G.mjs";import{errorHandler,notFoundHandler}from"./http/middleware/error-handler.mjs";import{a as parseQuery,c as resetQueryParsingDefaults,i as parsePagination,l as setQueryParsingDefaults,n as getQueryParsingDefaults,o as parseSearchQuery,r as parseFilters,s as parseSort,t as buildQueryParams,u as FILTER_OPERATORS}from"./query-CNa0mxSK.mjs";import{validate}from"./http/middleware/validate.mjs";import{r as resolveMimeTypes}from"./upload-config-BmpmZZVK.mjs";import{buildUploadMiddleware,cleanupFiles,upload}from"./http/middleware/upload.mjs";import{i as KickCronAdapter,n as isClusterPrimary,r as CRON_WORKER_ENV,t as bootstrap}from"./bootstrap-DUNvDJ1a.mjs";import{n as csrfGuard,r as randomHex,t as csrf}from"./csrf-CbFJiyKW.mjs";import{requestLogger}from"./http/middleware/request-logger.mjs";import{helmet}from"./http/middleware/helmet.mjs";import{cors}from"./http/middleware/cors.mjs";import{rateLimit}from"./http/middleware/rate-limit.mjs";import{n as KvSessionStore,r as rateLimitGuard,t as KvRateLimitStore}from"./kv-stores-5ocdxfdh.mjs";import{session}from"./http/middleware/session.mjs";import{ViewAdapter}from"./http/middleware/views.mjs";import{SpaAdapter}from"./http/middleware/spa.mjs";import{n as createConfigService,t as ConfigService}from"./config-C-sAHakM.mjs";var ModuleList=class extends Array{mount(module){return this.push(module),this}};function defineModules(...initial){let list=new ModuleList;for(let entry of initial)list.push(entry);return list}var CircuitOpenError=class extends Error{breakerName;constructor(breakerName){super(`Circuit breaker "${breakerName}" is open — request rejected`),this.breakerName=breakerName,this.name=`CircuitOpenError`}},CircuitBreaker=class{name;state=`closed`;failures=0;successes=0;halfOpenAttempts=0;lastFailure;openedAt;failureThreshold;resetTimeout;halfOpenMax;constructor(name,options){this.name=name,this.failureThreshold=options.failureThreshold,this.resetTimeout=options.resetTimeout,this.halfOpenMax=options.halfOpenMax??1}async execute(fn){if(this.maybeTransitionToHalfOpen(),this.state===`open`||this.state===`half_open`&&this.halfOpenAttempts>=this.halfOpenMax)throw new CircuitOpenError(this.name);this.state===`half_open`&&this.halfOpenAttempts++;try{let result=await fn();return this.onSuccess(),result}catch(error){throw this.onFailure(),error}}getState(){return this.maybeTransitionToHalfOpen(),this.state}getStats(){return this.maybeTransitionToHalfOpen(),{failures:this.failures,successes:this.successes,state:this.state,...this.lastFailure?{lastFailure:this.lastFailure}:{}}}reset(){this.state=`closed`,this.failures=0,this.successes=0,this.halfOpenAttempts=0,this.lastFailure=void 0,this.openedAt=void 0}onSuccess(){this.state===`half_open`&&(this.state=`closed`,this.failures=0,this.halfOpenAttempts=0,this.openedAt=void 0),this.successes++}onFailure(){this.failures++,this.lastFailure=new Date,this.state===`half_open`?(this.state=`open`,this.openedAt=Date.now(),this.halfOpenAttempts=0):this.state===`closed`&&this.failures>=this.failureThreshold&&(this.state=`open`,this.openedAt=Date.now())}maybeTransitionToHalfOpen(){this.state===`open`&&this.openedAt!=null&&Date.now()-this.openedAt>=this.resetTimeout&&(this.state=`half_open`,this.halfOpenAttempts=0)}};const mergeConfig=(defaults={},overrides={})=>({...defaults,...overrides}),composeName=(base,scope)=>`${base}:${scope}`;function definePlugin(options){let buildSync=(instanceName,scoped,overrides)=>{let config=mergeConfig(options.defaults,overrides),built=options.build(config,{name:instanceName,scoped});return built.name=instanceName,built},buildAsync=(instanceName,opts)=>{let inner,ensureInner=async container=>{if(inner)return inner;let deps=(opts.inject??[]).map(tok=>container.resolve(tok)),config=await opts.useFactory(...deps);return inner=buildSync(instanceName,!1,config),inner};return{name:instanceName,async onReady(container){let built=await ensureInner(container);built.register?.(container),await built.onReady?.(container)},async shutdown(){await inner?.shutdown?.()}}},factory=(config=>buildSync(options.name,!1,config));return factory.scoped=(scopeName,config)=>buildSync(composeName(options.name,scopeName),!0,config),factory.async=opts=>buildAsync(options.name,opts),Object.defineProperty(factory,"definition",{value:Object.freeze({...options})}),factory}function createHandler(options){let app,ready=()=>(app??=(async()=>{let instance=new Application(options);try{await instance.startWithoutServer()}catch(err){throw app=void 0,await instance.shutdown().catch(()=>{}),err}return instance})(),app);return{ready,async fetch(request,platform){let response=await(await ready()).fetch(request);return forwardBackgroundWork(platform),response},async node(req,res,platform){if((await ready()).handle(req,res),platform?.waitUntil){let sent=new Promise(resolve=>res.once(`close`,()=>resolve()));platform.waitUntil(sent.then(settleBackgroundWork))}},async close(){let current=app;app=void 0,current&&await(await current).shutdown()}}}function defineHttpContextDecoratorImpl(spec){return defineContextDecorator(spec)}const defineHttpContextDecorator=Object.freeze(Object.assign(defineHttpContextDecoratorImpl,{withParams:()=>spec=>defineHttpContextDecoratorImpl(spec)})),TRACEPARENT_HEADER=`traceparent`,TRACEPARENT_RE=/^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;function generateTraceId(){return randomHex(16)}function generateSpanId(){return randomHex(8)}function parseTraceparent(header){let match=TRACEPARENT_RE.exec(header.trim().toLowerCase());if(!match)return null;let version=match[1],traceId=match[2],parentSpanId=match[3],flags=match[4];return version===`ff`||traceId===`00000000000000000000000000000000`||parentSpanId===`0000000000000000`?null:{version,traceId,parentSpanId,flags}}function traceContext(options={}){return(req,res,next)=>{let store=requestStore.getStore(),traceparentHeader=req.headers[TRACEPARENT_HEADER],raw=Array.isArray(traceparentHeader)?traceparentHeader[0]:traceparentHeader,parsed=raw?parseTraceparent(raw):null,traceId=parsed?.traceId??generateTraceId(),spanId=generateSpanId();store&&(store.values.set(`traceId`,traceId),store.values.set(`spanId`,spanId),parsed&&(store.values.set(`parentSpanId`,parsed.parentSpanId),store.values.set(`traceFlags`,parsed.flags),store.values.set(`traceVersion`,parsed.version))),req.traceId=traceId,req.spanId=spanId,options.propagateResponse&&res.setHeader(`traceresponse`,traceId),next()}}export{ASSETS,ASSET_MANIFEST_VERSION,ApiQueryParams,Application,Asset,Autowired,Builder,CRON_META,CRON_WORKER_ENV,CacheEvict,Cacheable,CircuitBreaker,CircuitOpenError,Component,ConfigService,ConsoleLoggerProvider,Container,ContributorCycleError,Controller,Cron,Delete,DuplicateContributorError,DuplicateMountNameError,ERROR_CHANNEL,FILTER_OPERATORS,FileUpload,Get,HANDLER_CHANNEL,HEALTH_PROBE,HealthController,HttpException,HttpStatus,INJECTION_TOKEN,Inject,Injectable,JOB_CONTEXT_FIELD,JOB_DISPATCHER,JOB_META,Job,KickCronAdapter,KickError,KvRateLimitStore,KvSessionStore,Logger,METADATA,MemoryCacheProvider,Middleware,MissingContextValueError,MissingContributorError,MissingMountDepError,ModuleList,MountCycleError,MutableModuleRegistry,NoJobHandlerError,Patch,Post,PostConstruct,PreDestroy,ProblemException,Problems,Process,Put,REQUEST_ID_HEADER,RESPONSE_CHANNEL,Repository,RequestContext,RoutePolicyTable,Scope,Service,SpaAdapter,TRACEPARENT_HEADER,UnknownAssetError,Value,ViewAdapter,applyHandlerResult,assertFlagTest,assets,baseEnvSchema,bindRoutePolicy,bootstrap,buildMountPath,buildPipeline,buildQueryParams,buildRouteTable,buildRoutes,buildUploadMiddleware,cleanupFiles,clearAssetCache,computed,cors,createConfigService,createHandler,createLogger,createToken,cronScheduleId,csrf,csrfGuard,defaultProblemTitle,defineAdapter,defineContextDecorator,defineEnv,defineHttpContextDecorator,defineModule,defineModules,definePlugin,defineRouteFlag,describeError,disposeAll,errorHandler,expressRuntime,formatKickError,getCacheProvider,getClassMeta,getClassMetaOrUndefined,getCronJobs,getEnv,getMetaMap,getMetaRecord,getMethodMeta,getMethodMetaOrUndefined,getQueryParsingDefaults,getRequestStore,getRequestValue,getRouteFlags,groupAssetKeys,hasClassMeta,healthModule,helmet,isClusterPrimary,isCronJobEnabled,isInjectionToken,isRef,isReply,joinPaths,listCronJobs,listJobHandlers,listJobQueues,loadEnv,loadEnvFromSchema,matchesFlagTest,materializeRouter,mountSort,normalizeApiQueryParamsConfig,normalizePath,normalizeProblem,notFoundHandler,offerRoutePolicy,parseFilters,parsePagination,parseQuery,parseSearchQuery,parseSort,parseTraceparent,pushClassMeta,pushMethodMeta,rateLimit,rateLimitGuard,reactive,ref,registerDisposable,registerJobContext,reloadEnv,reply,reportError,reportResponse,requestId,requestLogger,requestScopeMiddleware,requestStore,resetEnvCache,resetQueryParsingDefaults,resolveAsset,resolveMimeTypes,resolveRouteFlags,runContributors,runCronJob,runCronJobs,runJob,session,setCacheProvider,setClassMeta,setInMetaMap,setInMetaRecord,setMethodMeta,setQueryParsingDefaults,settleBackgroundWork,stampJobContext,stripExt,toRefs,tokenName,traceContext,unref,upload,useAssets,validate,waitUntil,watch,withBuilder,withEnv};
12
12
  //# sourceMappingURL=index.mjs.map
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v9.0.2
2
+ * @forinda/kickjs v9.0.3
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{i as createToken}from"./container-DM3gPzGt.mjs";import{createLogger}from"./core/logger.mjs";import{c as pushClassMeta,n as getClassMetaOrUndefined,t as getClassMeta,u as setClassMeta}from"./metadata-DrCyWXgz.mjs";import{Service}from"./core/decorators.mjs";import{r as reportError}from"./observers-DzVW_zb7.mjs";const log=createLogger(`KickJobs`),JOB_META={JOB:`kick/queue/job`,PROCESS:`kick/queue/process`},jobClasses=new Map;function Job(queue){return target=>{Service()(target),setClassMeta(JOB_META.JOB,queue,target),jobClasses.set(target.name,target)}}function Process(jobName){return(target,propertyKey)=>{pushClassMeta(JOB_META.PROCESS,target.constructor,{handlerName:propertyKey,jobName})}}function listJobHandlers(container){let handlers=[];for(let target of jobClasses.values()){if(!container.has(target))continue;let queue=getClassMetaOrUndefined(JOB_META.JOB,target);if(queue===void 0)continue;let processes=getClassMeta(JOB_META.PROCESS,target,[]);processes.length===0&&log.warn(`@Job('${queue}') class ${target.name} has no @Process methods`);for(let process of processes)handlers.push({...process,queue,target})}return handlers}function listJobQueues(container){return[...new Set(listJobHandlers(container).map(h=>h.queue))]}var NoJobHandlerError=class extends Error{queue;jobName;constructor(queue,jobName){super(`No @Process handler for job "${jobName}" on queue "${queue}"`),this.queue=queue,this.jobName=jobName,this.name=`NoJobHandlerError`}};async function runJob(container,queue,job,report={}){let handlers=listJobHandlers(container).filter(h=>h.queue===queue),handler=handlers.find(h=>h.jobName===job.name)??handlers.find(h=>h.jobName===void 0);try{if(!handler)throw new NoJobHandlerError(queue,job.name);let instance=container.resolve(handler.target);return await withJobContext(job,()=>instance[handler.handlerName](job))()}catch(err){throw log.error({err},`Job ${queue}/${job.name} failed`),reportError(err,{source:`job`,context:{queue,job:job.name,id:job.id,...report}}),err}}const JOB_CONTEXT_FIELD=`__kickContext`,jobContextCarriers=new Map;function registerJobContext(carrier){jobContextCarriers.set(carrier.key,carrier)}const isPlainObject=v=>!!v&&typeof v==`object`&&Object.getPrototypeOf(v)===Object.prototype;function stampJobContext(data){if(!isPlainObject(data))return data;let{[JOB_CONTEXT_FIELD]:_untrusted,...rest}=data,context={};for(let[key,carrier]of jobContextCarriers){let value=carrier.capture();value!==void 0&&(context[key]=value)}return Object.keys(context).length>0?{...rest,[JOB_CONTEXT_FIELD]:context}:rest}function withJobContext(job,run){let data=job.data;if(!isPlainObject(data)||!(`__kickContext`in data))return run;let context=data[JOB_CONTEXT_FIELD];if(delete data[JOB_CONTEXT_FIELD],!isPlainObject(context))return run;let wrapped=run;for(let[key,value]of Object.entries(context)){let carrier=jobContextCarriers.get(key);if(!carrier)continue;let inner=wrapped;wrapped=()=>carrier.restore(value,inner)}return wrapped}const JOB_DISPATCHER=createToken(`kick/jobs/Dispatcher`);export{NoJobHandlerError as a,listJobQueues as c,stampJobContext as d,Job as i,registerJobContext as l,JOB_DISPATCHER as n,Process as o,JOB_META as r,listJobHandlers as s,JOB_CONTEXT_FIELD as t,runJob as u};
12
- //# sourceMappingURL=jobs-DIEQILZ8.mjs.map
11
+ import{i as createToken}from"./container-ByH0uCpG.mjs";import{createLogger}from"./core/logger.mjs";import{c as pushClassMeta,n as getClassMetaOrUndefined,t as getClassMeta,u as setClassMeta}from"./metadata-CIXErBTj.mjs";import{Service}from"./core/decorators.mjs";import{r as reportError}from"./observers-BtXkP5km.mjs";const log=createLogger(`KickJobs`),JOB_META={JOB:`kick/queue/job`,PROCESS:`kick/queue/process`},jobClasses=new Map;function Job(queue){return target=>{Service()(target),setClassMeta(JOB_META.JOB,queue,target),jobClasses.set(target.name,target)}}function Process(jobName){return(target,propertyKey)=>{pushClassMeta(JOB_META.PROCESS,target.constructor,{handlerName:propertyKey,jobName})}}function listJobHandlers(container){let handlers=[];for(let target of jobClasses.values()){if(!container.has(target))continue;let queue=getClassMetaOrUndefined(JOB_META.JOB,target);if(queue===void 0)continue;let processes=getClassMeta(JOB_META.PROCESS,target,[]);processes.length===0&&log.warn(`@Job('${queue}') class ${target.name} has no @Process methods`);for(let process of processes)handlers.push({...process,queue,target})}return handlers}function listJobQueues(container){return[...new Set(listJobHandlers(container).map(h=>h.queue))]}var NoJobHandlerError=class extends Error{queue;jobName;constructor(queue,jobName){super(`No @Process handler for job "${jobName}" on queue "${queue}"`),this.queue=queue,this.jobName=jobName,this.name=`NoJobHandlerError`}};async function runJob(container,queue,job,report={}){let handlers=listJobHandlers(container).filter(h=>h.queue===queue),handler=handlers.find(h=>h.jobName===job.name)??handlers.find(h=>h.jobName===void 0);try{if(!handler)throw new NoJobHandlerError(queue,job.name);let instance=container.resolve(handler.target);return await withJobContext(job,()=>instance[handler.handlerName](job))()}catch(err){throw log.error({err},`Job ${queue}/${job.name} failed`),reportError(err,{source:`job`,context:{queue,job:job.name,id:job.id,...report}}),err}}const JOB_CONTEXT_FIELD=`__kickContext`,jobContextCarriers=new Map;function registerJobContext(carrier){jobContextCarriers.set(carrier.key,carrier)}const isPlainObject=v=>!!v&&typeof v==`object`&&Object.getPrototypeOf(v)===Object.prototype;function stampJobContext(data){if(!isPlainObject(data))return data;let{[JOB_CONTEXT_FIELD]:_untrusted,...rest}=data,context={};for(let[key,carrier]of jobContextCarriers){let value=carrier.capture();value!==void 0&&(context[key]=value)}return Object.keys(context).length>0?{...rest,[JOB_CONTEXT_FIELD]:context}:rest}function withJobContext(job,run){let data=job.data;if(!isPlainObject(data)||!(`__kickContext`in data))return run;let context=data[JOB_CONTEXT_FIELD];if(delete data[JOB_CONTEXT_FIELD],!isPlainObject(context))return run;let wrapped=run;for(let[key,value]of Object.entries(context)){let carrier=jobContextCarriers.get(key);if(!carrier)continue;let inner=wrapped;wrapped=()=>carrier.restore(value,inner)}return wrapped}const JOB_DISPATCHER=createToken(`kick/jobs/Dispatcher`);export{NoJobHandlerError as a,listJobQueues as c,stampJobContext as d,Job as i,registerJobContext as l,JOB_DISPATCHER as n,Process as o,JOB_META as r,listJobHandlers as s,JOB_CONTEXT_FIELD as t,runJob as u};
12
+ //# sourceMappingURL=jobs-CeP4aLYR.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"jobs-DIEQILZ8.mjs","names":[],"sources":["../src/core/jobs.ts"],"sourcesContent":["/**\n * Background jobs, with whatever runs them.\n *\n * `@Job(queue)` / `@Process(name)` only record which method handles which\n * job — the same way `@Cron` only records a schedule. Any job tool turns that\n * into running code in a few lines: subscribe to each queue from\n * {@link listJobQueues}, and hand every job it receives to {@link runJob}.\n *\n * @Job('email')\n * export class EmailJobs {\n * @Process('welcome')\n * async welcome(job: JobLike<{ to: string }>) { await sendWelcome(job.data.to) }\n * }\n *\n * Enqueueing goes through {@link JOB_DISPATCHER}, which the active adapter\n * provides — code that dispatches never names the tool.\n *\n * `@forinda/kickjs-queue` ships a BullMQ adapter (plus RabbitMQ / Kafka /\n * Redis pub-sub providers); the web entry's `queue()` runs Cloudflare Queues.\n */\nimport { Service } from './decorators'\nimport { getClassMeta, getClassMetaOrUndefined, pushClassMeta, setClassMeta } from './metadata'\nimport { reportError } from './observers'\nimport { createLogger } from './logger'\nimport { createToken } from './token'\n\nconst log = createLogger('KickJobs')\n\n/** Metadata keys — the same ones `@forinda/kickjs-queue` used, so existing readers keep working. */\nexport const JOB_META = {\n JOB: 'kick/queue/job',\n PROCESS: 'kick/queue/process',\n} as const\n\n/** What a `@Process` method records. */\nexport interface ProcessDefinition {\n /** Method name on the class. */\n handlerName: string\n /** Job name it handles; `undefined` handles every job in the queue. */\n jobName?: string\n}\n\n/**\n * The least a job tool hands over. Runners pass their own job object through\n * unchanged — BullMQ's `Job`, a Cloudflare message — so a handler can still\n * reach tool-specific fields (`job.attemptsMade`, `job.updateProgress()`).\n */\nexport interface JobLike<Data = unknown> {\n name: string\n data: Data\n id?: string\n}\n\n/**\n * Every `@Job` class, keyed by class name like the DI registry: an HMR\n * re-evaluation replaces the old class instead of leaving both subscribed.\n */\nconst jobClasses = new Map<string, Function>()\n\n/** Mark a class as handling jobs on `queue`. The class is a DI singleton. */\nexport function Job(queue: string): ClassDecorator {\n return (target) => {\n Service()(target)\n setClassMeta(JOB_META.JOB, queue, target)\n jobClasses.set(target.name, target)\n }\n}\n\n/** Mark a method as handling `jobName` — or, without a name, every job its queue gets. */\nexport function Process(jobName?: string): MethodDecorator {\n return (target, propertyKey) => {\n pushClassMeta<ProcessDefinition>(JOB_META.PROCESS, target.constructor, {\n handlerName: propertyKey as string,\n jobName,\n })\n }\n}\n\n/** One handler: its queue, job name (absent for a catch-all), class and method. */\nexport interface JobHandler extends ProcessDefinition {\n queue: string\n target: Function\n}\n\n/**\n * Every `@Process` method of a `@Job` class the container can resolve.\n * Classes it doesn't know (not in a module, or from before an HMR reload)\n * are skipped.\n */\nexport function listJobHandlers(container: { has(token: unknown): boolean }): JobHandler[] {\n const handlers: JobHandler[] = []\n for (const target of jobClasses.values()) {\n if (!container.has(target)) continue\n const queue = getClassMetaOrUndefined<string>(JOB_META.JOB, target)\n if (queue === undefined) continue\n const processes = getClassMeta<ProcessDefinition[]>(JOB_META.PROCESS, target, [])\n if (processes.length === 0) {\n log.warn(`@Job('${queue}') class ${target.name} has no @Process methods`)\n }\n for (const process of processes) handlers.push({ ...process, queue, target })\n }\n return handlers\n}\n\n/** The queues something handles — what a runner subscribes to. */\nexport function listJobQueues(container: { has(token: unknown): boolean }): string[] {\n return [...new Set(listJobHandlers(container).map((h) => h.queue))]\n}\n\n/** Thrown when a queue receives a job no `@Process` method handles. */\nexport class NoJobHandlerError extends Error {\n constructor(\n readonly queue: string,\n readonly jobName: string,\n ) {\n super(`No @Process handler for job \"${jobName}\" on queue \"${queue}\"`)\n this.name = 'NoJobHandlerError'\n }\n}\n\n/**\n * Run one job: pick the `@Process` method for `job.name` (or the queue's\n * catch-all), resolve its class from the container and call it with `job`\n * as given. A failure is reported to the error observers (`source: 'job'`)\n * and rethrown, so the tool's own retry and dead-letter handling still\n * applies. A job nothing handles throws {@link NoJobHandlerError} rather\n * than being acknowledged and lost.\n */\nexport async function runJob(\n container: { has(token: unknown): boolean; resolve(token: unknown): unknown },\n queue: string,\n job: JobLike,\n /** Extra fields for the error report — e.g. the tool's attempt count. */\n report: Record<string, unknown> = {},\n): Promise<unknown> {\n const handlers = listJobHandlers(container).filter((h) => h.queue === queue)\n const handler =\n handlers.find((h) => h.jobName === job.name) ?? handlers.find((h) => h.jobName === undefined)\n try {\n if (!handler) throw new NoJobHandlerError(queue, job.name)\n const instance = container.resolve(handler.target) as Record<string, (j: JobLike) => unknown>\n return await withJobContext(job, () => instance[handler.handlerName]!(job))()\n } catch (err) {\n log.error({ err }, `Job ${queue}/${job.name} failed`)\n reportError(err, { source: 'job', context: { queue, job: job.name, id: job.id, ...report } })\n throw err\n }\n}\n\n/**\n * Something about where a job was dispatched from that its handler should\n * run with — the tenant, a trace id, the acting user. Captured when the job\n * is dispatched, carried in its data, and restored around the handler.\n */\nexport interface JobContextCarrier<T = unknown> {\n /** Unique: the field it travels under. */\n key: string\n /** The value to carry, or `undefined` for nothing. */\n capture(): T | undefined\n /** Run the handler with the value back in place. */\n restore<R>(value: T, run: () => R): R\n}\n\n/** The field a job's data carries its context in. Removed before the handler sees the data. */\nexport const JOB_CONTEXT_FIELD = '__kickContext'\n\nconst jobContextCarriers = new Map<string, JobContextCarrier>()\n\n/** Carry a value from dispatch to handler for every job. Registering a key again replaces it. */\nexport function registerJobContext<T>(carrier: JobContextCarrier<T>): void {\n jobContextCarriers.set(carrier.key, carrier as JobContextCarrier)\n}\n\nconst isPlainObject = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === 'object' && Object.getPrototypeOf(v) === Object.prototype\n\n/**\n * `data` with the current job context added — for a {@link JobDispatcher}\n * implementation to call before enqueueing. Any `__kickContext` already in\n * `data` is removed first: only this call may set it, so a dispatcher that\n * enqueues caller-supplied data without it lets that data choose the context. Only plain-object data can carry\n * it; anything else, or nothing to carry, is returned as is.\n */\nexport function stampJobContext<Data>(data: Data): Data {\n if (!isPlainObject(data)) return data\n // Only the dispatcher's own context travels. A caller's data (a request\n // body, say) carrying the field would otherwise pick the job's tenant.\n const { [JOB_CONTEXT_FIELD]: _untrusted, ...rest } = data\n const context: Record<string, unknown> = {}\n for (const [key, carrier] of jobContextCarriers) {\n const value = carrier.capture()\n if (value !== undefined) context[key] = value\n }\n const stamped = Object.keys(context).length > 0 ? { ...rest, [JOB_CONTEXT_FIELD]: context } : rest\n return stamped as Data\n}\n\n/** Take the carried context off the job's data, and wrap `run` in every restore. */\nfunction withJobContext<R>(job: JobLike, run: () => R): () => R {\n const data = job.data\n if (!isPlainObject(data) || !(JOB_CONTEXT_FIELD in data)) return run\n const context = data[JOB_CONTEXT_FIELD]\n delete data[JOB_CONTEXT_FIELD]\n // Only an object of carrier keys is context; anything else is dropped.\n if (!isPlainObject(context)) return run\n let wrapped = run\n for (const [key, value] of Object.entries(context)) {\n const carrier = jobContextCarriers.get(key)\n if (!carrier) continue\n const inner = wrapped\n wrapped = () => carrier.restore(value, inner)\n }\n return wrapped\n}\n\n/** Enqueue a job — implemented by whichever adapter runs the jobs. */\nexport interface JobDispatcher {\n dispatch<Data = unknown>(\n queue: string,\n name: string,\n data: Data,\n options?: Record<string, unknown>,\n ): Promise<unknown>\n}\n\n/**\n * The active job dispatcher. Inject it to enqueue without naming the tool:\n * `@Inject(JOB_DISPATCHER) private jobs!: JobDispatcher`.\n */\nexport const JOB_DISPATCHER = createToken<JobDispatcher>('kick/jobs/Dispatcher')\n"],"mappings":"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"}
1
+ {"version":3,"file":"jobs-CeP4aLYR.mjs","names":[],"sources":["../src/core/jobs.ts"],"sourcesContent":["/**\n * Background jobs, with whatever runs them.\n *\n * `@Job(queue)` / `@Process(name)` only record which method handles which\n * job — the same way `@Cron` only records a schedule. Any job tool turns that\n * into running code in a few lines: subscribe to each queue from\n * {@link listJobQueues}, and hand every job it receives to {@link runJob}.\n *\n * @Job('email')\n * export class EmailJobs {\n * @Process('welcome')\n * async welcome(job: JobLike<{ to: string }>) { await sendWelcome(job.data.to) }\n * }\n *\n * Enqueueing goes through {@link JOB_DISPATCHER}, which the active adapter\n * provides — code that dispatches never names the tool.\n *\n * `@forinda/kickjs-queue` ships a BullMQ adapter (plus RabbitMQ / Kafka /\n * Redis pub-sub providers); the web entry's `queue()` runs Cloudflare Queues.\n */\nimport { Service } from './decorators'\nimport { getClassMeta, getClassMetaOrUndefined, pushClassMeta, setClassMeta } from './metadata'\nimport { reportError } from './observers'\nimport { createLogger } from './logger'\nimport { createToken } from './token'\n\nconst log = createLogger('KickJobs')\n\n/** Metadata keys — the same ones `@forinda/kickjs-queue` used, so existing readers keep working. */\nexport const JOB_META = {\n JOB: 'kick/queue/job',\n PROCESS: 'kick/queue/process',\n} as const\n\n/** What a `@Process` method records. */\nexport interface ProcessDefinition {\n /** Method name on the class. */\n handlerName: string\n /** Job name it handles; `undefined` handles every job in the queue. */\n jobName?: string\n}\n\n/**\n * The least a job tool hands over. Runners pass their own job object through\n * unchanged — BullMQ's `Job`, a Cloudflare message — so a handler can still\n * reach tool-specific fields (`job.attemptsMade`, `job.updateProgress()`).\n */\nexport interface JobLike<Data = unknown> {\n name: string\n data: Data\n id?: string\n}\n\n/**\n * Every `@Job` class, keyed by class name like the DI registry: an HMR\n * re-evaluation replaces the old class instead of leaving both subscribed.\n */\nconst jobClasses = new Map<string, Function>()\n\n/** Mark a class as handling jobs on `queue`. The class is a DI singleton. */\nexport function Job(queue: string): ClassDecorator {\n return (target) => {\n Service()(target)\n setClassMeta(JOB_META.JOB, queue, target)\n jobClasses.set(target.name, target)\n }\n}\n\n/** Mark a method as handling `jobName` — or, without a name, every job its queue gets. */\nexport function Process(jobName?: string): MethodDecorator {\n return (target, propertyKey) => {\n pushClassMeta<ProcessDefinition>(JOB_META.PROCESS, target.constructor, {\n handlerName: propertyKey as string,\n jobName,\n })\n }\n}\n\n/** One handler: its queue, job name (absent for a catch-all), class and method. */\nexport interface JobHandler extends ProcessDefinition {\n queue: string\n target: Function\n}\n\n/**\n * Every `@Process` method of a `@Job` class the container can resolve.\n * Classes it doesn't know (not in a module, or from before an HMR reload)\n * are skipped.\n */\nexport function listJobHandlers(container: { has(token: unknown): boolean }): JobHandler[] {\n const handlers: JobHandler[] = []\n for (const target of jobClasses.values()) {\n if (!container.has(target)) continue\n const queue = getClassMetaOrUndefined<string>(JOB_META.JOB, target)\n if (queue === undefined) continue\n const processes = getClassMeta<ProcessDefinition[]>(JOB_META.PROCESS, target, [])\n if (processes.length === 0) {\n log.warn(`@Job('${queue}') class ${target.name} has no @Process methods`)\n }\n for (const process of processes) handlers.push({ ...process, queue, target })\n }\n return handlers\n}\n\n/** The queues something handles — what a runner subscribes to. */\nexport function listJobQueues(container: { has(token: unknown): boolean }): string[] {\n return [...new Set(listJobHandlers(container).map((h) => h.queue))]\n}\n\n/** Thrown when a queue receives a job no `@Process` method handles. */\nexport class NoJobHandlerError extends Error {\n constructor(\n readonly queue: string,\n readonly jobName: string,\n ) {\n super(`No @Process handler for job \"${jobName}\" on queue \"${queue}\"`)\n this.name = 'NoJobHandlerError'\n }\n}\n\n/**\n * Run one job: pick the `@Process` method for `job.name` (or the queue's\n * catch-all), resolve its class from the container and call it with `job`\n * as given. A failure is reported to the error observers (`source: 'job'`)\n * and rethrown, so the tool's own retry and dead-letter handling still\n * applies. A job nothing handles throws {@link NoJobHandlerError} rather\n * than being acknowledged and lost.\n */\nexport async function runJob(\n container: { has(token: unknown): boolean; resolve(token: unknown): unknown },\n queue: string,\n job: JobLike,\n /** Extra fields for the error report — e.g. the tool's attempt count. */\n report: Record<string, unknown> = {},\n): Promise<unknown> {\n const handlers = listJobHandlers(container).filter((h) => h.queue === queue)\n const handler =\n handlers.find((h) => h.jobName === job.name) ?? handlers.find((h) => h.jobName === undefined)\n try {\n if (!handler) throw new NoJobHandlerError(queue, job.name)\n const instance = container.resolve(handler.target) as Record<string, (j: JobLike) => unknown>\n return await withJobContext(job, () => instance[handler.handlerName]!(job))()\n } catch (err) {\n log.error({ err }, `Job ${queue}/${job.name} failed`)\n reportError(err, { source: 'job', context: { queue, job: job.name, id: job.id, ...report } })\n throw err\n }\n}\n\n/**\n * Something about where a job was dispatched from that its handler should\n * run with — the tenant, a trace id, the acting user. Captured when the job\n * is dispatched, carried in its data, and restored around the handler.\n */\nexport interface JobContextCarrier<T = unknown> {\n /** Unique: the field it travels under. */\n key: string\n /** The value to carry, or `undefined` for nothing. */\n capture(): T | undefined\n /** Run the handler with the value back in place. */\n restore<R>(value: T, run: () => R): R\n}\n\n/** The field a job's data carries its context in. Removed before the handler sees the data. */\nexport const JOB_CONTEXT_FIELD = '__kickContext'\n\nconst jobContextCarriers = new Map<string, JobContextCarrier>()\n\n/** Carry a value from dispatch to handler for every job. Registering a key again replaces it. */\nexport function registerJobContext<T>(carrier: JobContextCarrier<T>): void {\n jobContextCarriers.set(carrier.key, carrier as JobContextCarrier)\n}\n\nconst isPlainObject = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === 'object' && Object.getPrototypeOf(v) === Object.prototype\n\n/**\n * `data` with the current job context added — for a {@link JobDispatcher}\n * implementation to call before enqueueing. Any `__kickContext` already in\n * `data` is removed first: only this call may set it, so a dispatcher that\n * enqueues caller-supplied data without it lets that data choose the context. Only plain-object data can carry\n * it; anything else, or nothing to carry, is returned as is.\n */\nexport function stampJobContext<Data>(data: Data): Data {\n if (!isPlainObject(data)) return data\n // Only the dispatcher's own context travels. A caller's data (a request\n // body, say) carrying the field would otherwise pick the job's tenant.\n const { [JOB_CONTEXT_FIELD]: _untrusted, ...rest } = data\n const context: Record<string, unknown> = {}\n for (const [key, carrier] of jobContextCarriers) {\n const value = carrier.capture()\n if (value !== undefined) context[key] = value\n }\n const stamped = Object.keys(context).length > 0 ? { ...rest, [JOB_CONTEXT_FIELD]: context } : rest\n return stamped as Data\n}\n\n/** Take the carried context off the job's data, and wrap `run` in every restore. */\nfunction withJobContext<R>(job: JobLike, run: () => R): () => R {\n const data = job.data\n if (!isPlainObject(data) || !(JOB_CONTEXT_FIELD in data)) return run\n const context = data[JOB_CONTEXT_FIELD]\n delete data[JOB_CONTEXT_FIELD]\n // Only an object of carrier keys is context; anything else is dropped.\n if (!isPlainObject(context)) return run\n let wrapped = run\n for (const [key, value] of Object.entries(context)) {\n const carrier = jobContextCarriers.get(key)\n if (!carrier) continue\n const inner = wrapped\n wrapped = () => carrier.restore(value, inner)\n }\n return wrapped\n}\n\n/** Enqueue a job — implemented by whichever adapter runs the jobs. */\nexport interface JobDispatcher {\n dispatch<Data = unknown>(\n queue: string,\n name: string,\n data: Data,\n options?: Record<string, unknown>,\n ): Promise<unknown>\n}\n\n/**\n * The active job dispatcher. Inject it to enqueue without naming the tool:\n * `@Inject(JOB_DISPATCHER) private jobs!: JobDispatcher`.\n */\nexport const JOB_DISPATCHER = createToken<JobDispatcher>('kick/jobs/Dispatcher')\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v9.0.2
2
+ * @forinda/kickjs v9.0.3
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-DEUYr5Br.mjs";var LazyMemoryStore=class{windowMs;hits=new Map;constructor(windowMs){this.windowMs=windowMs}async increment(key){let now=Date.now(),entry=this.hits.get(key);if(entry&&entry.reset>now)return entry.hits++,{totalHits:entry.hits,resetTime:new Date(entry.reset)};if(this.hits.size>1e4)for(let[k,e]of this.hits)e.reset<=now&&this.hits.delete(k);let fresh={hits:1,reset:now+this.windowMs};return this.hits.set(key,fresh),{totalHits:1,resetTime:new Date(fresh.reset)}}async decrement(key){let entry=this.hits.get(key);entry&&entry.hits>0&&entry.hits--}async reset(key){this.hits.delete(key)}};function defaultKey(ctx){return ctx.ip??`global`}function rateLimitGuard(options={}){let max=options.max??100,windowMs=options.windowMs??6e4,message=options.message??`Too Many Requests`,statusCode=options.statusCode??429,keyGenerator=options.keyGenerator??defaultKey,sendHeaders=options.headers??!0,store=options.store??new LazyMemoryStore(windowMs),exemptWhen=options.exemptWhen;return exemptWhen!==void 0&&assertFlagTest(exemptWhen,`rateLimitGuard({ exemptWhen })`),async(ctx,next)=>{if(options.skip?.(ctx)||exemptWhen!==void 0&&matchesFlagTest(exemptWhen,ctx.route?.flags,ctx.route))return next();let{totalHits,resetTime}=await store.increment(keyGenerator(ctx)),remaining=Math.max(0,max-totalHits);if(sendHeaders&&(ctx.setHeader(`X-RateLimit-Limit`,String(max)),ctx.setHeader(`X-RateLimit-Remaining`,String(remaining)),ctx.setHeader(`X-RateLimit-Reset`,String(Math.ceil(resetTime.getTime()/1e3)))),totalHits>max){let retryAfter=Math.max(1,Math.ceil((resetTime.getTime()-Date.now())/1e3));sendHeaders&&ctx.setHeader(`Retry-After`,String(retryAfter)),ctx.json({message},statusCode);return}next()}}function kvTtlSeconds(ms){return Math.max(60,Math.ceil(ms/1e3))}var KvRateLimitStore=class{kv;prefix;windowMs;constructor(kv,options){this.kv=kv,this.windowMs=options.windowMs,this.prefix=options.prefix??`rl:`}async increment(key){let now=Date.now(),entry=await this.read(key);if(entry&&entry.reset>now)return entry.hits++,await this.write(key,entry),{totalHits:entry.hits,resetTime:new Date(entry.reset)};let fresh={hits:1,reset:now+this.windowMs};return await this.write(key,fresh),{totalHits:1,resetTime:new Date(fresh.reset)}}async decrement(key){let entry=await this.read(key);entry&&entry.hits>0&&(entry.hits--,await this.write(key,entry))}async reset(key){await this.kv.delete(this.prefix+key)}async read(key){let raw=await this.kv.get(this.prefix+key);if(raw===null)return null;try{return JSON.parse(raw)}catch{return null}}async write(key,entry){await this.kv.put(this.prefix+key,JSON.stringify(entry),{expirationTtl:kvTtlSeconds(Math.max(entry.reset-Date.now(),1e3))})}},KvSessionStore=class{kv;prefix;constructor(kv,options={}){this.kv=kv,this.prefix=options.prefix??`sess:`}async get(sid){let raw=await this.kv.get(this.prefix+sid);if(raw===null)return null;try{return JSON.parse(raw)}catch{return null}}async set(sid,data,maxAge){await this.kv.put(this.prefix+sid,JSON.stringify(data),{expirationTtl:kvTtlSeconds(maxAge)})}async destroy(sid){await this.kv.delete(this.prefix+sid)}async touch(sid,maxAge){let data=await this.get(sid);data!==null&&await this.set(sid,data,maxAge)}};export{KvSessionStore as n,rateLimitGuard as r,KvRateLimitStore as t};
12
- //# sourceMappingURL=kv-stores-D4dmpn6D.mjs.map
11
+ import{n as assertFlagTest,s as matchesFlagTest}from"./route-flag-CpTa8y1n.mjs";var LazyMemoryStore=class{windowMs;hits=new Map;constructor(windowMs){this.windowMs=windowMs}async increment(key){let now=Date.now(),entry=this.hits.get(key);if(entry&&entry.reset>now)return entry.hits++,{totalHits:entry.hits,resetTime:new Date(entry.reset)};if(this.hits.size>1e4)for(let[k,e]of this.hits)e.reset<=now&&this.hits.delete(k);let fresh={hits:1,reset:now+this.windowMs};return this.hits.set(key,fresh),{totalHits:1,resetTime:new Date(fresh.reset)}}async decrement(key){let entry=this.hits.get(key);entry&&entry.hits>0&&entry.hits--}async reset(key){this.hits.delete(key)}};function defaultKey(ctx){return ctx.ip??`global`}function rateLimitGuard(options={}){let max=options.max??100,windowMs=options.windowMs??6e4,message=options.message??`Too Many Requests`,statusCode=options.statusCode??429,keyGenerator=options.keyGenerator??defaultKey,sendHeaders=options.headers??!0,store=options.store??new LazyMemoryStore(windowMs),exemptWhen=options.exemptWhen;return exemptWhen!==void 0&&assertFlagTest(exemptWhen,`rateLimitGuard({ exemptWhen })`),async(ctx,next)=>{if(options.skip?.(ctx)||exemptWhen!==void 0&&matchesFlagTest(exemptWhen,ctx.route?.flags,ctx.route))return next();let{totalHits,resetTime}=await store.increment(keyGenerator(ctx)),remaining=Math.max(0,max-totalHits);if(sendHeaders&&(ctx.setHeader(`X-RateLimit-Limit`,String(max)),ctx.setHeader(`X-RateLimit-Remaining`,String(remaining)),ctx.setHeader(`X-RateLimit-Reset`,String(Math.ceil(resetTime.getTime()/1e3)))),totalHits>max){let retryAfter=Math.max(1,Math.ceil((resetTime.getTime()-Date.now())/1e3));sendHeaders&&ctx.setHeader(`Retry-After`,String(retryAfter)),ctx.json({message},statusCode);return}next()}}function kvTtlSeconds(ms){return Math.max(60,Math.ceil(ms/1e3))}var KvRateLimitStore=class{kv;prefix;windowMs;constructor(kv,options){this.kv=kv,this.windowMs=options.windowMs,this.prefix=options.prefix??`rl:`}async increment(key){let now=Date.now(),entry=await this.read(key);if(entry&&entry.reset>now)return entry.hits++,await this.write(key,entry),{totalHits:entry.hits,resetTime:new Date(entry.reset)};let fresh={hits:1,reset:now+this.windowMs};return await this.write(key,fresh),{totalHits:1,resetTime:new Date(fresh.reset)}}async decrement(key){let entry=await this.read(key);entry&&entry.hits>0&&(entry.hits--,await this.write(key,entry))}async reset(key){await this.kv.delete(this.prefix+key)}async read(key){let raw=await this.kv.get(this.prefix+key);if(raw===null)return null;try{return JSON.parse(raw)}catch{return null}}async write(key,entry){await this.kv.put(this.prefix+key,JSON.stringify(entry),{expirationTtl:kvTtlSeconds(Math.max(entry.reset-Date.now(),1e3))})}},KvSessionStore=class{kv;prefix;constructor(kv,options={}){this.kv=kv,this.prefix=options.prefix??`sess:`}async get(sid){let raw=await this.kv.get(this.prefix+sid);if(raw===null)return null;try{return JSON.parse(raw)}catch{return null}}async set(sid,data,maxAge){await this.kv.put(this.prefix+sid,JSON.stringify(data),{expirationTtl:kvTtlSeconds(maxAge)})}async destroy(sid){await this.kv.delete(this.prefix+sid)}async touch(sid,maxAge){let data=await this.get(sid);data!==null&&await this.set(sid,data,maxAge)}};export{KvSessionStore as n,rateLimitGuard as r,KvRateLimitStore as t};
12
+ //# sourceMappingURL=kv-stores-5ocdxfdh.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"kv-stores-D4dmpn6D.mjs","names":[],"sources":["../src/http/middleware/rate-limit-guard.ts","../src/http/middleware/kv-stores.ts"],"sourcesContent":["// Ctx-style rate limiter — runs on BOTH pipelines: the node runtimes (via\n// `@Middleware()` / class middleware) and the `@forinda/kickjs/web` fetch\n// entry (via `@Middleware()` or `createWebApp({ middleware })`), unlike the\n// connect-style `rateLimit()` which is node-only. Zero runtime imports —\n// part of the edge purity graph.\nimport { assertFlagTest, matchesFlagTest, type RouteFlagTest } from '../../core/route-flag'\nimport type { MiddlewareHandler } from '../../core/decorators'\nimport type { RequestContext } from '../context'\nimport type { RateLimitStore } from './rate-limit'\n\nexport interface RateLimitGuardOptions {\n /** Maximum number of requests per window (default: 100). */\n max?: number\n /** Time window in milliseconds (default: 60_000). */\n windowMs?: number\n /** Response message when the limit is exceeded (default: 'Too Many Requests'). */\n message?: string\n /** HTTP status code when the limit is exceeded (default: 429). */\n statusCode?: number\n /**\n * Rate-limit key per request. Defaults to `ctx.ip`, falling back to\n * `'global'`. Return a user/tenant id here for authenticated quotas.\n *\n * ::: warning Behind a proxy, configure `trustProxy` or every client shares\n * one bucket\n * On a node runtime `ctx.ip` is the runtime's vetted client address —\n * Express derives it from `trust proxy`, Fastify from `trustProxy`. Without\n * that setting it is the SOCKET address, which behind a load balancer or\n * Cloudflare is the proxy's, so every caller lands in the same bucket and one\n * noisy client exhausts the allowance for everyone.\n *\n * `bootstrap({ trustProxy: true })` is the fix. Forwarded headers are\n * deliberately not consulted here: `x-forwarded-for` is client-controllable\n * unless a proxy overwrites it, so trusting it unconditionally would let a\n * direct caller mint a fresh allowance per request by varying the header —\n * evading the limit rather than enforcing it. The header chain\n * (`cf-connecting-ip` → `x-forwarded-for` → `x-real-ip`) applies only on the\n * `@forinda/kickjs/web` edge entry, where there is no socket and the platform\n * has already terminated the connection.\n * :::\n */\n keyGenerator?: (ctx: RequestContext) => string\n /** Send `X-RateLimit-*` / `Retry-After` headers (default: true). */\n headers?: boolean\n /**\n * Counter backend. Defaults to a per-isolate in-memory map — fine for a\n * single node process, useless on edge where isolates recycle: pass a\n * `KvRateLimitStore` (or Redis-backed store) there.\n */\n store?: RateLimitStore\n /** Skip limiting for a request (health checks, allowlists). */\n skip?: (ctx: RequestContext) => boolean\n /**\n * Skip limiting on routes carrying a route flag — a name, a list of names\n * (any-of), or a predicate. Declared on the route rather than restated as a\n * path here, so it cannot drift when `apiPrefix` or a module's `version`\n * changes:\n *\n * ```ts\n * const Public = defineRouteFlag('auth.public')\n * rateLimitGuard({ max: 60, exemptWhen: 'auth.public' })\n * ```\n *\n * Only meaningful where a route has been matched — as `@Middleware()` on a\n * class or method, or in `createWebApp({ middleware })`. Mounted app-wide\n * ahead of routing there is no route to read, and nothing is exempted.\n */\n exemptWhen?: RouteFlagTest\n}\n\n/** Timer-free in-memory store: sweeps expired entries when the map grows. */\nclass LazyMemoryStore implements RateLimitStore {\n private readonly hits = new Map<string, { hits: number; reset: number }>()\n\n constructor(private readonly windowMs: number) {}\n\n async increment(key: string): Promise<{ totalHits: number; resetTime: Date }> {\n const now = Date.now()\n const entry = this.hits.get(key)\n if (entry && entry.reset > now) {\n entry.hits++\n return { totalHits: entry.hits, resetTime: new Date(entry.reset) }\n }\n // ponytail: sweep-on-growth instead of a cleanup interval — no timer to\n // dispose, edge-safe. Bound is 10k live keys before a full sweep.\n if (this.hits.size > 10_000) {\n for (const [k, e] of this.hits) if (e.reset <= now) this.hits.delete(k)\n }\n const fresh = { hits: 1, reset: now + this.windowMs }\n this.hits.set(key, fresh)\n return { totalHits: 1, resetTime: new Date(fresh.reset) }\n }\n\n async decrement(key: string): Promise<void> {\n const entry = this.hits.get(key)\n if (entry && entry.hits > 0) entry.hits--\n }\n\n async reset(key: string): Promise<void> {\n this.hits.delete(key)\n }\n}\n\nfunction defaultKey(ctx: RequestContext): string {\n // `ctx.ip` carries the resolution that used to live here: prefer the\n // runtime-computed address (Express derives it from `trust proxy`), fall\n // back to forwarded headers only for runtimes that compute none.\n return ctx.ip ?? 'global'\n}\n\n/**\n * Rate limiting as a `(ctx, next)` middleware.\n *\n * ```ts\n * // Edge (Cloudflare Workers) — app-wide:\n * const app = createWebApp({\n * h3, modules,\n * middlewares: [rateLimitGuard({ max: 60, windowMs: 60_000, store: new KvRateLimitStore(env.KV, { windowMs: 60_000 }) })],\n * })\n *\n * // Any runtime — per controller/route:\n * @Middleware(rateLimitGuard({ max: 10 }))\n * @Post('/login')\n * ```\n */\nexport function rateLimitGuard(options: RateLimitGuardOptions = {}): MiddlewareHandler {\n const max = options.max ?? 100\n const windowMs = options.windowMs ?? 60_000\n const message = options.message ?? 'Too Many Requests'\n const statusCode = options.statusCode ?? 429\n const keyGenerator = options.keyGenerator ?? defaultKey\n const sendHeaders = options.headers ?? true\n const store = options.store ?? new LazyMemoryStore(windowMs)\n\n const exemptWhen = options.exemptWhen\n if (exemptWhen !== undefined) assertFlagTest(exemptWhen, 'rateLimitGuard({ exemptWhen })')\n\n return async (ctx: RequestContext, next: () => void): Promise<void> => {\n if (options.skip?.(ctx)) return next()\n if (exemptWhen !== undefined && matchesFlagTest(exemptWhen, ctx.route?.flags, ctx.route)) {\n return next()\n }\n\n const { totalHits, resetTime } = await store.increment(keyGenerator(ctx))\n const remaining = Math.max(0, max - totalHits)\n\n if (sendHeaders) {\n ctx.setHeader('X-RateLimit-Limit', String(max))\n ctx.setHeader('X-RateLimit-Remaining', String(remaining))\n ctx.setHeader('X-RateLimit-Reset', String(Math.ceil(resetTime.getTime() / 1000)))\n }\n\n if (totalHits > max) {\n const retryAfter = Math.max(1, Math.ceil((resetTime.getTime() - Date.now()) / 1000))\n if (sendHeaders) ctx.setHeader('Retry-After', String(retryAfter))\n ctx.json({ message }, statusCode)\n return\n }\n\n next()\n }\n}\n","// Edge-safe store adapters over a minimal KV surface. Zero runtime imports —\n// this file must stay importable from the `@forinda/kickjs/web` purity graph.\nimport type { RateLimitStore } from './rate-limit'\nimport type { SessionData, SessionStore } from './session'\n\n/**\n * The minimal async KV surface both stores need. Structurally compatible\n * with a Cloudflare Workers `KVNamespace` binding — pass it straight in:\n *\n * ```ts\n * export default {\n * fetch(req: Request, env: Env) {\n * const store = new KvRateLimitStore(env.MY_KV, { windowMs: 60_000 })\n * // ...\n * },\n * }\n * ```\n *\n * Anything with the same shape works too (Deno KV wrapper, Upstash Redis\n * REST client adapter, a Map-backed fake in tests).\n */\nexport interface KvLike {\n get(key: string): Promise<string | null>\n put(key: string, value: string, options?: { expirationTtl?: number }): Promise<void>\n delete(key: string): Promise<void>\n}\n\n/** Cloudflare KV rejects TTLs under 60 seconds — clamp up, never down. */\nfunction kvTtlSeconds(ms: number): number {\n return Math.max(60, Math.ceil(ms / 1000))\n}\n\nexport interface KvRateLimitStoreOptions {\n /** Window length in ms — must match the `windowMs` given to the limiter. */\n windowMs: number\n /** Key prefix in the KV namespace (default `'rl:'`). */\n prefix?: string\n}\n\n/**\n * {@link RateLimitStore} over a {@link KvLike} namespace, for edge deployments\n * where in-memory counters reset on every isolate.\n *\n * KV is eventually consistent and this is a read-modify-write counter, so\n * limiting is **approximate** under concurrency — bursts racing across\n * isolates can each read the same count. That is the accepted trade-off for\n * abuse throttling; use a Durable Object / Redis store when you need an\n * exact ceiling (billing quotas, strict per-key locks).\n */\nexport class KvRateLimitStore implements RateLimitStore {\n private readonly prefix: string\n private readonly windowMs: number\n\n constructor(\n private readonly kv: KvLike,\n options: KvRateLimitStoreOptions,\n ) {\n this.windowMs = options.windowMs\n this.prefix = options.prefix ?? 'rl:'\n }\n\n async increment(key: string): Promise<{ totalHits: number; resetTime: Date }> {\n const now = Date.now()\n const entry = await this.read(key)\n if (entry && entry.reset > now) {\n entry.hits++\n await this.write(key, entry)\n return { totalHits: entry.hits, resetTime: new Date(entry.reset) }\n }\n const fresh = { hits: 1, reset: now + this.windowMs }\n await this.write(key, fresh)\n return { totalHits: 1, resetTime: new Date(fresh.reset) }\n }\n\n async decrement(key: string): Promise<void> {\n const entry = await this.read(key)\n if (entry && entry.hits > 0) {\n entry.hits--\n await this.write(key, entry)\n }\n }\n\n async reset(key: string): Promise<void> {\n await this.kv.delete(this.prefix + key)\n }\n\n private async read(key: string): Promise<{ hits: number; reset: number } | null> {\n const raw = await this.kv.get(this.prefix + key)\n if (raw === null) return null\n try {\n return JSON.parse(raw) as { hits: number; reset: number }\n } catch {\n return null\n }\n }\n\n private async write(key: string, entry: { hits: number; reset: number }): Promise<void> {\n await this.kv.put(this.prefix + key, JSON.stringify(entry), {\n expirationTtl: kvTtlSeconds(Math.max(entry.reset - Date.now(), 1000)),\n })\n }\n}\n\nexport interface KvSessionStoreOptions {\n /** Key prefix in the KV namespace (default `'sess:'`). */\n prefix?: string\n}\n\n/**\n * {@link SessionStore} over a {@link KvLike} namespace. Plug into the node\n * `session()` middleware today (e.g. backed by Upstash/Cloudflare KV via\n * REST) so sessions survive restarts and horizontal scaling; TTL expiry is\n * delegated to the KV layer via `expirationTtl`.\n *\n * Cost note: `touch()` is a get + put round-trip (KvLike has no TTL-only\n * refresh), so `session({ rolling: true })` doubles KV operations per\n * request — per-operation-billed platforms may prefer `rolling: false`.\n */\nexport class KvSessionStore implements SessionStore {\n private readonly prefix: string\n\n constructor(\n private readonly kv: KvLike,\n options: KvSessionStoreOptions = {},\n ) {\n this.prefix = options.prefix ?? 'sess:'\n }\n\n async get(sid: string): Promise<SessionData | null> {\n const raw = await this.kv.get(this.prefix + sid)\n if (raw === null) return null\n try {\n return JSON.parse(raw) as SessionData\n } catch {\n return null\n }\n }\n\n async set(sid: string, data: SessionData, maxAge: number): Promise<void> {\n await this.kv.put(this.prefix + sid, JSON.stringify(data), {\n expirationTtl: kvTtlSeconds(maxAge),\n })\n }\n\n async destroy(sid: string): Promise<void> {\n await this.kv.delete(this.prefix + sid)\n }\n\n async touch(sid: string, maxAge: number): Promise<void> {\n const data = await this.get(sid)\n if (data !== null) await this.set(sid, data, maxAge)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"kv-stores-5ocdxfdh.mjs","names":[],"sources":["../src/http/middleware/rate-limit-guard.ts","../src/http/middleware/kv-stores.ts"],"sourcesContent":["// Ctx-style rate limiter — runs on BOTH pipelines: the node runtimes (via\n// `@Middleware()` / class middleware) and the `@forinda/kickjs/web` fetch\n// entry (via `@Middleware()` or `createWebApp({ middleware })`), unlike the\n// connect-style `rateLimit()` which is node-only. Zero runtime imports —\n// part of the edge purity graph.\nimport { assertFlagTest, matchesFlagTest, type RouteFlagTest } from '../../core/route-flag'\nimport type { MiddlewareHandler } from '../../core/decorators'\nimport type { RequestContext } from '../context'\nimport type { RateLimitStore } from './rate-limit'\n\nexport interface RateLimitGuardOptions {\n /** Maximum number of requests per window (default: 100). */\n max?: number\n /** Time window in milliseconds (default: 60_000). */\n windowMs?: number\n /** Response message when the limit is exceeded (default: 'Too Many Requests'). */\n message?: string\n /** HTTP status code when the limit is exceeded (default: 429). */\n statusCode?: number\n /**\n * Rate-limit key per request. Defaults to `ctx.ip`, falling back to\n * `'global'`. Return a user/tenant id here for authenticated quotas.\n *\n * ::: warning Behind a proxy, configure `trustProxy` or every client shares\n * one bucket\n * On a node runtime `ctx.ip` is the runtime's vetted client address —\n * Express derives it from `trust proxy`, Fastify from `trustProxy`. Without\n * that setting it is the SOCKET address, which behind a load balancer or\n * Cloudflare is the proxy's, so every caller lands in the same bucket and one\n * noisy client exhausts the allowance for everyone.\n *\n * `bootstrap({ trustProxy: true })` is the fix. Forwarded headers are\n * deliberately not consulted here: `x-forwarded-for` is client-controllable\n * unless a proxy overwrites it, so trusting it unconditionally would let a\n * direct caller mint a fresh allowance per request by varying the header —\n * evading the limit rather than enforcing it. The header chain\n * (`cf-connecting-ip` → `x-forwarded-for` → `x-real-ip`) applies only on the\n * `@forinda/kickjs/web` edge entry, where there is no socket and the platform\n * has already terminated the connection.\n * :::\n */\n keyGenerator?: (ctx: RequestContext) => string\n /** Send `X-RateLimit-*` / `Retry-After` headers (default: true). */\n headers?: boolean\n /**\n * Counter backend. Defaults to a per-isolate in-memory map — fine for a\n * single node process, useless on edge where isolates recycle: pass a\n * `KvRateLimitStore` (or Redis-backed store) there.\n */\n store?: RateLimitStore\n /** Skip limiting for a request (health checks, allowlists). */\n skip?: (ctx: RequestContext) => boolean\n /**\n * Skip limiting on routes carrying a route flag — a name, a list of names\n * (any-of), or a predicate. Declared on the route rather than restated as a\n * path here, so it cannot drift when `apiPrefix` or a module's `version`\n * changes:\n *\n * ```ts\n * const Public = defineRouteFlag('auth.public')\n * rateLimitGuard({ max: 60, exemptWhen: 'auth.public' })\n * ```\n *\n * Only meaningful where a route has been matched — as `@Middleware()` on a\n * class or method, or in `createWebApp({ middleware })`. Mounted app-wide\n * ahead of routing there is no route to read, and nothing is exempted.\n */\n exemptWhen?: RouteFlagTest\n}\n\n/** Timer-free in-memory store: sweeps expired entries when the map grows. */\nclass LazyMemoryStore implements RateLimitStore {\n private readonly hits = new Map<string, { hits: number; reset: number }>()\n\n constructor(private readonly windowMs: number) {}\n\n async increment(key: string): Promise<{ totalHits: number; resetTime: Date }> {\n const now = Date.now()\n const entry = this.hits.get(key)\n if (entry && entry.reset > now) {\n entry.hits++\n return { totalHits: entry.hits, resetTime: new Date(entry.reset) }\n }\n // ponytail: sweep-on-growth instead of a cleanup interval — no timer to\n // dispose, edge-safe. Bound is 10k live keys before a full sweep.\n if (this.hits.size > 10_000) {\n for (const [k, e] of this.hits) if (e.reset <= now) this.hits.delete(k)\n }\n const fresh = { hits: 1, reset: now + this.windowMs }\n this.hits.set(key, fresh)\n return { totalHits: 1, resetTime: new Date(fresh.reset) }\n }\n\n async decrement(key: string): Promise<void> {\n const entry = this.hits.get(key)\n if (entry && entry.hits > 0) entry.hits--\n }\n\n async reset(key: string): Promise<void> {\n this.hits.delete(key)\n }\n}\n\nfunction defaultKey(ctx: RequestContext): string {\n // `ctx.ip` carries the resolution that used to live here: prefer the\n // runtime-computed address (Express derives it from `trust proxy`), fall\n // back to forwarded headers only for runtimes that compute none.\n return ctx.ip ?? 'global'\n}\n\n/**\n * Rate limiting as a `(ctx, next)` middleware.\n *\n * ```ts\n * // Edge (Cloudflare Workers) — app-wide:\n * const app = createWebApp({\n * h3, modules,\n * middlewares: [rateLimitGuard({ max: 60, windowMs: 60_000, store: new KvRateLimitStore(env.KV, { windowMs: 60_000 }) })],\n * })\n *\n * // Any runtime — per controller/route:\n * @Middleware(rateLimitGuard({ max: 10 }))\n * @Post('/login')\n * ```\n */\nexport function rateLimitGuard(options: RateLimitGuardOptions = {}): MiddlewareHandler {\n const max = options.max ?? 100\n const windowMs = options.windowMs ?? 60_000\n const message = options.message ?? 'Too Many Requests'\n const statusCode = options.statusCode ?? 429\n const keyGenerator = options.keyGenerator ?? defaultKey\n const sendHeaders = options.headers ?? true\n const store = options.store ?? new LazyMemoryStore(windowMs)\n\n const exemptWhen = options.exemptWhen\n if (exemptWhen !== undefined) assertFlagTest(exemptWhen, 'rateLimitGuard({ exemptWhen })')\n\n return async (ctx: RequestContext, next: () => void): Promise<void> => {\n if (options.skip?.(ctx)) return next()\n if (exemptWhen !== undefined && matchesFlagTest(exemptWhen, ctx.route?.flags, ctx.route)) {\n return next()\n }\n\n const { totalHits, resetTime } = await store.increment(keyGenerator(ctx))\n const remaining = Math.max(0, max - totalHits)\n\n if (sendHeaders) {\n ctx.setHeader('X-RateLimit-Limit', String(max))\n ctx.setHeader('X-RateLimit-Remaining', String(remaining))\n ctx.setHeader('X-RateLimit-Reset', String(Math.ceil(resetTime.getTime() / 1000)))\n }\n\n if (totalHits > max) {\n const retryAfter = Math.max(1, Math.ceil((resetTime.getTime() - Date.now()) / 1000))\n if (sendHeaders) ctx.setHeader('Retry-After', String(retryAfter))\n ctx.json({ message }, statusCode)\n return\n }\n\n next()\n }\n}\n","// Edge-safe store adapters over a minimal KV surface. Zero runtime imports —\n// this file must stay importable from the `@forinda/kickjs/web` purity graph.\nimport type { RateLimitStore } from './rate-limit'\nimport type { SessionData, SessionStore } from './session'\n\n/**\n * The minimal async KV surface both stores need. Structurally compatible\n * with a Cloudflare Workers `KVNamespace` binding — pass it straight in:\n *\n * ```ts\n * export default {\n * fetch(req: Request, env: Env) {\n * const store = new KvRateLimitStore(env.MY_KV, { windowMs: 60_000 })\n * // ...\n * },\n * }\n * ```\n *\n * Anything with the same shape works too (Deno KV wrapper, Upstash Redis\n * REST client adapter, a Map-backed fake in tests).\n */\nexport interface KvLike {\n get(key: string): Promise<string | null>\n put(key: string, value: string, options?: { expirationTtl?: number }): Promise<void>\n delete(key: string): Promise<void>\n}\n\n/** Cloudflare KV rejects TTLs under 60 seconds — clamp up, never down. */\nfunction kvTtlSeconds(ms: number): number {\n return Math.max(60, Math.ceil(ms / 1000))\n}\n\nexport interface KvRateLimitStoreOptions {\n /** Window length in ms — must match the `windowMs` given to the limiter. */\n windowMs: number\n /** Key prefix in the KV namespace (default `'rl:'`). */\n prefix?: string\n}\n\n/**\n * {@link RateLimitStore} over a {@link KvLike} namespace, for edge deployments\n * where in-memory counters reset on every isolate.\n *\n * KV is eventually consistent and this is a read-modify-write counter, so\n * limiting is **approximate** under concurrency — bursts racing across\n * isolates can each read the same count. That is the accepted trade-off for\n * abuse throttling; use a Durable Object / Redis store when you need an\n * exact ceiling (billing quotas, strict per-key locks).\n */\nexport class KvRateLimitStore implements RateLimitStore {\n private readonly prefix: string\n private readonly windowMs: number\n\n constructor(\n private readonly kv: KvLike,\n options: KvRateLimitStoreOptions,\n ) {\n this.windowMs = options.windowMs\n this.prefix = options.prefix ?? 'rl:'\n }\n\n async increment(key: string): Promise<{ totalHits: number; resetTime: Date }> {\n const now = Date.now()\n const entry = await this.read(key)\n if (entry && entry.reset > now) {\n entry.hits++\n await this.write(key, entry)\n return { totalHits: entry.hits, resetTime: new Date(entry.reset) }\n }\n const fresh = { hits: 1, reset: now + this.windowMs }\n await this.write(key, fresh)\n return { totalHits: 1, resetTime: new Date(fresh.reset) }\n }\n\n async decrement(key: string): Promise<void> {\n const entry = await this.read(key)\n if (entry && entry.hits > 0) {\n entry.hits--\n await this.write(key, entry)\n }\n }\n\n async reset(key: string): Promise<void> {\n await this.kv.delete(this.prefix + key)\n }\n\n private async read(key: string): Promise<{ hits: number; reset: number } | null> {\n const raw = await this.kv.get(this.prefix + key)\n if (raw === null) return null\n try {\n return JSON.parse(raw) as { hits: number; reset: number }\n } catch {\n return null\n }\n }\n\n private async write(key: string, entry: { hits: number; reset: number }): Promise<void> {\n await this.kv.put(this.prefix + key, JSON.stringify(entry), {\n expirationTtl: kvTtlSeconds(Math.max(entry.reset - Date.now(), 1000)),\n })\n }\n}\n\nexport interface KvSessionStoreOptions {\n /** Key prefix in the KV namespace (default `'sess:'`). */\n prefix?: string\n}\n\n/**\n * {@link SessionStore} over a {@link KvLike} namespace. Plug into the node\n * `session()` middleware today (e.g. backed by Upstash/Cloudflare KV via\n * REST) so sessions survive restarts and horizontal scaling; TTL expiry is\n * delegated to the KV layer via `expirationTtl`.\n *\n * Cost note: `touch()` is a get + put round-trip (KvLike has no TTL-only\n * refresh), so `session({ rolling: true })` doubles KV operations per\n * request — per-operation-billed platforms may prefer `rolling: false`.\n */\nexport class KvSessionStore implements SessionStore {\n private readonly prefix: string\n\n constructor(\n private readonly kv: KvLike,\n options: KvSessionStoreOptions = {},\n ) {\n this.prefix = options.prefix ?? 'sess:'\n }\n\n async get(sid: string): Promise<SessionData | null> {\n const raw = await this.kv.get(this.prefix + sid)\n if (raw === null) return null\n try {\n return JSON.parse(raw) as SessionData\n } catch {\n return null\n }\n }\n\n async set(sid: string, data: SessionData, maxAge: number): Promise<void> {\n await this.kv.put(this.prefix + sid, JSON.stringify(data), {\n expirationTtl: kvTtlSeconds(maxAge),\n })\n }\n\n async destroy(sid: string): Promise<void> {\n await this.kv.delete(this.prefix + sid)\n }\n\n async touch(sid: string, maxAge: number): Promise<void> {\n const data = await this.get(sid)\n if (data !== null) await this.set(sid, data, maxAge)\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v9.0.2
2
+ * @forinda/kickjs v9.0.3
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -9,4 +9,4 @@
9
9
  * @license MIT
10
10
  */
11
11
  import"reflect-metadata";function setClassMeta(key,value,target){Reflect.defineMetadata(key,value,target)}function setMethodMeta(key,value,target,method){Reflect.defineMetadata(key,value,target,method)}function getClassMeta(key,target,fallback){return Reflect.getMetadata(key,target)??fallback}function getMethodMeta(key,target,method,fallback){return Reflect.getMetadata(key,target,method)??fallback}function hasClassMeta(key,target){return Reflect.hasMetadata(key,target)}function getClassMetaOrUndefined(key,target){return Reflect.getMetadata(key,target)}function getMethodMetaOrUndefined(key,target,method){return Reflect.getMetadata(key,target,method)}function pushClassMeta(key,target,...items){let existing=Reflect.getOwnMetadata(key,target)??[];Reflect.defineMetadata(key,[...existing,...items],target)}function pushMethodMeta(key,target,method,...items){let existing=Reflect.getOwnMetadata(key,target,method)??[];Reflect.defineMetadata(key,[...existing,...items],target,method)}function setInMetaMap(key,target,mapKey,mapValue){let inherited=Reflect.getOwnMetadata(key,target),map=inherited?new Map(inherited):new Map;map.set(mapKey,mapValue),Reflect.defineMetadata(key,map,target)}function getMetaMap(key,target){return Reflect.getMetadata(key,target)??new Map}function setInMetaRecord(key,target,recKey,recValue){let inherited=Reflect.getOwnMetadata(key,target),rec=inherited?{...inherited}:{};rec[recKey]=recValue,Reflect.defineMetadata(key,rec,target)}function getMetaRecord(key,target){return Reflect.getMetadata(key,target)??{}}export{getMethodMeta as a,pushClassMeta as c,setInMetaMap as d,setInMetaRecord as f,getMetaRecord as i,pushMethodMeta as l,getClassMetaOrUndefined as n,getMethodMetaOrUndefined as o,setMethodMeta as p,getMetaMap as r,hasClassMeta as s,getClassMeta as t,setClassMeta as u};
12
- //# sourceMappingURL=metadata-DrCyWXgz.mjs.map
12
+ //# sourceMappingURL=metadata-CIXErBTj.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"metadata-DrCyWXgz.mjs","names":[],"sources":["../src/core/metadata.ts"],"sourcesContent":["import 'reflect-metadata'\nimport type { InjectionToken } from './token'\n\n/**\n * Typed utilities for reflect-metadata operations.\n *\n * Replaces scattered Reflect.defineMetadata/getMetadata calls with a single\n * source of truth. If we later move to Stage 3 decorators or a WeakMap-based\n * store, only this file changes — all 24+ consumer files stay untouched.\n *\n * Keys may be symbols, strings, or `InjectionToken<T>` objects produced by\n * `createToken()`. Using `createToken` is the preferred pattern because the\n * token's phantom type flows through the helpers, enabling type-safe reads.\n */\n\ntype MetaKey = symbol | string | InjectionToken<unknown>\n\n// ── Setters ─────────────────────────────────────────────────────\n\n/** Set metadata on a class (or prototype for property decorators) */\nexport function setClassMeta<T>(key: MetaKey, value: T, target: object): void {\n Reflect.defineMetadata(key, value, target)\n}\n\n/** Set metadata on a specific method */\nexport function setMethodMeta<T>(key: MetaKey, value: T, target: object, method: string): void {\n Reflect.defineMetadata(key, value, target, method)\n}\n\n// ── Getters ─────────────────────────────────────────────────────\n\n/** Get metadata from a class, returning `fallback` if not set */\nexport function getClassMeta<T>(key: MetaKey, target: object, fallback: T): T {\n return (Reflect.getMetadata(key, target) as T) ?? fallback\n}\n\n/** Get metadata from a specific method, returning `fallback` if not set */\nexport function getMethodMeta<T>(key: MetaKey, target: object, method: string, fallback: T): T {\n return (Reflect.getMetadata(key, target, method) as T) ?? fallback\n}\n\n/** Check if metadata exists on a class */\nexport function hasClassMeta(key: MetaKey, target: object): boolean {\n return Reflect.hasMetadata(key, target)\n}\n\n/** Get metadata from a class with no default (returns undefined if absent) */\nexport function getClassMetaOrUndefined<T>(key: MetaKey, target: object): T | undefined {\n return Reflect.getMetadata(key, target) as T | undefined\n}\n\n/** Get metadata from a method with no default (returns undefined if absent) */\nexport function getMethodMetaOrUndefined<T>(\n key: MetaKey,\n target: object,\n method: string,\n): T | undefined {\n return Reflect.getMetadata(key, target, method) as T | undefined\n}\n\n// ── Accumulate: Arrays ──────────────────────────────────────────\n\n/** Append items to an array stored in class metadata */\nexport function pushClassMeta<T>(key: MetaKey, target: object, ...items: T[]): void {\n const existing: T[] = Reflect.getOwnMetadata(key, target) ?? []\n Reflect.defineMetadata(key, [...existing, ...items], target)\n}\n\n/** Append items to an array stored in method metadata */\nexport function pushMethodMeta<T>(\n key: MetaKey,\n target: object,\n method: string,\n ...items: T[]\n): void {\n const existing: T[] = Reflect.getOwnMetadata(key, target, method) ?? []\n Reflect.defineMetadata(key, [...existing, ...items], target, method)\n}\n\n// ── Accumulate: Maps ────────────────────────────────────────────\n\n/** Set a key/value in a Map stored in class metadata */\nexport function setInMetaMap<K, V>(key: MetaKey, target: object, mapKey: K, mapValue: V): void {\n const inherited: Map<K, V> | undefined = Reflect.getOwnMetadata(key, target)\n const map = inherited ? new Map(inherited) : new Map<K, V>()\n map.set(mapKey, mapValue)\n Reflect.defineMetadata(key, map, target)\n}\n\n/** Get a Map from class metadata (returns empty Map if absent) */\nexport function getMetaMap<K, V>(key: MetaKey, target: object): Map<K, V> {\n return Reflect.getMetadata(key, target) ?? new Map()\n}\n\n// ── Accumulate: Records ─────────────────────────────────────────\n\n/** Set a key/value in a Record stored in class metadata */\nexport function setInMetaRecord<V>(\n key: MetaKey,\n target: object,\n recKey: string | number,\n recValue: V,\n): void {\n const inherited: Record<string | number, V> | undefined = Reflect.getOwnMetadata(key, target)\n const rec = inherited ? { ...inherited } : {}\n rec[recKey] = recValue\n Reflect.defineMetadata(key, rec, target)\n}\n\n/** Get a Record from class metadata (returns empty object if absent) */\nexport function getMetaRecord<V>(key: MetaKey, target: object): Record<string | number, V> {\n return Reflect.getMetadata(key, target) ?? {}\n}\n"],"mappings":";;;;;;;;;;yBAoBA,SAAgB,aAAgB,IAAc,MAAU,OAAsB,CAC5E,QAAQ,eAAe,IAAK,MAAO,MAAM,CAC3C,CAGA,SAAgB,cAAiB,IAAc,MAAU,OAAgB,OAAsB,CAC7F,QAAQ,eAAe,IAAK,MAAO,OAAQ,MAAM,CACnD,CAKA,SAAgB,aAAgB,IAAc,OAAgB,SAAgB,CAC5E,OAAQ,QAAQ,YAAY,IAAK,MAAM,GAAW,QACpD,CAGA,SAAgB,cAAiB,IAAc,OAAgB,OAAgB,SAAgB,CAC7F,OAAQ,QAAQ,YAAY,IAAK,OAAQ,MAAM,GAAW,QAC5D,CAGA,SAAgB,aAAa,IAAc,OAAyB,CAClE,OAAO,QAAQ,YAAY,IAAK,MAAM,CACxC,CAGA,SAAgB,wBAA2B,IAAc,OAA+B,CACtF,OAAO,QAAQ,YAAY,IAAK,MAAM,CACxC,CAGA,SAAgB,yBACd,IACA,OACA,OACe,CACf,OAAO,QAAQ,YAAY,IAAK,OAAQ,MAAM,CAChD,CAKA,SAAgB,cAAiB,IAAc,OAAgB,GAAG,MAAkB,CAClF,IAAM,SAAgB,QAAQ,eAAe,IAAK,MAAM,GAAK,CAAC,EAC9D,QAAQ,eAAe,IAAK,CAAC,GAAG,SAAU,GAAG,KAAK,EAAG,MAAM,CAC7D,CAGA,SAAgB,eACd,IACA,OACA,OACA,GAAG,MACG,CACN,IAAM,SAAgB,QAAQ,eAAe,IAAK,OAAQ,MAAM,GAAK,CAAC,EACtE,QAAQ,eAAe,IAAK,CAAC,GAAG,SAAU,GAAG,KAAK,EAAG,OAAQ,MAAM,CACrE,CAKA,SAAgB,aAAmB,IAAc,OAAgB,OAAW,SAAmB,CAC7F,IAAM,UAAmC,QAAQ,eAAe,IAAK,MAAM,EACrE,IAAM,UAAY,IAAI,IAAI,SAAS,EAAI,IAAI,IACjD,IAAI,IAAI,OAAQ,QAAQ,EACxB,QAAQ,eAAe,IAAK,IAAK,MAAM,CACzC,CAGA,SAAgB,WAAiB,IAAc,OAA2B,CACxE,OAAO,QAAQ,YAAY,IAAK,MAAM,GAAK,IAAI,GACjD,CAKA,SAAgB,gBACd,IACA,OACA,OACA,SACM,CACN,IAAM,UAAoD,QAAQ,eAAe,IAAK,MAAM,EACtF,IAAM,UAAY,CAAE,GAAG,SAAU,EAAI,CAAC,EAC5C,IAAI,QAAU,SACd,QAAQ,eAAe,IAAK,IAAK,MAAM,CACzC,CAGA,SAAgB,cAAiB,IAAc,OAA4C,CACzF,OAAO,QAAQ,YAAY,IAAK,MAAM,GAAK,CAAC,CAC9C"}
1
+ {"version":3,"file":"metadata-CIXErBTj.mjs","names":[],"sources":["../src/core/metadata.ts"],"sourcesContent":["import 'reflect-metadata'\nimport type { InjectionToken } from './token'\n\n/**\n * Typed utilities for reflect-metadata operations.\n *\n * Replaces scattered Reflect.defineMetadata/getMetadata calls with a single\n * source of truth. If we later move to Stage 3 decorators or a WeakMap-based\n * store, only this file changes — all 24+ consumer files stay untouched.\n *\n * Keys may be symbols, strings, or `InjectionToken<T>` objects produced by\n * `createToken()`. Using `createToken` is the preferred pattern because the\n * token's phantom type flows through the helpers, enabling type-safe reads.\n */\n\ntype MetaKey = symbol | string | InjectionToken<unknown>\n\n// ── Setters ─────────────────────────────────────────────────────\n\n/** Set metadata on a class (or prototype for property decorators) */\nexport function setClassMeta<T>(key: MetaKey, value: T, target: object): void {\n Reflect.defineMetadata(key, value, target)\n}\n\n/** Set metadata on a specific method */\nexport function setMethodMeta<T>(key: MetaKey, value: T, target: object, method: string): void {\n Reflect.defineMetadata(key, value, target, method)\n}\n\n// ── Getters ─────────────────────────────────────────────────────\n\n/** Get metadata from a class, returning `fallback` if not set */\nexport function getClassMeta<T>(key: MetaKey, target: object, fallback: T): T {\n return (Reflect.getMetadata(key, target) as T) ?? fallback\n}\n\n/** Get metadata from a specific method, returning `fallback` if not set */\nexport function getMethodMeta<T>(key: MetaKey, target: object, method: string, fallback: T): T {\n return (Reflect.getMetadata(key, target, method) as T) ?? fallback\n}\n\n/** Check if metadata exists on a class */\nexport function hasClassMeta(key: MetaKey, target: object): boolean {\n return Reflect.hasMetadata(key, target)\n}\n\n/** Get metadata from a class with no default (returns undefined if absent) */\nexport function getClassMetaOrUndefined<T>(key: MetaKey, target: object): T | undefined {\n return Reflect.getMetadata(key, target) as T | undefined\n}\n\n/** Get metadata from a method with no default (returns undefined if absent) */\nexport function getMethodMetaOrUndefined<T>(\n key: MetaKey,\n target: object,\n method: string,\n): T | undefined {\n return Reflect.getMetadata(key, target, method) as T | undefined\n}\n\n// ── Accumulate: Arrays ──────────────────────────────────────────\n\n/** Append items to an array stored in class metadata */\nexport function pushClassMeta<T>(key: MetaKey, target: object, ...items: T[]): void {\n const existing: T[] = Reflect.getOwnMetadata(key, target) ?? []\n Reflect.defineMetadata(key, [...existing, ...items], target)\n}\n\n/** Append items to an array stored in method metadata */\nexport function pushMethodMeta<T>(\n key: MetaKey,\n target: object,\n method: string,\n ...items: T[]\n): void {\n const existing: T[] = Reflect.getOwnMetadata(key, target, method) ?? []\n Reflect.defineMetadata(key, [...existing, ...items], target, method)\n}\n\n// ── Accumulate: Maps ────────────────────────────────────────────\n\n/** Set a key/value in a Map stored in class metadata */\nexport function setInMetaMap<K, V>(key: MetaKey, target: object, mapKey: K, mapValue: V): void {\n const inherited: Map<K, V> | undefined = Reflect.getOwnMetadata(key, target)\n const map = inherited ? new Map(inherited) : new Map<K, V>()\n map.set(mapKey, mapValue)\n Reflect.defineMetadata(key, map, target)\n}\n\n/** Get a Map from class metadata (returns empty Map if absent) */\nexport function getMetaMap<K, V>(key: MetaKey, target: object): Map<K, V> {\n return Reflect.getMetadata(key, target) ?? new Map()\n}\n\n// ── Accumulate: Records ─────────────────────────────────────────\n\n/** Set a key/value in a Record stored in class metadata */\nexport function setInMetaRecord<V>(\n key: MetaKey,\n target: object,\n recKey: string | number,\n recValue: V,\n): void {\n const inherited: Record<string | number, V> | undefined = Reflect.getOwnMetadata(key, target)\n const rec = inherited ? { ...inherited } : {}\n rec[recKey] = recValue\n Reflect.defineMetadata(key, rec, target)\n}\n\n/** Get a Record from class metadata (returns empty object if absent) */\nexport function getMetaRecord<V>(key: MetaKey, target: object): Record<string | number, V> {\n return Reflect.getMetadata(key, target) ?? {}\n}\n"],"mappings":";;;;;;;;;;yBAoBA,SAAgB,aAAgB,IAAc,MAAU,OAAsB,CAC5E,QAAQ,eAAe,IAAK,MAAO,MAAM,CAC3C,CAGA,SAAgB,cAAiB,IAAc,MAAU,OAAgB,OAAsB,CAC7F,QAAQ,eAAe,IAAK,MAAO,OAAQ,MAAM,CACnD,CAKA,SAAgB,aAAgB,IAAc,OAAgB,SAAgB,CAC5E,OAAQ,QAAQ,YAAY,IAAK,MAAM,GAAW,QACpD,CAGA,SAAgB,cAAiB,IAAc,OAAgB,OAAgB,SAAgB,CAC7F,OAAQ,QAAQ,YAAY,IAAK,OAAQ,MAAM,GAAW,QAC5D,CAGA,SAAgB,aAAa,IAAc,OAAyB,CAClE,OAAO,QAAQ,YAAY,IAAK,MAAM,CACxC,CAGA,SAAgB,wBAA2B,IAAc,OAA+B,CACtF,OAAO,QAAQ,YAAY,IAAK,MAAM,CACxC,CAGA,SAAgB,yBACd,IACA,OACA,OACe,CACf,OAAO,QAAQ,YAAY,IAAK,OAAQ,MAAM,CAChD,CAKA,SAAgB,cAAiB,IAAc,OAAgB,GAAG,MAAkB,CAClF,IAAM,SAAgB,QAAQ,eAAe,IAAK,MAAM,GAAK,CAAC,EAC9D,QAAQ,eAAe,IAAK,CAAC,GAAG,SAAU,GAAG,KAAK,EAAG,MAAM,CAC7D,CAGA,SAAgB,eACd,IACA,OACA,OACA,GAAG,MACG,CACN,IAAM,SAAgB,QAAQ,eAAe,IAAK,OAAQ,MAAM,GAAK,CAAC,EACtE,QAAQ,eAAe,IAAK,CAAC,GAAG,SAAU,GAAG,KAAK,EAAG,OAAQ,MAAM,CACrE,CAKA,SAAgB,aAAmB,IAAc,OAAgB,OAAW,SAAmB,CAC7F,IAAM,UAAmC,QAAQ,eAAe,IAAK,MAAM,EACrE,IAAM,UAAY,IAAI,IAAI,SAAS,EAAI,IAAI,IACjD,IAAI,IAAI,OAAQ,QAAQ,EACxB,QAAQ,eAAe,IAAK,IAAK,MAAM,CACzC,CAGA,SAAgB,WAAiB,IAAc,OAA2B,CACxE,OAAO,QAAQ,YAAY,IAAK,MAAM,GAAK,IAAI,GACjD,CAKA,SAAgB,gBACd,IACA,OACA,OACA,SACM,CACN,IAAM,UAAoD,QAAQ,eAAe,IAAK,MAAM,EACtF,IAAM,UAAY,CAAE,GAAG,SAAU,EAAI,CAAC,EAC5C,IAAI,QAAU,SACd,QAAQ,eAAe,IAAK,IAAK,MAAM,CACzC,CAGA,SAAgB,cAAiB,IAAc,OAA4C,CACzF,OAAO,QAAQ,YAAY,IAAK,MAAM,GAAK,CAAC,CAC9C"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v9.0.2
2
+ * @forinda/kickjs v9.0.3
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -9,4 +9,4 @@
9
9
  * @license MIT
10
10
  */
11
11
  var MutableModuleRegistry=class{entries=[];mount(module){this.entries.push(module)}};export{MutableModuleRegistry as t};
12
- //# sourceMappingURL=module-registry-Bv3NMDX5.mjs.map
12
+ //# sourceMappingURL=module-registry-IO8RfVjX.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"module-registry-Bv3NMDX5.mjs","names":[],"sources":["../src/core/module-registry.ts"],"sourcesContent":["import type { AppModuleEntry } from './app-module'\n\n/**\n * Imperative receiver passed to {@link KickPlugin.setup} and the\n * top-level `bootstrap({ setup })` callback so adopters can register\n * modules conditionally — based on env flags, runtime config,\n * tenant lists, etc. — instead of being limited to the static\n * `modules: [...]` array.\n *\n * @example\n * ```ts\n * bootstrap({\n * modules: [HelloModule()], // static — always mounted\n * setup(registry) {\n * if (env.ENABLE_ADMIN) registry.mount(AdminModule())\n * for (const tenant of env.TENANTS) {\n * registry.mount(TenantModule.scoped(tenant.id, { id: tenant.id }))\n * }\n * },\n * })\n * ```\n *\n * **Currently exposes only `.mount(module)` — the HTTP-feature path.**\n * A future `.use(module)` for non-HTTP modules (queues, cron, workers,\n * DI-only seeds) is planned but not yet implemented; existing non-HTTP\n * modules continue returning `null` from `routes()` and registering\n * via `.mount()`.\n *\n * Same dispatch rules as the static `modules: [...]` array — class\n * entries get `new`-ed at boot, `defineModule` factory output is used\n * directly. The registry doesn't validate the entry shape itself; the\n * Application loader does the typeof discrimination at mount time.\n */\nexport interface ModuleRegistry {\n /**\n * Register a module for the full HTTP lifecycle: `register()` runs,\n * `import.meta.glob` decorator side-effects fire, `routes()` is\n * called and its result mounted, `contributors()` are merged at\n * `'module'` precedence and applied to the mounted routes.\n *\n * Modules are mounted in registration order (first call → first\n * mount). Order across sources is: plugin static modules → plugin\n * `setup()` calls → user static modules → user `setup()` callback.\n */\n mount(module: AppModuleEntry): void\n}\n\n/**\n * Internal collector implementing {@link ModuleRegistry}. Application\n * passes one of these to every `setup()` callback during bootstrap;\n * the collected `entries` array is then run through the same\n * class-vs-instance dispatch as the static `modules: [...]` array.\n *\n * Not exported from the public surface — adopters interact only\n * through the {@link ModuleRegistry} interface.\n */\nexport class MutableModuleRegistry implements ModuleRegistry {\n readonly entries: AppModuleEntry[] = []\n\n mount(module: AppModuleEntry): void {\n this.entries.push(module)\n }\n}\n"],"mappings":";;;;;;;;;;AAwDA,IAAa,sBAAb,KAA6D,CAC3D,QAAqC,CAAC,EAEtC,MAAM,OAA8B,CAClC,KAAK,QAAQ,KAAK,MAAM,CAC1B,CACF"}
1
+ {"version":3,"file":"module-registry-IO8RfVjX.mjs","names":[],"sources":["../src/core/module-registry.ts"],"sourcesContent":["import type { AppModuleEntry } from './app-module'\n\n/**\n * Imperative receiver passed to {@link KickPlugin.setup} and the\n * top-level `bootstrap({ setup })` callback so adopters can register\n * modules conditionally — based on env flags, runtime config,\n * tenant lists, etc. — instead of being limited to the static\n * `modules: [...]` array.\n *\n * @example\n * ```ts\n * bootstrap({\n * modules: [HelloModule()], // static — always mounted\n * setup(registry) {\n * if (env.ENABLE_ADMIN) registry.mount(AdminModule())\n * for (const tenant of env.TENANTS) {\n * registry.mount(TenantModule.scoped(tenant.id, { id: tenant.id }))\n * }\n * },\n * })\n * ```\n *\n * **Currently exposes only `.mount(module)` — the HTTP-feature path.**\n * A future `.use(module)` for non-HTTP modules (queues, cron, workers,\n * DI-only seeds) is planned but not yet implemented; existing non-HTTP\n * modules continue returning `null` from `routes()` and registering\n * via `.mount()`.\n *\n * Same dispatch rules as the static `modules: [...]` array — class\n * entries get `new`-ed at boot, `defineModule` factory output is used\n * directly. The registry doesn't validate the entry shape itself; the\n * Application loader does the typeof discrimination at mount time.\n */\nexport interface ModuleRegistry {\n /**\n * Register a module for the full HTTP lifecycle: `register()` runs,\n * `import.meta.glob` decorator side-effects fire, `routes()` is\n * called and its result mounted, `contributors()` are merged at\n * `'module'` precedence and applied to the mounted routes.\n *\n * Modules are mounted in registration order (first call → first\n * mount). Order across sources is: plugin static modules → plugin\n * `setup()` calls → user static modules → user `setup()` callback.\n */\n mount(module: AppModuleEntry): void\n}\n\n/**\n * Internal collector implementing {@link ModuleRegistry}. Application\n * passes one of these to every `setup()` callback during bootstrap;\n * the collected `entries` array is then run through the same\n * class-vs-instance dispatch as the static `modules: [...]` array.\n *\n * Not exported from the public surface — adopters interact only\n * through the {@link ModuleRegistry} interface.\n */\nexport class MutableModuleRegistry implements ModuleRegistry {\n readonly entries: AppModuleEntry[] = []\n\n mount(module: AppModuleEntry): void {\n this.entries.push(module)\n }\n}\n"],"mappings":";;;;;;;;;;AAwDA,IAAa,sBAAb,KAA6D,CAC3D,QAAqC,CAAC,EAEtC,MAAM,OAA8B,CAClC,KAAK,QAAQ,KAAK,MAAM,CAC1B,CACF"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v9.0.2
2
+ * @forinda/kickjs v9.0.3
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -9,4 +9,4 @@
9
9
  * @license MIT
10
10
  */
11
11
  import{createLogger}from"./core/logger.mjs";const log=createLogger(`Observers`);let observers=[],publisher,publisherWantsResponses=()=>!1;function setObservers(list){return observers=list.filter(o=>o.onError||o.onResponse),observers}function releaseObservers(installed){observers===installed&&(observers=[])}function setObserverPublisher(next,wantsResponses=()=>!1){publisher=next,publisherWantsResponses=wantsResponses}function hasResponseObservers(){return observers.some(o=>o.onResponse)||publisherWantsResponses()}function notify(pick,what){for(let observer of publisher?[...observers,publisher]:observers)try{let result=pick(observer);result&&typeof result.catch==`function`&&result.catch(err=>failed(observer,what,err))}catch(err){failed(observer,what,err)}}function failed(observer,what,err){try{log.error({err},`${observer.name??`observer`}.${what} threw — skipped`)}catch{}}function reportError(error,info){notify(o=>o.onError?.(error,info),`onError`)}function reportResponse(info){notify(o=>o.onResponse?.(info),`onResponse`)}export{setObserverPublisher as a,reportResponse as i,releaseObservers as n,setObservers as o,reportError as r,hasResponseObservers as t};
12
- //# sourceMappingURL=observers-DzVW_zb7.mjs.map
12
+ //# sourceMappingURL=observers-BtXkP5km.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"observers-DzVW_zb7.mjs","names":[],"sources":["../src/core/observers.ts"],"sourcesContent":["/**\n * One place every error and every response is reported to — so an error\n * tracker, a metrics exporter, or an APM integration hooks in once instead of\n * replacing the error handler and patching each source.\n *\n * Observers are adapters and plugins with `onError` / `onResponse`, plus\n * whatever the Node server's tracing channels publish (see `http/tracing.ts`).\n * Observe-only: they cannot change the response, and one that throws is\n * logged and skipped — it never breaks a request or another observer.\n *\n * Free of `node:` imports: `waitUntil` (shared with the web entry) reports\n * through here.\n *\n * @module @forinda/kickjs/core/observers\n */\nimport { createLogger } from './logger'\n\nconst log = createLogger('Observers')\n\n/** Where an error came from. Integrations may report their own sources. */\nexport type ErrorSource =\n | 'request'\n | 'uncaught'\n | 'unhandled-rejection'\n | 'background'\n | 'job'\n | (string & {})\n\nexport interface ErrorInfo {\n source: ErrorSource\n requestId?: string\n method?: string\n /** The request URL path, as received. */\n path?: string\n /** The matched route pattern, e.g. `/api/v1/users/:id`, when one matched. */\n route?: string\n /** The status the error was answered with, for request errors. */\n status?: number\n /** Anything else the reporter knows — a queue and job name, a cron schedule. */\n context?: Record<string, unknown>\n}\n\nexport interface ResponseInfo {\n method: string\n path: string\n /** The matched route pattern, or undefined when no route matched (404s). */\n route?: string\n status: number\n durationMs: number\n requestId?: string\n}\n\nexport interface Observer {\n name?: string\n onError?(error: unknown, info: ErrorInfo): void | Promise<void>\n onResponse?(info: ResponseInfo): void | Promise<void>\n}\n\nlet observers: readonly Observer[] = []\nlet publisher: Observer | undefined\nlet publisherWantsResponses: () => boolean = () => false\n\n/**\n * Replace the observer set — called by the Application from its adapters and\n * plugins. Returns the installed set, so its owner can later release exactly\n * that set (see {@link releaseObservers}).\n */\nexport function setObservers(list: readonly Observer[]): readonly Observer[] {\n observers = list.filter((o) => o.onError || o.onResponse)\n return observers\n}\n\n/**\n * Clear the observer set — but only if it is still the one `installed`. The\n * set is process-wide: a newer Application may have replaced it, and an older\n * one shutting down must not clear the newer one's observers.\n */\nexport function releaseObservers(installed: readonly Observer[]): void {\n if (observers === installed) observers = []\n}\n\n/**\n * The Node server's tracing channels, fed from the same reports. `wantsResponses`\n * is checked per request: a channel can gain a subscriber at any time.\n */\nexport function setObserverPublisher(\n next: Observer | undefined,\n wantsResponses: () => boolean = () => false,\n): void {\n publisher = next\n publisherWantsResponses = wantsResponses\n}\n\n/** Whether anything is listening for responses — lets the server skip timing work. */\nexport function hasResponseObservers(): boolean {\n return observers.some((o) => o.onResponse) || publisherWantsResponses()\n}\n\nfunction notify(pick: (o: Observer) => void | Promise<void> | undefined, what: string): void {\n for (const observer of publisher ? [...observers, publisher] : observers) {\n try {\n const result = pick(observer)\n if (result && typeof (result as Promise<void>).catch === 'function') {\n ;(result as Promise<void>).catch((err: unknown) => failed(observer, what, err))\n }\n } catch (err) {\n failed(observer, what, err)\n }\n }\n}\n\nfunction failed(observer: Observer, what: string, err: unknown): void {\n // Logged directly, never re-reported: an observer failing inside onError\n // must not loop back into onError. Guarded too: a custom LoggerProvider that\n // throws would otherwise escape reportError() — or, from an async\n // observer, become an unhandled rejection that bootstrap reports right back\n // here.\n try {\n log.error({ err }, `${observer.name ?? 'observer'}.${what} threw — skipped`)\n } catch {\n // Nothing left to tell; reportError's \"never throws\" contract wins.\n }\n}\n\n/**\n * Report an error to every observer. The framework calls this for request\n * errors, uncaught exceptions, unhandled rejections, `waitUntil` failures and\n * queue job failures; call it from your own integrations too (a cron runner,\n * a message consumer). Never throws.\n */\nexport function reportError(error: unknown, info: ErrorInfo): void {\n notify((o) => o.onError?.(error, info), 'onError')\n}\n\n/** Report a finished response to every observer. Never throws. */\nexport function reportResponse(info: ResponseInfo): void {\n notify((o) => o.onResponse?.(info), 'onResponse')\n}\n"],"mappings":";;;;;;;;;;4CAiBA,MAAM,IAAM,aAAa,WAAW,EAyCpC,IAAI,UAAiC,CAAC,EAClC,UACA,4BAA+C,GAOnD,SAAgB,aAAa,KAAgD,CAE3E,MADA,WAAY,KAAK,OAAQ,GAAM,EAAE,SAAW,EAAE,UAAU,EACjD,SACT,CAOA,SAAgB,iBAAiB,UAAsC,CACjE,YAAc,YAAW,UAAY,CAAC,EAC5C,CAMA,SAAgB,qBACd,KACA,mBAAsC,GAChC,CACN,UAAY,KACZ,wBAA0B,cAC5B,CAGA,SAAgB,sBAAgC,CAC9C,OAAO,UAAU,KAAM,GAAM,EAAE,UAAU,GAAK,wBAAwB,CACxE,CAEA,SAAS,OAAO,KAAyD,KAAoB,CAC3F,IAAK,IAAM,YAAY,UAAY,CAAC,GAAG,UAAW,SAAS,EAAI,UAC7D,GAAI,CACF,IAAM,OAAS,KAAK,QAAQ,EACxB,QAAU,OAAQ,OAAyB,OAAU,YACtD,OAA0B,MAAO,KAAiB,OAAO,SAAU,KAAM,GAAG,CAAC,CAElF,OAAS,IAAK,CACZ,OAAO,SAAU,KAAM,GAAG,CAC5B,CAEJ,CAEA,SAAS,OAAO,SAAoB,KAAc,IAAoB,CAMpE,GAAI,CACF,IAAI,MAAM,CAAE,GAAI,EAAG,GAAG,SAAS,MAAQ,WAAW,GAAG,KAAK,iBAAiB,CAC7E,MAAQ,CAER,CACF,CAQA,SAAgB,YAAY,MAAgB,KAAuB,CACjE,OAAQ,GAAM,EAAE,UAAU,MAAO,IAAI,EAAG,SAAS,CACnD,CAGA,SAAgB,eAAe,KAA0B,CACvD,OAAQ,GAAM,EAAE,aAAa,IAAI,EAAG,YAAY,CAClD"}
1
+ {"version":3,"file":"observers-BtXkP5km.mjs","names":[],"sources":["../src/core/observers.ts"],"sourcesContent":["/**\n * One place every error and every response is reported to — so an error\n * tracker, a metrics exporter, or an APM integration hooks in once instead of\n * replacing the error handler and patching each source.\n *\n * Observers are adapters and plugins with `onError` / `onResponse`, plus\n * whatever the Node server's tracing channels publish (see `http/tracing.ts`).\n * Observe-only: they cannot change the response, and one that throws is\n * logged and skipped — it never breaks a request or another observer.\n *\n * Free of `node:` imports: `waitUntil` (shared with the web entry) reports\n * through here.\n *\n * @module @forinda/kickjs/core/observers\n */\nimport { createLogger } from './logger'\n\nconst log = createLogger('Observers')\n\n/** Where an error came from. Integrations may report their own sources. */\nexport type ErrorSource =\n | 'request'\n | 'uncaught'\n | 'unhandled-rejection'\n | 'background'\n | 'job'\n | (string & {})\n\nexport interface ErrorInfo {\n source: ErrorSource\n requestId?: string\n method?: string\n /** The request URL path, as received. */\n path?: string\n /** The matched route pattern, e.g. `/api/v1/users/:id`, when one matched. */\n route?: string\n /** The status the error was answered with, for request errors. */\n status?: number\n /** Anything else the reporter knows — a queue and job name, a cron schedule. */\n context?: Record<string, unknown>\n}\n\nexport interface ResponseInfo {\n method: string\n path: string\n /** The matched route pattern, or undefined when no route matched (404s). */\n route?: string\n status: number\n durationMs: number\n requestId?: string\n}\n\nexport interface Observer {\n name?: string\n onError?(error: unknown, info: ErrorInfo): void | Promise<void>\n onResponse?(info: ResponseInfo): void | Promise<void>\n}\n\nlet observers: readonly Observer[] = []\nlet publisher: Observer | undefined\nlet publisherWantsResponses: () => boolean = () => false\n\n/**\n * Replace the observer set — called by the Application from its adapters and\n * plugins. Returns the installed set, so its owner can later release exactly\n * that set (see {@link releaseObservers}).\n */\nexport function setObservers(list: readonly Observer[]): readonly Observer[] {\n observers = list.filter((o) => o.onError || o.onResponse)\n return observers\n}\n\n/**\n * Clear the observer set — but only if it is still the one `installed`. The\n * set is process-wide: a newer Application may have replaced it, and an older\n * one shutting down must not clear the newer one's observers.\n */\nexport function releaseObservers(installed: readonly Observer[]): void {\n if (observers === installed) observers = []\n}\n\n/**\n * The Node server's tracing channels, fed from the same reports. `wantsResponses`\n * is checked per request: a channel can gain a subscriber at any time.\n */\nexport function setObserverPublisher(\n next: Observer | undefined,\n wantsResponses: () => boolean = () => false,\n): void {\n publisher = next\n publisherWantsResponses = wantsResponses\n}\n\n/** Whether anything is listening for responses — lets the server skip timing work. */\nexport function hasResponseObservers(): boolean {\n return observers.some((o) => o.onResponse) || publisherWantsResponses()\n}\n\nfunction notify(pick: (o: Observer) => void | Promise<void> | undefined, what: string): void {\n for (const observer of publisher ? [...observers, publisher] : observers) {\n try {\n const result = pick(observer)\n if (result && typeof (result as Promise<void>).catch === 'function') {\n ;(result as Promise<void>).catch((err: unknown) => failed(observer, what, err))\n }\n } catch (err) {\n failed(observer, what, err)\n }\n }\n}\n\nfunction failed(observer: Observer, what: string, err: unknown): void {\n // Logged directly, never re-reported: an observer failing inside onError\n // must not loop back into onError. Guarded too: a custom LoggerProvider that\n // throws would otherwise escape reportError() — or, from an async\n // observer, become an unhandled rejection that bootstrap reports right back\n // here.\n try {\n log.error({ err }, `${observer.name ?? 'observer'}.${what} threw — skipped`)\n } catch {\n // Nothing left to tell; reportError's \"never throws\" contract wins.\n }\n}\n\n/**\n * Report an error to every observer. The framework calls this for request\n * errors, uncaught exceptions, unhandled rejections, `waitUntil` failures and\n * queue job failures; call it from your own integrations too (a cron runner,\n * a message consumer). Never throws.\n */\nexport function reportError(error: unknown, info: ErrorInfo): void {\n notify((o) => o.onError?.(error, info), 'onError')\n}\n\n/** Report a finished response to every observer. Never throws. */\nexport function reportResponse(info: ResponseInfo): void {\n notify((o) => o.onResponse?.(info), 'onResponse')\n}\n"],"mappings":";;;;;;;;;;4CAiBA,MAAM,IAAM,aAAa,WAAW,EAyCpC,IAAI,UAAiC,CAAC,EAClC,UACA,4BAA+C,GAOnD,SAAgB,aAAa,KAAgD,CAE3E,MADA,WAAY,KAAK,OAAQ,GAAM,EAAE,SAAW,EAAE,UAAU,EACjD,SACT,CAOA,SAAgB,iBAAiB,UAAsC,CACjE,YAAc,YAAW,UAAY,CAAC,EAC5C,CAMA,SAAgB,qBACd,KACA,mBAAsC,GAChC,CACN,UAAY,KACZ,wBAA0B,cAC5B,CAGA,SAAgB,sBAAgC,CAC9C,OAAO,UAAU,KAAM,GAAM,EAAE,UAAU,GAAK,wBAAwB,CACxE,CAEA,SAAS,OAAO,KAAyD,KAAoB,CAC3F,IAAK,IAAM,YAAY,UAAY,CAAC,GAAG,UAAW,SAAS,EAAI,UAC7D,GAAI,CACF,IAAM,OAAS,KAAK,QAAQ,EACxB,QAAU,OAAQ,OAAyB,OAAU,YACtD,OAA0B,MAAO,KAAiB,OAAO,SAAU,KAAM,GAAG,CAAC,CAElF,OAAS,IAAK,CACZ,OAAO,SAAU,KAAM,GAAG,CAC5B,CAEJ,CAEA,SAAS,OAAO,SAAoB,KAAc,IAAoB,CAMpE,GAAI,CACF,IAAI,MAAM,CAAE,GAAI,EAAG,GAAG,SAAS,MAAQ,WAAW,GAAG,KAAK,iBAAiB,CAC7E,MAAQ,CAER,CACF,CAQA,SAAgB,YAAY,MAAgB,KAAuB,CACjE,OAAQ,GAAM,EAAE,UAAU,MAAO,IAAI,EAAG,SAAS,CACnD,CAGA,SAAgB,eAAe,KAA0B,CACvD,OAAQ,GAAM,EAAE,aAAa,IAAI,EAAG,YAAY,CAClD"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v9.0.2
2
+ * @forinda/kickjs v9.0.3
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -9,4 +9,4 @@
9
9
  * @license MIT
10
10
  */
11
11
  const FILTER_OPERATORS=new Set([`eq`,`neq`,`gt`,`gte`,`lt`,`lte`,`between`,`in`,`contains`,`starts`,`ends`]),BUILTIN_DEFAULTS={maxLimit:100,defaultLimit:20,maxSearchLength:200,defaultPage:1},defaults={...BUILTIN_DEFAULTS};function setQueryParsingDefaults(partial){partial.maxLimit!==void 0&&(defaults.maxLimit=partial.maxLimit),partial.defaultLimit!==void 0&&(defaults.defaultLimit=partial.defaultLimit),partial.maxSearchLength!==void 0&&(defaults.maxSearchLength=partial.maxSearchLength)}function getQueryParsingDefaults(){return{...defaults}}function resetQueryParsingDefaults(){Object.assign(defaults,BUILTIN_DEFAULTS)}function parseFilters(filterParam,allowedFields,options){if(!filterParam)return[];let items=Array.isArray(filterParam)?filterParam:[filterParam],results=[],allowed=allowedFields?new Set(allowedFields):null,onReject=options?.onReject;for(let item of items){let firstColon=item.indexOf(`:`);if(firstColon===-1)continue;let secondColon=item.indexOf(`:`,firstColon+1);if(secondColon===-1)continue;let field=item.slice(0,firstColon),operator=item.slice(firstColon+1,secondColon),value=item.slice(secondColon+1);if(field&&value){if(!FILTER_OPERATORS.has(operator)){onReject?.({kind:`filter`,field,reason:`unknown-operator`});continue}if(allowed&&!allowed.has(field)){onReject?.({kind:`filter`,field,reason:`field-not-allowed`});continue}results.push({field,operator,value})}}return results}function parseSort(sortParam,allowedFields,options){if(!sortParam)return[];let items=Array.isArray(sortParam)?sortParam:[sortParam],results=[],allowed=allowedFields?new Set(allowedFields):null,onReject=options?.onReject;for(let item of items){let lastColon=item.lastIndexOf(`:`);if(lastColon===-1)continue;let field=item.slice(0,lastColon),dir=item.slice(lastColon+1).toLowerCase();if(field&&(dir===`asc`||dir===`desc`)){if(allowed&&!allowed.has(field)){onReject?.({kind:`sort`,field,reason:`field-not-allowed`});continue}results.push({field,direction:dir})}}return results}function parsePagination(params,options){let defaultLimit=options?.defaultLimit??defaults.defaultLimit,maxLimit=options?.maxLimit??defaults.maxLimit,page=typeof params.page==`string`?Number.parseInt(params.page,10):params.page??defaults.defaultPage,limit=typeof params.limit==`string`?Number.parseInt(params.limit,10):params.limit??defaultLimit;return(Number.isNaN(page)||page<1)&&(page=defaults.defaultPage),(Number.isNaN(limit)||limit<1)&&(limit=defaultLimit),limit>maxLimit&&(limit=maxLimit),{page,limit,offset:(page-1)*limit}}function parseSearchQuery(q,options){if(!q)return``;let maxLen=options?.maxSearchLength??defaults.maxSearchLength,trimmed=q.trim();return trimmed.length>maxLen?(options?.onReject?.({kind:`search`,field:`q`,reason:`truncated`}),trimmed.slice(0,maxLen)):trimmed}function normalizeFieldConfig(config){if(config)return`columns`in config&&config.columns&&typeof config.columns==`object`?{filterable:Object.keys(config.columns),sortable:config.sortable?Object.keys(config.sortable):void 0,searchable:void 0}:config}function parseQuery(query,fieldConfig,options){let normalized=normalizeFieldConfig(fieldConfig),rejectOpts=options?.onReject?{onReject:options.onReject}:void 0;return{filters:parseFilters(query.filter,normalized?.filterable,rejectOpts),sort:parseSort(query.sort,normalized?.sortable,rejectOpts),pagination:parsePagination({page:query.page,limit:query.limit},options),search:parseSearchQuery(query.q,options)}}function buildQueryParams(parsed){let params={};return parsed.filters?.length&&(params.filter=parsed.filters.map(f=>`${f.field}:${f.operator}:${f.value}`)),parsed.sort?.length&&(params.sort=parsed.sort.map(s=>`${s.field}:${s.direction}`)),parsed.pagination&&(params.page=parsed.pagination.page,params.limit=parsed.pagination.limit),parsed.search&&(params.q=parsed.search),params}export{parseQuery as a,resetQueryParsingDefaults as c,parsePagination as i,setQueryParsingDefaults as l,getQueryParsingDefaults as n,parseSearchQuery as o,parseFilters as r,parseSort as s,buildQueryParams as t,FILTER_OPERATORS as u};
12
- //# sourceMappingURL=query-BGQdAxkX.mjs.map
12
+ //# sourceMappingURL=query-CNa0mxSK.mjs.map