honovajs 0.0.1

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 (122) hide show
  1. package/README.md +366 -0
  2. package/dist/adapters/db/drizzle.d.ts +7 -0
  3. package/dist/adapters/db/drizzle.d.ts.map +1 -0
  4. package/dist/adapters/db/drizzle.js +8 -0
  5. package/dist/adapters/db/drizzle.js.map +1 -0
  6. package/dist/adapters/db/index.d.ts +5 -0
  7. package/dist/adapters/db/index.d.ts.map +1 -0
  8. package/dist/adapters/db/index.js +5 -0
  9. package/dist/adapters/db/index.js.map +1 -0
  10. package/dist/adapters/db/mondel.d.ts +2 -0
  11. package/dist/adapters/db/mondel.d.ts.map +1 -0
  12. package/dist/adapters/db/mondel.js +2 -0
  13. package/dist/adapters/db/mondel.js.map +1 -0
  14. package/dist/adapters/db/mongodb.d.ts +7 -0
  15. package/dist/adapters/db/mongodb.d.ts.map +1 -0
  16. package/dist/adapters/db/mongodb.js +8 -0
  17. package/dist/adapters/db/mongodb.js.map +1 -0
  18. package/dist/adapters/db/prisma.d.ts +9 -0
  19. package/dist/adapters/db/prisma.d.ts.map +1 -0
  20. package/dist/adapters/db/prisma.js +16 -0
  21. package/dist/adapters/db/prisma.js.map +1 -0
  22. package/dist/core/application.d.ts +45 -0
  23. package/dist/core/application.d.ts.map +1 -0
  24. package/dist/core/application.js +138 -0
  25. package/dist/core/application.js.map +1 -0
  26. package/dist/core/constants.d.ts +9 -0
  27. package/dist/core/constants.d.ts.map +1 -0
  28. package/dist/core/constants.js +9 -0
  29. package/dist/core/constants.js.map +1 -0
  30. package/dist/core/container/container.d.ts +34 -0
  31. package/dist/core/container/container.d.ts.map +1 -0
  32. package/dist/core/container/container.js +243 -0
  33. package/dist/core/container/container.js.map +1 -0
  34. package/dist/core/container/index.d.ts +3 -0
  35. package/dist/core/container/index.d.ts.map +1 -0
  36. package/dist/core/container/index.js +3 -0
  37. package/dist/core/container/index.js.map +1 -0
  38. package/dist/core/container/inject.d.ts +3 -0
  39. package/dist/core/container/inject.d.ts.map +1 -0
  40. package/dist/core/container/inject.js +9 -0
  41. package/dist/core/container/inject.js.map +1 -0
  42. package/dist/core/decorators/context.d.ts +12 -0
  43. package/dist/core/decorators/context.d.ts.map +1 -0
  44. package/dist/core/decorators/context.js +2 -0
  45. package/dist/core/decorators/context.js.map +1 -0
  46. package/dist/core/decorators/controller.d.ts +2 -0
  47. package/dist/core/decorators/controller.d.ts.map +1 -0
  48. package/dist/core/decorators/controller.js +52 -0
  49. package/dist/core/decorators/controller.js.map +1 -0
  50. package/dist/core/decorators/http-methods.d.ts +9 -0
  51. package/dist/core/decorators/http-methods.d.ts.map +1 -0
  52. package/dist/core/decorators/http-methods.js +45 -0
  53. package/dist/core/decorators/http-methods.js.map +1 -0
  54. package/dist/core/decorators/index.d.ts +8 -0
  55. package/dist/core/decorators/index.d.ts.map +1 -0
  56. package/dist/core/decorators/index.js +8 -0
  57. package/dist/core/decorators/index.js.map +1 -0
  58. package/dist/core/decorators/inject.d.ts +3 -0
  59. package/dist/core/decorators/inject.d.ts.map +1 -0
  60. package/dist/core/decorators/inject.js +8 -0
  61. package/dist/core/decorators/inject.js.map +1 -0
  62. package/dist/core/decorators/injectable.d.ts +6 -0
  63. package/dist/core/decorators/injectable.d.ts.map +1 -0
  64. package/dist/core/decorators/injectable.js +10 -0
  65. package/dist/core/decorators/injectable.js.map +1 -0
  66. package/dist/core/decorators/middleware.d.ts +3 -0
  67. package/dist/core/decorators/middleware.d.ts.map +1 -0
  68. package/dist/core/decorators/middleware.js +45 -0
  69. package/dist/core/decorators/middleware.js.map +1 -0
  70. package/dist/core/decorators/module.d.ts +3 -0
  71. package/dist/core/decorators/module.d.ts.map +1 -0
  72. package/dist/core/decorators/module.js +7 -0
  73. package/dist/core/decorators/module.js.map +1 -0
  74. package/dist/core/metadata.d.ts +17 -0
  75. package/dist/core/metadata.d.ts.map +1 -0
  76. package/dist/core/metadata.js +72 -0
  77. package/dist/core/metadata.js.map +1 -0
  78. package/dist/core/router/index.d.ts +2 -0
  79. package/dist/core/router/index.d.ts.map +1 -0
  80. package/dist/core/router/index.js +2 -0
  81. package/dist/core/router/index.js.map +1 -0
  82. package/dist/core/router/router.d.ts +14 -0
  83. package/dist/core/router/router.d.ts.map +1 -0
  84. package/dist/core/router/router.js +60 -0
  85. package/dist/core/router/router.js.map +1 -0
  86. package/dist/core/types.d.ts +29 -0
  87. package/dist/core/types.d.ts.map +1 -0
  88. package/dist/core/types.js +2 -0
  89. package/dist/core/types.js.map +1 -0
  90. package/dist/database/index.d.ts +4 -0
  91. package/dist/database/index.d.ts.map +1 -0
  92. package/dist/database/index.js +4 -0
  93. package/dist/database/index.js.map +1 -0
  94. package/dist/database/manager.d.ts +72 -0
  95. package/dist/database/manager.d.ts.map +1 -0
  96. package/dist/database/manager.js +160 -0
  97. package/dist/database/manager.js.map +1 -0
  98. package/dist/database/middleware.d.ts +21 -0
  99. package/dist/database/middleware.d.ts.map +1 -0
  100. package/dist/database/middleware.js +23 -0
  101. package/dist/database/middleware.js.map +1 -0
  102. package/dist/database/types.d.ts +31 -0
  103. package/dist/database/types.d.ts.map +1 -0
  104. package/dist/database/types.js +24 -0
  105. package/dist/database/types.js.map +1 -0
  106. package/dist/index.d.ts +10 -0
  107. package/dist/index.d.ts.map +1 -0
  108. package/dist/index.js +10 -0
  109. package/dist/index.js.map +1 -0
  110. package/dist/middleware/auth/apikey.d.ts +16 -0
  111. package/dist/middleware/auth/apikey.d.ts.map +1 -0
  112. package/dist/middleware/auth/apikey.js +37 -0
  113. package/dist/middleware/auth/apikey.js.map +1 -0
  114. package/dist/middleware/auth/bearer.d.ts +17 -0
  115. package/dist/middleware/auth/bearer.d.ts.map +1 -0
  116. package/dist/middleware/auth/bearer.js +41 -0
  117. package/dist/middleware/auth/bearer.js.map +1 -0
  118. package/dist/middleware/auth/index.d.ts +3 -0
  119. package/dist/middleware/auth/index.d.ts.map +1 -0
  120. package/dist/middleware/auth/index.js +3 -0
  121. package/dist/middleware/auth/index.js.map +1 -0
  122. package/package.json +47 -0
