honovajs 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/README.md +159 -7
  2. package/dist/adapters/db/drizzle.d.ts +1 -1
  3. package/dist/adapters/db/drizzle.d.ts.map +1 -1
  4. package/dist/adapters/db/index.d.ts +4 -4
  5. package/dist/adapters/db/index.d.ts.map +1 -1
  6. package/dist/adapters/db/index.js +4 -4
  7. package/dist/adapters/db/index.js.map +1 -1
  8. package/dist/adapters/db/mondel.d.ts +1 -1
  9. package/dist/adapters/db/mondel.d.ts.map +1 -1
  10. package/dist/adapters/db/mondel.js +1 -1
  11. package/dist/adapters/db/mondel.js.map +1 -1
  12. package/dist/adapters/db/mongodb.d.ts +1 -1
  13. package/dist/adapters/db/mongodb.d.ts.map +1 -1
  14. package/dist/adapters/db/prisma.d.ts +1 -1
  15. package/dist/adapters/db/prisma.d.ts.map +1 -1
  16. package/dist/core/application.d.ts +68 -4
  17. package/dist/core/application.d.ts.map +1 -1
  18. package/dist/core/application.js +116 -18
  19. package/dist/core/application.js.map +1 -1
  20. package/dist/core/container/container.d.ts +59 -7
  21. package/dist/core/container/container.d.ts.map +1 -1
  22. package/dist/core/container/container.js +384 -56
  23. package/dist/core/container/container.js.map +1 -1
  24. package/dist/core/container/index.d.ts +2 -2
  25. package/dist/core/container/index.d.ts.map +1 -1
  26. package/dist/core/container/index.js +2 -2
  27. package/dist/core/container/index.js.map +1 -1
  28. package/dist/core/container/inject.d.ts +6 -1
  29. package/dist/core/container/inject.d.ts.map +1 -1
  30. package/dist/core/container/inject.js +7 -2
  31. package/dist/core/container/inject.js.map +1 -1
  32. package/dist/core/decorators/controller.d.ts.map +1 -1
  33. package/dist/core/decorators/controller.js +38 -16
  34. package/dist/core/decorators/controller.js.map +1 -1
  35. package/dist/core/decorators/http-methods.d.ts +1 -0
  36. package/dist/core/decorators/http-methods.d.ts.map +1 -1
  37. package/dist/core/decorators/http-methods.js +4 -1
  38. package/dist/core/decorators/http-methods.js.map +1 -1
  39. package/dist/core/decorators/http-response.d.ts +15 -0
  40. package/dist/core/decorators/http-response.d.ts.map +1 -0
  41. package/dist/core/decorators/http-response.js +44 -0
  42. package/dist/core/decorators/http-response.js.map +1 -0
  43. package/dist/core/decorators/index.d.ts +7 -7
  44. package/dist/core/decorators/index.d.ts.map +1 -1
  45. package/dist/core/decorators/index.js +7 -7
  46. package/dist/core/decorators/index.js.map +1 -1
  47. package/dist/core/decorators/inject.d.ts +17 -2
  48. package/dist/core/decorators/inject.d.ts.map +1 -1
  49. package/dist/core/decorators/inject.js +43 -2
  50. package/dist/core/decorators/inject.js.map +1 -1
  51. package/dist/core/decorators/injectable.js +1 -1
  52. package/dist/core/decorators/injectable.js.map +1 -1
  53. package/dist/core/decorators/middleware.d.ts.map +1 -1
  54. package/dist/core/decorators/middleware.js +4 -2
  55. package/dist/core/decorators/middleware.js.map +1 -1
  56. package/dist/core/decorators/module.d.ts +1 -1
  57. package/dist/core/decorators/module.d.ts.map +1 -1
  58. package/dist/core/decorators/module.js +1 -1
  59. package/dist/core/decorators/module.js.map +1 -1
  60. package/dist/core/design-types.d.ts +21 -0
  61. package/dist/core/design-types.d.ts.map +1 -0
  62. package/dist/core/design-types.js +86 -0
  63. package/dist/core/design-types.js.map +1 -0
  64. package/dist/core/exceptions/filters.d.ts +22 -0
  65. package/dist/core/exceptions/filters.d.ts.map +1 -0
  66. package/dist/core/exceptions/filters.js +40 -0
  67. package/dist/core/exceptions/filters.js.map +1 -0
  68. package/dist/core/exceptions/http-exception.d.ts +91 -0
  69. package/dist/core/exceptions/http-exception.d.ts.map +1 -0
  70. package/dist/core/exceptions/http-exception.js +72 -0
  71. package/dist/core/exceptions/http-exception.js.map +1 -0
  72. package/dist/core/exceptions/index.d.ts +3 -0
  73. package/dist/core/exceptions/index.d.ts.map +1 -0
  74. package/dist/core/exceptions/index.js +3 -0
  75. package/dist/core/exceptions/index.js.map +1 -0
  76. package/dist/core/execution-context.d.ts +30 -0
  77. package/dist/core/execution-context.d.ts.map +1 -0
  78. package/dist/core/execution-context.js +46 -0
  79. package/dist/core/execution-context.js.map +1 -0
  80. package/dist/core/metadata.d.ts +1 -1
  81. package/dist/core/metadata.d.ts.map +1 -1
  82. package/dist/core/metadata.js +14 -0
  83. package/dist/core/metadata.js.map +1 -1
  84. package/dist/core/pipeline/filters-binding.d.ts +7 -0
  85. package/dist/core/pipeline/filters-binding.d.ts.map +1 -0
  86. package/dist/core/pipeline/filters-binding.js +45 -0
  87. package/dist/core/pipeline/filters-binding.js.map +1 -0
  88. package/dist/core/pipeline/guards.d.ts +15 -0
  89. package/dist/core/pipeline/guards.d.ts.map +1 -0
  90. package/dist/core/pipeline/guards.js +48 -0
  91. package/dist/core/pipeline/guards.js.map +1 -0
  92. package/dist/core/pipeline/index.d.ts +5 -0
  93. package/dist/core/pipeline/index.d.ts.map +1 -0
  94. package/dist/core/pipeline/index.js +5 -0
  95. package/dist/core/pipeline/index.js.map +1 -0
  96. package/dist/core/pipeline/interceptors.d.ts +20 -0
  97. package/dist/core/pipeline/interceptors.d.ts.map +1 -0
  98. package/dist/core/pipeline/interceptors.js +45 -0
  99. package/dist/core/pipeline/interceptors.js.map +1 -0
  100. package/dist/core/pipeline/public.d.ts +3 -0
  101. package/dist/core/pipeline/public.d.ts.map +1 -0
  102. package/dist/core/pipeline/public.js +46 -0
  103. package/dist/core/pipeline/public.js.map +1 -0
  104. package/dist/core/reflector.d.ts +26 -0
  105. package/dist/core/reflector.d.ts.map +1 -0
  106. package/dist/core/reflector.js +81 -0
  107. package/dist/core/reflector.js.map +1 -0
  108. package/dist/core/router/index.d.ts +1 -1
  109. package/dist/core/router/index.d.ts.map +1 -1
  110. package/dist/core/router/index.js +1 -1
  111. package/dist/core/router/index.js.map +1 -1
  112. package/dist/core/router/router.d.ts +28 -3
  113. package/dist/core/router/router.d.ts.map +1 -1
  114. package/dist/core/router/router.js +152 -11
  115. package/dist/core/router/router.js.map +1 -1
  116. package/dist/core/standalone.d.ts +18 -0
  117. package/dist/core/standalone.d.ts.map +1 -0
  118. package/dist/core/standalone.js +61 -0
  119. package/dist/core/standalone.js.map +1 -0
  120. package/dist/core/types.d.ts +28 -3
  121. package/dist/core/types.d.ts.map +1 -1
  122. package/dist/core/validation/index.d.ts +3 -0
  123. package/dist/core/validation/index.d.ts.map +1 -0
  124. package/dist/core/validation/index.js +3 -0
  125. package/dist/core/validation/index.js.map +1 -0
  126. package/dist/core/validation/standard-schema.d.ts +29 -0
  127. package/dist/core/validation/standard-schema.d.ts.map +1 -0
  128. package/dist/core/validation/standard-schema.js +7 -0
  129. package/dist/core/validation/standard-schema.js.map +1 -0
  130. package/dist/core/validation/validate.d.ts +32 -0
  131. package/dist/core/validation/validate.d.ts.map +1 -0
  132. package/dist/core/validation/validate.js +130 -0
  133. package/dist/core/validation/validate.js.map +1 -0
  134. package/dist/database/index.d.ts +4 -3
  135. package/dist/database/index.d.ts.map +1 -1
  136. package/dist/database/index.js +4 -3
  137. package/dist/database/index.js.map +1 -1
  138. package/dist/database/lazy-proxy.d.ts +26 -0
  139. package/dist/database/lazy-proxy.d.ts.map +1 -0
  140. package/dist/database/lazy-proxy.js +67 -0
  141. package/dist/database/lazy-proxy.js.map +1 -0
  142. package/dist/database/manager.d.ts +10 -0
  143. package/dist/database/manager.d.ts.map +1 -1
  144. package/dist/database/manager.js +26 -2
  145. package/dist/database/manager.js.map +1 -1
  146. package/dist/database/middleware.d.ts +1 -1
  147. package/dist/database/middleware.d.ts.map +1 -1
  148. package/dist/database/middleware.js +1 -1
  149. package/dist/database/middleware.js.map +1 -1
  150. package/dist/database/types.d.ts +3 -3
  151. package/dist/database/types.d.ts.map +1 -1
  152. package/dist/database/types.js +1 -1
  153. package/dist/database/types.js.map +1 -1
  154. package/dist/index.d.ts +19 -9
  155. package/dist/index.d.ts.map +1 -1
  156. package/dist/index.js +19 -9
  157. package/dist/index.js.map +1 -1
  158. package/dist/middleware/auth/apikey.js +1 -1
  159. package/dist/middleware/auth/apikey.js.map +1 -1
  160. package/dist/middleware/auth/bearer.js +1 -1
  161. package/dist/middleware/auth/bearer.js.map +1 -1
  162. package/dist/middleware/auth/index.d.ts +3 -2
  163. package/dist/middleware/auth/index.d.ts.map +1 -1
  164. package/dist/middleware/auth/index.js +3 -2
  165. package/dist/middleware/auth/index.js.map +1 -1
  166. package/dist/middleware/auth/jwt.d.ts +41 -0
  167. package/dist/middleware/auth/jwt.d.ts.map +1 -0
  168. package/dist/middleware/auth/jwt.js +81 -0
  169. package/dist/middleware/auth/jwt.js.map +1 -0
  170. package/dist/scheduling/index.d.ts +28 -0
  171. package/dist/scheduling/index.d.ts.map +1 -0
  172. package/dist/scheduling/index.js +92 -0
  173. package/dist/scheduling/index.js.map +1 -0
  174. package/dist/testing/index.d.ts +38 -0
  175. package/dist/testing/index.d.ts.map +1 -0
  176. package/dist/testing/index.js +77 -0
  177. package/dist/testing/index.js.map +1 -0
  178. package/package.json +10 -4
