honestjs 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -48,6 +48,16 @@ ultra-fast performance of Hono, giving you the best of both worlds.
48
48
  </a>
49
49
  </p>
50
50
 
51
+ ## Cursor / Agent skills
52
+
53
+ Install the Honest skill so your editor agent (e.g. Cursor) can use Honest-specific guidance:
54
+
55
+ ```bash
56
+ bunx skills add https://github.com/honestjs/skills --skill honest
57
+ ```
58
+
59
+ See [honestjs/skills](https://github.com/honestjs/skills) for details.
60
+
51
61
  > 🚨 **Early Development Warning** 🚨
52
62
  >
53
63
  > Honest is currently in early development (pre-v1.0.0). Please be aware that:
@@ -61,104 +71,98 @@ ultra-fast performance of Hono, giving you the best of both worlds.
61
71
 
62
72
  > ⚠️ **Documentation is not yet complete** ⚠️
63
73
  >
64
- > If you find any issues or have suggestions for improvements, please open an issue or submit a pull request. See [CONTRIBUTING.md](CONTRIBUTING.md) for how to contribute and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for community guidelines.
65
-
66
- ## Features
67
-
68
- - 🚀 **High Performance** - Built on top of the ultra-fast Hono framework
69
- - 📦 **Modular Architecture** - Organize your code into reusable, feature-focused modules
70
- - 💉 **Dependency Injection** - Built-in DI container for better code organization and testing
71
- - 🔌 **Plugin System** - Extend functionality through a flexible plugin system
72
- - 🛣️ **Advanced Routing** - Support for versioning, prefixes, and nested routes
73
- - 🔒 **Built-in Security** - Guards, middleware, and error handling out of the box
74
- - 🔄 **Request Pipeline** - Powerful middleware, guards, pipes, and filters
75
- - 📝 **TypeScript-First** - Built with TypeScript for excellent type safety and IDE support
74
+ > If you find any issues or have suggestions for improvements, please open an issue or submit a pull request. See
75
+ > [CONTRIBUTING.md](CONTRIBUTING.md) for how to contribute and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for community
76
+ > guidelines.
76
77
 
77
78
  ## Quick Start
78
79
 
79
- ### Using Honest CLI
80
-
81
- The fastest way to create a new Honest application is to use the Honest CLI:
82
-
83
80
  ```bash
84
- # Install Honest CLI globally
85
81
  bun add -g @honestjs/cli
86
-
87
- # Create a new project
88
- honestjs new my-project # alias: honest, hnjs
82
+ honestjs new my-project # alias: honest, hnjs
89
83
  cd my-project
90
-
91
- # Start the development server
92
84
  bun dev
93
85
  ```
94
86
 
95
- This will create a new project with a standard directory structure and all necessary configuration files.
96
-
97
- ### Manual Setup
87
+ This creates a new project with a standard structure and config. Use the [website](https://github.com/honestjs/website)
88
+ for full docs.
98
89
 
99
- If you prefer to set up your project manually, follow these steps:
100
-
101
- 1. Install packages
90
+ ## Features
102
91
 
103
- ```bash
104
- bun add honestjs hono reflect-metadata
105
- # or
106
- pnpm add honestjs hono reflect-metadata
107
- # or
108
- yarn add honestjs hono reflect-metadata
109
- # or
110
- npm install honestjs hono reflect-metadata
111
- ```
92
+ - **🚀 High performance** — Built on Hono for maximum speed and minimal overhead.
93
+ - **🏗️ Familiar architecture** — Decorator-based API inspired by NestJS; TypeScript-first.
94
+ - **💉 Dependency injection** — Built-in DI container for clean, testable code and automatic wiring.
95
+ - **🔌 Plugin system** — Extend the app with custom plugins, middleware, pipes, and filters.
96
+ - **🛣️ Advanced routing** — Prefixes, API versioning, and nested route organization.
97
+ - **🛡️ Request pipeline** — Middleware, guards, pipes, and filters at app, controller, or handler level.
98
+ - **📝 TypeScript-first** — Strong typing and great IDE support out of the box.
99
+ - **🖥️ MVC & SSR** — Full-stack apps with Hono JSX views; use the `mvc` template or the docs.
112
100
 
113
- 2. Create your first controller:
101
+ ### In code
114
102
 
115
103
  ```typescript
116
- // app.controller.ts
117
- import { Controller, Get } from 'honestjs'
104
+ import 'reflect-metadata'
105
+ import { Application, Controller, Get, Module, Service } from 'honestjs'
106
+ import { LoggerMiddleware } from '@honestjs/middleware'
107
+ import { AuthGuard } from '@honestjs/guards'
108
+ import { ValidationPipe } from '@honestjs/pipes'
109
+ import { HttpExceptionFilter } from '@honestjs/filters'
110
+ import { ApiDocsPlugin } from '@honestjs/api-docs-plugin'
111
+
112
+ @Service()
113
+ class AppService {
114
+ hello(): string {
115
+ return 'Hello, Honest!'
116
+ }
117
+ }
118
118
 
119
119
  @Controller()
120
120
  class AppController {
121
+ constructor(private readonly appService: AppService) {}
122
+
121
123
  @Get()
122
- helloWorld() {
123
- return 'Hello, World!'
124
+ hello() {
125
+ return this.appService.hello()
124
126
  }
125
127
  }
126
128
 
127
- export default AppController
128
- ```
129
-
130
- 3. Create a module:
131
-
132
- ```typescript
133
- // app.module.ts
134
- import { Module } from 'honestjs'
135
- import { AppController } from './app.controller.ts'
136
-
137
129
  @Module({
138
- controllers: [AppController]
130
+ controllers: [AppController],
131
+ services: [AppService]
139
132
  })
140
133
  class AppModule {}
141
134
 
142
- export default AppModule
143
- ```
144
-
145
- 4. Bootstrap your application:
146
-
147
- ```typescript
148
- import 'reflect-metadata'
149
- import { Application } from 'honestjs'
150
- import { AppModule } from './app.module'
151
-
152
135
  const { app, hono } = await Application.create(AppModule, {
136
+ debug: { routes: true, plugins: true },
137
+ strict: { requireRoutes: true },
138
+ deprecations: { printPreV1Warning: true },
139
+ container: myContainer,
140
+ hono: {
141
+ strict: true,
142
+ router: customRouter
143
+ },
153
144
  routing: {
154
145
  prefix: 'api',
155
146
  version: 1
156
- }
147
+ },
148
+ // Components: use class (e.g. AuthGuard) or instance (e.g. new LoggerMiddleware()) to pass options
149
+ components: {
150
+ middleware: [new LoggerMiddleware()],
151
+ guards: [AuthGuard],
152
+ pipes: [ValidationPipe],
153
+ filters: [HttpExceptionFilter]
154
+ },
155
+ plugins: [new RPCPlugin(), new ApiDocsPlugin(), { plugin: MyPlugin, preProcessors: [pre], postProcessors: [post] }],
156
+ onError: (err, c) => c.json({ error: err.message }, 500),
157
+ notFound: (c) => c.json({ error: 'Not found' }, 404)
157
158
  })
158
159
 
159
160
  export default hono
160
161
  ```
161
162
 
163
+ Controllers, services, and modules are wired by decorators; use **guards** for auth, **pipes** for validation, and
164
+ **filters** for error handling. See the [documentation](https://github.com/honestjs/website) for details.
165
+
162
166
  ## License
163
167
 
164
168
  MIT © [Orkhan Karimov](https://github.com/kerimovok)
@@ -1,5 +1,5 @@
1
1
  import { Hono } from 'hono';
2
- import type { HonestOptions, IApplicationContext, RouteInfo } from './interfaces';
2
+ import type { DiContainer, HonestOptions, IApplicationContext, RouteInfo } from './interfaces';
3
3
  import type { Constructor } from './types';
4
4
  /**
5
5
  * Main application class for the Honest framework.
@@ -26,6 +26,7 @@ export declare class Application {
26
26
  hono: Hono;
27
27
  }>;
28
28
  getApp(): Hono;
29
+ getContainer(): DiContainer;
29
30
  getContext(): IApplicationContext;
30
31
  getRoutes(): ReadonlyArray<RouteInfo>;
31
32
  }
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import"reflect-metadata";import{Hono as $$}from"hono";class C{store=new Map;get($){return this.store.get($)}set($,_){this.store.set($,_)}has($){return this.store.has($)}delete($){return this.store.delete($)}keys(){return this.store.keys()}}class Z{static routes=new Map;static controllers=new Map;static controllerOptions=new Map;static services=new Set;static modules=new Map;static parameters=new Map;static contextIndices=new Map;static controller=new Map([["middleware",new Map],["guard",new Map],["pipe",new Map],["filter",new Map]]);static handler=new Map([["middleware",new Map],["guard",new Map],["pipe",new Map],["filter",new Map]]);static getRoutes($){return this.routes.get($)||[]}static setRoutes($,_){this.routes.set($,_)}static addRoute($,_){if(!this.routes.has($))this.routes.set($,[]);this.routes.get($).push(_)}static getControllerPath($){return this.controllers.get($)||""}static hasController($){return this.controllers.has($)}static setControllerPath($,_){this.controllers.set($,_)}static getControllerOptions($){return this.controllerOptions.get($)||{}}static setControllerOptions($,_){this.controllerOptions.set($,_)}static isService($){return this.services.has($)}static addService($){this.services.add($)}static getAllServices(){return this.services}static getModuleOptions($){return this.modules.get($)}static setModuleOptions($,_){this.modules.set($,_)}static getParameters($){return this.parameters.get($)||new Map}static setParameterMap($,_){this.parameters.set($,_)}static getContextIndices($){return this.contextIndices.get($)||new Map}static setContextIndices($,_){this.contextIndices.set($,_)}static registerController($,_,W){let Y=this.controller.get($);if(!Y.has(_))Y.set(_,[]);Y.get(_).push(W)}static getController($,_){return this.controller.get($).get(_)||[]}static registerHandler($,_,W){let Y=this.handler.get($);if(!Y.has(_))Y.set(_,[]);Y.get(_).push(W)}static getHandler($,_){return this.handler.get($).get(_)||[]}static clear(){this.routes.clear(),this.controllers.clear(),this.controllerOptions.clear(),this.services.clear(),this.modules.clear(),this.parameters.clear(),this.contextIndices.clear();for(let $ of this.controller.values())$.clear();for(let $ of this.handler.values())$.clear()}}class M{routes=[];registerRoute($){if(!$)throw Error("Route info is required");if(!$.controller)throw Error("Route controller is required");if(!$.handler)throw Error("Route handler is required");if(!$.method)throw Error("Route method is required");if(!$.fullPath)throw Error("Route fullPath is required");if(this.routes.some((W)=>W.fullPath===$.fullPath&&W.method.toUpperCase()===$.method.toUpperCase()))throw Error(`Duplicate route detected: ${$.method.toUpperCase()} ${$.fullPath} (${String($.controller)}.${String($.handler)})`);this.routes.push($)}getRoutes(){return this.routes}getRoutesByController($){return this.routes.filter((_)=>_.controller===$)}getRoutesByMethod($){return this.routes.filter((_)=>_.method.toUpperCase()===$.toUpperCase())}getRoutesByPath($){return this.routes.filter((_)=>$.test(_.fullPath))}clear(){this.routes.length=0}}class f{instances=new Map;resolve($){return this.resolveWithTracking($,new Set)}resolveWithTracking($,_){if(this.instances.has($))return this.instances.get($);if(_.has($))throw Error(`Circular dependency detected: ${[..._.keys(),$].map((X)=>X.name).join(" -> ")}`);_.add($);let W=Reflect.getMetadata("design:paramtypes",$)||[];if($.length>0&&W.length===0){if(!Z.isService($))throw Error(`Cannot resolve ${$.name}: it is not decorated with @Service(). Did you forget to add @Service() to the class?`);throw Error(`Cannot resolve dependencies for ${$.name}: constructor metadata is missing. Ensure 'reflect-metadata' is imported and 'emitDecoratorMetadata' is enabled.`)}let Y=W.map((X,B)=>{if(!X||X===Object||X===Array||X===Function)throw Error(`Cannot resolve dependency at index ${B} of ${$.name}. Use concrete class types for constructor dependencies.`);return this.resolveWithTracking(X,new Set(_))}),Q=new $(...Y);return this.instances.set($,Q),Q}register($,_){this.instances.set($,_)}has($){return this.instances.has($)}clear(){this.instances.clear()}}import{HTTPException as t}from"hono/http-exception";function b($,_,W){let Y=new Date().toISOString(),Q=_.get("requestId"),X=_.req.path;if($ instanceof t)return{response:{status:W?.status||$.status,message:W?.title||$.message,timestamp:Y,path:X,requestId:Q,code:W?.code,details:W?.additionalDetails,...W?.detail&&{detail:W.detail}},status:W?.status||$.status};if($.statusCode||$.status){let q=$.statusCode||$.status,z=W?.status||q;return{response:{status:z,message:W?.title||$.message,timestamp:Y,path:X,requestId:Q,code:W?.code||$.name,details:W?.additionalDetails,...W?.detail&&{detail:W.detail}},status:z}}let B=W?.status||500;return{response:{status:B,message:W?.title||$.message,timestamp:Y,path:X,requestId:Q,code:W?.code||$.name,details:W?.additionalDetails||{stack:$.stack},...W?.detail&&{detail:W.detail}},status:B}}function k($){return(_="",W={})=>{return(Y,Q,X)=>{let B=Y.constructor;Z.addRoute(B,{path:_,method:$,handlerName:Q,version:W.version,prefix:W.prefix})}}}function j($,_){let W=(Y,Q)=>{if(Y===void 0)return Q;return Q.get(Y)};return(Y)=>{return(Q,X,B)=>{let J=Q.constructor;if(!Z.getParameters(J).size)Z.setParameterMap(J,new Map);let q=Z.getParameters(J);if(!q.has(X))q.set(X,[]);let U=Reflect.getMetadata("design:paramtypes",Q,X)?.[B];if(q.get(X).push({index:B,name:$,data:Y,factory:_||W,metatype:U}),$==="context"){if(!Z.getContextIndices(J).size)Z.setContextIndices(J,new Map);Z.getContextIndices(J).set(X,B)}}}}class x{static handle(){return async($,_)=>{let{response:W,status:Y}=b($,_);return _.json(W,Y)}}}class I{static handle(){return async($)=>{return $.json({message:`Not Found - ${$.req.path}`},404)}}}var K$=($)=>typeof $>"u",T=($)=>$===null||typeof $>"u",O=($)=>$!==null&&typeof $==="object",w$=($)=>{if(!O($))return!1;let _=Object.getPrototypeOf($);if(_===null)return!0;let W=Object.prototype.hasOwnProperty.call(_,"constructor")&&_.constructor;return typeof W==="function"&&W instanceof W&&Function.prototype.toString.call(W)===Function.prototype.toString.call(Object)},p=($)=>typeof $==="function",v=($)=>typeof $==="string",R$=($)=>typeof $==="number",C$=($)=>$.length===0,M$=($)=>typeof $==="symbol",f$=($)=>typeof $==="string"?$.charAt(0)!=="/"?"/"+$:$:"",y=($)=>$?$.startsWith("/")?("/"+$.replace(/\/+$/,"")).replace(/\/+/g,"/"):"/"+$.replace(/\/+$/,""):"/",x$=($)=>$.endsWith("/")?$.slice(0,-1):$,s=($)=>{return p($)&&!T($.prototype)&&!p($.prototype)&&Object.getOwnPropertyNames($.prototype).length>=1};class g{container;globalComponents=new Map([["middleware",new Set],["guard",new Set],["pipe",new Set],["filter",new Set]]);constructor($){this.container=$}setupGlobalComponents($){let _=$.components||{};if(_.middleware)this.registerGlobal("middleware",..._.middleware);if(_.guards)this.registerGlobal("guard",..._.guards);if(_.pipes)this.registerGlobal("pipe",..._.pipes);if(_.filters)this.registerGlobal("filter",..._.filters)}registerGlobal($,..._){_.forEach((W)=>{this.globalComponents.get($).add(W)})}getGlobal($){return this.globalComponents.get($)}getComponents($,_,W){let Y=`${_.name}:${String(W)}`,Q=Z.getHandler($,Y),X=Z.getController($,_);return[...Array.from(this.globalComponents.get($)||[]),...X,...Q]}resolveMiddleware($){return $.map((_)=>{if(O(_)&&"use"in _)return _.use.bind(_);let W=this.container.resolve(_);return W.use.bind(W)})}getHandlerMiddleware($,_){let W=this.getComponents("middleware",$,_);return this.resolveMiddleware(W)}getGlobalMiddleware(){let $=Array.from(this.globalComponents.get("middleware")||[]);return this.resolveMiddleware($)}resolveGuards($){return $.map((_)=>{if(O(_)&&"canActivate"in _)return _;return this.container.resolve(_)})}getHandlerGuards($,_){let W=this.getComponents("guard",$,_);return this.resolveGuards(W)}resolvePipes($){return $.map((_)=>{if(O(_)&&"transform"in _)return _;return this.container.resolve(_)})}getHandlerPipes($,_){let W=this.getComponents("pipe",$,_);return this.resolvePipes(W)}async executePipes($,_,W){let Y=$;for(let Q of W)Y=await Q.transform(Y,_);return Y}async handleException($,_){let W=_.get("__honest_controllerClass"),Y=_.get("__honest_handlerName");if(W&&Y){let J=Z.getHandler("filter",`${W.name}:${Y}`);if(J.length>0){let q=await this.executeFilters(J,$,_);if(q)return q}}if(W){let J=Z.getController("filter",W);if(J.length>0){let q=await this.executeFilters(J,$,_);if(q)return q}}let Q=Array.from(this.globalComponents.get("filter")||[]);if(Q.length>0){let J=await this.executeFilters(Q,$,_);if(J)return J}let{response:X,status:B}=b($,_);return _.json(X,B)}async executeFilters($,_,W){for(let Y of $){let Q;if(O(Y)&&"catch"in Y)Q=Y;else Q=this.container.resolve(Y);try{let X=await Q.catch(_,W);if(X!==void 0)return X}catch(X){let B=Q.constructor?.name||"UnknownFilter";console.error(`Error in exception filter ${B}:`,X);let{response:J,status:q}=b(X,W);return W.json(J,q)}}return}async registerModule($,_=new Set){if(_.has($))return[];_.add($);let W=Z.getModuleOptions($);if(!W)throw Error(`Module ${$.name} is not properly decorated with @Module()`);let Y=[];if(W.imports&&W.imports.length>0)for(let Q of W.imports){let X=await this.registerModule(Q,_);Y.push(...X)}if(W.services&&W.services.length>0)for(let Q of W.services)this.container.resolve(Q);if(W.controllers&&W.controllers.length>0)Y.push(...W.controllers);return Y}}import{HTTPException as e}from"hono/http-exception";var h=Symbol("VERSION_NEUTRAL");class m{hono;container;routeRegistry;componentManager;globalPrefix;globalVersion;constructor($,_,W,Y,Q={}){this.hono=$,this.container=_,this.routeRegistry=W,this.componentManager=Y,this.globalPrefix=Q.prefix!==void 0?this.normalizePath(Q.prefix):void 0,this.globalVersion=Q.version,this.applyGlobalMiddleware()}applyGlobalMiddleware(){let $=this.componentManager.getGlobalMiddleware();for(let _ of $)this.hono.use("*",_)}normalizePath($){if(!v($))throw Error(`Invalid path: expected a string but received ${typeof $}. Check your @Controller() and route decorator arguments.`);return y($)}registerRouteHandler($,_,W,Y){if(W.length>0)this.hono.on($.toUpperCase(),_,...W,Y);else this.hono.on($.toUpperCase(),_,Y)}buildRoutePath($,_,W,Y){return y(`${$}${_}${W}${Y}`)}formatVersionSegment($){if(T($))return"";return $===h?"":`/v${String($)}`}async registerController($){if(!Z.hasController($))throw Error(`Controller ${$.name} is not decorated with @Controller()`);let _=Z.getControllerPath($)||"",W=Z.getControllerOptions($)||{},Y=Z.getRoutes($)||[],Q=Z.getParameters($)||new Map,X=Z.getContextIndices($)||new Map,B=this.normalizePath(_),J=this.container.resolve($),q=W.prefix!==void 0?W.prefix:this.globalPrefix,z=W.version!==void 0?W.version:this.globalVersion;if(Y.length===0)throw Error(`Controller ${$.name} has no route handlers. Add HTTP method decorators like @Get()`);for(let U of Y){let{path:E,method:V,version:H,prefix:K}=U,w=K!==void 0?K:q,D=!T(w)?this.normalizePath(w):"",A=H!==void 0?H:z,S=this.normalizePath(E);if(T(A)){this.registerRoute(J,U,Q,X,$,D,"",B,S,V);continue}if(A===h){this.registerRoute(J,U,Q,X,$,D,"",B,S,V),this.registerRoute(J,U,Q,X,$,D,"/:version{v[0-9]+}",B,S,V);continue}if(Array.isArray(A)){for(let P of A){let R=this.formatVersionSegment(P);this.registerRoute(J,U,Q,X,$,D,R,B,S,V)}continue}let F=this.formatVersionSegment(A);this.registerRoute(J,U,Q,X,$,D,F,B,S,V)}}registerRoute($,_,W,Y,Q,X,B,J,q,z){let{handlerName:U}=_,E=this.buildRoutePath(X,B,J,q),V=$[U].bind($),H=W.get(U)||[],K=Y.get(U),w=this.componentManager.getHandlerMiddleware(Q,U),D=this.componentManager.getHandlerPipes(Q,U);this.routeRegistry.registerRoute({controller:Q.name,handler:U,method:z,prefix:X,version:B,route:J,path:q,fullPath:E,parameters:H});let A=this.componentManager,S=async(F)=>{try{F.set("__honest_controllerClass",Q),F.set("__honest_handlerName",String(U));let P=A.getHandlerGuards(Q,U);for(let G of P)if(!await G.canActivate(F))throw new e(403,{message:`Forbidden by ${G.constructor?.name||"UnknownGuard"} at ${Q.name}.${String(U)}`});let R=H.length>0?Math.max(...H.map((G)=>G.index)):-1,c=Array(Math.max(V.length,R+1));for(let G of H){if(typeof G.factory!=="function")throw Error(`Invalid parameter decorator metadata for ${Q.name}.${String(U)}`);let i=G.factory(G.data,F),r=await A.executePipes(i,{type:G.name,metatype:G.metatype,data:G.data},D);c[G.index]=r}let N=await V(...c);if(K!==void 0)return N;if(N instanceof Response)return N;if(T(N))return F.json(null);if(v(N))return F.text(N);return F.json(N)}catch(P){return A.handleException(P,F)}};this.registerRouteHandler(z,E,w,S)}}class l{hono;container;context;routeRegistry;componentManager;routeManager;options;constructor($={}){if(this.options=O($)?$:{},this.hono=new $$(this.options.hono),this.container=this.options.container||new f,this.context=new C,this.routeRegistry=new M,this.componentManager=new g(this.container),this.componentManager.setupGlobalComponents(this.options),this.setupErrorHandlers(),this.routeManager=new m(this.hono,this.container,this.routeRegistry,this.componentManager,{prefix:this.options.routing?.prefix,version:this.options.routing?.version}),this.options.deprecations?.printPreV1Warning)console.warn("[HonestJS] Pre-v1 warning: APIs may change before 1.0.0.")}setupErrorHandlers(){this.hono.notFound(this.options.notFound||I.handle()),this.hono.onError(this.options.onError||x.handle())}resolvePlugin($){if(s($))return new $;return $}normalizePluginEntry($){if($&&typeof $==="object"&&"plugin"in $){let _=$;return{plugin:this.resolvePlugin(_.plugin),preProcessors:_.preProcessors??[],postProcessors:_.postProcessors??[]}}return{plugin:this.resolvePlugin($),preProcessors:[],postProcessors:[]}}async register($){let _=await this.componentManager.registerModule($);for(let W of _)await this.routeManager.registerController(W);return this}static async create($,_={}){let W=new l(_),Y=(_.plugins||[]).map((z)=>W.normalizePluginEntry(z)),Q=W.getContext(),X=_.debug,B=X===!0||typeof X==="object"&&X.plugins,J=X===!0||typeof X==="object"&&X.routes;if(B&&Y.length>0)console.info("[HonestJS] Plugin order:",Y.map(({plugin:z})=>z.constructor?.name||"AnonymousPlugin").join(" -> "));for(let{plugin:z,preProcessors:U}of Y){for(let E of U)await E(W,W.hono,Q);if(z.beforeModulesRegistered)await z.beforeModulesRegistered(W,W.hono)}await W.register($);let q=W.getRoutes();if(_.strict?.requireRoutes&&q.length===0)throw Error("Strict mode: no routes were registered. Check your module/controller decorators.");if(J)console.info("[HonestJS] Registered routes:",q.map((z)=>`${z.method.toUpperCase()} ${z.fullPath}`));for(let{plugin:z,postProcessors:U}of Y){if(z.afterModulesRegistered)await z.afterModulesRegistered(W,W.hono);for(let E of U)await E(W,W.hono,Q)}return{app:W,hono:W.getApp()}}getApp(){return this.hono}getContext(){return this.context}getRoutes(){return this.routeRegistry.getRoutes()}}import{html as L,raw as u}from"hono/html";var _$={type:"website",locale:"en_US"},d=($)=>{if(!$)return"";return Object.entries($).map(([_,W])=>{if(typeof W==="boolean")return W?_:"";let Y=String(W).replace(/"/g,"&quot;");return`${_}="${Y}"`}).filter(Boolean).join(" ")},B_=($)=>{let _={..._$,...$};return L`
1
+ import"reflect-metadata";import{Hono as $$}from"hono";class C{store=new Map;get($){return this.store.get($)}set($,_){this.store.set($,_)}has($){return this.store.has($)}delete($){return this.store.delete($)}keys(){return this.store.keys()}}class Z{static routes=new Map;static controllers=new Map;static controllerOptions=new Map;static services=new Set;static modules=new Map;static parameters=new Map;static contextIndices=new Map;static controller=new Map([["middleware",new Map],["guard",new Map],["pipe",new Map],["filter",new Map]]);static handler=new Map([["middleware",new Map],["guard",new Map],["pipe",new Map],["filter",new Map]]);static getRoutes($){return this.routes.get($)||[]}static setRoutes($,_){this.routes.set($,_)}static addRoute($,_){if(!this.routes.has($))this.routes.set($,[]);this.routes.get($).push(_)}static getControllerPath($){return this.controllers.get($)||""}static hasController($){return this.controllers.has($)}static setControllerPath($,_){this.controllers.set($,_)}static getControllerOptions($){return this.controllerOptions.get($)||{}}static setControllerOptions($,_){this.controllerOptions.set($,_)}static isService($){return this.services.has($)}static addService($){this.services.add($)}static getAllServices(){return this.services}static getModuleOptions($){return this.modules.get($)}static setModuleOptions($,_){this.modules.set($,_)}static getParameters($){return this.parameters.get($)||new Map}static setParameterMap($,_){this.parameters.set($,_)}static getContextIndices($){return this.contextIndices.get($)||new Map}static setContextIndices($,_){this.contextIndices.set($,_)}static registerController($,_,W){let Y=this.controller.get($);if(!Y.has(_))Y.set(_,[]);Y.get(_).push(W)}static getController($,_){return this.controller.get($).get(_)||[]}static registerHandler($,_,W){let Y=this.handler.get($);if(!Y.has(_))Y.set(_,[]);Y.get(_).push(W)}static getHandler($,_){return this.handler.get($).get(_)||[]}static clear(){this.routes.clear(),this.controllers.clear(),this.controllerOptions.clear(),this.services.clear(),this.modules.clear(),this.parameters.clear(),this.contextIndices.clear();for(let $ of this.controller.values())$.clear();for(let $ of this.handler.values())$.clear()}}class M{routes=[];registerRoute($){if(!$)throw Error("Route info is required");if(!$.controller)throw Error("Route controller is required");if(!$.handler)throw Error("Route handler is required");if(!$.method)throw Error("Route method is required");if(!$.fullPath)throw Error("Route fullPath is required");if(this.routes.some((W)=>W.fullPath===$.fullPath&&W.method.toUpperCase()===$.method.toUpperCase()))throw Error(`Duplicate route detected: ${$.method.toUpperCase()} ${$.fullPath} (${String($.controller)}.${String($.handler)})`);this.routes.push($)}getRoutes(){return this.routes}getRoutesByController($){return this.routes.filter((_)=>_.controller===$)}getRoutesByMethod($){return this.routes.filter((_)=>_.method.toUpperCase()===$.toUpperCase())}getRoutesByPath($){return this.routes.filter((_)=>$.test(_.fullPath))}clear(){this.routes.length=0}}class f{instances=new Map;resolve($){return this.resolveWithTracking($,new Set)}resolveWithTracking($,_){if(this.instances.has($))return this.instances.get($);if(_.has($))throw Error(`Circular dependency detected: ${[..._.keys(),$].map((X)=>X.name).join(" -> ")}`);_.add($);let W=Reflect.getMetadata("design:paramtypes",$)||[];if($.length>0&&W.length===0){if(!Z.isService($))throw Error(`Cannot resolve ${$.name}: it is not decorated with @Service(). Did you forget to add @Service() to the class?`);throw Error(`Cannot resolve dependencies for ${$.name}: constructor metadata is missing. Ensure 'reflect-metadata' is imported and 'emitDecoratorMetadata' is enabled.`)}let Y=W.map((X,B)=>{if(!X||X===Object||X===Array||X===Function)throw Error(`Cannot resolve dependency at index ${B} of ${$.name}. Use concrete class types for constructor dependencies.`);return this.resolveWithTracking(X,new Set(_))}),Q=new $(...Y);return this.instances.set($,Q),Q}register($,_){this.instances.set($,_)}has($){return this.instances.has($)}clear(){this.instances.clear()}}import{HTTPException as t}from"hono/http-exception";function b($,_,W){let Y=new Date().toISOString(),Q=_.get("requestId"),X=_.req.path;if($ instanceof t)return{response:{status:W?.status||$.status,message:W?.title||$.message,timestamp:Y,path:X,requestId:Q,code:W?.code,details:W?.additionalDetails,...W?.detail&&{detail:W.detail}},status:W?.status||$.status};if($.statusCode||$.status){let q=$.statusCode||$.status,z=W?.status||q;return{response:{status:z,message:W?.title||$.message,timestamp:Y,path:X,requestId:Q,code:W?.code||$.name,details:W?.additionalDetails,...W?.detail&&{detail:W.detail}},status:z}}let B=W?.status||500;return{response:{status:B,message:W?.title||$.message,timestamp:Y,path:X,requestId:Q,code:W?.code||$.name,details:W?.additionalDetails||{stack:$.stack},...W?.detail&&{detail:W.detail}},status:B}}function k($){return(_="",W={})=>{return(Y,Q,X)=>{let B=Y.constructor;Z.addRoute(B,{path:_,method:$,handlerName:Q,version:W.version,prefix:W.prefix})}}}function j($,_){let W=(Y,Q)=>{if(Y===void 0)return Q;return Q.get(Y)};return(Y)=>{return(Q,X,B)=>{let J=Q.constructor;if(!Z.getParameters(J).size)Z.setParameterMap(J,new Map);let q=Z.getParameters(J);if(!q.has(X))q.set(X,[]);let U=Reflect.getMetadata("design:paramtypes",Q,X)?.[B];if(q.get(X).push({index:B,name:$,data:Y,factory:_||W,metatype:U}),$==="context"){if(!Z.getContextIndices(J).size)Z.setContextIndices(J,new Map);Z.getContextIndices(J).set(X,B)}}}}class x{static handle(){return async($,_)=>{let{response:W,status:Y}=b($,_);return _.json(W,Y)}}}class I{static handle(){return async($)=>{return $.json({message:`Not Found - ${$.req.path}`},404)}}}var K$=($)=>typeof $>"u",T=($)=>$===null||typeof $>"u",O=($)=>$!==null&&typeof $==="object",w$=($)=>{if(!O($))return!1;let _=Object.getPrototypeOf($);if(_===null)return!0;let W=Object.prototype.hasOwnProperty.call(_,"constructor")&&_.constructor;return typeof W==="function"&&W instanceof W&&Function.prototype.toString.call(W)===Function.prototype.toString.call(Object)},p=($)=>typeof $==="function",v=($)=>typeof $==="string",R$=($)=>typeof $==="number",C$=($)=>$.length===0,M$=($)=>typeof $==="symbol",f$=($)=>typeof $==="string"?$.charAt(0)!=="/"?"/"+$:$:"",y=($)=>$?$.startsWith("/")?("/"+$.replace(/\/+$/,"")).replace(/\/+/g,"/"):"/"+$.replace(/\/+$/,""):"/",x$=($)=>$.endsWith("/")?$.slice(0,-1):$,s=($)=>{return p($)&&!T($.prototype)&&!p($.prototype)&&Object.getOwnPropertyNames($.prototype).length>=1};class g{container;globalComponents=new Map([["middleware",new Set],["guard",new Set],["pipe",new Set],["filter",new Set]]);constructor($){this.container=$}setupGlobalComponents($){let _=$.components||{};if(_.middleware)this.registerGlobal("middleware",..._.middleware);if(_.guards)this.registerGlobal("guard",..._.guards);if(_.pipes)this.registerGlobal("pipe",..._.pipes);if(_.filters)this.registerGlobal("filter",..._.filters)}registerGlobal($,..._){_.forEach((W)=>{this.globalComponents.get($).add(W)})}getGlobal($){return this.globalComponents.get($)}getComponents($,_,W){let Y=`${_.name}:${String(W)}`,Q=Z.getHandler($,Y),X=Z.getController($,_);return[...Array.from(this.globalComponents.get($)||[]),...X,...Q]}resolveMiddleware($){return $.map((_)=>{if(O(_)&&"use"in _)return _.use.bind(_);let W=this.container.resolve(_);return W.use.bind(W)})}getHandlerMiddleware($,_){let W=this.getComponents("middleware",$,_);return this.resolveMiddleware(W)}getGlobalMiddleware(){let $=Array.from(this.globalComponents.get("middleware")||[]);return this.resolveMiddleware($)}resolveGuards($){return $.map((_)=>{if(O(_)&&"canActivate"in _)return _;return this.container.resolve(_)})}getHandlerGuards($,_){let W=this.getComponents("guard",$,_);return this.resolveGuards(W)}resolvePipes($){return $.map((_)=>{if(O(_)&&"transform"in _)return _;return this.container.resolve(_)})}getHandlerPipes($,_){let W=this.getComponents("pipe",$,_);return this.resolvePipes(W)}async executePipes($,_,W){let Y=$;for(let Q of W)Y=await Q.transform(Y,_);return Y}async handleException($,_){let W=_.get("__honest_controllerClass"),Y=_.get("__honest_handlerName");if(W&&Y){let J=Z.getHandler("filter",`${W.name}:${Y}`);if(J.length>0){let q=await this.executeFilters(J,$,_);if(q)return q}}if(W){let J=Z.getController("filter",W);if(J.length>0){let q=await this.executeFilters(J,$,_);if(q)return q}}let Q=Array.from(this.globalComponents.get("filter")||[]);if(Q.length>0){let J=await this.executeFilters(Q,$,_);if(J)return J}let{response:X,status:B}=b($,_);return _.json(X,B)}async executeFilters($,_,W){for(let Y of $){let Q;if(O(Y)&&"catch"in Y)Q=Y;else Q=this.container.resolve(Y);try{let X=await Q.catch(_,W);if(X!==void 0)return X}catch(X){let B=Q.constructor?.name||"UnknownFilter";console.error(`Error in exception filter ${B}:`,X);let{response:J,status:q}=b(X,W);return W.json(J,q)}}return}async registerModule($,_=new Set){if(_.has($))return[];_.add($);let W=Z.getModuleOptions($);if(!W)throw Error(`Module ${$.name} is not properly decorated with @Module()`);let Y=[];if(W.imports&&W.imports.length>0)for(let Q of W.imports){let X=await this.registerModule(Q,_);Y.push(...X)}if(W.services&&W.services.length>0)for(let Q of W.services)this.container.resolve(Q);if(W.controllers&&W.controllers.length>0)Y.push(...W.controllers);return Y}}import{HTTPException as e}from"hono/http-exception";var h=Symbol("VERSION_NEUTRAL");class m{hono;container;routeRegistry;componentManager;globalPrefix;globalVersion;constructor($,_,W,Y,Q={}){this.hono=$,this.container=_,this.routeRegistry=W,this.componentManager=Y,this.globalPrefix=Q.prefix!==void 0?this.normalizePath(Q.prefix):void 0,this.globalVersion=Q.version,this.applyGlobalMiddleware()}applyGlobalMiddleware(){let $=this.componentManager.getGlobalMiddleware();for(let _ of $)this.hono.use("*",_)}normalizePath($){if(!v($))throw Error(`Invalid path: expected a string but received ${typeof $}. Check your @Controller() and route decorator arguments.`);return y($)}registerRouteHandler($,_,W,Y){if(W.length>0)this.hono.on($.toUpperCase(),[_],...W,Y);else this.hono.on($.toUpperCase(),[_],Y)}buildRoutePath($,_,W,Y){return y(`${$}${_}${W}${Y}`)}formatVersionSegment($){if(T($))return"";return $===h?"":`/v${String($)}`}async registerController($){if(!Z.hasController($))throw Error(`Controller ${$.name} is not decorated with @Controller()`);let _=Z.getControllerPath($)||"",W=Z.getControllerOptions($)||{},Y=Z.getRoutes($)||[],Q=Z.getParameters($)||new Map,X=Z.getContextIndices($)||new Map,B=this.normalizePath(_),J=this.container.resolve($),q=W.prefix!==void 0?W.prefix:this.globalPrefix,z=W.version!==void 0?W.version:this.globalVersion;if(Y.length===0)throw Error(`Controller ${$.name} has no route handlers. Add HTTP method decorators like @Get()`);for(let U of Y){let{path:E,method:V,version:H,prefix:K}=U,w=K!==void 0?K:q,D=!T(w)?this.normalizePath(w):"",A=H!==void 0?H:z,S=this.normalizePath(E);if(T(A)){this.registerRoute(J,U,Q,X,$,D,"",B,S,V);continue}if(A===h){this.registerRoute(J,U,Q,X,$,D,"",B,S,V),this.registerRoute(J,U,Q,X,$,D,"/:version{v[0-9]+}",B,S,V);continue}if(Array.isArray(A)){for(let P of A){let R=this.formatVersionSegment(P);this.registerRoute(J,U,Q,X,$,D,R,B,S,V)}continue}let F=this.formatVersionSegment(A);this.registerRoute(J,U,Q,X,$,D,F,B,S,V)}}registerRoute($,_,W,Y,Q,X,B,J,q,z){let{handlerName:U}=_,E=this.buildRoutePath(X,B,J,q),V=$[U].bind($),H=W.get(U)||[],K=Y.get(U),w=this.componentManager.getHandlerMiddleware(Q,U),D=this.componentManager.getHandlerPipes(Q,U);this.routeRegistry.registerRoute({controller:Q.name,handler:U,method:z,prefix:X,version:B,route:J,path:q,fullPath:E,parameters:H});let A=this.componentManager,S=async(F)=>{try{F.set("__honest_controllerClass",Q),F.set("__honest_handlerName",String(U));let P=A.getHandlerGuards(Q,U);for(let G of P)if(!await G.canActivate(F))throw new e(403,{message:`Forbidden by ${G.constructor?.name||"UnknownGuard"} at ${Q.name}.${String(U)}`});let R=H.length>0?Math.max(...H.map((G)=>G.index)):-1,c=Array(Math.max(V.length,R+1));for(let G of H){if(typeof G.factory!=="function")throw Error(`Invalid parameter decorator metadata for ${Q.name}.${String(U)}`);let i=G.factory(G.data,F),r=await A.executePipes(i,{type:G.name,metatype:G.metatype,data:G.data},D);c[G.index]=r}let N=await V(...c);if(K!==void 0)return N;if(N instanceof Response)return N;if(T(N))return F.json(null);if(v(N))return F.text(N);return F.json(N)}catch(P){return A.handleException(P,F)}};this.registerRouteHandler(z,E,w,S)}}class l{hono;container;context;routeRegistry;componentManager;routeManager;options;constructor($={}){if(this.options=O($)?$:{},this.hono=new $$(this.options.hono),this.container=this.options.container||new f,this.context=new C,this.routeRegistry=new M,this.componentManager=new g(this.container),this.componentManager.setupGlobalComponents(this.options),this.setupErrorHandlers(),this.routeManager=new m(this.hono,this.container,this.routeRegistry,this.componentManager,{prefix:this.options.routing?.prefix,version:this.options.routing?.version}),this.options.deprecations?.printPreV1Warning)console.warn("[HonestJS] Pre-v1 warning: APIs may change before 1.0.0.")}setupErrorHandlers(){this.hono.notFound(this.options.notFound||I.handle()),this.hono.onError(this.options.onError||x.handle())}resolvePlugin($){if(s($))return new $;return $}normalizePluginEntry($){if($&&typeof $==="object"&&"plugin"in $){let _=$;return{plugin:this.resolvePlugin(_.plugin),preProcessors:_.preProcessors??[],postProcessors:_.postProcessors??[]}}return{plugin:this.resolvePlugin($),preProcessors:[],postProcessors:[]}}async register($){let _=await this.componentManager.registerModule($);for(let W of _)await this.routeManager.registerController(W);return this}static async create($,_={}){let W=new l(_),Y=(_.plugins||[]).map((z)=>W.normalizePluginEntry(z)),Q=W.getContext(),X=_.debug,B=X===!0||typeof X==="object"&&X.plugins,J=X===!0||typeof X==="object"&&X.routes;if(B&&Y.length>0)console.info("[HonestJS] Plugin order:",Y.map(({plugin:z})=>z.constructor?.name||"AnonymousPlugin").join(" -> "));for(let{plugin:z,preProcessors:U}of Y){for(let E of U)await E(W,W.hono,Q);if(z.beforeModulesRegistered)await z.beforeModulesRegistered(W,W.hono)}await W.register($);let q=W.getRoutes();if(_.strict?.requireRoutes&&q.length===0)throw Error("Strict mode: no routes were registered. Check your module/controller decorators.");if(J)console.info("[HonestJS] Registered routes:",q.map((z)=>`${z.method.toUpperCase()} ${z.fullPath}`));for(let{plugin:z,postProcessors:U}of Y){if(z.afterModulesRegistered)await z.afterModulesRegistered(W,W.hono);for(let E of U)await E(W,W.hono,Q)}return{app:W,hono:W.getApp()}}getApp(){return this.hono}getContainer(){return this.container}getContext(){return this.context}getRoutes(){return this.routeRegistry.getRoutes()}}import{html as L,raw as u}from"hono/html";var _$={type:"website",locale:"en_US"},d=($)=>{if(!$)return"";return Object.entries($).map(([_,W])=>{if(typeof W==="boolean")return W?_:"";let Y=String(W).replace(/"/g,"&quot;");return`${_}="${Y}"`}).filter(Boolean).join(" ")},B_=($)=>{let _={..._$,...$};return L`
2
2
  <!DOCTYPE html>
3
3
  <html lang="${_.locale?.split("_")[0]||"en"}" ${u(d(_.htmlAttributes))}>
4
4
  <head ${u(d(_.headAttributes))}>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "honestjs",
3
3
  "description": "HonestJS - a modern web framework built on top of Hono",
4
- "version": "0.1.13",
4
+ "version": "0.1.15",
5
5
  "author": "Orkhan Karimov <karimovok1@gmail.com> (https://github.com/kerimovok)",
6
6
  "repository": {
7
7
  "type": "git",
@@ -12,19 +12,19 @@
12
12
  "type": "module",
13
13
  "types": "dist/index.d.ts",
14
14
  "devDependencies": {
15
- "@eslint/js": "^9.39.4",
15
+ "@eslint/js": "^10.0.1",
16
16
  "@types/bun": "^1.3.10",
17
- "eslint": "^9.39.4",
17
+ "eslint": "^10.0.3",
18
18
  "eslint-config-prettier": "^10.1.8",
19
- "globals": "^16.5.0",
19
+ "globals": "^17.4.0",
20
20
  "husky": "^9.1.7",
21
21
  "lint-staged": "^16.4.0",
22
- "prettier": "3.6.2",
22
+ "prettier": "3.8.1",
23
23
  "typescript-eslint": "^8.57.0"
24
24
  },
25
25
  "peerDependencies": {
26
- "hono": "^4.8.4",
27
- "typescript": "^5.8.3",
26
+ "hono": "^4.12.8",
27
+ "typescript": "^5.9.3",
28
28
  "reflect-metadata": "^0.2.2"
29
29
  },
30
30
  "files": [