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.
- package/README.md +159 -7
- package/dist/adapters/db/drizzle.d.ts +1 -1
- package/dist/adapters/db/drizzle.d.ts.map +1 -1
- package/dist/adapters/db/index.d.ts +4 -4
- package/dist/adapters/db/index.d.ts.map +1 -1
- package/dist/adapters/db/index.js +4 -4
- package/dist/adapters/db/index.js.map +1 -1
- package/dist/adapters/db/mondel.d.ts +1 -1
- package/dist/adapters/db/mondel.d.ts.map +1 -1
- package/dist/adapters/db/mondel.js +1 -1
- package/dist/adapters/db/mondel.js.map +1 -1
- package/dist/adapters/db/mongodb.d.ts +1 -1
- package/dist/adapters/db/mongodb.d.ts.map +1 -1
- package/dist/adapters/db/prisma.d.ts +1 -1
- package/dist/adapters/db/prisma.d.ts.map +1 -1
- package/dist/core/application.d.ts +68 -4
- package/dist/core/application.d.ts.map +1 -1
- package/dist/core/application.js +116 -18
- package/dist/core/application.js.map +1 -1
- package/dist/core/container/container.d.ts +59 -7
- package/dist/core/container/container.d.ts.map +1 -1
- package/dist/core/container/container.js +384 -56
- package/dist/core/container/container.js.map +1 -1
- package/dist/core/container/index.d.ts +2 -2
- package/dist/core/container/index.d.ts.map +1 -1
- package/dist/core/container/index.js +2 -2
- package/dist/core/container/index.js.map +1 -1
- package/dist/core/container/inject.d.ts +6 -1
- package/dist/core/container/inject.d.ts.map +1 -1
- package/dist/core/container/inject.js +7 -2
- package/dist/core/container/inject.js.map +1 -1
- package/dist/core/decorators/controller.d.ts.map +1 -1
- package/dist/core/decorators/controller.js +38 -16
- package/dist/core/decorators/controller.js.map +1 -1
- package/dist/core/decorators/http-methods.d.ts +1 -0
- package/dist/core/decorators/http-methods.d.ts.map +1 -1
- package/dist/core/decorators/http-methods.js +4 -1
- package/dist/core/decorators/http-methods.js.map +1 -1
- package/dist/core/decorators/http-response.d.ts +15 -0
- package/dist/core/decorators/http-response.d.ts.map +1 -0
- package/dist/core/decorators/http-response.js +44 -0
- package/dist/core/decorators/http-response.js.map +1 -0
- package/dist/core/decorators/index.d.ts +7 -7
- package/dist/core/decorators/index.d.ts.map +1 -1
- package/dist/core/decorators/index.js +7 -7
- package/dist/core/decorators/index.js.map +1 -1
- package/dist/core/decorators/inject.d.ts +17 -2
- package/dist/core/decorators/inject.d.ts.map +1 -1
- package/dist/core/decorators/inject.js +43 -2
- package/dist/core/decorators/inject.js.map +1 -1
- package/dist/core/decorators/injectable.js +1 -1
- package/dist/core/decorators/injectable.js.map +1 -1
- package/dist/core/decorators/middleware.d.ts.map +1 -1
- package/dist/core/decorators/middleware.js +4 -2
- package/dist/core/decorators/middleware.js.map +1 -1
- package/dist/core/decorators/module.d.ts +1 -1
- package/dist/core/decorators/module.d.ts.map +1 -1
- package/dist/core/decorators/module.js +1 -1
- package/dist/core/decorators/module.js.map +1 -1
- package/dist/core/design-types.d.ts +21 -0
- package/dist/core/design-types.d.ts.map +1 -0
- package/dist/core/design-types.js +86 -0
- package/dist/core/design-types.js.map +1 -0
- package/dist/core/exceptions/filters.d.ts +22 -0
- package/dist/core/exceptions/filters.d.ts.map +1 -0
- package/dist/core/exceptions/filters.js +40 -0
- package/dist/core/exceptions/filters.js.map +1 -0
- package/dist/core/exceptions/http-exception.d.ts +91 -0
- package/dist/core/exceptions/http-exception.d.ts.map +1 -0
- package/dist/core/exceptions/http-exception.js +72 -0
- package/dist/core/exceptions/http-exception.js.map +1 -0
- package/dist/core/exceptions/index.d.ts +3 -0
- package/dist/core/exceptions/index.d.ts.map +1 -0
- package/dist/core/exceptions/index.js +3 -0
- package/dist/core/exceptions/index.js.map +1 -0
- package/dist/core/execution-context.d.ts +30 -0
- package/dist/core/execution-context.d.ts.map +1 -0
- package/dist/core/execution-context.js +46 -0
- package/dist/core/execution-context.js.map +1 -0
- package/dist/core/metadata.d.ts +1 -1
- package/dist/core/metadata.d.ts.map +1 -1
- package/dist/core/metadata.js +14 -0
- package/dist/core/metadata.js.map +1 -1
- package/dist/core/pipeline/filters-binding.d.ts +7 -0
- package/dist/core/pipeline/filters-binding.d.ts.map +1 -0
- package/dist/core/pipeline/filters-binding.js +45 -0
- package/dist/core/pipeline/filters-binding.js.map +1 -0
- package/dist/core/pipeline/guards.d.ts +15 -0
- package/dist/core/pipeline/guards.d.ts.map +1 -0
- package/dist/core/pipeline/guards.js +48 -0
- package/dist/core/pipeline/guards.js.map +1 -0
- package/dist/core/pipeline/index.d.ts +5 -0
- package/dist/core/pipeline/index.d.ts.map +1 -0
- package/dist/core/pipeline/index.js +5 -0
- package/dist/core/pipeline/index.js.map +1 -0
- package/dist/core/pipeline/interceptors.d.ts +20 -0
- package/dist/core/pipeline/interceptors.d.ts.map +1 -0
- package/dist/core/pipeline/interceptors.js +45 -0
- package/dist/core/pipeline/interceptors.js.map +1 -0
- package/dist/core/pipeline/public.d.ts +3 -0
- package/dist/core/pipeline/public.d.ts.map +1 -0
- package/dist/core/pipeline/public.js +46 -0
- package/dist/core/pipeline/public.js.map +1 -0
- package/dist/core/reflector.d.ts +26 -0
- package/dist/core/reflector.d.ts.map +1 -0
- package/dist/core/reflector.js +81 -0
- package/dist/core/reflector.js.map +1 -0
- package/dist/core/router/index.d.ts +1 -1
- package/dist/core/router/index.d.ts.map +1 -1
- package/dist/core/router/index.js +1 -1
- package/dist/core/router/index.js.map +1 -1
- package/dist/core/router/router.d.ts +28 -3
- package/dist/core/router/router.d.ts.map +1 -1
- package/dist/core/router/router.js +152 -11
- package/dist/core/router/router.js.map +1 -1
- package/dist/core/standalone.d.ts +18 -0
- package/dist/core/standalone.d.ts.map +1 -0
- package/dist/core/standalone.js +61 -0
- package/dist/core/standalone.js.map +1 -0
- package/dist/core/types.d.ts +28 -3
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/validation/index.d.ts +3 -0
- package/dist/core/validation/index.d.ts.map +1 -0
- package/dist/core/validation/index.js +3 -0
- package/dist/core/validation/index.js.map +1 -0
- package/dist/core/validation/standard-schema.d.ts +29 -0
- package/dist/core/validation/standard-schema.d.ts.map +1 -0
- package/dist/core/validation/standard-schema.js +7 -0
- package/dist/core/validation/standard-schema.js.map +1 -0
- package/dist/core/validation/validate.d.ts +32 -0
- package/dist/core/validation/validate.d.ts.map +1 -0
- package/dist/core/validation/validate.js +130 -0
- package/dist/core/validation/validate.js.map +1 -0
- package/dist/database/index.d.ts +4 -3
- package/dist/database/index.d.ts.map +1 -1
- package/dist/database/index.js +4 -3
- package/dist/database/index.js.map +1 -1
- package/dist/database/lazy-proxy.d.ts +26 -0
- package/dist/database/lazy-proxy.d.ts.map +1 -0
- package/dist/database/lazy-proxy.js +67 -0
- package/dist/database/lazy-proxy.js.map +1 -0
- package/dist/database/manager.d.ts +10 -0
- package/dist/database/manager.d.ts.map +1 -1
- package/dist/database/manager.js +26 -2
- package/dist/database/manager.js.map +1 -1
- package/dist/database/middleware.d.ts +1 -1
- package/dist/database/middleware.d.ts.map +1 -1
- package/dist/database/middleware.js +1 -1
- package/dist/database/middleware.js.map +1 -1
- package/dist/database/types.d.ts +3 -3
- package/dist/database/types.d.ts.map +1 -1
- package/dist/database/types.js +1 -1
- package/dist/database/types.js.map +1 -1
- package/dist/index.d.ts +19 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -9
- package/dist/index.js.map +1 -1
- package/dist/middleware/auth/apikey.js +1 -1
- package/dist/middleware/auth/apikey.js.map +1 -1
- package/dist/middleware/auth/bearer.js +1 -1
- package/dist/middleware/auth/bearer.js.map +1 -1
- package/dist/middleware/auth/index.d.ts +3 -2
- package/dist/middleware/auth/index.d.ts.map +1 -1
- package/dist/middleware/auth/index.js +3 -2
- package/dist/middleware/auth/index.js.map +1 -1
- package/dist/middleware/auth/jwt.d.ts +41 -0
- package/dist/middleware/auth/jwt.d.ts.map +1 -0
- package/dist/middleware/auth/jwt.js +81 -0
- package/dist/middleware/auth/jwt.js.map +1 -0
- package/dist/scheduling/index.d.ts +28 -0
- package/dist/scheduling/index.d.ts.map +1 -0
- package/dist/scheduling/index.js +92 -0
- package/dist/scheduling/index.js.map +1 -0
- package/dist/testing/index.d.ts +38 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +77 -0
- package/dist/testing/index.js.map +1 -0
- 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
|
|
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
|
-
- [
|
|
68
|
-
- [
|
|
69
|
-
- [
|
|
70
|
-
- [
|
|
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
|
-
- [
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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;
|
|
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;AAGpD,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAA6B,KAAK,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAEvG,OAAO,KAAK,EAAE,WAAW,EAAsB,KAAK,EAAE,MAAM,YAAY,CAAC;AAEzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAE1F,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5C,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,aAAa;IAC5B,4EAA4E;IAC5E,MAAM,EAAE,gBAAgB,CAAC;IACzB,wEAAwE;IACxE,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,6DAA6D;AAC7D,eAAO,MAAM,MAAM,EAAE,OAAO,MAAgC,CAAC;AAE7D,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;QACH,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB;;;;;;;;;;;;WAYG;QACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;KAC5B,CAAC;IACF,wEAAwE;IACxE,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,mDAAmD;IACnD,YAAY,CAAC,EAAE,cAAc,EAAE,CAAC;IAChC,iEAAiE;IACjE,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;IACtB,uDAAuD;IACvD,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAID,kEAAkE;AAClE,eAAO,MAAM,sBAAsB,cAAc,CAAC;AAElD,sEAAsE;AACtE,wBAAgB,YAAY,CAAC,CAAC,EAAE;IAAE,GAAG,CAAC,GAAG,EAAE,KAAK,GAAG,OAAO,CAAA;CAAE,GAAG,MAAM,GAAG,SAAS,CAEhF;AAyDD,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;IAC3D,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,aAAa,CAA4B;gBAErC,OAAO,GAAE,kBAAkB,CAAC,GAAG,CAAM;IAgEjD;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IAYtB;;;;OAIG;IACG,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IA2B/C,cAAc,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI;IAiD9C,OAAO,CAAC,0BAA0B;IA6BlC,GAAG,CAAC,UAAU,EAAE,iBAAiB,GAAG,IAAI;IAKxC,YAAY,IAAI,SAAS;IAIzB,OAAO,IAAI,IAAI,CAAC;QAAE,QAAQ,EAAE,GAAG,CAAA;KAAE,CAAC;IAIlC,6EAA6E;IAC7E,sBAAsB,IAAI,WAAW,EAAE;IAIvC,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"}
|
package/dist/core/application.js
CHANGED
|
@@ -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 {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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
|
-
|
|
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
|
|
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(
|
|
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({
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|