package/README.md CHANGED
@@ -6,9 +6,10 @@ Honova provides a module system, decorators, dependency injection, route metadat
6
6
 
7
7
  ## Status
8
8
 
9
- - Package version: `0.0.1`
9
+ - Package version: `0.1.0`
10
10
  - Runtime target: Cloudflare Workers (also works anywhere Hono runs)
11
11
  - Module format: ESM
12
+ - `hono` is a peer dependency: `>=4.13.0 <5`
12
13
 
13
14
  ## Feature Checklist
14
15
 
@@ -27,6 +28,28 @@ Honova provides a module system, decorators, dependency injection, route metadat
27
28
  - [x] Controller-level and route-level middleware via `@UseMiddleware`
28
29
  - [x] Built-in `not_found` and `internal_error` JSON responses
29
30
 
31
+ ### Request Pipeline (0.1.0)
32
+
33
+ - [x] `HttpException` hierarchy + `ValidationException`
34
+ - [x] Exception filters: `@Catch()`, `@UseFilters()`, global filters
35
+ - [x] Guards: `CanActivate`, `@UseGuards()`, global guards
36
+ - [x] Interceptors without RxJS (async onion), `@UseInterceptors()`, global interceptors
37
+ - [x] `ExecutionContext` (class/handler/Hono context/env/`waitUntil`)
38
+ - [x] Metadata API: `createDecorator<T>()` + `Reflector`
39
+ - [x] Validation via Standard Schema v1 (`@Validate`, `getInput`) — zod/valibot/arktype, zero deps
40
+ - [x] Response serialization (plain values → JSON/text/204) + `@HttpCode` / `@Header`
41
+ - [x] Custom providers (`useValue`/`useFactory`/`useClass`/`useExisting`), async factories
42
+ - [x] Async `onModuleInit` (awaited by the request pipeline, single-flight per isolate)
43
+ - [x] Scope bubbling (request scope propagates to consumers, as in Nest)
44
+ - [x] Stage-3 DI: `@Inject` on fields/accessors + `static inject = [...]`
45
+ - [x] Env config validation (`config: { schema }` → `CONFIG` token)
46
+ - [x] JWT guard (`createJwtGuard`) + `@Roles` / `RolesGuard`
47
+ - [x] `@Cron` → Cloudflare Cron Triggers dispatcher (`createScheduledDispatcher`)
48
+ - [x] Standalone context (`createApplicationContext`) for `scheduled()`/`queue()`
49
+ - [x] Testing module (`createTestingModule`, `overrideProvider`, `flushWaitUntil`)
50
+ - [x] HTTP `QUERY` verb (`@HttpQuery`) and `methodNotAllowed` option
51
+ - [x] `@Public()` / `isPublicHandler()` — opt a route out of a global guard
52
+
30
53
  ### Security and Observability
