honestjs 0.1.16 โ 0.1.18
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 +101 -1
- package/dist/application.d.ts +6 -0
- package/dist/application.di-harness.test.d.ts +1 -0
- package/dist/application.diagnostics.test.d.ts +1 -0
- package/dist/application.isolation.test.d.ts +1 -0
- package/dist/application.plugins.test.d.ts +1 -0
- package/dist/application.regressions.test.d.ts +1 -0
- package/dist/application.routing-errors.test.d.ts +1 -0
- package/dist/application.runtime-metadata.test.d.ts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -5
- package/dist/interfaces/honest-options.interface.d.ts +8 -0
- package/dist/interfaces/plugin.interface.d.ts +34 -0
- package/dist/registries/metadata.registry.d.ts +7 -1
- package/dist/testing/create-controller-test-application.d.ts +5 -0
- package/dist/testing/create-service-test-container.d.ts +5 -0
- package/dist/testing/create-test-application.d.ts +5 -0
- package/dist/testing/create-test-application.test.d.ts +1 -0
- package/dist/testing/create-testing-module.d.ts +6 -0
- package/dist/testing/fixtures/application-test-fixtures.d.ts +17 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/testing.interface.d.ts +96 -0
- package/package.json +2 -1
- /package/dist/{application.test.d.ts โ application.bootstrap.test.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -93,8 +93,11 @@ for full docs.
|
|
|
93
93
|
- **๐๏ธ Familiar architecture** โ Decorator-based API inspired by NestJS; TypeScript-first.
|
|
94
94
|
- **๐ Dependency injection** โ Built-in DI container for clean, testable code and automatic wiring.
|
|
95
95
|
- **๐ Plugin system** โ Extend the app with custom plugins, middleware, pipes, and filters.
|
|
96
|
+
- **๐งฉ Plugin contracts** โ Deterministic plugin ordering and startup capability validation.
|
|
96
97
|
- **๐ฃ๏ธ Advanced routing** โ Prefixes, API versioning, and nested route organization.
|
|
97
98
|
- **๐ก๏ธ Request pipeline** โ Middleware, guards, pipes, and filters at app, controller, or handler level.
|
|
99
|
+
- **๐งช Lightweight testing harness** โ Helpers for application, controller, and service-level tests.
|
|
100
|
+
- **๐งญ Startup guide mode** โ Actionable diagnostics hints for startup failures.
|
|
98
101
|
- **๐ TypeScript-first** โ Strong typing and great IDE support out of the box.
|
|
99
102
|
- **๐ฅ๏ธ MVC & SSR** โ Full-stack apps with Hono JSX views; use the `mvc` template or the docs.
|
|
100
103
|
|
|
@@ -133,6 +136,7 @@ class AppController {
|
|
|
133
136
|
class AppModule {}
|
|
134
137
|
|
|
135
138
|
const { app, hono } = await Application.create(AppModule, {
|
|
139
|
+
startupGuide: { verbose: true },
|
|
136
140
|
debug: {
|
|
137
141
|
routes: true,
|
|
138
142
|
plugins: true,
|
|
@@ -159,7 +163,17 @@ const { app, hono } = await Application.create(AppModule, {
|
|
|
159
163
|
pipes: [ValidationPipe],
|
|
160
164
|
filters: [HttpExceptionFilter]
|
|
161
165
|
},
|
|
162
|
-
plugins: [
|
|
166
|
+
plugins: [
|
|
167
|
+
new RPCPlugin(),
|
|
168
|
+
new ApiDocsPlugin(),
|
|
169
|
+
{
|
|
170
|
+
plugin: MyPlugin,
|
|
171
|
+
name: 'core',
|
|
172
|
+
preProcessors: [pre],
|
|
173
|
+
postProcessors: [post]
|
|
174
|
+
},
|
|
175
|
+
{ plugin: MetricsPlugin, name: 'metrics', after: ['core'] }
|
|
176
|
+
],
|
|
163
177
|
onError: (err, c) => c.json({ error: err.message }, 500),
|
|
164
178
|
notFound: (c) => c.json({ error: 'Not found' }, 404)
|
|
165
179
|
})
|
|
@@ -176,6 +190,92 @@ Decorator metadata is still collected globally, but each application instance no
|
|
|
176
190
|
captured during startup. This prevents metadata mutations made after bootstrap from changing behavior in already-running
|
|
177
191
|
applications.
|
|
178
192
|
|
|
193
|
+
## Plugin ordering and capability contracts
|
|
194
|
+
|
|
195
|
+
Use named plugin entries when order matters and optionally declare startup capability contracts via `meta`.
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
class ArtifactPlugin {
|
|
199
|
+
meta = {
|
|
200
|
+
name: 'artifact',
|
|
201
|
+
provides: ['artifact:routes']
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
class DocsPlugin {
|
|
206
|
+
meta = {
|
|
207
|
+
name: 'docs',
|
|
208
|
+
requires: ['artifact:routes']
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await Application.create(AppModule, {
|
|
213
|
+
plugins: [
|
|
214
|
+
{ plugin: new DocsPlugin(), name: 'docs', after: ['artifact'] },
|
|
215
|
+
{ plugin: new ArtifactPlugin(), name: 'artifact' }
|
|
216
|
+
]
|
|
217
|
+
})
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
If constraints are invalid (missing dependency, cycle, or missing required capability), startup fails fast.
|
|
221
|
+
|
|
222
|
+
## Testing harness
|
|
223
|
+
|
|
224
|
+
Honest exports lightweight helpers for common test setups.
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { createControllerTestApplication, createServiceTestContainer, createTestApplication } from 'honestjs'
|
|
228
|
+
|
|
229
|
+
const app = await createTestApplication({
|
|
230
|
+
controllers: [UsersController],
|
|
231
|
+
services: [UsersService]
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
const response = await app.request('/users')
|
|
235
|
+
|
|
236
|
+
const controllerApp = await createControllerTestApplication({
|
|
237
|
+
controller: UsersController
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
const services = createServiceTestContainer({
|
|
241
|
+
preload: [UsersService],
|
|
242
|
+
overrides: [{ provide: UsersService, useValue: mockUsersService }]
|
|
243
|
+
})
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Running tests in this package
|
|
247
|
+
|
|
248
|
+
This repo uses [Bun's test runner](https://bun.sh/docs/cli/test). From the package root:
|
|
249
|
+
|
|
250
|
+
- `bun test` โ run all tests once
|
|
251
|
+
- `bun test --watch` โ watch mode
|
|
252
|
+
- `bun test <pattern>` โ limit to matching file or test names (for example `bun test application.bootstrap`)
|
|
253
|
+
- `bun run test:coverage` โ same suite with coverage (summary in the terminal and `coverage/lcov.info`)
|
|
254
|
+
|
|
255
|
+
Co-locate tests as `*.test.ts` next to sources. Import `reflect-metadata` first in any file that loads decorated
|
|
256
|
+
classes, same as in application code.
|
|
257
|
+
|
|
258
|
+
Integration-style cases use `*.integration.test.ts` where the whole `Application` stack is exercised (for example the
|
|
259
|
+
request pipeline). Shared HTTP fixtures for application tests live under `src/testing/fixtures/` as **factory
|
|
260
|
+
functions** so each test gets fresh decorator metadata after `MetadataRegistry.clear()` in `afterEach`.
|
|
261
|
+
|
|
262
|
+
## Startup diagnostics guide mode
|
|
263
|
+
|
|
264
|
+
Enable startup guidance to get actionable remediation hints when initialization fails.
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
await Application.create(AppModule, {
|
|
268
|
+
startupGuide: true
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
await Application.create(AppModule, {
|
|
272
|
+
startupGuide: { verbose: true }
|
|
273
|
+
})
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Guide mode emits startup diagnostics hints for common issues such as missing decorators, strict no-routes startup,
|
|
277
|
+
metadata issues, and plugin ordering/capability errors.
|
|
278
|
+
|
|
179
279
|
## License
|
|
180
280
|
|
|
181
281
|
MIT ยฉ [Orkhan Karimov](https://github.com/kerimovok)
|
package/dist/application.d.ts
CHANGED
|
@@ -18,11 +18,17 @@ export declare class Application {
|
|
|
18
18
|
private readonly routeManager;
|
|
19
19
|
private readonly diagnosticsEmitter;
|
|
20
20
|
private readonly options;
|
|
21
|
+
private static readonly DEFAULT_PLUGIN_NAME;
|
|
21
22
|
constructor(options?: HonestOptions, metadataRepository?: IMetadataRepository);
|
|
22
23
|
private setupErrorHandlers;
|
|
23
24
|
private resolvePlugin;
|
|
24
25
|
private normalizePluginEntry;
|
|
26
|
+
private resolvePluginName;
|
|
27
|
+
private resolvePluginExecutionOrder;
|
|
28
|
+
private validatePluginCapabilities;
|
|
25
29
|
private shouldEmitRouteDiagnostics;
|
|
30
|
+
private emitStartupGuide;
|
|
31
|
+
private createStartupGuideHints;
|
|
26
32
|
register(moduleClass: Constructor): Promise<Application>;
|
|
27
33
|
static create(rootModule: Constructor, options?: HonestOptions): Promise<{
|
|
28
34
|
app: Application;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import"reflect-metadata";import{Hono as U$}from"hono";class x{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 v{emit($){let _=`[HonestJS:${$.category}]`,W=$.details?[_,$.message,$.details]:[_,$.message];switch($.level){case"debug":case"info":console.info(...W);break;case"warn":console.warn(...W);break;case"error":console.error(...W);break}}}class A{emit($){}}class Y{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 Q=this.controller.get($);if(!Q.has(_))Q.set(_,[]);Q.get(_).push(W)}static getController($,_){return this.controller.get($).get(_)||[]}static registerHandler($,_,W){let Q=this.handler.get($);if(!Q.has(_))Q.set(_,[]);Q.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 O{hasController($){return Y.hasController($)}getControllerPath($){return Y.getControllerPath($)}getControllerOptions($){return Y.getControllerOptions($)}getRoutes($){return Y.getRoutes($)}getParameters($){return Y.getParameters($)}getContextIndices($){return Y.getContextIndices($)}getModuleOptions($){return Y.getModuleOptions($)}getControllerComponents($,_){return Y.getController($,_)}getHandlerComponents($,_){return Y.getHandler($,_)}}class E{controllerPaths=new Map;controllerOptions=new Map;routes=new Map;parameters=new Map;contextIndices=new Map;modules=new Map;controllerComponents=new Map([["middleware",new Map],["guard",new Map],["pipe",new Map],["filter",new Map]]);handlerComponents=new Map([["middleware",new Map],["guard",new Map],["pipe",new Map],["filter",new Map]]);static fromRootModule($,_=new O){let W=new E;return W.captureModuleGraph($,_),W}hasController($){return this.controllerPaths.has($)}getControllerPath($){return this.controllerPaths.get($)||""}getControllerOptions($){let _=this.controllerOptions.get($);return _?{..._}:{}}getRoutes($){return(this.routes.get($)||[]).map((_)=>this.cloneRouteDefinition(_))}getParameters($){let _=this.parameters.get($);if(!_)return new Map;let W=new Map;for(let[Q,X]of _.entries())W.set(Q,X.map((Z)=>({...Z})));return W}getContextIndices($){return new Map(this.contextIndices.get($)||new Map)}getModuleOptions($){let _=this.modules.get($);if(!_)return;return{controllers:_.controllers?[..._.controllers]:void 0,services:_.services?[..._.services]:void 0,imports:_.imports?[..._.imports]:void 0}}getControllerComponents($,_){return[...this.controllerComponents.get($).get(_)||[]]}getHandlerComponents($,_){return[...this.handlerComponents.get($).get(_)||[]]}captureModuleGraph($,_){let W=new Set,Q=new Set,X=(Z)=>{if(W.has(Z))return;W.add(Z);let J=_.getModuleOptions(Z);if(!J)return;let U={controllers:J.controllers?[...J.controllers]:void 0,services:J.services?[...J.services]:void 0,imports:J.imports?[...J.imports]:void 0};this.modules.set(Z,U);for(let q of U.controllers||[])Q.add(q);for(let q of U.imports||[])X(q)};X($);for(let Z of Q)this.captureController(Z,_)}captureController($,_){if(!_.hasController($))return;this.controllerPaths.set($,_.getControllerPath($)||""),this.controllerOptions.set($,{..._.getControllerOptions($)});let W=(_.getRoutes($)||[]).map((Z)=>this.cloneRouteDefinition(Z));this.routes.set($,W);let Q=_.getParameters($),X=new Map;for(let[Z,J]of Q.entries())X.set(Z,(J||[]).map((U)=>({...U})));this.parameters.set($,X),this.contextIndices.set($,new Map(_.getContextIndices($)||new Map));for(let Z of["middleware","guard","pipe","filter"])this.controllerComponents.get(Z).set($,[..._.getControllerComponents(Z,$)||[]]);for(let Z of W){let J=`${$.name}:${String(Z.handlerName)}`;for(let U of["middleware","guard","pipe","filter"])this.handlerComponents.get(U).set(J,[..._.getHandlerComponents(U,J)||[]])}}cloneRouteDefinition($){return{...$,version:Array.isArray($.version)?[...$.version]:$.version}}}class y{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 h{isService($){return Y.isService($)}}class g{serviceRegistry;diagnosticsEmitter;debugDi;constructor($=new h,_=new A,W=!1){this.serviceRegistry=$;this.diagnosticsEmitter=_;this.debugDi=W}instances=new Map;emitDiagnostic($){if(!this.debugDi)return;this.diagnosticsEmitter.emit($)}resolve($){return this.resolveWithTracking($,new Set)}resolveWithTracking($,_){if(this.instances.has($))return this.emitDiagnostic({level:"debug",category:"di",message:`Resolved ${$.name} from DI cache`}),this.instances.get($);if(_.has($)){let Z=[..._.keys(),$].map((J)=>J.name).join(" -> ");throw this.emitDiagnostic({level:"error",category:"di",message:`Circular dependency detected while resolving ${$.name}`,details:{cycle:Z}}),Error(`Circular dependency detected: ${Z}`)}_.add($),this.emitDiagnostic({level:"debug",category:"di",message:`Resolving ${$.name}`,details:{resolving:[..._].map((Z)=>Z.name)}});let W=Reflect.getMetadata("design:paramtypes",$)||[];if($.length>0&&W.length===0){if(!this.serviceRegistry.isService($))throw this.emitDiagnostic({level:"error",category:"di",message:`Cannot resolve ${$.name}: missing @Service() decorator`}),Error(`Cannot resolve ${$.name}: it is not decorated with @Service(). Did you forget to add @Service() to the class?`);throw this.emitDiagnostic({level:"error",category:"di",message:`Cannot resolve ${$.name}: missing constructor metadata`}),Error(`Cannot resolve dependencies for ${$.name}: constructor metadata is missing. Ensure 'reflect-metadata' is imported and 'emitDecoratorMetadata' is enabled.`)}let Q=W.map((Z,J)=>{if(!Z||Z===Object||Z===Array||Z===Function)throw this.emitDiagnostic({level:"error",category:"di",message:`Cannot resolve dependency at index ${J} of ${$.name}`}),Error(`Cannot resolve dependency at index ${J} of ${$.name}. Use concrete class types for constructor dependencies.`);return this.resolveWithTracking(Z,new Set(_))}),X=new $(...Q);return this.instances.set($,X),this.emitDiagnostic({level:"debug",category:"di",message:`Created ${$.name} instance`,details:{dependencyCount:Q.length}}),X}register($,_){this.instances.set($,_)}has($){return this.instances.has($)}clear(){this.instances.clear()}}import{HTTPException as Z$}from"hono/http-exception";class m extends Error{status;code;category;remediation;details;constructor($,_){super($);if(this.name="FrameworkError",this.status=_.status,this.code=_.code,this.category=_.category,this.remediation=_.remediation,this.details=_.details,_.cause!==void 0)this.cause=_.cause}}function P($,_,W){let Q=$ instanceof Error?$:Error(String($)),X=new Date().toISOString(),Z=_.get("requestId"),J=_.req.path;if(Q instanceof Z$)return{response:{status:W?.status||Q.status,message:W?.title||Q.message,timestamp:X,path:J,requestId:Z,code:W?.code,details:W?.additionalDetails,...W?.detail&&{detail:W.detail}},status:W?.status||Q.status};if(Q instanceof m){let G=W?.status||Q.status||500;return{response:{status:G,message:W?.title||Q.message,timestamp:X,path:J,requestId:Z,code:W?.code||Q.code,details:{category:Q.category,remediation:Q.remediation,...Q.details,...W?.additionalDetails},...W?.detail&&{detail:W.detail}},status:G}}if(Q.statusCode||Q.status){let G=Q.statusCode||Q.status||500,B=W?.status||G;return{response:{status:B,message:W?.title||Q.message,timestamp:X,path:J,requestId:Z,code:W?.code||Q.name,details:W?.additionalDetails,...W?.detail&&{detail:W.detail}},status:B}}let U=W?.status||500;return{response:{status:U,message:W?.title||Q.message,timestamp:X,path:J,requestId:Z,code:W?.code||Q.name,details:W?.additionalDetails||{stack:Q.stack},...W?.detail&&{detail:W.detail}},status:U}}function D($){return(_="",W={})=>{return(Q,X,Z)=>{let J=Q.constructor;Y.addRoute(J,{path:_,method:$,handlerName:X,version:W.version,prefix:W.prefix})}}}function F($,_){let W=(Q,X)=>{if(Q===void 0)return X;return X.get(String(Q))};return(Q)=>{return(X,Z,J)=>{let U=X.constructor;if(!Y.getParameters(U).size)Y.setParameterMap(U,new Map);let q=Y.getParameters(U);if(!q.has(Z))q.set(Z,[]);let B=Reflect.getMetadata("design:paramtypes",X,Z)?.[J];if(q.get(Z).push({index:J,name:$,data:Q,factory:_||W,metatype:B}),$==="context"){if(!Y.getContextIndices(U).size)Y.setContextIndices(U,new Map);Y.getContextIndices(U).set(Z,J)}}}}class u{static handle(){return async($,_)=>{let{response:W,status:Q}=P($,_);return _.json(W,Q)}}}class d{static handle(){return async($)=>{return $.json({message:`Not Found - ${$.req.path}`},404)}}}var c=Symbol("VERSION_NEUTRAL");var R="__honest_controllerClass",C="__honest_handlerName",p="__honest.body.cache";var W_=($)=>typeof $>"u",S=($)=>$===null||typeof $>"u",T=($)=>$!==null&&typeof $==="object",Q_=($)=>{if(!T($))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)},e=($)=>typeof $==="function",M=($)=>typeof $==="string",X_=($)=>typeof $==="number",Z_=($)=>$.length===0,J_=($)=>typeof $==="symbol",U_=($)=>typeof $==="string"?$.charAt(0)!=="/"?"/"+$:$:"",i=($)=>$?$.startsWith("/")?("/"+$.replace(/\/+$/,"")).replace(/\/+/g,"/"):"/"+$.replace(/\/+$/,""):"/",Y_=($)=>$.endsWith("/")?$.slice(0,-1):$,$$=($)=>{return e($)&&!S($.prototype)&&!e($.prototype)&&Object.getOwnPropertyNames($.prototype).length>=1};class s{container;metadataRepository;diagnosticsEmitter;globalComponents=new Map([["middleware",new Set],["guard",new Set],["pipe",new Set],["filter",new Set]]);constructor($,_=new O,W=new A){this.container=$;this.metadataRepository=_;this.diagnosticsEmitter=W}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 Q=`${_.name}:${String(W)}`,X=this.metadataRepository.getHandlerComponents($,Q),Z=this.metadataRepository.getControllerComponents($,_);return[...Array.from(this.globalComponents.get($)||[]),...Z,...X]}resolveMiddleware($){return $.map((_)=>{if(T(_)&&"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(T(_)&&"canActivate"in _)return _;return this.container.resolve(_)})}getHandlerGuards($,_){let W=this.getComponents("guard",$,_);return this.resolveGuards(W)}resolvePipes($){return $.map((_)=>{if(T(_)&&"transform"in _)return _;return this.container.resolve(_)})}getHandlerPipes($,_){let W=this.getComponents("pipe",$,_);return this.resolvePipes(W)}async executePipes($,_,W){let Q=$;for(let X of W)Q=await X.transform(Q,_);return Q}async handleException($,_){let W=_.get(R),Q=_.get(C);if(W&&Q){let U=this.metadataRepository.getHandlerComponents("filter",`${W.name}:${Q}`);if(U.length>0){let q=await this.executeFilters(U,$,_);if(q)return q}}if(W){let U=this.metadataRepository.getControllerComponents("filter",W);if(U.length>0){let q=await this.executeFilters(U,$,_);if(q)return q}}let X=Array.from(this.globalComponents.get("filter")||[]);if(X.length>0){let U=await this.executeFilters(X,$,_);if(U)return U}let{response:Z,status:J}=P($,_);return _.json(Z,J)}async executeFilters($,_,W){for(let Q of $){let X;if(T(Q)&&"catch"in Q)X=Q;else X=this.container.resolve(Q);try{let Z=await X.catch(_,W);if(Z!==void 0)return Z}catch(Z){let J=X.constructor?.name||"UnknownFilter";this.diagnosticsEmitter.emit({level:"error",category:"errors",message:`Error in exception filter ${J}`,details:{error:Z instanceof Error?Z.message:String(Z)}});let{response:U,status:q}=P(Z,W);return W.json(U,q)}}return}async registerModule($,_=new Set){if(_.has($))return[];_.add($);let W=this.metadataRepository.getModuleOptions($);if(!W)throw this.diagnosticsEmitter.emit({level:"error",category:"startup",message:`Module ${$.name} is not properly decorated with @Module()`}),Error(`Module ${$.name} is not properly decorated with @Module()`);let Q=[];if(W.imports&&W.imports.length>0)for(let X of W.imports){let Z=await this.registerModule(X,_);Q.push(...Z)}if(W.services&&W.services.length>0)for(let X of W.services)this.container.resolve(X);if(W.controllers&&W.controllers.length>0)Q.push(...W.controllers);return Q}}class l{async invoke({handler:$,args:_,context:W,contextIndex:Q}){let X=await $(..._);if(Q!==void 0)return X;if(X instanceof Response)return X;if(S(X))return W.json(null);if(M(X))return W.text(X);return W.json(X)}}class n{componentManager;constructor($){this.componentManager=$}async resolveArguments($){let{controllerName:_,handlerName:W,handlerArity:Q,handlerParams:X,handlerPipes:Z,context:J}=$,U=X.length>0?Math.max(...X.map((G)=>G.index)):-1,q=Array(Math.max(Q,U+1));for(let G of X){if(typeof G.factory!=="function")throw Error(`Invalid parameter decorator metadata for ${_}.${String(W)}`);let B=await G.factory(G.data,J),z=await this.componentManager.executePipes(B,{type:G.name,metatype:G.metatype,data:typeof G.data==="string"||typeof G.data>"u"?G.data:String(G.data)},Z);q[G.index]=z}return q}}import{HTTPException as J$}from"hono/http-exception";class a{componentManager;parameterResolver;handlerInvoker;diagnosticsEmitter;debugPipeline;constructor($,_,W,Q=new A,X=!1){this.componentManager=$;this.parameterResolver=_;this.handlerInvoker=W;this.diagnosticsEmitter=Q;this.debugPipeline=X}async execute($){let{controllerClass:_,handlerName:W,handler:Q,handlerParams:X,handlerPipes:Z,contextIndex:J,context:U}=$;U.set(R,_),U.set(C,String(W));let q=this.componentManager.getHandlerGuards(_,W);for(let B of q)if(!await B.canActivate(U)){if(this.debugPipeline)this.diagnosticsEmitter.emit({level:"warn",category:"pipeline",message:`Guard rejected request at ${_.name}.${String(W)}`,details:{guard:B.constructor?.name||"UnknownGuard"}});throw new J$(403,{message:`Forbidden by ${B.constructor?.name||"UnknownGuard"} at ${_.name}.${String(W)}`})}let G=await this.parameterResolver.resolveArguments({controllerName:_.name,handlerName:W,handlerArity:Q.length,handlerParams:X,handlerPipes:Z,context:U});if(this.debugPipeline)this.diagnosticsEmitter.emit({level:"debug",category:"pipeline",message:`Resolved handler arguments for ${_.name}.${String(W)}`,details:{guardCount:q.length,parameterCount:X.length,pipeCount:Z.length}});return this.handlerInvoker.invoke({handler:Q,args:G,context:U,contextIndex:J})}}class r{hono;container;routeRegistry;componentManager;parameterResolver;pipelineExecutor;metadataRepository;diagnosticsEmitter;globalPrefix;globalVersion;constructor($,_,W,Q,X=new O,Z=new A,J={}){this.hono=$,this.container=_,this.routeRegistry=W,this.componentManager=Q,this.parameterResolver=new n(this.componentManager),this.diagnosticsEmitter=Z,this.pipelineExecutor=new a(this.componentManager,this.parameterResolver,new l,this.diagnosticsEmitter,Boolean(J.debugPipeline)),this.metadataRepository=X,this.globalPrefix=J.prefix!==void 0?this.normalizePath(J.prefix):void 0,this.globalVersion=J.version,this.applyGlobalMiddleware()}applyGlobalMiddleware(){let $=this.componentManager.getGlobalMiddleware();for(let _ of $)this.hono.use("*",_)}normalizePath($){if(!M($))throw Error(`Invalid path: expected a string but received ${typeof $}. Check your @Controller() and route decorator arguments.`);return i($)}registerRouteHandler($,_,W,Q){if(W.length>0)this.hono.on($.toUpperCase(),[_],...W,Q);else this.hono.on($.toUpperCase(),[_],Q)}buildRoutePath($,_,W,Q){return i(`${$}${_}${W}${Q}`)}formatVersionSegment($){if(S($))return"";return $===c?"":`/v${String($)}`}async registerController($){if(!this.metadataRepository.hasController($))throw Error(`Controller ${$.name} is not decorated with @Controller()`);let _=this.metadataRepository.getControllerPath($)||"",W=this.metadataRepository.getControllerOptions($)||{},Q=this.metadataRepository.getRoutes($)||[],X=this.metadataRepository.getParameters($)||new Map,Z=this.metadataRepository.getContextIndices($)||new Map,J=this.normalizePath(_),U=this.container.resolve($),q=W.prefix!==void 0?W.prefix:this.globalPrefix,G=W.version!==void 0?W.version:this.globalVersion;if(Q.length===0)throw Error(`Controller ${$.name} has no route handlers. Add HTTP method decorators like @Get()`);for(let B of Q){let{path:z,method:L,version:V,prefix:H}=B,k=H!==void 0?H:q,N=!S(k)?this.normalizePath(k):"",K=V!==void 0?V:G,w=this.normalizePath(z);if(S(K)){this.registerRoute(U,B,X,Z,$,N,"",J,w,L);continue}if(K===c){this.registerRoute(U,B,X,Z,$,N,"",J,w,L),this.registerRoute(U,B,X,Z,$,N,"/:version{v[0-9]+}",J,w,L);continue}if(Array.isArray(K)){for(let b of K){let f=this.formatVersionSegment(b);this.registerRoute(U,B,X,Z,$,N,f,J,w,L)}continue}let I=this.formatVersionSegment(K);this.registerRoute(U,B,X,Z,$,N,I,J,w,L)}}registerRoute($,_,W,Q,X,Z,J,U,q,G){let{handlerName:B}=_,z=this.buildRoutePath(Z,J,U,q),L=$[B].bind($),V=W.get(B)||[],H=Q.get(B),k=this.componentManager.getHandlerMiddleware(X,B),N=this.componentManager.getHandlerPipes(X,B);this.routeRegistry.registerRoute({controller:X.name,handler:B,method:G,prefix:Z,version:J,route:U,path:q,fullPath:z,parameters:V});let K=this.componentManager,w=this.pipelineExecutor,I=async(b)=>{try{return await w.execute({controllerClass:X,handlerName:B,handler:L,handlerParams:V,handlerPipes:N,contextIndex:H,context:b})}catch(f){return K.handleException(f,b)}};this.registerRouteHandler(G,z,k,I)}}class _${hono;container;context;routeRegistry;metadataRepository;componentManager;routeManager;diagnosticsEmitter;options;constructor($={},_=new O){this.options=T($)?$:{};let W=this.options.debug===!0||typeof this.options.debug==="object"&&Boolean(this.options.debug.pipeline),Q=this.options.debug===!0||typeof this.options.debug==="object"&&Boolean(this.options.debug.di);if(this.hono=new U$(this.options.hono),this.diagnosticsEmitter=this.options.diagnostics||new v,this.container=this.options.container||new g(void 0,this.diagnosticsEmitter,Q),this.context=new x,this.routeRegistry=new y,this.metadataRepository=_,this.componentManager=new s(this.container,this.metadataRepository,this.diagnosticsEmitter),this.componentManager.setupGlobalComponents(this.options),this.setupErrorHandlers(),this.routeManager=new r(this.hono,this.container,this.routeRegistry,this.componentManager,this.metadataRepository,this.diagnosticsEmitter,{prefix:this.options.routing?.prefix,version:this.options.routing?.version,debugPipeline:W}),this.options.deprecations?.printPreV1Warning)this.diagnosticsEmitter.emit({level:"warn",category:"deprecations",message:"Pre-v1 warning: APIs may change before 1.0.0."})}setupErrorHandlers(){this.hono.notFound(this.options.notFound||d.handle()),this.hono.onError(this.options.onError||u.handle())}resolvePlugin($){if($$($))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:[]}}shouldEmitRouteDiagnostics(){let $=this.options.debug;return $===!0||typeof $==="object"&&Boolean($.routes)}async register($){let _=await this.componentManager.registerModule($),W=this.shouldEmitRouteDiagnostics();for(let Q of _){let X=Date.now(),Z=this.routeRegistry.getRoutes().length;try{if(await this.routeManager.registerController(Q),W)this.diagnosticsEmitter.emit({level:"info",category:"routes",message:"Registered controller routes",details:{controller:Q.name,routeCountAdded:this.routeRegistry.getRoutes().length-Z,registrationDurationMs:Date.now()-X}})}catch(J){if(W)this.diagnosticsEmitter.emit({level:"error",category:"routes",message:"Failed to register controller routes",details:{controller:Q.name,registrationDurationMs:Date.now()-X,errorMessage:J instanceof Error?J.message:String(J)}});throw J}}return this}static async create($,_={}){let W=Date.now(),Q=E.fromRootModule($),X=new _$(_,Q),Z=(_.plugins||[]).map((L)=>X.normalizePluginEntry(L)),J=X.getContext(),U=_.debug,q=U===!0||typeof U==="object"&&U.plugins,G=U===!0||typeof U==="object"&&U.routes,B=U===!0||typeof U==="object"&&(U.startup||G),z=!1;try{if(q&&Z.length>0)X.diagnosticsEmitter.emit({level:"info",category:"plugins",message:`Plugin order: ${Z.map(({plugin:V})=>V.constructor?.name||"AnonymousPlugin").join(" -> ")}`});for(let{plugin:V,preProcessors:H}of Z){for(let k of H)await k(X,X.hono,J);if(V.beforeModulesRegistered)await V.beforeModulesRegistered(X,X.hono)}await X.register($);let L=X.getRoutes();if(B)X.diagnosticsEmitter.emit({level:"info",category:"startup",message:`Application registered ${L.length} route(s)`,details:{routeCount:L.length,rootModule:$.name}});if(_.strict?.requireRoutes&&L.length===0)throw z=!0,X.diagnosticsEmitter.emit({level:"error",category:"startup",message:"Strict mode failed: no routes were registered",details:{rootModule:$.name,requireRoutes:!0,startupDurationMs:Date.now()-W}}),Error("Strict mode: no routes were registered. Check your module/controller decorators.");if(G)X.diagnosticsEmitter.emit({level:"info",category:"routes",message:"Registered routes",details:{routes:L.map((V)=>`${V.method.toUpperCase()} ${V.fullPath}`)}});for(let{plugin:V,postProcessors:H}of Z){if(V.afterModulesRegistered)await V.afterModulesRegistered(X,X.hono);for(let k of H)await k(X,X.hono,J)}if(B)X.diagnosticsEmitter.emit({level:"info",category:"startup",message:"Application startup completed",details:{rootModule:$.name,pluginCount:Z.length,routeCount:L.length,startupDurationMs:Date.now()-W}});return{app:X,hono:X.getApp()}}catch(L){if(B&&!z)X.diagnosticsEmitter.emit({level:"error",category:"startup",message:"Application startup failed",details:{rootModule:$.name,startupDurationMs:Date.now()-W,errorMessage:L instanceof Error?L.message:String(L)}});throw L}}getApp(){return this.hono}getContainer(){return this.container}getContext(){return this.context}getRoutes(){return this.routeRegistry.getRoutes()}}import{html as j,raw as o}from"hono/html";var Y$={type:"website",locale:"en_US"},t=($)=>{if(!$)return"";return Object.entries($).map(([_,W])=>{if(typeof W==="boolean")return W?_:"";let Q=String(W).replace(/"/g,""");return`${_}="${Q}"`}).filter(Boolean).join(" ")},a_=($)=>{let _={...Y$,...$};return j`
|
|
1
|
+
import"reflect-metadata";import{Hono as B$}from"hono";class h{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 g{emit($){let _=`[HonestJS:${$.category}]`,W=$.details?[_,$.message,$.details]:[_,$.message];switch($.level){case"debug":case"info":console.info(...W);break;case"warn":console.warn(...W);break;case"error":console.error(...W);break}}}class H{emit($){}}class L{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 Q=this.controller.get($);if(!Q.has(_))Q.set(_,[]);Q.get(_).push(W)}static getController($,_){return this.controller.get($).get(_)||[]}static registerHandler($,_,W){let Q=this.handler.get($);if(!Q.has(_))Q.set(_,[]);Q.get(_).push(W)}static getHandler($,_){return this.handler.get($).get(_)||[]}static clearHandlerComponents(){for(let $ of this.handler.values())$.clear()}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 D{hasController($){return L.hasController($)}getControllerPath($){return L.getControllerPath($)}getControllerOptions($){return L.getControllerOptions($)}getRoutes($){return L.getRoutes($)}getParameters($){return L.getParameters($)}getContextIndices($){return L.getContextIndices($)}getModuleOptions($){return L.getModuleOptions($)}getControllerComponents($,_){return L.getController($,_)}getHandlerComponents($,_){return L.getHandler($,_)}}class C{controllerPaths=new Map;controllerOptions=new Map;routes=new Map;parameters=new Map;contextIndices=new Map;modules=new Map;controllerComponents=new Map([["middleware",new Map],["guard",new Map],["pipe",new Map],["filter",new Map]]);handlerComponents=new Map([["middleware",new Map],["guard",new Map],["pipe",new Map],["filter",new Map]]);static fromRootModule($,_=new D){let W=new C;return W.captureModuleGraph($,_),W}hasController($){return this.controllerPaths.has($)}getControllerPath($){return this.controllerPaths.get($)||""}getControllerOptions($){let _=this.controllerOptions.get($);return _?{..._}:{}}getRoutes($){return(this.routes.get($)||[]).map((_)=>this.cloneRouteDefinition(_))}getParameters($){let _=this.parameters.get($);if(!_)return new Map;let W=new Map;for(let[Q,X]of _.entries())W.set(Q,X.map((Z)=>({...Z})));return W}getContextIndices($){return new Map(this.contextIndices.get($)||new Map)}getModuleOptions($){let _=this.modules.get($);if(!_)return;return{controllers:_.controllers?[..._.controllers]:void 0,services:_.services?[..._.services]:void 0,imports:_.imports?[..._.imports]:void 0}}getControllerComponents($,_){return[...this.controllerComponents.get($).get(_)||[]]}getHandlerComponents($,_){return[...this.handlerComponents.get($).get(_)||[]]}captureModuleGraph($,_){let W=new Set,Q=new Set,X=(Z)=>{if(W.has(Z))return;W.add(Z);let U=_.getModuleOptions(Z);if(!U)return;let J={controllers:U.controllers?[...U.controllers]:void 0,services:U.services?[...U.services]:void 0,imports:U.imports?[...U.imports]:void 0};this.modules.set(Z,J);for(let Y of J.controllers||[])Q.add(Y);for(let Y of J.imports||[])X(Y)};X($);for(let Z of Q)this.captureController(Z,_)}captureController($,_){if(!_.hasController($))return;this.controllerPaths.set($,_.getControllerPath($)||""),this.controllerOptions.set($,{..._.getControllerOptions($)});let W=(_.getRoutes($)||[]).map((Z)=>this.cloneRouteDefinition(Z));this.routes.set($,W);let Q=_.getParameters($),X=new Map;for(let[Z,U]of Q.entries())X.set(Z,(U||[]).map((J)=>({...J})));this.parameters.set($,X),this.contextIndices.set($,new Map(_.getContextIndices($)||new Map));for(let Z of["middleware","guard","pipe","filter"])this.controllerComponents.get(Z).set($,[..._.getControllerComponents(Z,$)||[]]);for(let Z of W){let U=`${$.name}:${String(Z.handlerName)}`;for(let J of["middleware","guard","pipe","filter"])this.handlerComponents.get(J).set(U,[..._.getHandlerComponents(J,U)||[]])}}cloneRouteDefinition($){return{...$,version:Array.isArray($.version)?[...$.version]:$.version}}}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 u{isService($){return L.isService($)}}class E{serviceRegistry;diagnosticsEmitter;debugDi;constructor($=new u,_=new H,W=!1){this.serviceRegistry=$;this.diagnosticsEmitter=_;this.debugDi=W}instances=new Map;emitDiagnostic($){if(!this.debugDi)return;this.diagnosticsEmitter.emit($)}resolve($){return this.resolveWithTracking($,new Set)}resolveWithTracking($,_){if(this.instances.has($))return this.emitDiagnostic({level:"debug",category:"di",message:`Resolved ${$.name} from DI cache`}),this.instances.get($);if(_.has($)){let Z=[..._.keys(),$].map((U)=>U.name).join(" -> ");throw this.emitDiagnostic({level:"error",category:"di",message:`Circular dependency detected while resolving ${$.name}`,details:{cycle:Z}}),Error(`Circular dependency detected: ${Z}`)}_.add($),this.emitDiagnostic({level:"debug",category:"di",message:`Resolving ${$.name}`,details:{resolving:[..._].map((Z)=>Z.name)}});let W=Reflect.getMetadata("design:paramtypes",$)||[];if($.length>0&&W.length===0){if(!this.serviceRegistry.isService($))throw this.emitDiagnostic({level:"error",category:"di",message:`Cannot resolve ${$.name}: missing @Service() decorator`}),Error(`Cannot resolve ${$.name}: it is not decorated with @Service(). Did you forget to add @Service() to the class?`);throw this.emitDiagnostic({level:"error",category:"di",message:`Cannot resolve ${$.name}: missing constructor metadata`}),Error(`Cannot resolve dependencies for ${$.name}: constructor metadata is missing. Ensure 'reflect-metadata' is imported and 'emitDecoratorMetadata' is enabled.`)}let Q=W.map((Z,U)=>{if(!Z||Z===Object||Z===Array||Z===Function)throw this.emitDiagnostic({level:"error",category:"di",message:`Cannot resolve dependency at index ${U} of ${$.name}`}),Error(`Cannot resolve dependency at index ${U} of ${$.name}. Use concrete class types for constructor dependencies.`);return this.resolveWithTracking(Z,new Set(_))}),X=new $(...Q);return this.instances.set($,X),this.emitDiagnostic({level:"debug",category:"di",message:`Created ${$.name} instance`,details:{dependencyCount:Q.length}}),X}register($,_){this.instances.set($,_)}has($){return this.instances.has($)}clear(){this.instances.clear()}}import{HTTPException as U$}from"hono/http-exception";class d extends Error{status;code;category;remediation;details;constructor($,_){super($);if(this.name="FrameworkError",this.status=_.status,this.code=_.code,this.category=_.category,this.remediation=_.remediation,this.details=_.details,_.cause!==void 0)this.cause=_.cause}}function R($,_,W){let Q=$ instanceof Error?$:Error(String($)),X=new Date().toISOString(),Z=_.get("requestId"),U=_.req.path;if(Q instanceof U$)return{response:{status:W?.status||Q.status,message:W?.title||Q.message,timestamp:X,path:U,requestId:Z,code:W?.code,details:W?.additionalDetails,...W?.detail&&{detail:W.detail}},status:W?.status||Q.status};if(Q instanceof d){let B=W?.status||Q.status||500;return{response:{status:B,message:W?.title||Q.message,timestamp:X,path:U,requestId:Z,code:W?.code||Q.code,details:{category:Q.category,remediation:Q.remediation,...Q.details,...W?.additionalDetails},...W?.detail&&{detail:W.detail}},status:B}}if(Q.statusCode||Q.status){let B=Q.statusCode||Q.status||500,q=W?.status||B;return{response:{status:q,message:W?.title||Q.message,timestamp:X,path:U,requestId:Z,code:W?.code||Q.name,details:W?.additionalDetails,...W?.detail&&{detail:W.detail}},status:q}}let J=W?.status||500;return{response:{status:J,message:W?.title||Q.message,timestamp:X,path:U,requestId:Z,code:W?.code||Q.name,details:W?.additionalDetails||{stack:Q.stack},...W?.detail&&{detail:W.detail}},status:J}}function T($){return(_="",W={})=>{return(Q,X,Z)=>{let U=Q.constructor;L.addRoute(U,{path:_,method:$,handlerName:X,version:W.version,prefix:W.prefix})}}}function z($,_){let W=(Q,X)=>{if(Q===void 0)return X;return X.get(String(Q))};return(Q)=>{return(X,Z,U)=>{let J=X.constructor;if(!L.getParameters(J).size)L.setParameterMap(J,new Map);let Y=L.getParameters(J);if(!Y.has(Z))Y.set(Z,[]);let q=Reflect.getMetadata("design:paramtypes",X,Z)?.[U];if(Y.get(Z).push({index:U,name:$,data:Q,factory:_||W,metatype:q}),$==="context"){if(!L.getContextIndices(J).size)L.setContextIndices(J,new Map);L.getContextIndices(J).set(Z,U)}}}}class c{static handle(){return async($,_)=>{let{response:W,status:Q}=R($,_);return _.json(W,Q)}}}class p{static handle(){return async($)=>{return $.json({message:`Not Found - ${$.req.path}`},404)}}}var i=Symbol("VERSION_NEUTRAL");var I="__honest_controllerClass",M="__honest_handlerName",l="__honest.body.cache";var X_=($)=>typeof $>"u",S=($)=>$===null||typeof $>"u",K=($)=>$!==null&&typeof $==="object",Z_=($)=>{if(!K($))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)},_$=($)=>typeof $==="function",f=($)=>typeof $==="string",J_=($)=>typeof $==="number",U_=($)=>$.length===0,Y_=($)=>typeof $==="symbol",B_=($)=>typeof $==="string"?$.charAt(0)!=="/"?"/"+$:$:"",s=($)=>$?$.startsWith("/")?("/"+$.replace(/\/+$/,"")).replace(/\/+/g,"/"):"/"+$.replace(/\/+$/,""):"/",L_=($)=>$.endsWith("/")?$.slice(0,-1):$,W$=($)=>{return _$($)&&!S($.prototype)&&!_$($.prototype)&&Object.getOwnPropertyNames($.prototype).length>=1};class n{container;metadataRepository;diagnosticsEmitter;globalComponents=new Map([["middleware",new Set],["guard",new Set],["pipe",new Set],["filter",new Set]]);constructor($,_=new D,W=new H){this.container=$;this.metadataRepository=_;this.diagnosticsEmitter=W}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 Q=`${_.name}:${String(W)}`,X=this.metadataRepository.getHandlerComponents($,Q),Z=this.metadataRepository.getControllerComponents($,_);return[...Array.from(this.globalComponents.get($)||[]),...Z,...X]}resolveMiddleware($){return $.map((_)=>{if(K(_)&&"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(K(_)&&"canActivate"in _)return _;return this.container.resolve(_)})}getHandlerGuards($,_){let W=this.getComponents("guard",$,_);return this.resolveGuards(W)}resolvePipes($){return $.map((_)=>{if(K(_)&&"transform"in _)return _;return this.container.resolve(_)})}getHandlerPipes($,_){let W=this.getComponents("pipe",$,_);return this.resolvePipes(W)}async executePipes($,_,W){let Q=$;for(let X of W)Q=await X.transform(Q,_);return Q}async handleException($,_){let W=_.get(I),Q=_.get(M);if(W&&Q){let J=this.metadataRepository.getHandlerComponents("filter",`${W.name}:${Q}`);if(J.length>0){let Y=await this.executeFilters(J,$,_);if(Y)return Y}}if(W){let J=this.metadataRepository.getControllerComponents("filter",W);if(J.length>0){let Y=await this.executeFilters(J,$,_);if(Y)return Y}}let X=Array.from(this.globalComponents.get("filter")||[]);if(X.length>0){let J=await this.executeFilters(X,$,_);if(J)return J}let{response:Z,status:U}=R($,_);return _.json(Z,U)}async executeFilters($,_,W){for(let Q of $){let X;if(K(Q)&&"catch"in Q)X=Q;else X=this.container.resolve(Q);try{let Z=await X.catch(_,W);if(Z!==void 0)return Z}catch(Z){let U=X.constructor?.name||"UnknownFilter";this.diagnosticsEmitter.emit({level:"error",category:"errors",message:`Error in exception filter ${U}`,details:{error:Z instanceof Error?Z.message:String(Z)}});let{response:J,status:Y}=R(Z,W);return W.json(J,Y)}}return}async registerModule($,_=new Set){if(_.has($))return[];_.add($);let W=this.metadataRepository.getModuleOptions($);if(!W)throw this.diagnosticsEmitter.emit({level:"error",category:"startup",message:`Module ${$.name} is not properly decorated with @Module()`}),Error(`Module ${$.name} is not properly decorated with @Module()`);let Q=[];if(W.imports&&W.imports.length>0)for(let X of W.imports){let Z=await this.registerModule(X,_);Q.push(...Z)}if(W.services&&W.services.length>0)for(let X of W.services)this.container.resolve(X);if(W.controllers&&W.controllers.length>0)Q.push(...W.controllers);return Q}}class a{async invoke({handler:$,args:_,context:W,contextIndex:Q}){let X=await $(..._);if(Q!==void 0)return X;if(X instanceof Response)return X;if(S(X))return W.json(null);if(f(X))return W.text(X);return W.json(X)}}class o{componentManager;constructor($){this.componentManager=$}async resolveArguments($){let{controllerName:_,handlerName:W,handlerArity:Q,handlerParams:X,handlerPipes:Z,context:U}=$,J=X.length>0?Math.max(...X.map((B)=>B.index)):-1,Y=Array(Math.max(Q,J+1));for(let B of X){if(typeof B.factory!=="function")throw Error(`Invalid parameter decorator metadata for ${_}.${String(W)}`);let q=await B.factory(B.data,U),F=await this.componentManager.executePipes(q,{type:B.name,metatype:B.metatype,data:typeof B.data==="string"||typeof B.data>"u"?B.data:String(B.data)},Z);Y[B.index]=F}return Y}}import{HTTPException as Y$}from"hono/http-exception";class r{componentManager;parameterResolver;handlerInvoker;diagnosticsEmitter;debugPipeline;constructor($,_,W,Q=new H,X=!1){this.componentManager=$;this.parameterResolver=_;this.handlerInvoker=W;this.diagnosticsEmitter=Q;this.debugPipeline=X}async execute($){let{controllerClass:_,handlerName:W,handler:Q,handlerParams:X,handlerPipes:Z,contextIndex:U,context:J}=$;J.set(I,_),J.set(M,String(W));let Y=this.componentManager.getHandlerGuards(_,W);for(let q of Y)if(!await q.canActivate(J)){if(this.debugPipeline)this.diagnosticsEmitter.emit({level:"warn",category:"pipeline",message:`Guard rejected request at ${_.name}.${String(W)}`,details:{guard:q.constructor?.name||"UnknownGuard"}});throw new Y$(403,{message:`Forbidden by ${q.constructor?.name||"UnknownGuard"} at ${_.name}.${String(W)}`})}let B=await this.parameterResolver.resolveArguments({controllerName:_.name,handlerName:W,handlerArity:Q.length,handlerParams:X,handlerPipes:Z,context:J});if(this.debugPipeline)this.diagnosticsEmitter.emit({level:"debug",category:"pipeline",message:`Resolved handler arguments for ${_.name}.${String(W)}`,details:{guardCount:Y.length,parameterCount:X.length,pipeCount:Z.length}});return this.handlerInvoker.invoke({handler:Q,args:B,context:J,contextIndex:U})}}class t{hono;container;routeRegistry;componentManager;parameterResolver;pipelineExecutor;metadataRepository;diagnosticsEmitter;globalPrefix;globalVersion;constructor($,_,W,Q,X=new D,Z=new H,U={}){this.hono=$,this.container=_,this.routeRegistry=W,this.componentManager=Q,this.parameterResolver=new o(this.componentManager),this.diagnosticsEmitter=Z,this.pipelineExecutor=new r(this.componentManager,this.parameterResolver,new a,this.diagnosticsEmitter,Boolean(U.debugPipeline)),this.metadataRepository=X,this.globalPrefix=U.prefix!==void 0?this.normalizePath(U.prefix):void 0,this.globalVersion=U.version,this.applyGlobalMiddleware()}applyGlobalMiddleware(){let $=this.componentManager.getGlobalMiddleware();for(let _ of $)this.hono.use("*",_)}normalizePath($){if(!f($))throw Error(`Invalid path: expected a string but received ${typeof $}. Check your @Controller() and route decorator arguments.`);return s($)}registerRouteHandler($,_,W,Q){if(W.length>0)this.hono.on($.toUpperCase(),[_],...W,Q);else this.hono.on($.toUpperCase(),[_],Q)}buildRoutePath($,_,W,Q){return s(`${$}${_}${W}${Q}`)}formatVersionSegment($){if(S($))return"";return $===i?"":`/v${String($)}`}async registerController($){if(!this.metadataRepository.hasController($))throw Error(`Controller ${$.name} is not decorated with @Controller()`);let _=this.metadataRepository.getControllerPath($)||"",W=this.metadataRepository.getControllerOptions($)||{},Q=this.metadataRepository.getRoutes($)||[],X=this.metadataRepository.getParameters($)||new Map,Z=this.metadataRepository.getContextIndices($)||new Map,U=this.normalizePath(_),J=this.container.resolve($),Y=W.prefix!==void 0?W.prefix:this.globalPrefix,B=W.version!==void 0?W.version:this.globalVersion;if(Q.length===0)throw Error(`Controller ${$.name} has no route handlers. Add HTTP method decorators like @Get()`);for(let q of Q){let{path:F,method:A,version:V,prefix:G}=q,O=G!==void 0?G:Y,k=!S(O)?this.normalizePath(O):"",N=V!==void 0?V:B,P=this.normalizePath(F);if(S(N)){this.registerRoute(J,q,X,Z,$,k,"",U,P,A);continue}if(N===i){this.registerRoute(J,q,X,Z,$,k,"",U,P,A),this.registerRoute(J,q,X,Z,$,k,"/:version{v[0-9]+}",U,P,A);continue}if(Array.isArray(N)){for(let b of N){let y=this.formatVersionSegment(b);this.registerRoute(J,q,X,Z,$,k,y,U,P,A)}continue}let v=this.formatVersionSegment(N);this.registerRoute(J,q,X,Z,$,k,v,U,P,A)}}registerRoute($,_,W,Q,X,Z,U,J,Y,B){let{handlerName:q}=_,F=this.buildRoutePath(Z,U,J,Y),A=$[q].bind($),V=W.get(q)||[],G=Q.get(q),O=this.componentManager.getHandlerMiddleware(X,q),k=this.componentManager.getHandlerPipes(X,q);this.routeRegistry.registerRoute({controller:X.name,handler:q,method:B,prefix:Z,version:U,route:J,path:Y,fullPath:F,parameters:V});let N=this.componentManager,P=this.pipelineExecutor,v=async(b)=>{try{return await P.execute({controllerClass:X,handlerName:q,handler:A,handlerParams:V,handlerPipes:k,contextIndex:G,context:b})}catch(y){return N.handleException(y,b)}};this.registerRouteHandler(B,F,O,v)}}class w{hono;container;context;routeRegistry;metadataRepository;componentManager;routeManager;diagnosticsEmitter;options;static DEFAULT_PLUGIN_NAME="AnonymousPlugin";constructor($={},_=new D){this.options=K($)?$:{};let W=this.options.debug===!0||typeof this.options.debug==="object"&&Boolean(this.options.debug.pipeline),Q=this.options.debug===!0||typeof this.options.debug==="object"&&Boolean(this.options.debug.di);if(this.hono=new B$(this.options.hono),this.diagnosticsEmitter=this.options.diagnostics||new g,this.container=this.options.container||new E(void 0,this.diagnosticsEmitter,Q),this.context=new h,this.routeRegistry=new m,this.metadataRepository=_,this.componentManager=new n(this.container,this.metadataRepository,this.diagnosticsEmitter),this.componentManager.setupGlobalComponents(this.options),this.setupErrorHandlers(),this.routeManager=new t(this.hono,this.container,this.routeRegistry,this.componentManager,this.metadataRepository,this.diagnosticsEmitter,{prefix:this.options.routing?.prefix,version:this.options.routing?.version,debugPipeline:W}),this.options.deprecations?.printPreV1Warning)this.diagnosticsEmitter.emit({level:"warn",category:"deprecations",message:"Pre-v1 warning: APIs may change before 1.0.0."})}setupErrorHandlers(){this.hono.notFound(this.options.notFound||p.handle()),this.hono.onError(this.options.onError||c.handle())}resolvePlugin($){if(W$($))return new $;return $}normalizePluginEntry($,_){if($&&typeof $==="object"&&"plugin"in $){let Q=$,X=this.resolvePlugin(Q.plugin),Z=this.resolvePluginName(X,_,Q.name);return{plugin:X,name:Z,before:Q.before??[],after:Q.after??[],provides:X.meta?.provides??[],requires:X.meta?.requires??[],index:_,preProcessors:Q.preProcessors??[],postProcessors:Q.postProcessors??[]}}let W=this.resolvePlugin($);return{plugin:W,name:this.resolvePluginName(W,_),before:[],after:[],provides:W.meta?.provides??[],requires:W.meta?.requires??[],index:_,preProcessors:[],postProcessors:[]}}resolvePluginName($,_,W){let Q=W||$.meta?.name||$.constructor?.name;if(!Q||Q===w.DEFAULT_PLUGIN_NAME)return`${w.DEFAULT_PLUGIN_NAME}#${_+1}`;return Q}resolvePluginExecutionOrder($){if($.length===0)return $;let _=new Map;for(let J=0;J<$.length;J++){let Y=$[J];if(_.has(Y.name))throw Error(`Duplicate plugin name detected: ${Y.name}. Use unique plugin names in options.plugins.`);_.set(Y.name,J)}let W=Array($.length).fill(0),Q=new Map,X=(J,Y)=>{if(!Q.has(J))Q.set(J,new Set);let B=Q.get(J);if(!B.has(Y))B.add(Y),W[Y]++};for(let J=0;J<$.length;J++){let Y=$[J];for(let B of Y.after){let q=_.get(B);if(q===void 0)throw Error(`Plugin ordering error: ${Y.name} declares after '${B}', but no such plugin is registered.`);X(q,J)}for(let B of Y.before){let q=_.get(B);if(q===void 0)throw Error(`Plugin ordering error: ${Y.name} declares before '${B}', but no such plugin is registered.`);X(J,q)}}let Z=[];for(let J=0;J<$.length;J++)if(W[J]===0)Z.push(J);Z.sort((J,Y)=>$[J].index-$[Y].index);let U=[];while(Z.length>0){let J=Z.shift();U.push(J);let Y=Q.get(J);if(!Y)continue;for(let B of Y)if(W[B]--,W[B]===0)Z.push(B);Z.sort((B,q)=>$[B].index-$[q].index)}if(U.length!==$.length)throw Error("Plugin ordering cycle detected. Check before/after constraints in options.plugins.");return U.map((J)=>$[J])}validatePluginCapabilities($){if($.length===0)return;let _=new Set;for(let W of $){for(let Q of W.requires)if(!_.has(Q))throw Error(`Plugin capability error: ${W.name} requires '${Q}', but it was not provided by any previous plugin. Use before/after ordering or register the provider plugin earlier.`);for(let Q of W.provides)_.add(Q)}}shouldEmitRouteDiagnostics(){let $=this.options.debug;return $===!0||typeof $==="object"&&Boolean($.routes)}emitStartupGuide($,_){let W=this.options.startupGuide;if(!W)return;let Q=typeof W==="object"&&Boolean(W.verbose),X=$ instanceof Error?$.message:String($),Z=this.createStartupGuideHints(X);if(this.diagnosticsEmitter.emit({level:"warn",category:"startup",message:"Startup guide",details:{rootModule:_.name,errorMessage:X,hints:Z,verbose:Q}}),Q)this.diagnosticsEmitter.emit({level:"warn",category:"startup",message:"Startup guide (verbose)",details:{steps:["Verify decorators are present for controllers/services used by DI and routing.","Ensure 'reflect-metadata' is imported once at entry and 'emitDecoratorMetadata' is enabled.","Enable debug.startup for extra startup diagnostics and timing details."]}})}createStartupGuideHints($){let _=new Set;if(_.add("Check module wiring: root module imports, controllers, and services should be registered correctly."),$.includes("not decorated with @Controller()"))_.add("Add @Controller() to the class or remove it from module.controllers.");if($.includes("has no route handlers"))_.add("Add at least one HTTP method decorator such as @Get() or @Post() in the controller.");if($.includes("not decorated with @Service()"))_.add("Add @Service() to injectable classes used in constructor dependencies.");if($.includes("constructor metadata is missing")||$.includes("reflect-metadata"))_.add("Import 'reflect-metadata' in your entry file and enable 'emitDecoratorMetadata' in tsconfig.");if($.includes("Strict mode: no routes were registered"))_.add("Disable strict.requireRoutes for empty modules, or add a controller with at least one route.");if($.includes("Plugin ordering error")||$.includes("Plugin capability error"))_.add("Check plugin order and before/after constraints, then ensure required capabilities are provided earlier.");return[..._]}async register($){let _=await this.componentManager.registerModule($),W=this.shouldEmitRouteDiagnostics();for(let Q of _){let X=Date.now(),Z=this.routeRegistry.getRoutes().length;try{if(await this.routeManager.registerController(Q),W)this.diagnosticsEmitter.emit({level:"info",category:"routes",message:"Registered controller routes",details:{controller:Q.name,routeCountAdded:this.routeRegistry.getRoutes().length-Z,registrationDurationMs:Date.now()-X}})}catch(U){if(W)this.diagnosticsEmitter.emit({level:"error",category:"routes",message:"Failed to register controller routes",details:{controller:Q.name,registrationDurationMs:Date.now()-X,errorMessage:U instanceof Error?U.message:String(U)}});throw U}}return this}static async create($,_={}){let W=Date.now(),Q=C.fromRootModule($),X=new w(_,Q),Z=(_.plugins||[]).map((V,G)=>X.normalizePluginEntry(V,G)),U=X.resolvePluginExecutionOrder(Z);X.validatePluginCapabilities(U);let J=X.getContext(),Y=_.debug,B=Y===!0||typeof Y==="object"&&Y.plugins,q=Y===!0||typeof Y==="object"&&Y.routes,F=Y===!0||typeof Y==="object"&&(Y.startup||q),A=!1;try{if(B&&U.length>0)X.diagnosticsEmitter.emit({level:"info",category:"plugins",message:`Plugin order: ${U.map(({name:G})=>G).join(" -> ")}`});for(let{plugin:G,preProcessors:O}of U){for(let k of O)await k(X,X.hono,J);if(G.beforeModulesRegistered)await G.beforeModulesRegistered(X,X.hono)}await X.register($);let V=X.getRoutes();if(F)X.diagnosticsEmitter.emit({level:"info",category:"startup",message:`Application registered ${V.length} route(s)`,details:{routeCount:V.length,rootModule:$.name}});if(_.strict?.requireRoutes&&V.length===0){A=!0,X.diagnosticsEmitter.emit({level:"error",category:"startup",message:"Strict mode failed: no routes were registered",details:{rootModule:$.name,requireRoutes:!0,startupDurationMs:Date.now()-W}});let G=Error("Strict mode: no routes were registered. Check your module/controller decorators.");throw X.emitStartupGuide(G,$),G}if(q)X.diagnosticsEmitter.emit({level:"info",category:"routes",message:"Registered routes",details:{routes:V.map((G)=>`${G.method.toUpperCase()} ${G.fullPath}`)}});for(let{plugin:G,postProcessors:O}of U){if(G.afterModulesRegistered)await G.afterModulesRegistered(X,X.hono);for(let k of O)await k(X,X.hono,J)}if(F)X.diagnosticsEmitter.emit({level:"info",category:"startup",message:"Application startup completed",details:{rootModule:$.name,pluginCount:U.length,routeCount:V.length,startupDurationMs:Date.now()-W}});return{app:X,hono:X.getApp()}}catch(V){if(X.emitStartupGuide(V,$),F&&!A)X.diagnosticsEmitter.emit({level:"error",category:"startup",message:"Application startup failed",details:{rootModule:$.name,startupDurationMs:Date.now()-W,errorMessage:V instanceof Error?V.message:String(V)}});throw V}}getApp(){return this.hono}getContainer(){return this.container}getContext(){return this.context}getRoutes(){return this.routeRegistry.getRoutes()}}import{html as j,raw as e}from"hono/html";var L$={type:"website",locale:"en_US"},$$=($)=>{if(!$)return"";return Object.entries($).map(([_,W])=>{if(typeof W==="boolean")return W?_:"";let Q=String(W).replace(/"/g,""");return`${_}="${Q}"`}).filter(Boolean).join(" ")},r_=($)=>{let _={...L$,...$};return j`
|
|
2
2
|
<!DOCTYPE html>
|
|
3
|
-
<html lang="${_.locale?.split("_")[0]||"en"}" ${
|
|
4
|
-
<head ${
|
|
3
|
+
<html lang="${_.locale?.split("_")[0]||"en"}" ${e($$(_.htmlAttributes))}>
|
|
4
|
+
<head ${e($$(_.headAttributes))}>
|
|
5
5
|
<meta charset="UTF-8" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
${_.csp?j`<meta http-equiv="Content-Security-Policy" content="${_.csp}" />`:""}
|
|
@@ -39,8 +39,8 @@ import"reflect-metadata";import{Hono as U$}from"hono";class x{store=new Map;get(
|
|
|
39
39
|
<!-- Scripts -->
|
|
40
40
|
${_.scripts?_.scripts.map((W)=>{if(typeof W==="string")return j`<script src="${W}"></script>`;let{src:Q,async:X,defer:Z}=W;if(X&&Z)return j`<script src="${Q}" async defer></script>`;if(X)return j`<script src="${Q}" async></script>`;if(Z)return j`<script src="${Q}" defer></script>`;return j`<script src="${Q}"></script>`}):""}
|
|
41
41
|
</head>
|
|
42
|
-
<body ${
|
|
42
|
+
<body ${e($$(_.bodyAttributes))}>
|
|
43
43
|
${_.children}
|
|
44
44
|
</body>
|
|
45
45
|
</html>
|
|
46
|
-
`};function
|
|
46
|
+
`};function Q$($="",_={}){return(W)=>{L.setControllerPath(W,$),L.setControllerOptions(W,_)}}var X$=T("get"),XW=T("post"),ZW=T("put"),JW=T("delete"),UW=T("patch"),YW=T("options"),BW=T("all");function x($={}){return(_)=>{L.setModuleOptions(_,$)}}function zW($="",_={prefix:null,version:null}){return Q$($,_)}var AW=X$;function kW($={}){return x({imports:$.imports,services:$.services,controllers:($.views||[]).concat($.controllers||[])})}var TW=z("body",async($,_)=>{let W=_.get(l);if(W===void 0)W=await _.req.json(),_.set(l,W);if($&&W&&typeof W==="object")return W[String($)];return W}),KW=z("param",($,_)=>{return $?_.req.param(String($)):_.req.param()}),NW=z("query",($,_)=>{return $?_.req.query(String($)):_.req.query()}),PW=z("header",($,_)=>{return $?_.req.header(String($)):_.req.header()}),SW=z("request",($,_)=>_.req),wW=z("request",($,_)=>_.req),EW=z("response",($,_)=>_.res),RW=z("response",($,_)=>_.res),bW=z("context",($,_)=>_),CW=z("context",($,_)=>_),IW=z("variable",($,_)=>$===void 0?void 0:_.get(String($))),MW=z("variable",($,_)=>$===void 0?void 0:_.get(String($)));function vW(){return($)=>{L.addService($)}}function gW($,..._){return(W,Q)=>{if(Q){let Z=`${W.constructor.name}:${String(Q)}`;_.forEach((U)=>L.registerHandler($,Z,U))}else _.forEach((X)=>L.registerController($,W,X))}}function dW(...$){return(_,W)=>{if(W){let X=`${_.constructor.name}:${String(W)}`;$.forEach((Z)=>L.registerHandler("filter",X,Z))}else $.forEach((Q)=>L.registerController("filter",_,Q))}}function iW(...$){return(_,W)=>{if(W){let X=`${_.constructor.name}:${String(W)}`;$.forEach((Z)=>L.registerHandler("guard",X,Z))}else $.forEach((Q)=>L.registerController("guard",_,Q))}}function nW(...$){return(_,W)=>{if(W){let X=`${_.constructor.name}:${String(W)}`;$.forEach((Z)=>L.registerHandler("middleware",X,Z))}else $.forEach((Q)=>L.registerController("middleware",_,Q))}}function rW(...$){return(_,W)=>{if(W){let X=`${_.constructor.name}:${String(W)}`;$.forEach((Z)=>L.registerHandler("pipe",X,Z))}else $.forEach((Q)=>L.registerController("pipe",_,Q))}}function Z$($={}){let{controllers:_,services:W,imports:Q,name:X="TestModule"}=$,Z={[X]:class{}}[X];return x({controllers:_,services:W,imports:Q})(Z),Z}async function J$($={}){let{module:_,appOptions:W,...Q}=$,X=_??Z$(Q),{app:Z,hono:U}=await w.create(X,W);return{app:Z,hono:U,request:(Y,B)=>{if(typeof Y==="string"){let q=Y.startsWith("http://")||Y.startsWith("https://")?Y:`http://localhost${Y.startsWith("/")?Y:`/${Y}`}`;return Promise.resolve(U.request(q,B))}return Promise.resolve(U.request(Y))}}}async function g8($){let{controller:_,...W}=$;return J$({...W,controllers:[_]})}function c8($={}){let _=$.diagnostics??new H,W=new E(void 0,_,Boolean($.debugDi));for(let Q of $.overrides??[]){let X=Q;W.register(X.provide,X.useValue)}for(let Q of $.preload??[])W.resolve(Q);return{container:W,get(Q){return W.resolve(Q)},register(Q,X){W.register(Q,X)},has(Q){return W.has(Q)},clear(){W.clear()}}}export{L_ as stripEndSlash,s as normalizePath,X_ as isUndefined,Y_ as isSymbol,f as isString,Z_ as isPlainObject,K as isObject,J_ as isNumber,S as isNil,_$ as isFunction,U_ as isEmpty,W$ as isConstructor,Z$ as createTestingModule,J$ as createTestApplication,c8 as createServiceTestContainer,z as createParamDecorator,T as createHttpMethodDecorator,R as createErrorResponse,g8 as createControllerTestApplication,B_ as addLeadingSlash,zW as View,MW as Variable,IW as Var,i as VERSION_NEUTRAL,rW as UsePipes,nW as UseMiddleware,iW as UseGuards,dW as UseFilters,gW as UseComponent,u as StaticServiceRegistry,D as StaticMetadataRepository,C as SnapshotMetadataRepository,vW as Service,m as RouteRegistry,RW as Response,EW as Res,wW as Request,SW as Req,NW as Query,ZW as Put,XW as Post,UW as Patch,KW as Param,AW as Page,YW as Options,p as NotFoundHandler,H as NoopDiagnosticsEmitter,kW as MvcModule,x as Module,L as MetadataRegistry,r_ as Layout,PW as Header,M as HONEST_PIPELINE_HANDLER_KEY,I as HONEST_PIPELINE_CONTROLLER_KEY,l as HONEST_PIPELINE_BODY_CACHE_KEY,X$ as Get,d as FrameworkError,c as ErrorHandler,JW as Delete,bW as Ctx,Q$ as Controller,CW as Context,E as Container,g as ConsoleDiagnosticsEmitter,TW as Body,h as ApplicationContext,w as Application,BW as All};
|
|
@@ -6,6 +6,14 @@ import type { DiContainer } from './di-container.interface';
|
|
|
6
6
|
* Options for configuring the Honest application
|
|
7
7
|
*/
|
|
8
8
|
export interface HonestOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Emit actionable startup guidance when initialization fails.
|
|
11
|
+
* - `true` enables concise hints
|
|
12
|
+
* - object form enables verbose hints with additional context
|
|
13
|
+
*/
|
|
14
|
+
startupGuide?: boolean | {
|
|
15
|
+
verbose?: boolean;
|
|
16
|
+
};
|
|
9
17
|
/**
|
|
10
18
|
* Enable debug logging for startup diagnostics.
|
|
11
19
|
* - `true` enables all debug logs
|
|
@@ -7,12 +7,42 @@ import type { Constructor } from '../types';
|
|
|
7
7
|
* Receives app, hono, and the application context (registry) for sharing pipeline data.
|
|
8
8
|
*/
|
|
9
9
|
export type PluginProcessor = (app: Application, hono: Hono, ctx: IApplicationContext) => void | Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Optional metadata for plugin composition and startup contract checks.
|
|
12
|
+
*/
|
|
13
|
+
export interface PluginMeta {
|
|
14
|
+
/**
|
|
15
|
+
* Stable plugin name used for ordering constraints and diagnostics.
|
|
16
|
+
*/
|
|
17
|
+
name?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Capabilities this plugin provides for subsequent plugins.
|
|
20
|
+
*/
|
|
21
|
+
provides?: string[];
|
|
22
|
+
/**
|
|
23
|
+
* Capabilities this plugin expects to be provided earlier in the pipeline.
|
|
24
|
+
*/
|
|
25
|
+
requires?: string[];
|
|
26
|
+
}
|
|
10
27
|
/**
|
|
11
28
|
* Object form of a plugin entry with optional pre/post processors.
|
|
12
29
|
* Processors run before (pre) or after (post) the plugin's lifecycle hooks.
|
|
13
30
|
*/
|
|
14
31
|
export interface PluginEntryObject {
|
|
15
32
|
plugin: IPlugin | Constructor<IPlugin>;
|
|
33
|
+
/**
|
|
34
|
+
* Optional stable plugin name used for ordering and diagnostics.
|
|
35
|
+
* Takes precedence over plugin.meta.name.
|
|
36
|
+
*/
|
|
37
|
+
name?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Ensure this plugin runs before listed plugin names.
|
|
40
|
+
*/
|
|
41
|
+
before?: string[];
|
|
42
|
+
/**
|
|
43
|
+
* Ensure this plugin runs after listed plugin names.
|
|
44
|
+
*/
|
|
45
|
+
after?: string[];
|
|
16
46
|
preProcessors?: PluginProcessor[];
|
|
17
47
|
postProcessors?: PluginProcessor[];
|
|
18
48
|
}
|
|
@@ -22,6 +52,10 @@ export interface PluginEntryObject {
|
|
|
22
52
|
* different stages of the application lifecycle
|
|
23
53
|
*/
|
|
24
54
|
export interface IPlugin {
|
|
55
|
+
/**
|
|
56
|
+
* Optional metadata for plugin capabilities and diagnostics.
|
|
57
|
+
*/
|
|
58
|
+
meta?: PluginMeta;
|
|
25
59
|
/**
|
|
26
60
|
* Hook that runs before module registration begins
|
|
27
61
|
* Use this to set up plugin functionality that modules might depend on
|
|
@@ -148,9 +148,15 @@ export declare class MetadataRegistry {
|
|
|
148
148
|
* Get all handler-level components of a specific type for a handler
|
|
149
149
|
*/
|
|
150
150
|
static getHandler<T extends ComponentType>(type: T, handlerKey: string): ComponentTypeMap[T][];
|
|
151
|
+
/**
|
|
152
|
+
* Clears handler-level component registrations created via {@link registerHandler}.
|
|
153
|
+
* Does not remove decorator-defined routes, controllers, or modules.
|
|
154
|
+
*/
|
|
155
|
+
static clearHandlerComponents(): void;
|
|
151
156
|
/**
|
|
152
157
|
* Clears all registered decorator metadata.
|
|
153
|
-
* Primarily used for testing.
|
|
158
|
+
* Primarily used for testing. Warning: clearing after importing decorated classes
|
|
159
|
+
* (e.g. shared fixtures) removes their metadata until those modules are re-evaluated.
|
|
154
160
|
*/
|
|
155
161
|
static clear(): void;
|
|
156
162
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CreateControllerTestApplicationOptions, TestApplication } from './testing.interface';
|
|
2
|
+
/**
|
|
3
|
+
* Create a test application for a single controller with optional services/imports.
|
|
4
|
+
*/
|
|
5
|
+
export declare function createControllerTestApplication(options: CreateControllerTestApplicationOptions): Promise<TestApplication>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CreateServiceTestContainerOptions, TestServiceContainer } from './testing.interface';
|
|
2
|
+
/**
|
|
3
|
+
* Create a lightweight DI container for service-only tests without HTTP bootstrap.
|
|
4
|
+
*/
|
|
5
|
+
export declare function createServiceTestContainer(options?: CreateServiceTestContainerOptions): TestServiceContainer;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CreateTestApplicationOptions, TestApplication } from './testing.interface';
|
|
2
|
+
/**
|
|
3
|
+
* Create a test-friendly application instance with a convenience request helper.
|
|
4
|
+
*/
|
|
5
|
+
export declare function createTestApplication(options?: CreateTestApplicationOptions): Promise<TestApplication>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Constructor } from '../types';
|
|
2
|
+
import type { TestModuleOptions } from './testing.interface';
|
|
3
|
+
/**
|
|
4
|
+
* Create a runtime module class for tests without boilerplate.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createTestingModule(options?: TestModuleOptions): Constructor;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Constructor } from '../../types';
|
|
2
|
+
export declare function createTestController(): Constructor;
|
|
3
|
+
export declare function createPayloadController(): Constructor;
|
|
4
|
+
export declare function createRawResponseController(): Constructor;
|
|
5
|
+
export declare function createOnlyAController(): Constructor;
|
|
6
|
+
export declare function createOnlyBController(): Constructor;
|
|
7
|
+
export declare function createUndecoratedController(): Constructor;
|
|
8
|
+
export declare function createBrokenControllerModule(): Constructor;
|
|
9
|
+
export declare function createEmptyModule(): Constructor;
|
|
10
|
+
export declare function createDuplicateRouteControllers(): {
|
|
11
|
+
a: Constructor;
|
|
12
|
+
b: Constructor;
|
|
13
|
+
};
|
|
14
|
+
export declare function createUnsafeParamController(): Constructor;
|
|
15
|
+
export declare function createDiagnosticsAController(): Constructor;
|
|
16
|
+
export declare function createDiagnosticsBController(): Constructor;
|
|
17
|
+
export declare function createRuntimeMetadataController(): Constructor;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { Hono } from 'hono';
|
|
2
|
+
import type { Application } from '../application';
|
|
3
|
+
import type { DiContainer, HonestOptions, IDiagnosticsEmitter, ModuleOptions } from '../interfaces';
|
|
4
|
+
import type { Constructor } from '../types';
|
|
5
|
+
/**
|
|
6
|
+
* Options for creating a lightweight test module.
|
|
7
|
+
*/
|
|
8
|
+
export interface TestModuleOptions extends ModuleOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Optional class name to improve diagnostics in test output.
|
|
11
|
+
*/
|
|
12
|
+
name?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Options for creating a test application instance.
|
|
16
|
+
*/
|
|
17
|
+
export interface CreateTestApplicationOptions extends TestModuleOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Existing module class to bootstrap.
|
|
20
|
+
* If omitted, a module is created from controllers/services/imports.
|
|
21
|
+
*/
|
|
22
|
+
module?: Constructor;
|
|
23
|
+
/**
|
|
24
|
+
* Honest application options passed to Application.create.
|
|
25
|
+
*/
|
|
26
|
+
appOptions?: HonestOptions;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Options for creating a test application around a single controller.
|
|
30
|
+
*/
|
|
31
|
+
export interface CreateControllerTestApplicationOptions extends Omit<TestModuleOptions, 'controllers'> {
|
|
32
|
+
/**
|
|
33
|
+
* Controller class to mount in the generated test module.
|
|
34
|
+
*/
|
|
35
|
+
controller: Constructor;
|
|
36
|
+
/**
|
|
37
|
+
* Honest application options passed to Application.create.
|
|
38
|
+
*/
|
|
39
|
+
appOptions?: HonestOptions;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Result object returned by createTestApplication.
|
|
43
|
+
*/
|
|
44
|
+
export interface TestApplication {
|
|
45
|
+
/**
|
|
46
|
+
* Honest application instance.
|
|
47
|
+
*/
|
|
48
|
+
app: Application;
|
|
49
|
+
/**
|
|
50
|
+
* Underlying Hono app.
|
|
51
|
+
*/
|
|
52
|
+
hono: Hono;
|
|
53
|
+
/**
|
|
54
|
+
* Convenience request helper for tests.
|
|
55
|
+
* Relative paths are resolved against http://localhost.
|
|
56
|
+
*/
|
|
57
|
+
request: (input: string | Request, init?: RequestInit) => Promise<Response>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Override a service token with a pre-built test instance.
|
|
61
|
+
*/
|
|
62
|
+
export interface ServiceTestOverride<T = unknown> {
|
|
63
|
+
provide: Constructor<T>;
|
|
64
|
+
useValue: T;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Options for creating a service-only test container.
|
|
68
|
+
*/
|
|
69
|
+
export interface CreateServiceTestContainerOptions {
|
|
70
|
+
/**
|
|
71
|
+
* Optional service overrides registered before any resolve calls.
|
|
72
|
+
*/
|
|
73
|
+
overrides?: ServiceTestOverride[];
|
|
74
|
+
/**
|
|
75
|
+
* Optional services to resolve immediately so tests can assert warm startup state.
|
|
76
|
+
*/
|
|
77
|
+
preload?: Constructor[];
|
|
78
|
+
/**
|
|
79
|
+
* Optional diagnostics emitter used when debugDi is enabled.
|
|
80
|
+
*/
|
|
81
|
+
diagnostics?: IDiagnosticsEmitter;
|
|
82
|
+
/**
|
|
83
|
+
* Enable DI diagnostics while resolving services.
|
|
84
|
+
*/
|
|
85
|
+
debugDi?: boolean;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Service-only test harness around the DI container.
|
|
89
|
+
*/
|
|
90
|
+
export interface TestServiceContainer {
|
|
91
|
+
container: DiContainer;
|
|
92
|
+
get<T>(target: Constructor<T>): T;
|
|
93
|
+
register<T>(target: Constructor<T>, instance: T): void;
|
|
94
|
+
has<T>(target: Constructor<T>): boolean;
|
|
95
|
+
clear(): void;
|
|
96
|
+
}
|
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.
|
|
4
|
+
"version": "0.1.18",
|
|
5
5
|
"author": "Orkhan Karimov <karimovok1@gmail.com> (https://github.com/kerimovok)",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"prepare": "husky",
|
|
54
54
|
"test": "bun test",
|
|
55
55
|
"test:watch": "bun test --watch",
|
|
56
|
+
"test:coverage": "bun test --coverage --coverage-reporter=text --coverage-reporter=lcov",
|
|
56
57
|
"build": "bun run clean && bun build ./src/index.ts --outdir=dist --target=node --minify --external hono --external reflect-metadata && bun run build:types",
|
|
57
58
|
"build:types": "tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
58
59
|
"lint": "eslint .",
|
|
File without changes
|