package/README.md ADDED
@@ -0,0 +1,366 @@
1
+ # Honova
2
+
3
+ Nest-like framework for Cloudflare Workers built on top of Hono.
4
+
5
+ Honova provides a module system, decorators, dependency injection, route metadata, request-aware lifecycle, and typed database middleware so you can build structured APIs on the edge without bringing a full Node-centric runtime.
6
+
7
+ ## Status
8
+
9
+ - Package version: `0.0.1`
10
+ - Runtime target: Cloudflare Workers (also works anywhere Hono runs)
11
+ - Module format: ESM
12
+
13
+ ## Feature Checklist
14
+
15
+ ### Core Framework
16
+
17
+ - [x] `@Module()` with `controllers`, `providers`, and `imports`
18
+ - [x] `@Controller()` with route prefixing
19
+ - [x] HTTP method decorators: `@Get`, `@Post`, `@Put`, `@Patch`, `@Delete`, `@Options`, `@Head`
20
+ - [x] Dependency Injection container
21
+ - [x] `@Injectable()` scopes (`singleton`, `request`, `transient`)
22
+ - [x] `@Inject()` token-based constructor injection
23
+ - [x] Provider auto-inference by constructor parameter name
24
+ - [x] Request-aware provider resolution (`resolveWithContext`)
25
+ - [x] `OnModuleInit` lifecycle hook (sync)
26
+ - [x] Global middleware registration
27
+ - [x] Controller-level and route-level middleware via `@UseMiddleware`
28
+ - [x] Built-in `not_found` and `internal_error` JSON responses
29
+
30
+ ### Security and Observability
31
+
32
+ - [x] CORS integration (`hono/cors`)
33
+ - [x] Optional secure headers (`hono/secure-headers`)
34
+ - [x] Request ID propagation (`x-request-id` by default)
35
+ - [x] Access logs with log level control (`debug`, `info`, `warn`, `error`)
36
+ - [x] Header redaction in debug logs
37
+
38
+ ### Authentication Middleware
39
+
40
+ - [x] Bearer auth middleware (`UseBearerAuth`, `BearerAuth`)
41
+ - [x] API key middleware (`UseApiKey`, `ApiKeyAuth`)
42
+ - [x] Auth principal injection into context (`c.set("auth", principal)`)
43
+ - [x] Overrideable auth error handling
44
+
45
+ ### Database Layer
46
+
47
+ - [x] `defineDatabase()` typed config helper
48
+ - [x] `DatabaseManager` with per-request lifecycle
49
+ - [x] Single and multi-connection support
50
+ - [x] `defaultConnection` enforcement for multi-DB setups
51
+ - [x] URL resolution from static URL, env binding, or async resolver
52
+ - [x] Eager connection initialization
53
+ - [x] Connection cleanup hooks
54
+ - [x] Context bindings: `c.db`, `c.get("db")`, `c.get("dbConnections")`
55
+ - [x] Adapter helpers: Drizzle, Prisma, MongoDB (`createMongoDbAdapter`) with legacy alias (`createMondelAdapter`)
56
+
57
+ ### Tests
58
+
59
+ - [x] DI behavior tests
60
+ - [x] Provider validation tests (`@Injectable` required)
61
+ - [x] Lifecycle behavior tests (`onModuleInit` with request context)
62
+ - [x] Database manager and middleware tests
63
+
64
+ ### Planned / Missing Features
65
+
66
+ - [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
71
+ - [ ] First-class OpenAPI/Swagger generation
72
+ - [ ] CLI scaffolding (`create-honova-app`, generators)
73
+ - [ ] Official Cloudflare starter templates (D1, KV, R2 examples)
74
+ - [ ] More auth primitives (JWT helper, role/permission guard, session middleware)
75
+ - [ ] E2E test suite with Worker runtime integration
76
+ - [ ] Benchmark/performance suite and guidance
77
+ - [ ] Complete API docs website
78
+
79
+ ## Installation
80
+
81
+ ```bash
82
+ npm i honovajs
83
+ ```
84
+
85
+ ```bash
86
+ pnpm add honovajs
87
+ ```
88
+
89
+ ```bash
90
+ yarn add honovajs
91
+ ```
92
+
93
+ ```bash
94
+ bun add honovajs
95
+ ```
96
+
97
+ ## Quick Start
98
+
99
+ ```ts
100
+ import { Controller, Get, Injectable, Module, createApp } from "honovajs";
101
+
102
+ @Injectable()
103
+ class HealthService {
104
+ getStatus() {
105
+ return { ok: true };
106
+ }
107
+ }
108
+
109
+ @Controller("/health")
110
+ class HealthController {
111
+ constructor(private readonly healthService: HealthService) {}
112
+
113
+ @Get("/")
114
+ handle(): Response {
115
+ return Response.json(this.healthService.getStatus());
116
+ }
117
+ }
118
+
119
+ @Module({
120
+ controllers: [HealthController],
121
+ providers: [HealthService],
122
+ })
123
+ class AppModule {}
124
+
125
+ const app = createApp({ basePath: "/api" });
126
+ app.registerModule(AppModule);
127
+
128
+ export default {
129
+ fetch: app.fetch,
130
+ };
131
+ ```
132
+
133
+ ## Core Concepts
134
+
135
+ ### Modules
136
+
137
+ `@Module()` organizes app boundaries:
138
+
139
+ - `controllers`: classes that expose HTTP handlers
140
+ - `providers`: services managed by the DI container
141
+ - `imports`: other modules to compose features
142
+
143
+ All providers listed in `providers` must have `@Injectable()`.
144
+
145
+ ### Controllers and Routes
146
+
147
+ `@Controller("/prefix")` defines a route prefix. Route handlers are defined with HTTP decorators.
148
+
149
+ ```ts
150
+ @Controller("/users")
151
+ class UserController {
152
+ @Get("/")
153
+ list() {
154
+ return Response.json([]);
155
+ }
156
+ }
157
+ ```
158
+
159
+ ### Dependency Injection
160
+
161
+ Providers are resolved through the container. Constructor dependencies can be resolved by:
162
+
163
+ - explicit token with `@Inject(token)`
164
+ - inferred class token by constructor parameter name
165
+
166
+ Strict DI mode is enabled by default and throws on unresolved dependencies.
167
+
168
+ Provider scopes:
169
+
170
+ - `singleton`: one instance per app container
171
+ - `request`: one instance per request context (reused within the same request)
172
+ - `transient`: new instance on each resolution
173
+
174
+ ### Lifecycle: `OnModuleInit`
175
+
176
+ A provider can implement `onModuleInit(context?)`.
177
+
178
+ Current runtime behavior:
179
+
180
+ - It runs when the provider is resolved for the first time.
181
+ - For singleton providers, it runs once per app instance.
182
+ - Request context is passed when the provider is resolved during a request.
183
+ - It must be synchronous in `0.0.1`.
184
+
185
+ ## Middleware
186
+
187
+ ### Global Middleware
188
+
189
+ ```ts
190
+ const app = createApp({
191
+ globalMiddlewares: [async (c, next) => { await next(); }],
192
+ });
193
+ ```
194
+
195
+ ### Decorator Middleware
196
+
197
+ ```ts
198
+ import { Controller, Get, UseMiddleware } from "honovajs";
199
+
200
+ const requireHeader = async (c: any, next: any) => {
201
+ if (!c.req.header("x-trace")) {
202
+ return c.json({ error: "missing x-trace" }, 400);
203
+ }
204
+ await next();
205
+ };
206
+
207
+ @UseMiddleware(requireHeader)
208
+ @Controller("/demo")
209
+ class DemoController {
210
+ @Get("/")
211
+ handle() {
212
+ return Response.json({ ok: true });
213
+ }
214
+ }
215
+ ```
216
+
217
+ ## Security and Observability
218
+
219
+ Configure at app creation:
220
+
221
+ ```ts
222
+ const app = createApp({
223
+ security: {
224
+ cors: { origin: ["https://app.example.com"] },
225
+ secureHeaders: true,
226
+ },
227
+ observability: {
228
+ requestIdHeader: "x-request-id",
229
+ enableAccessLogs: true,
230
+ logLevel: "info",
231
+ redactHeaders: ["authorization", "cookie", "x-api-key"],
232
+ },
233
+ });
234
+ ```
235
+
236
+ ## Authentication
237
+
238
+ ### Bearer Token
239
+
240
+ ```ts
241
+ import { Controller, Get, UseBearerAuth } from "honovajs";
242
+
243
+ @UseBearerAuth({
244
+ verify: async (token) => (token === "valid-token" ? { id: "user_1" } : null),
245
+ })
246
+ @Controller("/me")
247
+ class MeController {
248
+ @Get("/")
249
+ handle() {
250
+ return Response.json({ ok: true });
251
+ }
252
+ }
253
+ ```
254
+
255
+ ### API Key
256
+
257
+ ```ts
258
+ import { Controller, Get, UseApiKey } from "honovajs";
259
+
260
+ @UseApiKey({
261
+ header: "x-api-key",
262
+ verify: async (key) => (key === "secret" ? { id: "service_1" } : null),
263
+ })
264
+ @Controller("/internal")
265
+ class InternalController {
266
+ @Get("/")
267
+ handle() {
268
+ return Response.json({ ok: true });
269
+ }
270
+ }
271
+ ```
272
+
273
+ ## Database Integration
274
+
275
+ ### Define Connections
276
+
277
+ ```ts
278
+ import { createDbManagerMiddleware, defineDatabase } from "honovajs";
279
+
280
+ type Env = { DATABASE_URL: string; ANALYTICS_URL: string };
281
+
282
+ const database = defineDatabase<Env>()({
283
+ lifecycle: { strategy: "request" }, // default
284
+ defaultConnection: "main",
285
+ connections: [
286
+ {
287
+ connectionName: "main",
288
+ adapter: {
289
+ name: "custom",
290
+ connect: async ({ url }) => ({ url }),
291
+ },
292
+ urlFromEnv: "DATABASE_URL",
293
+ // default connection is always initialized per request
294
+ },
295
+ {
296
+ connectionName: "analytics",
297
+ adapter: {
298
+ name: "custom",
299
+ connect: async ({ url }) => ({ url }),
300
+ },
301
+ urlFromEnv: "ANALYTICS_URL",
302
+ eager: true, // optional; initialize this non-default connection on every request
303
+ },
304
+ ],
305
+ });
306
+
307
+ const app = createApp<Env>({
308
+ database,
309
+ });
310
+ ```
311
+
312
+ Inside handlers:
313
+
314
+ - `c.db` points to the default connection and also exposes named connections.
315
+ - `c.get("dbConnections")` returns all named connections.
316
+ - Non-default connections are lazy by default (`eager: false`).
317
+ - `lifecycle.strategy` defaults to `"request"` (cleanup after each request). Use `"manual"` only when you intentionally manage connection lifecycle yourself.
318
+
319
+ ## Cloudflare Workers Deployment
320
+
321
+ Honova exposes `app.fetch`, so Worker export is direct:
322
+
323
+ ```ts
324
+ export default {
325
+ fetch: app.fetch,
326
+ };
327
+ ```
328
+
329
+ Use your standard Worker workflow with Wrangler:
330
+
331
+ ```bash
332
+ npm run build
333
+ wrangler deploy
334
+ ```
335
+
336
+ ## API Surface
337
+
338
+ Main exports include:
339
+
340
+ - App/runtime: `createApp`, `Application`
341
+ - Decorators: `Module`, `Controller`, `Injectable`, `Inject`, `UseMiddleware`, HTTP method decorators
342
+ - DI helper: `inject`
343
+ - Database: `defineDatabase`, `DatabaseManager`, `createDbManagerMiddleware`, `createDrizzleAdapter`, `createPrismaAdapter`, `createMongoDbAdapter` (legacy `createMondelAdapter`), helpers from `database/types`
344
+ - Auth middleware: `UseBearerAuth`, `BearerAuth`, `UseApiKey`, `ApiKeyAuth`
345
+
346
+ ## Development Scripts
347
+
348
+ ```bash
349
+ npm run typecheck
350
+ npm run test
351
+ npm run build
352
+ ```
353
+
354
+ ## Publish Checklist (npm)
355
+
356
+ - [x] Version set to `0.0.1`
357
+ - [x] ESM build output in `dist/`
358
+ - [x] Type declarations emitted
359
+ - [x] Package `files` restricted to `dist`
360
+ - [x] `prepublishOnly` runs typecheck + tests + build
361
+ - [ ] npm organization metadata (`repository`, `bugs`, `homepage`) if needed
362
+ - [ ] `npm publish --access public`
363
+
364
+ ## License
365
+
366
+ MIT
@@ -0,0 +1,7 @@
1
+ import type { DatabaseAdapter } from "../../database/manager";
2
+ export interface DrizzleAdapterOptions<TClient> {
3
+ connect: (url: string) => Promise<TClient> | TClient;
4
+ disconnect?: (client: TClient) => Promise<void> | void;
5
+ }
6
+ export declare function createDrizzleAdapter<Env extends Record<string, unknown>, TClient>(options: DrizzleAdapterOptions<TClient>): DatabaseAdapter<Env, TClient, unknown>;
7
+ //# sourceMappingURL=drizzle.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,8 @@
1
+ export function createDrizzleAdapter(options) {
2
+ return {
3
+ name: "drizzle",
4
+ connect: async ({ url }) => options.connect(url),
5
+ disconnect: options.disconnect,
6
+ };
7
+ }
8
+ //# sourceMappingURL=drizzle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drizzle.js","sourceRoot":"","sources":["../../../src/adapters/db/drizzle.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,oBAAoB,CAIlC,OAAuC;IAEvC,OAAO;QACL,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;QAChD,UAAU,EAAE,OAAO,CAAC,UAAU;KAC/B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from "./drizzle";
2
+ export * from "./mongodb";
3
+ export * from "./mondel";
4
+ export * from "./prisma";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,5 @@
1
+ export * from "./drizzle";
2
+ export * from "./mongodb";
3
+ export * from "./mondel";
4
+ export * from "./prisma";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"}
@@ -0,0 +1,2 @@
1
+ export { type MongoDbAdapterOptions as MondelAdapterOptions, createMongoDbAdapter as createMondelAdapter, } from "./mongodb";
2
+ //# sourceMappingURL=mondel.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,2 @@
1
+ export { createMongoDbAdapter as createMondelAdapter, } from "./mongodb";
2
+ //# sourceMappingURL=mondel.js.map
@@ -0,0 +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"}
@@ -0,0 +1,7 @@
1
+ import type { DatabaseAdapter } from "../../database/manager";
2
+ export interface MongoDbAdapterOptions<TClient, TConnectOptions> {
3
+ connect: (url: string, options: TConnectOptions) => Promise<TClient> | TClient;
4
+ disconnect?: (client: TClient) => Promise<void> | void;
5
+ }
6
+ export declare function createMongoDbAdapter<Env extends Record<string, unknown>, TClient, TConnectOptions>(adapterOptions: MongoDbAdapterOptions<TClient, TConnectOptions>): DatabaseAdapter<Env, TClient, TConnectOptions>;
7
+ //# sourceMappingURL=mongodb.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,8 @@
1
+ export function createMongoDbAdapter(adapterOptions) {
2
+ return {
3
+ name: "mongodb",
4
+ connect: async ({ url, options }) => adapterOptions.connect(url, options),
5
+ disconnect: adapterOptions.disconnect,
6
+ };
7
+ }
8
+ //# sourceMappingURL=mongodb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mongodb.js","sourceRoot":"","sources":["../../../src/adapters/db/mongodb.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,oBAAoB,CAKlC,cAA+D;IAE/D,OAAO;QACL,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC;QACzE,UAAU,EAAE,cAAc,CAAC,UAAU;KACtC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { DatabaseAdapter } from "../../database/manager";
2
+ export interface PrismaAdapterOptions<TClient> {
3
+ connect: (url: string) => Promise<TClient> | TClient;
4
+ disconnect?: (client: TClient) => Promise<void> | void;
5
+ }
6
+ export declare function createPrismaAdapter<Env extends Record<string, unknown>, TClient extends {
7
+ $disconnect?: () => Promise<void> | void;
8
+ }>(options: PrismaAdapterOptions<TClient>): DatabaseAdapter<Env, TClient, unknown>;
9
+ //# sourceMappingURL=prisma.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,16 @@
1
+ export function createPrismaAdapter(options) {
2
+ return {
3
+ name: "prisma",
4
+ connect: async ({ url }) => options.connect(url),
5
+ disconnect: async (client) => {
6
+ if (options.disconnect) {
7
+ await options.disconnect(client);
8
+ return;
9
+ }
10
+ if (typeof client.$disconnect === "function") {
11
+ await client.$disconnect();
12
+ }
13
+ },
14
+ };
15
+ }
16
+ //# sourceMappingURL=prisma.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma.js","sourceRoot":"","sources":["../../../src/adapters/db/prisma.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,mBAAmB,CAIjC,OAAsC;IAEtC,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;QAChD,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;YAC3B,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;gBACvB,MAAM,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;gBACjC,OAAO;YACT,CAAC;YAED,IAAI,OAAO,MAAM,CAAC,WAAW,KAAK,UAAU,EAAE,CAAC;gBAC7C,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;YAC7B,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,45 @@
1
+ import { Hono, type MiddlewareHandler } from "hono";
2
+ import { cors } from "hono/cors";
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";
7
+ export interface SecurityOptions {
8
+ cors?: Parameters<typeof cors>[0];
9
+ secureHeaders?: boolean | Parameters<typeof secureHeaders>[0];
10
+ }
11
+ export interface ObservabilityOptions {
12
+ requestIdHeader?: string;
13
+ enableAccessLogs?: boolean;
14
+ logLevel?: "debug" | "info" | "warn" | "error";
15
+ redactHeaders?: string[];
16
+ }
17
+ export interface ApplicationOptions<Env extends Record<string, unknown>> {
18
+ basePath?: string;
19
+ globalMiddlewares?: MiddlewareHandler[];
20
+ database?: DbManagerMiddlewareOptions<Env>;
21
+ security?: SecurityOptions;
22
+ observability?: ObservabilityOptions;
23
+ di?: {
24
+ strict?: boolean;
25
+ };
26
+ }
27
+ export declare class Application<Env extends Record<string, unknown> = Record<string, unknown>> {
28
+ private app;
29
+ private readonly container;
30
+ private readonly router;
31
+ private readonly registeredProviders;
32
+ private readonly registeredControllers;
33
+ private readonly registeredModules;
34
+ private readonly moduleResolutionStack;
35
+ constructor(options?: ApplicationOptions<Env>);
36
+ registerModule(moduleClass: Constructor): this;
37
+ use(middleware: MiddlewareHandler): this;
38
+ getContainer(): Container;
39
+ getHono(): Hono<{
40
+ Bindings: Env;
41
+ }>;
42
+ fetch: (request: Request, env: Env, ctx: any) => Response | Promise<Response>;
43
+ }
44
+ export declare function createApp<Env extends Record<string, unknown> = Record<string, unknown>>(options?: ApplicationOptions<Env>): Application<Env>;
45
+ //# sourceMappingURL=application.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,138 @@
1
+ import { Hono } from "hono";
2
+ import { cors } from "hono/cors";
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";
8
+ const defaultRedactHeaders = ["authorization", "cookie", "set-cookie", "x-api-key"];
9
+ function createObservabilityMiddleware(options) {
10
+ const requestIdHeader = options?.requestIdHeader ?? "x-request-id";
11
+ const accessLogs = options?.enableAccessLogs ?? true;
12
+ const level = options?.logLevel ?? "info";
13
+ const redactHeaders = options?.redactHeaders ?? defaultRedactHeaders;
14
+ return async (c, next) => {
15
+ const requestId = c.req.header(requestIdHeader) ?? crypto.randomUUID();
16
+ const start = Date.now();
17
+ await next();
18
+ c.res.headers.set(requestIdHeader, requestId);
19
+ if (!accessLogs) {
20
+ return;
21
+ }
22
+ const status = c.res.status;
23
+ const line = `[${requestId}] ${c.req.method} ${c.req.path} -> ${status} (${Date.now() - start}ms)`;
24
+ if (status >= 500) {
25
+ console.error(line);
26
+ }
27
+ else if (status >= 400) {
28
+ console.warn(line);
29
+ }
30
+ else if (level !== "error" && level !== "warn") {
31
+ console.info(line);
32
+ }
33
+ if (level === "debug") {
34
+ const headers = {};
35
+ const redacted = new Set(redactHeaders.map((h) => h.toLowerCase()));
36
+ c.req.raw.headers.forEach((value, key) => {
37
+ headers[key] = redacted.has(key.toLowerCase()) ? "[REDACTED]" : value;
38
+ });
39
+ console.debug(`[${requestId}] request_headers`, headers);
40
+ }
41
+ };
42
+ }
43
+ export class Application {
44
+ constructor(options = {}) {
45
+ this.registeredProviders = new Set();
46
+ this.registeredControllers = new Set();
47
+ this.registeredModules = new Set();
48
+ this.moduleResolutionStack = [];
49
+ this.fetch = (request, env, ctx) => {
50
+ return this.app.fetch(request, env, ctx);
51
+ };
52
+ this.app = new Hono({ strict: false });
53
+ this.container = new Container();
54
+ this.container.configure({ strict: options.di?.strict ?? true });
55
+ if (options.basePath) {
56
+ this.app = this.app.basePath(options.basePath);
57
+ }
58
+ this.app.use("*", createObservabilityMiddleware(options.observability));
59
+ this.app.use("*", cors(options.security?.cors));
60
+ if (options.security?.secureHeaders) {
61
+ const settings = typeof options.security.secureHeaders === "boolean"
62
+ ? undefined
63
+ : options.security.secureHeaders;
64
+ this.app.use("*", secureHeaders(settings));
65
+ }
66
+ if (options.database) {
67
+ this.app.use("*", createDbManagerMiddleware(options.database));
68
+ }
69
+ for (const middleware of options.globalMiddlewares ?? []) {
70
+ this.app.use("*", middleware);
71
+ }
72
+ this.app.notFound((c) => c.json({ error: { code: "not_found", message: "Route not found" } }, 404));
73
+ this.app.onError((err, c) => {
74
+ console.error(err);
75
+ return c.json({ error: { code: "internal_error", message: "Internal server error" } }, 500);
76
+ });
77
+ this.router = new Router(this.app, this.container);
78
+ }
79
+ registerModule(moduleClass) {
80
+ if (this.registeredModules.has(moduleClass)) {
81
+ return this;
82
+ }
83
+ const cycleStartIndex = this.moduleResolutionStack.indexOf(moduleClass);
84
+ if (cycleStartIndex >= 0) {
85
+ const cycle = [...this.moduleResolutionStack.slice(cycleStartIndex), moduleClass]
86
+ .map((moduleToken) => moduleToken.name)
87
+ .join(" -> ");
88
+ throw new Error(`Circular module import detected: ${cycle}`);
89
+ }
90
+ this.moduleResolutionStack.push(moduleClass);
91
+ try {
92
+ const metadata = getModuleMetadata(moduleClass);
93
+ if (!metadata) {
94
+ throw new Error(`Class ${moduleClass.name} is not decorated with @Module()`);
95
+ }
96
+ for (const imported of metadata.imports ?? []) {
97
+ this.registerModule(imported);
98
+ }
99
+ 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
+ }
108
+ }
109
+ for (const controller of metadata.controllers ?? []) {
110
+ if (this.registeredControllers.has(controller)) {
111
+ continue;
112
+ }
113
+ this.container.register(controller);
114
+ this.router.registerController(controller);
115
+ this.registeredControllers.add(controller);
116
+ }
117
+ this.registeredModules.add(moduleClass);
118
+ return this;
119
+ }
120
+ finally {
121
+ this.moduleResolutionStack.pop();
122
+ }
123
+ }
124
+ use(middleware) {
125
+ this.app.use("*", middleware);
126
+ return this;
127
+ }
128
+ getContainer() {
129
+ return this.container;
130
+ }
131
+ getHono() {
132
+ return this.app;
133
+ }
134
+ }
135
+ export function createApp(options = {}) {
136
+ return new Application(options);
137
+ }
138
+ //# sourceMappingURL=application.js.map