31
54
 
32
55
  - [x] CORS integration (`hono/cors`)
@@ -64,14 +87,14 @@ Honova provides a module system, decorators, dependency injection, route metadat
64
87
  ### Planned / Missing Features
65
88
 
66
89
  - [x] Request-scoped and transient instance caching semantics validated by tests
67
- - [ ] Async `onModuleInit` support in runtime
68
- - [ ] Validation pipes and DTO validation layer
69
- - [ ] Guard/interceptor abstraction (Nest-style)
70
- - [ ] Exception filter abstraction
90
+ - [x] Async `onModuleInit` support in runtime
91
+ - [x] Validation pipes and DTO validation layer
92
+ - [x] Guard/interceptor abstraction (Nest-style)
93
+ - [x] Exception filter abstraction
71
94
  - [ ] First-class OpenAPI/Swagger generation
72
95
  - [ ] CLI scaffolding (`create-honova-app`, generators)
73
96
  - [ ] Official Cloudflare starter templates (D1, KV, R2 examples)
74
- - [ ] More auth primitives (JWT helper, role/permission guard, session middleware)
97
+ - [x] More auth primitives (JWT helper, role/permission guard, session middleware)
75
98
  - [ ] E2E test suite with Worker runtime integration
76
99
  - [ ] Benchmark/performance suite and guidance
77
100
  - [ ] Complete API docs website
@@ -180,7 +203,9 @@ Current runtime behavior:
180
203
  - It runs when the provider is resolved for the first time.
181
204
  - For singleton providers, it runs once per app instance.
182
205
  - Request context is passed when the provider is resolved during a request.
183
- - It must be synchronous in `0.0.1`.
206
+ - It may be async: the request pipeline awaits it (single-flight per isolate).
207
+ Synchronous resolution paths (`container.resolve`, `inject()`) still require
208
+ sync inits and throw otherwise.
184
209
 
185
210
  ## Middleware
186
211
 
@@ -252,6 +277,52 @@ class MeController {
252
277
  }
253
278
  ```
254
279
 
280
+ ### Opting a route out of a global guard
281
+
282
+ An application that puts authentication in a global guard has made it the
283
+ default, which is the point: a new route cannot ship unprotected because
284
+ somebody forgot a decorator. The few routes that must stay open then need a way
285
+ to say so.
286
+
287
+ `@Public()` marks the handler. It does **not** make the router skip guards,
288
+ because only a guard knows whether "public" applies to it: a session guard
289
+ should stand down on a login form, and a CSRF guard should not. Each guard opts
290
+ in by asking.
291
+
292
+ ```ts
293
+ import { Controller, Get, Public, createApp, isPublicHandler } from "honovajs";
294
+ import type { CanActivate, ExecutionContext } from "honovajs";
295
+
296
+ const session: CanActivate = {
297
+ canActivate(ctx: ExecutionContext) {
298
+ if (isPublicHandler(ctx.getHandler())) return true;
299
+
300
+ return Boolean(ctx.getContext().req.header("cookie"));
301
+ },
302
+ };
303
+
304
+ createApp({ guards: [session] });
305
+
306
+ @Controller("/")
307
+ class Routes {
308
+ @Get("/me")
309
+ me() {
310
+ return { id: "u_1" };
311
+ }
312
+
313
+ @Get("/health")
314
+ @Public()
315
+ health() {
316
+ return { ok: true };
317
+ }
318
+ }
319
+ ```
320
+
321
+ The mark is keyed on the handler function, not on a route path, so a handler
322
+ cannot be moved, renamed or copied into another controller and quietly lose, or
323
+ quietly keep, its exemption. It works written above or below the HTTP method
324
+ decorator.
325
+
255
326
  ### API Key
256
327
 
257
328
  ```ts
@@ -364,3 +435,84 @@ npm run build
364
435
  ## License
365
436
 
366
437
  MIT
438
+
439
+ ## The 0.1.0 Pipeline in One Example
440
+
441
+ ```ts
442
+ import {
443
+ Controller, Get, Post, Module, Injectable, Inject, createApp,
444
+ UseGuards, UseInterceptors, Validate, getInput, HttpCode,
445
+ createJwtGuard, Roles, RolesGuard, NotFoundException,
446
+ type CanActivate, type HonovaInterceptor, type ExecutionContext,
447
+ CONFIG, createScheduledDispatcher, Cron,
448
+ } from "honovajs";
449
+
450
+ // Any Standard Schema library works; zod >= 3.24 shown here.
451
+ import { z } from "zod";
452
+
453
+ const CreateLink = z.object({ url: z.string().url(), code: z.string().min(3) });
454
+
455
+ class TimingInterceptor implements HonovaInterceptor {
456
+ async intercept(ctx: ExecutionContext, next: () => Promise<unknown>) {
457
+ const start = Date.now();
458
+ const result = await next(); // handler return value, not a Response
459
+ console.log(`${ctx.getContext().req.path} took ${Date.now() - start}ms`);
460
+ return result; // may reshape: { data: result }
461
+ }
462
+ }
463
+
464
+ @Injectable()
465
+ class LinkService {
466
+ async find(code: string) {
467
+ if (code === "missing") throw new NotFoundException("Unknown link");
468
+ return { code, url: "https://example.com" };
469
+ }
470
+
471
+ @Cron("*/20 * * * *") // matches wrangler triggers.crons
472
+ async expireOutdated() { /* ... */ }
473
+ }
474
+
475
+ @Controller("/links")
476
+ @UseGuards(createJwtGuard({ secretFromEnv: "JWT_SIGNING_KEY" }), RolesGuard)
477
+ @UseInterceptors(TimingInterceptor)
478
+ class LinkController {
479
+ constructor(private readonly linkService: LinkService) {}
480
+
481
+ @Get("/:code")
482
+ get(c: any) {
483
+ return this.linkService.find(c.req.param("code")); // plain object → JSON
484
+ }
485
+
486
+ @Post("/")
487
+ @Roles(["admin"])
488
+ @HttpCode(201)
489
+ @Validate({ body: CreateLink })
490
+ create(c: any) {
491
+ const { body } = getInput<{ body: z.infer<typeof CreateLink> }>(c);
492
+ return body; // 201 JSON
493
+ }
494
+ }
495
+
496
+ @Module({ controllers: [LinkController], providers: [LinkService] })
497
+ class AppModule {}
498
+
499
+ const app = createApp({ security: { cors: true } });
500
+ app.registerModule(AppModule);
501
+
502
+ export default {
503
+ fetch: app.fetch,
504
+ scheduled: createScheduledDispatcher(app),
505
+ };
506
+ ```
507
+
508
+ ## Testing
509
+
510
+ ```ts
511
+ import { createTestingModule } from "honovajs";
512
+
513
+ const testApp = createTestingModule({ controllers: [LinkController], providers: [LinkService] })
514
+ .overrideProvider(LinkService).useValue({ find: async () => ({ code: "x", url: "y" }) })
515
+ .compile();
516
+
517
+ const res = await testApp.request("/links/x", {}, { JWT_SIGNING_KEY: "test" });
518
+ ```
@@ -1,4 +1,4 @@
1
- import type { DatabaseAdapter } from "../../database/manager";
1
+ import type { DatabaseAdapter } from "../../database/manager.js";
2
2
  export interface DrizzleAdapterOptions<TClient> {
3
3
  connect: (url: string) => Promise<TClient> | TClient;
4
4
  disconnect?: (client: TClient) => Promise<void> | void;
@@ -1 +1 @@
1
- {"version":3,"file":"drizzle.d.ts","sourceRoot":"","sources":["../../../src/adapters/db/drizzle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,MAAM,WAAW,qBAAqB,CAAC,OAAO;IAC5C,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACrD,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED,wBAAgB,oBAAoB,CAClC,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,OAAO,EAEP,OAAO,EAAE,qBAAqB,CAAC,OAAO,CAAC,GACtC,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAMxC"}
1
+ {"version":3,"file":"drizzle.d.ts","sourceRoot":"","sources":["../../../src/adapters/db/drizzle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEjE,MAAM,WAAW,qBAAqB,CAAC,OAAO;IAC5C,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACrD,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED,wBAAgB,oBAAoB,CAClC,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,OAAO,EAEP,OAAO,EAAE,qBAAqB,CAAC,OAAO,CAAC,GACtC,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAMxC"}
@@ -1,5 +1,5 @@
1
- export * from "./drizzle";
2
- export * from "./mongodb";
3
- export * from "./mondel";
4
- export * from "./prisma";
1
+ export * from "./drizzle.js";
2
+ export * from "./mongodb.js";
3
+ export * from "./mondel.js";
4
+ export * from "./prisma.js";
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/db/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/db/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
@@ -1,5 +1,5 @@
1
- export * from "./drizzle";
2
- export * from "./mongodb";
3
- export * from "./mondel";
4
- export * from "./prisma";
1
+ export * from "./drizzle.js";
2
+ export * from "./mongodb.js";
3
+ export * from "./mondel.js";
4
+ export * from "./prisma.js";
5
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/db/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/db/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
@@ -1,2 +1,2 @@
1
- export { type MongoDbAdapterOptions as MondelAdapterOptions, createMongoDbAdapter as createMondelAdapter, } from "./mongodb";
1
+ export { type MongoDbAdapterOptions as MondelAdapterOptions, createMongoDbAdapter as createMondelAdapter, } from "./mongodb.js";
2
2
  //# sourceMappingURL=mondel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mondel.d.ts","sourceRoot":"","sources":["../../../src/adapters/db/mondel.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,qBAAqB,IAAI,oBAAoB,EAClD,oBAAoB,IAAI,mBAAmB,GAC5C,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"mondel.d.ts","sourceRoot":"","sources":["../../../src/adapters/db/mondel.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,qBAAqB,IAAI,oBAAoB,EAClD,oBAAoB,IAAI,mBAAmB,GAC5C,MAAM,cAAc,CAAC"}
@@ -1,2 +1,2 @@
1
- export { createMongoDbAdapter as createMondelAdapter, } from "./mongodb";
1
+ export { createMongoDbAdapter as createMondelAdapter, } from "./mongodb.js";
2
2
  //# sourceMappingURL=mondel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mondel.js","sourceRoot":"","sources":["../../../src/adapters/db/mondel.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,oBAAoB,IAAI,mBAAmB,GAC5C,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"mondel.js","sourceRoot":"","sources":["../../../src/adapters/db/mondel.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,oBAAoB,IAAI,mBAAmB,GAC5C,MAAM,cAAc,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { DatabaseAdapter } from "../../database/manager";
1
+ import type { DatabaseAdapter } from "../../database/manager.js";
2
2
  export interface MongoDbAdapterOptions<TClient, TConnectOptions> {
3
3
  connect: (url: string, options: TConnectOptions) => Promise<TClient> | TClient;
4
4
  disconnect?: (client: TClient) => Promise<void> | void;
@@ -1 +1 @@
1
- {"version":3,"file":"mongodb.d.ts","sourceRoot":"","sources":["../../../src/adapters/db/mongodb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,MAAM,WAAW,qBAAqB,CAAC,OAAO,EAAE,eAAe;IAC7D,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAC/E,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED,wBAAgB,oBAAoB,CAClC,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,OAAO,EACP,eAAe,EAEf,cAAc,EAAE,qBAAqB,CAAC,OAAO,EAAE,eAAe,CAAC,GAC9D,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,eAAe,CAAC,CAMhD"}
1
+ {"version":3,"file":"mongodb.d.ts","sourceRoot":"","sources":["../../../src/adapters/db/mongodb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEjE,MAAM,WAAW,qBAAqB,CAAC,OAAO,EAAE,eAAe;IAC7D,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAC/E,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED,wBAAgB,oBAAoB,CAClC,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,OAAO,EACP,eAAe,EAEf,cAAc,EAAE,qBAAqB,CAAC,OAAO,EAAE,eAAe,CAAC,GAC9D,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,eAAe,CAAC,CAMhD"}
@@ -1,4 +1,4 @@
1
- import type { DatabaseAdapter } from "../../database/manager";
1
+ import type { DatabaseAdapter } from "../../database/manager.js";
2
2
  export interface PrismaAdapterOptions<TClient> {
3
3
  connect: (url: string) => Promise<TClient> | TClient;
4
4
  disconnect?: (client: TClient) => Promise<void> | void;
@@ -1 +1 @@
1
- {"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["../../../src/adapters/db/prisma.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,MAAM,WAAW,oBAAoB,CAAC,OAAO;IAC3C,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACrD,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED,wBAAgB,mBAAmB,CACjC,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,OAAO,SAAS;IAAE,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAAE,EAE5D,OAAO,EAAE,oBAAoB,CAAC,OAAO,CAAC,GACrC,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAexC"}
1
+ {"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["../../../src/adapters/db/prisma.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEjE,MAAM,WAAW,oBAAoB,CAAC,OAAO;IAC3C,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACrD,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED,wBAAgB,mBAAmB,CACjC,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,OAAO,SAAS;IAAE,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAAE,EAE5D,OAAO,EAAE,oBAAoB,CAAC,OAAO,CAAC,GACrC,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAexC"}
@@ -1,11 +1,19 @@
1
1
  import { Hono, type MiddlewareHandler } from "hono";
2
2
  import { cors } from "hono/cors";
3
3
  import { secureHeaders } from "hono/secure-headers";
4
- import { Container } from "./container/container";
5
- import { type DbManagerMiddlewareOptions } from "../database/middleware";
6
- import type { Constructor } from "./types";
4
+ import { Container } from "./container/container.js";
5
+ import { type DbManagerMiddlewareOptions } from "../database/middleware.js";
6
+ import type { Constructor, Token } from "./types.js";
7
+ import type { GuardRef } from "./pipeline/guards.js";
8
+ import type { InterceptorRef } from "./pipeline/interceptors.js";
9
+ import type { FilterRef } from "./pipeline/filters-binding.js";
10
+ import { type StandardSchemaV1 } from "./validation/standard-schema.js";
7
11
  export interface SecurityOptions {
8
- cors?: Parameters<typeof cors>[0];
12
+ /**
13
+ * CORS is opt-in: pass `true` for hono's defaults or a config object.
14
+ * (Earlier versions applied permissive CORS unconditionally.)
15
+ */
16
+ cors?: boolean | Parameters<typeof cors>[0];
9
17
  secureHeaders?: boolean | Parameters<typeof secureHeaders>[0];
10
18
  }
11
19
  export interface ObservabilityOptions {
@@ -14,6 +22,14 @@ export interface ObservabilityOptions {
14
22
  logLevel?: "debug" | "info" | "warn" | "error";
15
23
  redactHeaders?: string[];
16
24
  }
25
+ export interface ConfigOptions {
26
+ /** Standard Schema validated against the env bindings, once per isolate. */
27
+ schema: StandardSchemaV1;
28
+ /** DI token the validated config is provided under. Default: CONFIG. */
29
+ token?: Token;
30
+ }
31
+ /** Default DI token for the validated environment config. */
32
+ export declare const CONFIG: unique symbol;
17
33
  export interface ApplicationOptions<Env extends Record<string, unknown>> {
18
34
  basePath?: string;
19
35
  globalMiddlewares?: MiddlewareHandler[];
@@ -22,8 +38,38 @@ export interface ApplicationOptions<Env extends Record<string, unknown>> {
22
38
  observability?: ObservabilityOptions;
23
39
  di?: {
24
40
  strict?: boolean;
41
+ /**
42
+ * Resolve a constructor dependency from the parameter NAME when no other
43
+ * token is available.
44
+ *
45
+ * On by default for compatibility, and it warns whenever it actually
46
+ * resolves something — it reads identifiers out of
47
+ * `Function.prototype.toString()`, so a minifier silently breaks it, and it
48
+ * masks a genuinely missing token by appearing to work in development.
49
+ *
50
+ * Set it to `false` in any application that is minified or bundled, which
51
+ * turns that silent degradation into a boot-time error. Prefer
52
+ * `design:paramtypes` (`emitDecoratorMetadata`) or an explicit `@Inject`.
53
+ */
54
+ inferByParamName?: boolean;
25
55
  };
56
+ /** Global guards, first in the guard chain. Classes are DI-resolved. */
57
+ guards?: GuardRef[];
58
+ /** Global interceptors, outermost in the onion. */
59
+ interceptors?: InterceptorRef[];
60
+ /** Global exception filters, last before the default mapping. */
61
+ filters?: FilterRef[];
62
+ /** Env-bindings validation + typed CONFIG provider. */
63
+ config?: ConfigOptions;
64
+ /** Respond 405 + Allow instead of 404 for known paths (hono middleware). */
65
+ methodNotAllowed?: boolean;
26
66
  }
67
+ /** Context key holding the id assigned to the current request. */
68
+ export declare const REQUEST_ID_CONTEXT_KEY = "requestId";
69
+ /** Reads the current request id, wherever in the pipeline you are. */
70
+ export declare function getRequestId(c: {
71
+ get(key: never): unknown;
72
+ }): string | undefined;
27
73
  export declare class Application<Env extends Record<string, unknown> = Record<string, unknown>> {
28
74
  private app;
29
75
  private readonly container;
@@ -32,13 +78,31 @@ export declare class Application<Env extends Record<string, unknown> = Record<st
32
78
  private readonly registeredControllers;
33
79
  private readonly registeredModules;
34
80
  private readonly moduleResolutionStack;
81
+ private configValidation;
82
+ private configOptions;
35
83
  constructor(options?: ApplicationOptions<Env>);
84
+ /**
85
+ * Validates env bindings once per isolate (single-flight across concurrent
86
+ * first requests) and provides the parsed value under the config token.
87
+ * Env bindings only exist at request time on Workers, so this cannot run at
88
+ * construction.
89
+ */
90
+ private registerConfig;
91
+ /**
92
+ * Validates env bindings once per isolate and registers the CONFIG
93
+ * provider. Public because non-fetch handlers (scheduled(), queue()) enter
94
+ * outside the HTTP middleware chain and must be able to prepare config too.
95
+ */
96
+ ensureConfig(env: unknown): Promise<void>;
36
97
  registerModule(moduleClass: Constructor): this;
98
+ private registerProviderDefinition;
37
99
  use(middleware: MiddlewareHandler): this;
38
100
  getContainer(): Container;
39
101
  getHono(): Hono<{
40
102
  Bindings: Env;
41
103
  }>;
104
+ /** Class providers registered so far — used by the scheduling dispatcher. */
105
+ getRegisteredProviders(): Constructor[];
42
106
  fetch: (request: Request, env: Env, ctx: any) => Response | Promise<Response>;
43
107
  }
44
108
  export declare function createApp<Env extends Record<string, unknown> = Record<string, unknown>>(options?: ApplicationOptions<Env>): Application<Env>;
@@ -1 +1 @@
1
- {"version":3,"file":"application.d.ts","sourceRoot":"","sources":["../../src/core/application.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,KAAK,iBAAiB,EAAE,MAAM,MAAM,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAA6B,KAAK,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAEpG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAG3C,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAClC,aAAa,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,oBAAoB;IACnC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAC/C,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB,CAAC,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACxC,QAAQ,CAAC,EAAE,0BAA0B,CAAC,GAAG,CAAC,CAAC;IAC3C,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,aAAa,CAAC,EAAE,oBAAoB,CAAC;IACrC,EAAE,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC3B;AA6CD,qBAAa,WAAW,CAAC,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACpF,OAAO,CAAC,GAAG,CAA0B;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAA0B;IAC9D,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAA0B;IAChE,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA0B;IAC5D,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAqB;gBAE/C,OAAO,GAAE,kBAAkB,CAAC,GAAG,CAAM;IA0CjD,cAAc,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI;IA2D9C,GAAG,CAAC,UAAU,EAAE,iBAAiB,GAAG,IAAI;IAKxC,YAAY,IAAI,SAAS;IAIzB,OAAO,IAAI,IAAI,CAAC;QAAE,QAAQ,EAAE,GAAG,CAAA;KAAE,CAAC;IAIlC,KAAK,GAAI,SAAS,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,GAAG,KAAG,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAE1E;CACH;AAED,wBAAgB,SAAS,CAAC,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrF,OAAO,GAAE,kBAAkB,CAAC,GAAG,CAAM,GACpC,WAAW,CAAC,GAAG,CAAC,CAElB"}
1
+ {"version":3,"file":"application.d.ts","sourceRoot":"","sources":["../../src/core/application.ts"],"names":[],"mappings":"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"}
@@ -1,21 +1,43 @@
1
1
  import { Hono } from "hono";
2
2
  import { cors } from "hono/cors";
3
3
  import { secureHeaders } from "hono/secure-headers";
4
- import { Container } from "./container/container";
5
- import { createDbManagerMiddleware } from "../database/middleware";
6
- import { Router } from "./router/router";
7
- import { getInjectableMetadata, getModuleMetadata } from "./metadata";
4
+ import { methodNotAllowed } from "hono/method-not-allowed";
5
+ import { Container } from "./container/container.js";
6
+ import { createDbManagerMiddleware } from "../database/middleware.js";
7
+ import { Router } from "./router/router.js";
8
+ import { getInjectableMetadata, getModuleMetadata } from "./metadata.js";
9
+ import { isStandardSchema } from "./validation/standard-schema.js";
10
+ /** Default DI token for the validated environment config. */
11
+ export const CONFIG = Symbol("honova:config");
8
12
  const defaultRedactHeaders = ["authorization", "cookie", "set-cookie", "x-api-key"];
13
+ /** Context key holding the id assigned to the current request. */
14
+ export const REQUEST_ID_CONTEXT_KEY = "requestId";
15
+ /** Reads the current request id, wherever in the pipeline you are. */
16
+ export function getRequestId(c) {
17
+ return c.get(REQUEST_ID_CONTEXT_KEY);
18
+ }
9
19
  function createObservabilityMiddleware(options) {
10
20
  const requestIdHeader = options?.requestIdHeader ?? "x-request-id";
11
21
  const accessLogs = options?.enableAccessLogs ?? true;
12
22
  const level = options?.logLevel ?? "info";
13
- const redactHeaders = options?.redactHeaders ?? defaultRedactHeaders;
23
+ // Custom entries extend the defaults rather than replace them — replacing
24
+ // silently re-enabled Authorization/Cookie logging at debug level.
25
+ const redactHeaders = [...defaultRedactHeaders, ...(options?.redactHeaders ?? [])];
26
+ // Client-supplied ids are convenient for tracing but must not become a
27
+ // header/log injection vector: accept a conservative charset or mint one.
28
+ const safeIdPattern = /^[A-Za-z0-9._-]{1,128}$/;
14
29
  return async (c, next) => {
15
- const requestId = c.req.header(requestIdHeader) ?? crypto.randomUUID();
30
+ const incoming = c.req.header(requestIdHeader);
31
+ const requestId = incoming && safeIdPattern.test(incoming) ? incoming : crypto.randomUUID();
16
32
  const start = Date.now();
33
+ // Published on the context before the request runs, not just on the way
34
+ // out: exception filters and handlers build response bodies mid-request and
35
+ // need the same id that ends up in the header and the access log.
36
+ c.set(REQUEST_ID_CONTEXT_KEY, requestId);
17
37
  await next();
18
- c.res.headers.set(requestIdHeader, requestId);
38
+ // c.header() instead of c.res.headers.set(): a handler that returns a
39
+ // proxied fetch() Response has immutable headers, which .set() throws on.
40
+ c.header(requestIdHeader, requestId);
19
41
  if (!accessLogs) {
20
42
  return;
21
43
  }
@@ -51,30 +73,90 @@ export class Application {
51
73
  };
52
74
  this.app = new Hono({ strict: false });
53
75
  this.container = new Container();
54
- this.container.configure({ strict: options.di?.strict ?? true });
76
+ this.container.configure({
77
+ strict: options.di?.strict ?? true,
78
+ inferByParamName: options.di?.inferByParamName ?? true,
79
+ });
80
+ Container.setActive(this.container);
55
81
  if (options.basePath) {
56
82
  this.app = this.app.basePath(options.basePath);
57
83
  }
58
84
  this.app.use("*", createObservabilityMiddleware(options.observability));
59
- this.app.use("*", cors(options.security?.cors));
85
+ if (options.security?.cors) {
86
+ const settings = typeof options.security.cors === "boolean" ? undefined : options.security.cors;
87
+ this.app.use("*", cors(settings));
88
+ }
60
89
  if (options.security?.secureHeaders) {
61
90
  const settings = typeof options.security.secureHeaders === "boolean"
62
91
  ? undefined
63
92
  : options.security.secureHeaders;
64
93
  this.app.use("*", secureHeaders(settings));
65
94
  }
95
+ if (options.config) {
96
+ this.registerConfig(options.config);
97
+ }
66
98
  if (options.database) {
67
99
  this.app.use("*", createDbManagerMiddleware(options.database));
68
100
  }
69
101
  for (const middleware of options.globalMiddlewares ?? []) {
70
102
  this.app.use("*", middleware);
71
103
  }
104
+ if (options.methodNotAllowed) {
105
+ this.app.use("*", methodNotAllowed({ app: this.app }));
106
+ }
72
107
  this.app.notFound((c) => c.json({ error: { code: "not_found", message: "Route not found" } }, 404));
73
108
  this.app.onError((err, c) => {
74
109
  console.error(err);
75
110
  return c.json({ error: { code: "internal_error", message: "Internal server error" } }, 500);
76
111
  });
77
- this.router = new Router(this.app, this.container);
112
+ const globals = {
113
+ guards: options.guards ?? [],
114
+ interceptors: options.interceptors ?? [],
115
+ filters: options.filters ?? [],
116
+ };
117
+ this.router = new Router(this.app, this.container, globals);
118
+ }
119
+ /**
120
+ * Validates env bindings once per isolate (single-flight across concurrent
121
+ * first requests) and provides the parsed value under the config token.
122
+ * Env bindings only exist at request time on Workers, so this cannot run at
123
+ * construction.
124
+ */
125
+ registerConfig(config) {
126
+ if (!isStandardSchema(config.schema)) {
127
+ throw new Error("config.schema must implement Standard Schema v1 (zod >= 3.24, valibot, arktype).");
128
+ }
129
+ this.configOptions = config;
130
+ this.app.use("*", async (c, next) => {
131
+ await this.ensureConfig(c.env);
132
+ await next();
133
+ });
134
+ }
135
+ /**
136
+ * Validates env bindings once per isolate and registers the CONFIG
137
+ * provider. Public because non-fetch handlers (scheduled(), queue()) enter
138
+ * outside the HTTP middleware chain and must be able to prepare config too.
139
+ */
140
+ async ensureConfig(env) {
141
+ const config = this.configOptions;
142
+ if (!config) {
143
+ return;
144
+ }
145
+ if (!this.configValidation) {
146
+ const token = config.token ?? CONFIG;
147
+ this.configValidation = Promise.resolve(config.schema["~standard"].validate(env)).then((result) => {
148
+ if (result.issues) {
149
+ const detail = result.issues.map((issue) => issue.message).join("; ");
150
+ throw new Error(`Environment validation failed: ${detail}`);
151
+ }
152
+ this.container.registerProvider({ provide: token, useValue: result.value });
153
+ });
154
+ // Allow retry on the next request if validation itself failed.
155
+ this.configValidation.catch(() => {
156
+ this.configValidation = undefined;
157
+ });
158
+ }
159
+ await this.configValidation;
78
160
  }
79
161
  registerModule(moduleClass) {
80
162
  if (this.registeredModules.has(moduleClass)) {
@@ -97,14 +179,7 @@ export class Application {
97
179
  this.registerModule(imported);
98
180
  }
99
181
  for (const provider of metadata.providers ?? []) {
100
- const injectableMetadata = getInjectableMetadata(provider);
101
- if (!injectableMetadata) {
102
- throw new Error(`Provider ${provider.name} in ${moduleClass.name} must be decorated with @Injectable().`);
103
- }
104
- if (!this.registeredProviders.has(provider)) {
105
- this.container.register(provider);
106
- this.registeredProviders.add(provider);
107
- }
182
+ this.registerProviderDefinition(provider, moduleClass);
108
183
  }
109
184
  for (const controller of metadata.controllers ?? []) {
110
185
  if (this.registeredControllers.has(controller)) {
@@ -121,6 +196,25 @@ export class Application {
121
196
  this.moduleResolutionStack.pop();
122
197
  }
123
198
  }
199
+ registerProviderDefinition(provider, moduleClass) {
200
+ if (typeof provider === "function") {
201
+ const injectableMetadata = getInjectableMetadata(provider);
202
+ if (!injectableMetadata) {
203
+ throw new Error(`Provider ${provider.name} in ${moduleClass.name} must be decorated with @Injectable().`);
204
+ }
205
+ if (!this.registeredProviders.has(provider)) {
206
+ this.container.register(provider);
207
+ this.registeredProviders.add(provider);
208
+ }
209
+ return;
210
+ }
211
+ // Custom providers (useValue/useFactory/useClass/useExisting) need no
212
+ // @Injectable — the definition itself is the registration.
213
+ this.container.registerProvider(provider);
214
+ if ("useClass" in provider) {
215
+ this.registeredProviders.add(provider.useClass);
216
+ }
217
+ }
124
218
  use(middleware) {
125
219
  this.app.use("*", middleware);
126
220
  return this;
@@ -131,6 +225,10 @@ export class Application {
131
225
  getHono() {
132
226
  return this.app;
133
227
  }
228
+ /** Class providers registered so far — used by the scheduling dispatcher. */
229
+ getRegisteredProviders() {
230
+ return [...this.registeredProviders];
231
+ }
134
232
  }
135
233
  export function createApp(options = {}) {
136
234
  return new Application(options);