@venizia/ignis-docs 0.0.7 → 0.0.8-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 (158) hide show
  1. package/dist/mcp-server/common/paths.d.ts +4 -2
  2. package/dist/mcp-server/common/paths.d.ts.map +1 -1
  3. package/dist/mcp-server/common/paths.js +8 -6
  4. package/dist/mcp-server/common/paths.js.map +1 -1
  5. package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts +1 -1
  6. package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts.map +1 -1
  7. package/dist/mcp-server/tools/docs/get-document-content.tool.js +7 -7
  8. package/dist/mcp-server/tools/docs/get-document-metadata.tool.js +3 -3
  9. package/dist/mcp-server/tools/docs/get-package-overview.tool.d.ts +1 -1
  10. package/dist/mcp-server/tools/docs/get-package-overview.tool.js +1 -1
  11. package/package.json +1 -1
  12. package/wiki/best-practices/api-usage-examples.md +9 -9
  13. package/wiki/best-practices/architectural-patterns.md +19 -3
  14. package/wiki/best-practices/architecture-decisions.md +6 -6
  15. package/wiki/best-practices/code-style-standards/advanced-patterns.md +1 -1
  16. package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
  17. package/wiki/best-practices/code-style-standards/function-patterns.md +2 -2
  18. package/wiki/best-practices/code-style-standards/index.md +2 -2
  19. package/wiki/best-practices/code-style-standards/naming-conventions.md +1 -1
  20. package/wiki/best-practices/code-style-standards/route-definitions.md +4 -4
  21. package/wiki/best-practices/data-modeling.md +1 -1
  22. package/wiki/best-practices/deployment-strategies.md +1 -1
  23. package/wiki/best-practices/error-handling.md +2 -2
  24. package/wiki/best-practices/performance-optimization.md +3 -3
  25. package/wiki/best-practices/security-guidelines.md +2 -2
  26. package/wiki/best-practices/troubleshooting-tips.md +1 -1
  27. package/wiki/{references → extensions}/components/authentication/api.md +12 -20
  28. package/wiki/{references → extensions}/components/authentication/errors.md +1 -1
  29. package/wiki/{references → extensions}/components/authentication/index.md +5 -8
  30. package/wiki/{references → extensions}/components/authentication/usage.md +20 -36
  31. package/wiki/{references → extensions}/components/authorization/api.md +62 -13
  32. package/wiki/{references → extensions}/components/authorization/errors.md +12 -7
  33. package/wiki/{references → extensions}/components/authorization/index.md +93 -6
  34. package/wiki/{references → extensions}/components/authorization/usage.md +42 -4
  35. package/wiki/{references → extensions}/components/health-check.md +5 -4
  36. package/wiki/{references → extensions}/components/index.md +2 -0
  37. package/wiki/{references → extensions}/components/mail/index.md +1 -1
  38. package/wiki/{references → extensions}/components/request-tracker.md +1 -1
  39. package/wiki/{references → extensions}/components/socket-io/api.md +2 -2
  40. package/wiki/{references → extensions}/components/socket-io/errors.md +2 -0
  41. package/wiki/{references → extensions}/components/socket-io/index.md +24 -20
  42. package/wiki/{references → extensions}/components/socket-io/usage.md +2 -2
  43. package/wiki/{references → extensions}/components/static-asset/api.md +14 -15
  44. package/wiki/{references → extensions}/components/static-asset/errors.md +3 -1
  45. package/wiki/{references → extensions}/components/static-asset/index.md +158 -89
  46. package/wiki/{references → extensions}/components/static-asset/usage.md +8 -5
  47. package/wiki/{references → extensions}/components/swagger.md +3 -3
  48. package/wiki/{references → extensions}/components/template/index.md +4 -4
  49. package/wiki/{references → extensions}/components/template/setup-page.md +1 -1
  50. package/wiki/{references → extensions}/components/template/single-page.md +1 -1
  51. package/wiki/{references → extensions}/components/websocket/api.md +7 -6
  52. package/wiki/{references → extensions}/components/websocket/errors.md +17 -3
  53. package/wiki/{references → extensions}/components/websocket/index.md +17 -11
  54. package/wiki/{references → extensions}/components/websocket/usage.md +2 -2
  55. package/wiki/{references → extensions}/helpers/crypto/index.md +1 -1
  56. package/wiki/{references → extensions}/helpers/env/index.md +9 -5
  57. package/wiki/{references → extensions}/helpers/error/index.md +2 -7
  58. package/wiki/{references → extensions}/helpers/index.md +18 -6
  59. package/wiki/{references → extensions}/helpers/kafka/admin.md +13 -1
  60. package/wiki/{references → extensions}/helpers/kafka/consumer.md +28 -28
  61. package/wiki/{references → extensions}/helpers/kafka/examples.md +19 -19
  62. package/wiki/{references → extensions}/helpers/kafka/index.md +51 -48
  63. package/wiki/{references → extensions}/helpers/kafka/producer.md +18 -18
  64. package/wiki/{references → extensions}/helpers/kafka/schema-registry.md +25 -25
  65. package/wiki/{references → extensions}/helpers/logger/index.md +2 -2
  66. package/wiki/{references → extensions}/helpers/queue/index.md +400 -4
  67. package/wiki/{references → extensions}/helpers/storage/api.md +170 -10
  68. package/wiki/{references → extensions}/helpers/storage/index.md +44 -8
  69. package/wiki/{references → extensions}/helpers/template/index.md +1 -1
  70. package/wiki/{references → extensions}/helpers/testing/index.md +4 -4
  71. package/wiki/{references → extensions}/helpers/types/index.md +63 -16
  72. package/wiki/{references → extensions}/helpers/websocket/index.md +1 -1
  73. package/wiki/extensions/index.md +48 -0
  74. package/wiki/guides/core-concepts/application/bootstrapping.md +55 -37
  75. package/wiki/guides/core-concepts/application/index.md +95 -35
  76. package/wiki/guides/core-concepts/components-guide.md +23 -19
  77. package/wiki/guides/core-concepts/components.md +34 -10
  78. package/wiki/guides/core-concepts/dependency-injection.md +99 -34
  79. package/wiki/guides/core-concepts/grpc-controllers.md +295 -0
  80. package/wiki/guides/core-concepts/persistent/datasources.md +27 -8
  81. package/wiki/guides/core-concepts/persistent/models.md +43 -1
  82. package/wiki/guides/core-concepts/persistent/repositories.md +75 -8
  83. package/wiki/guides/core-concepts/persistent/transactions.md +38 -8
  84. package/wiki/guides/core-concepts/{controllers.md → rest-controllers.md} +30 -33
  85. package/wiki/guides/core-concepts/services.md +19 -5
  86. package/wiki/guides/get-started/5-minute-quickstart.md +6 -7
  87. package/wiki/guides/get-started/philosophy.md +1 -1
  88. package/wiki/guides/index.md +2 -2
  89. package/wiki/guides/reference/glossary.md +7 -7
  90. package/wiki/guides/reference/mcp-docs-server.md +1 -1
  91. package/wiki/guides/tutorials/building-a-crud-api.md +2 -2
  92. package/wiki/guides/tutorials/complete-installation.md +17 -14
  93. package/wiki/guides/tutorials/ecommerce-api.md +18 -18
  94. package/wiki/guides/tutorials/realtime-chat.md +8 -8
  95. package/wiki/guides/tutorials/testing.md +2 -2
  96. package/wiki/index.md +4 -3
  97. package/wiki/references/base/application.md +341 -21
  98. package/wiki/references/base/bootstrapping.md +43 -13
  99. package/wiki/references/base/components.md +259 -8
  100. package/wiki/references/base/controllers.md +556 -253
  101. package/wiki/references/base/datasources.md +159 -79
  102. package/wiki/references/base/dependency-injection.md +299 -48
  103. package/wiki/references/base/filter-system/application-usage.md +18 -2
  104. package/wiki/references/base/filter-system/array-operators.md +14 -6
  105. package/wiki/references/base/filter-system/comparison-operators.md +9 -3
  106. package/wiki/references/base/filter-system/default-filter.md +28 -3
  107. package/wiki/references/base/filter-system/fields-order-pagination.md +17 -13
  108. package/wiki/references/base/filter-system/index.md +169 -11
  109. package/wiki/references/base/filter-system/json-filtering.md +51 -18
  110. package/wiki/references/base/filter-system/list-operators.md +4 -3
  111. package/wiki/references/base/filter-system/logical-operators.md +7 -2
  112. package/wiki/references/base/filter-system/null-operators.md +50 -0
  113. package/wiki/references/base/filter-system/quick-reference.md +82 -243
  114. package/wiki/references/base/filter-system/range-operators.md +7 -1
  115. package/wiki/references/base/filter-system/tips.md +34 -7
  116. package/wiki/references/base/filter-system/use-cases.md +6 -5
  117. package/wiki/references/base/grpc-controllers.md +984 -0
  118. package/wiki/references/base/index.md +32 -24
  119. package/wiki/references/base/middleware.md +347 -0
  120. package/wiki/references/base/models.md +390 -46
  121. package/wiki/references/base/providers.md +14 -14
  122. package/wiki/references/base/repositories/advanced.md +84 -69
  123. package/wiki/references/base/repositories/index.md +447 -12
  124. package/wiki/references/base/repositories/mixins.md +103 -98
  125. package/wiki/references/base/repositories/relations.md +129 -45
  126. package/wiki/references/base/repositories/soft-deletable.md +104 -23
  127. package/wiki/references/base/services.md +94 -14
  128. package/wiki/references/index.md +12 -10
  129. package/wiki/references/quick-reference.md +98 -65
  130. package/wiki/references/utilities/crypto.md +21 -4
  131. package/wiki/references/utilities/date.md +25 -7
  132. package/wiki/references/utilities/index.md +26 -24
  133. package/wiki/references/utilities/jsx.md +54 -54
  134. package/wiki/references/utilities/module.md +8 -6
  135. package/wiki/references/utilities/parse.md +16 -9
  136. package/wiki/references/utilities/performance.md +22 -7
  137. package/wiki/references/utilities/promise.md +19 -16
  138. package/wiki/references/utilities/request.md +48 -26
  139. package/wiki/references/utilities/schema.md +69 -6
  140. package/wiki/references/utilities/statuses.md +131 -140
  141. /package/wiki/{references → extensions}/components/mail/api.md +0 -0
  142. /package/wiki/{references → extensions}/components/mail/errors.md +0 -0
  143. /package/wiki/{references → extensions}/components/mail/usage.md +0 -0
  144. /package/wiki/{references → extensions}/components/template/api-page.md +0 -0
  145. /package/wiki/{references → extensions}/components/template/errors-page.md +0 -0
  146. /package/wiki/{references → extensions}/components/template/usage-page.md +0 -0
  147. /package/wiki/{references → extensions}/helpers/cron/index.md +0 -0
  148. /package/wiki/{references → extensions}/helpers/inversion/index.md +0 -0
  149. /package/wiki/{references → extensions}/helpers/network/api.md +0 -0
  150. /package/wiki/{references → extensions}/helpers/network/index.md +0 -0
  151. /package/wiki/{references → extensions}/helpers/redis/index.md +0 -0
  152. /package/wiki/{references → extensions}/helpers/socket-io/api.md +0 -0
  153. /package/wiki/{references → extensions}/helpers/socket-io/index.md +0 -0
  154. /package/wiki/{references → extensions}/helpers/template/single-page.md +0 -0
  155. /package/wiki/{references → extensions}/helpers/uid/index.md +0 -0
  156. /package/wiki/{references → extensions}/helpers/websocket/api.md +0 -0
  157. /package/wiki/{references → extensions}/helpers/worker-thread/index.md +0 -0
  158. /package/wiki/{references → extensions}/src-details/mcp-server.md +0 -0
@@ -1,64 +1,422 @@
1
1
  ---
2
- title: Controllers Reference
3
- description: Technical reference for controller classes and API endpoints
2
+ title: REST Controllers Reference
3
+ description: Technical reference for REST controller classes and API endpoints
4
4
  difficulty: beginner
5
5
  ---
6
6
 
7
- # Deep Dive: Controllers
7
+ # Deep Dive: REST Controllers
8
8
 
9
- Technical reference for controller classes - the foundation for creating API endpoints in Ignis.
9
+ Technical reference for REST controller classes - the foundation for creating HTTP/JSON API endpoints in Ignis.
10
+
11
+ > [!NOTE]
12
+ > This page covers **REST controllers** (HTTP/JSON). For gRPC controllers using ConnectRPC, see the [gRPC Controllers Reference](./grpc-controllers.md).
10
13
 
11
14
  **Files:**
12
- - `packages/core/src/base/controllers/abstract.ts`
13
- - `packages/core/src/base/controllers/base.ts`
15
+ - `packages/core/src/base/controllers/rest/abstract.ts` - Abstract base class
16
+ - `packages/core/src/base/controllers/rest/base.ts` - Concrete base class
17
+ - `packages/core/src/base/controllers/common/types.ts` - Shared types and interfaces
18
+ - `packages/core/src/base/controllers/common/constants.ts` - Transport constants and headers
19
+ - `packages/core/src/base/metadata/routes/rest.ts` - Route decorators (`@api`, `@get`, `@post`, etc.)
20
+ - `packages/core/src/base/metadata/routes/controller.ts` - `@controller` decorator
21
+ - `packages/core/src/base/controllers/factory/controller.ts` - CRUD controller factory
22
+ - `packages/core/src/components/controller/rest/rest.component.ts` - RestComponent
14
23
 
15
24
  ## Quick Reference
16
25
 
17
- | Class | Purpose | Route Definition Methods |
18
- |-------|---------|--------------------------|
19
- | **AbstractController** | Base class with Hono router integration | `binding()`, `registerRoutesFromRegistry()` |
20
- | **BaseController** | Concrete implementation for API routes | `defineRoute()`, `bindRoute()`, `@get`, `@post`, `@api` decorators |
26
+ | Class | Purpose | Key Methods |
27
+ |-------|---------|-------------|
28
+ | **AbstractRestController** | Base class with Hono router, auth middleware, and OpenAPI integration | `binding()`, `registerRoutesFromRegistry()`, `getRouteConfigs()`, `getJSXRouteConfigs()`, `buildRouteMiddlewares()` |
29
+ | **BaseRestController** | Concrete implementation with route registration methods | `defineRoute()`, `bindRoute()`, `defineJSXRoute()`, `toHonoHandler()` |
30
+ | **RestComponent** | Configures and mounts all REST controllers onto the application router | `binding()` |
31
+ | **ControllerFactory** | Generates typed CRUD controllers from entity definitions | `defineCrudController()` |
32
+
33
+ ## Controller Transport System
34
+
35
+ Ignis supports multiple controller transports. The `@controller` decorator accepts a `transport` field to distinguish between REST and gRPC controllers.
36
+
37
+ ### `ControllerTransports`
38
+
39
+ ```typescript
40
+ class ControllerTransports {
41
+ static readonly REST = 'rest';
42
+ static readonly GRPC = 'grpc';
43
+ }
44
+
45
+ type TControllerTransport = 'rest' | 'grpc';
46
+ ```
47
+
48
+ ### `TControllerMetadata`
49
+
50
+ The `@controller` decorator metadata is a union type:
51
+
52
+ ```typescript
53
+ interface IBaseControllerMetadata {
54
+ path: string;
55
+ tags?: string[];
56
+ description?: string;
57
+ }
58
+
59
+ interface IRestControllerMetadata extends IBaseControllerMetadata {
60
+ transport?: typeof ControllerTransports.REST; // Optional — defaults to REST
61
+ }
62
+
63
+ interface IGrpcControllerMetadata<ServiceType = unknown> extends IBaseControllerMetadata {
64
+ transport: typeof ControllerTransports.GRPC; // Required for gRPC
65
+ service: ServiceType;
66
+ }
67
+
68
+ type TControllerMetadata = IRestControllerMetadata | IGrpcControllerMetadata;
69
+ ```
70
+
71
+ REST controllers do not need to specify `transport` explicitly — it defaults to REST when omitted.
72
+
73
+ ### Application Transport Configuration
74
+
75
+ The application configures which transports to enable:
76
+
77
+ ```typescript
78
+ class MyApp extends BaseApplication {
79
+ constructor() {
80
+ super({
81
+ // Defaults to ['rest'] if omitted
82
+ transports: [ControllerTransports.REST, ControllerTransports.GRPC],
83
+ });
84
+ }
85
+ }
86
+ ```
87
+
88
+ During `registerControllers()`, the application creates a `RestComponent` for REST transport and a `GrpcComponent` for gRPC transport.
89
+
90
+ ## `RestComponent`
91
+
92
+ **File:** `packages/core/src/components/controller/rest/rest.component.ts`
93
+
94
+ The `RestComponent` is responsible for discovering, configuring, and mounting all REST controllers onto the application's root Hono router. It is automatically instantiated by `BaseApplication.registerControllers()` when the REST transport is enabled.
95
+
96
+ ### Behavior
97
+
98
+ 1. Finds all bindings tagged with the `controllers` namespace
99
+ 2. Skips any controller whose metadata has `transport: 'grpc'`
100
+ 3. Validates that each remaining controller has a `path` in its metadata
101
+ 4. Resolves each controller instance from the IoC container
102
+ 5. Calls `configure()` on the controller (which runs `binding()` + `registerRoutesFromRegistry()`)
103
+ 6. Mounts the controller's router at `metadata.path` on the application's root router
104
+ 7. Dynamically re-fetches controller bindings after each mount to pick up any controllers added during configuration
105
+
106
+ ```typescript
107
+ export class RestComponent extends BaseComponent {
108
+ constructor(private application: BaseApplication) {
109
+ super({
110
+ scope: RestComponent.name,
111
+ initDefault: { enable: true, container: application },
112
+ bindings: {
113
+ [RestBindingKeys.REST_COMPONENT_OPTIONS]: Binding.bind<IRestComponentConfig>({
114
+ key: RestBindingKeys.REST_COMPONENT_OPTIONS,
115
+ }).toValue({}),
116
+ },
117
+ });
118
+ }
119
+ }
120
+ ```
21
121
 
22
122
  ## Routing Approaches
23
123
 
24
124
  | Approach | When to Use | Example |
25
125
  |----------|-------------|---------|
26
126
  | **Decorator-Based** (Recommended) | Clean, declarative routes | `@get({ configs: {...} })` |
27
- | **Manual Definition** | Complex routing logic | `this.defineRoute({ configs, handler })` |
127
+ | **Imperative (`defineRoute`)** | Complex routing logic, feature flags | `this.defineRoute({ configs, handler })` |
128
+ | **Fluent (`bindRoute`)** | Two-step route registration | `this.bindRoute({ configs }).to({ handler })` |
129
+ | **JSX (`defineJSXRoute`)** | Server-rendered HTML pages | `this.defineJSXRoute({ configs, handler })` |
28
130
 
29
- ## `AbstractController`
131
+ ## `AbstractRestController`
30
132
 
31
- Base class integrating Hono routing with Ignis DI and OpenAPI generation.
133
+ Base class integrating Hono routing with Ignis DI, authentication/authorization middleware, and OpenAPI generation.
32
134
 
33
- ### Key Features
135
+ **Generic Parameters:**
34
136
 
35
- | Feature | Description |
36
- | :--- | :--- |
37
- | **Hono Router** | Each controller manages its own `OpenAPIHono` router |
38
- | **Lifecycle** | `binding()` for manual routes, `registerRoutesFromRegistry()` for decorators |
39
- | **OpenAPI Integration** | Integrates with `@hono/zod-openapi` for schema generation |
40
- | **Standard Route Configs** | `getRouteConfigs` adds auth strategies, default responses, controller tags |
137
+ ```typescript
138
+ abstract class AbstractRestController<
139
+ RouteEnv extends Env = Env,
140
+ RouteSchema extends Schema = {},
141
+ BasePath extends string = '/',
142
+ ConfigurableOptions extends object = {},
143
+ Definitions extends Record<string, IAuthRouteConfig> = Record<string, IAuthRouteConfig>,
144
+ > extends BaseHelper implements IController<RouteEnv, RouteSchema, BasePath, ConfigurableOptions>
145
+ ```
146
+
147
+ ### Constructor
148
+
149
+ ```typescript
150
+ constructor(opts: IControllerOptions)
151
+ ```
152
+
153
+ | Option | Type | Default | Description |
154
+ | :--- | :--- | :--- | :--- |
155
+ | `scope` | `string` | Required | Logger scope name |
156
+ | `path` | `string` | — | Route base path. Falls back to `@controller` decorator path if not provided |
157
+ | `isStrict` | `boolean` | `true` | When `true`, `/users` and `/users/` are different routes |
158
+
159
+ Path resolution priority: `@controller` decorator metadata > constructor `path` option. Throws if neither provides a path.
160
+
161
+ ### Key Properties
162
+
163
+ | Property | Type | Description |
164
+ | :--- | :--- | :--- |
165
+ | `isConfigured` | `boolean` | Guards against double configuration |
166
+ | `router` | `OpenAPIHono` | The controller's Hono router instance |
167
+ | `path` | `string` | Resolved base path |
168
+ | `definitions` | `Definitions` | Route definition configs (used by factory-generated controllers) |
169
+
170
+ ### Methods
171
+
172
+ #### `configure(opts?): Promise<OpenAPIHono>`
173
+
174
+ Configures the controller. Idempotent — returns the router immediately if already configured.
175
+
176
+ 1. Calls `binding()` (your manual route definitions)
177
+ 2. Calls `registerRoutesFromRegistry()` (decorator-based routes)
178
+ 3. Sets `isConfigured = true`
179
+
180
+ #### `registerRoutesFromRegistry(): void`
181
+
182
+ Reads route metadata registered by `@get`, `@post`, `@api`, etc. decorators and binds them to the router using `bindRoute().to()`.
183
+
184
+ #### `getRouteConfigs<RouteConfig>(opts: { configs: RouteConfig })`
185
+
186
+ Processes a route config, injecting authentication/authorization middleware and OpenAPI security specs. Returns a Hono `createRoute` result. Automatically appends the controller's `scope` as a tag.
187
+
188
+ #### `getJSXRouteConfigs<RouteConfig>(opts: { configs: RouteConfig })`
41
189
 
42
- ## `BaseController`
190
+ Like `getRouteConfigs` but additionally merges an HTML response schema for JSX/server-rendered routes.
43
191
 
44
- Extends `AbstractController` with concrete implementations for defining API routes.
192
+ #### `buildRouteMiddlewares<RouteConfig>(opts: { configs: RouteConfig })`
45
193
 
46
- ### Decorator-Based Routing (Recommended)
194
+ Internal method that extracts `authenticate`, `authorize`, and `middleware` from a route config and builds the middleware chain:
47
195
 
48
- With the latest updates, the recommended way to define routes is by using decorators directly on your controller methods. This approach is more declarative, cleaner, and reduces boilerplate. The framework automatically discovers and registers these routes during startup via the `registerRoutesFromRegistry()` method.
196
+ 1. If `authenticate.strategies` is non-empty, adds `authenticateFn` middleware
197
+ 2. If `authorize` is present (single spec or array), adds `authorizeFn` middleware(s)
198
+ 3. If `middleware` is present (single or array), appends custom middleware(s)
49
199
 
50
- The `binding()` method is no longer required if you are using only decorator-based routing.
200
+ Returns `{ restConfig, security, mws }`.
51
201
 
52
- :::tip Type Safety without Boilerplate
53
- For decorator-based routes, you do not need to explicitly annotate the return type with `TRouteResponse`. TypeScript will automatically infer and validate the return type against the OpenAPI response schema you define in your `configs`. This gives you full type safety with less code.
54
- :::
202
+ #### `binding(): ValueOrPromise<void>` (abstract)
203
+
204
+ Override to register routes manually using `bindRoute` or `defineRoute`.
205
+
206
+ ## `BaseRestController`
207
+
208
+ Extends `AbstractRestController` with concrete implementations for `bindRoute`, `defineRoute`, and `defineJSXRoute`.
209
+
210
+ ### `defineRoute<RouteConfig, ResponseType>(opts)`
211
+
212
+ Defines and registers a route with its handler in a single call.
213
+
214
+ ```typescript
215
+ defineRoute<RouteConfig extends IAuthRouteConfig, ResponseType = unknown>(opts: {
216
+ configs: RouteConfig;
217
+ handler: TRouteHandler<ResponseType, RouteEnv>;
218
+ hook?: Hook<any, RouteEnv, string, ValueOrPromise<any>>;
219
+ }): IDefineRouteOptions<RouteConfig, RouteEnv, RouteSchema, BasePath>
220
+ ```
221
+
222
+ - **`configs`**: Route configuration including path, method, request/response schemas, and optional auth
223
+ - **`handler`**: Route handler function `(context: TRouteContext) => Response`
224
+ - **`hook`**: Optional Hono hook for validation error handling
225
+
226
+ Returns `{ configs, route }`.
227
+
228
+ ### `bindRoute<RouteConfig>(opts)`
229
+
230
+ Creates a fluent binding for two-step route registration.
231
+
232
+ ```typescript
233
+ bindRoute<RouteConfig extends IAuthRouteConfig>(opts: {
234
+ configs: RouteConfig;
235
+ }): IBindRouteOptions<RouteConfig, RouteEnv, RouteSchema, BasePath>
236
+ ```
237
+
238
+ Returns `{ configs, to }` where `to({ handler })` completes the registration and returns `{ configs, route }`.
239
+
240
+ ### `defineJSXRoute<RouteConfig, ResponseType>(opts)`
241
+
242
+ Defines a route that renders server-side HTML via `c.html()`. Same signature as `defineRoute` but uses `getJSXRouteConfigs` instead of `getRouteConfigs`, which automatically adds an HTML response schema.
243
+
244
+ ```typescript
245
+ defineJSXRoute<RouteConfig extends IAuthRouteConfig, ResponseType = unknown>(opts: {
246
+ configs: RouteConfig;
247
+ handler: TRouteHandler<ResponseType, RouteEnv>;
248
+ hook?: Hook<any, RouteEnv, string, ValueOrPromise<any>>;
249
+ }): IDefineRouteOptions<RouteConfig, RouteEnv, RouteSchema, BasePath>
250
+ ```
251
+
252
+ ### `toHonoHandler<ResponseType>(opts: { handler })`
253
+
254
+ Casts a `TRouteHandler` to Hono's OpenAPI handler type.
255
+
256
+ ## Key Types
257
+
258
+ ### `IAuthRouteConfig`
259
+
260
+ Route configuration extended with optional authentication and authorization fields. Extends Hono's `RouteConfig`.
261
+
262
+ ```typescript
263
+ interface IAuthRouteConfig extends HonoRouteConfig {
264
+ authenticate?: { strategies?: TAuthStrategy[]; mode?: TAuthMode };
265
+ authorize?: IAuthorizationSpec | IAuthorizationSpec[];
266
+ }
267
+ ```
268
+
269
+ ### `IControllerOptions`
270
+
271
+ ```typescript
272
+ interface IControllerOptions {
273
+ scope: string;
274
+ path?: string; // Falls back to @controller decorator path
275
+ isStrict?: boolean; // Default: true
276
+ }
277
+ ```
278
+
279
+ ### `TRouteContext`
280
+
281
+ Lightweight typed context that provides type-safe `req.valid()` calls:
282
+
283
+ ```typescript
284
+ type TRouteContext<RouteEnv extends Env = Env> = TContext<RouteEnv, keyof IValidRequestProps>;
285
+ ```
286
+
287
+ Where `IValidRequestProps` supports: `json`, `query`, `param`, `header`, `cookie`, `form`.
288
+
289
+ ### `TRouteHandler`
290
+
291
+ ```typescript
292
+ type TRouteHandler<ResponseType = unknown, RouteEnv extends Env = Env> = (
293
+ context: TRouteContext<RouteEnv>,
294
+ ) => ValueOrPromise<Response | TypedResponse<ResponseType>>;
295
+ ```
296
+
297
+ ### `IBindRouteOptions`
298
+
299
+ ```typescript
300
+ interface IBindRouteOptions<RouteConfig, RouteEnv, RouteSchema, BasePath> {
301
+ configs: RouteConfig;
302
+ to: <ResponseType = unknown>(opts: {
303
+ handler: TRouteHandler<ResponseType, RouteEnv>;
304
+ }) => IDefineRouteOptions<RouteConfig, RouteEnv, RouteSchema, BasePath>;
305
+ }
306
+ ```
307
+
308
+ ### `IDefineRouteOptions`
309
+
310
+ ```typescript
311
+ interface IDefineRouteOptions<RouteConfig, RouteEnv, RouteSchema, BasePath> {
312
+ configs: ReturnType<typeof createRoute<string, RouteConfig>>;
313
+ route: OpenAPIHono<RouteEnv, RouteSchema, BasePath>;
314
+ }
315
+ ```
316
+
317
+ ### `IController`
318
+
319
+ Base controller interface:
320
+
321
+ ```typescript
322
+ interface IController<RouteEnv, RouteSchema, BasePath, ConfigurableOptions>
323
+ extends IConfigurable<ConfigurableOptions, OpenAPIHono<RouteEnv, RouteSchema, BasePath>> {
324
+ router: OpenAPIHono<RouteEnv, RouteSchema, BasePath>;
325
+ bindRoute<RouteConfig>(opts: { configs: RouteConfig }): IBindRouteOptions<...>;
326
+ defineRoute<RouteConfig, ResponseType>(opts: { configs; handler; hook? }): IDefineRouteOptions<...>;
327
+ }
328
+ ```
55
329
 
56
- #### `@api` Decorator
330
+ ### `asTypedContext`
57
331
 
58
- The generic `@api` decorator allows you to define a route with a full configuration object. The decorated method will automatically have its `context` parameter and return type inferred and type-checked against the provided route configuration. This ensures strong type safety throughout your API definitions.
332
+ Utility to cast middleware context to `TContext`:
59
333
 
60
334
  ```typescript
61
- import { api, BaseController, controller, jsonContent, jsonResponse, z, TRouteContext } from '@venizia/ignis';
335
+ const asTypedContext = <E extends Env>(context: unknown): TContext<E, string> => {
336
+ return context as TContext<E, string>;
337
+ };
338
+ ```
339
+
340
+ ### Route Auth Types (for CRUD Controllers)
341
+
342
+ ```typescript
343
+ // Per-route authentication config
344
+ type TRouteAuthenticateConfig =
345
+ | { skip: true }
346
+ | { skip?: false; strategies?: TAuthStrategy[]; mode?: TAuthMode };
347
+
348
+ // Per-route authorization config
349
+ type TRouteAuthorizeConfig = { skip: true } | IAuthorizationSpec | IAuthorizationSpec[];
350
+
351
+ // Combined per-route auth config
352
+ type TRouteAuthConfig = {
353
+ authenticate?: TRouteAuthenticateConfig;
354
+ authorize?: TRouteAuthorizeConfig;
355
+ };
356
+ ```
357
+
358
+ ### `TCustomizableRouteConfig`
359
+
360
+ Per-route customization for CRUD controller endpoints:
361
+
362
+ ```typescript
363
+ type TCustomizableRouteConfig = TRouteAuthConfig & {
364
+ request?: {
365
+ params?: TAnyObjectSchema;
366
+ query?: TAnyObjectSchema;
367
+ body?: TAnyObjectSchema;
368
+ headers?: TAnyObjectSchema;
369
+ };
370
+ response?: {
371
+ schema?: z.ZodTypeAny;
372
+ headers?: TResponseHeaders;
373
+ };
374
+ };
375
+ ```
376
+
377
+ ### `ICustomizableRoutes`
378
+
379
+ ```typescript
380
+ interface ICustomizableRoutes<
381
+ RouteConfig extends TCustomizableRouteConfig = TCustomizableRouteConfig,
382
+ > {
383
+ count?: RouteConfig;
384
+ find?: RouteConfig;
385
+ findById?: RouteConfig;
386
+ findOne?: RouteConfig;
387
+ create?: RouteConfig;
388
+ updateById?: RouteConfig;
389
+ updateBy?: RouteConfig;
390
+ deleteById?: RouteConfig;
391
+ deleteBy?: RouteConfig;
392
+ }
393
+ ```
394
+
395
+ ## Route Decorators
396
+
397
+ **File:** `packages/core/src/base/metadata/routes/rest.ts`
398
+
399
+ ### `@controller` Decorator
400
+
401
+ Registers controller metadata (path, transport, tags, description) via the `MetadataRegistry`.
402
+
403
+ ```typescript
404
+ import { controller } from '@venizia/ignis';
405
+
406
+ @controller({ path: '/users' })
407
+ export class UserController extends BaseRestController { ... }
408
+
409
+ // With additional metadata
410
+ @controller({ path: '/users', tags: ['Users'], description: 'User management' })
411
+ export class UserController extends BaseRestController { ... }
412
+ ```
413
+
414
+ ### `@api` Decorator
415
+
416
+ Generic route decorator. Registers route config in the metadata registry.
417
+
418
+ ```typescript
419
+ import { api, BaseRestController, controller, jsonResponse, z, TRouteContext } from '@venizia/ignis';
62
420
  import { HTTP } from '@venizia/ignis-helpers';
63
421
 
64
422
  const MyRouteConfig = {
@@ -68,7 +426,7 @@ const MyRouteConfig = {
68
426
  } as const;
69
427
 
70
428
  @controller({ path: '/my-feature' })
71
- export class MyFeatureController extends BaseController {
429
+ export class MyFeatureController extends BaseRestController {
72
430
 
73
431
  @api({ configs: MyRouteConfig })
74
432
  getData(c: TRouteContext) {
@@ -77,15 +435,15 @@ export class MyFeatureController extends BaseController {
77
435
  }
78
436
  ```
79
437
 
80
- #### HTTP Method Decorators (`@get`, `@post`, etc.)
438
+ ### HTTP Method Decorators (`@get`, `@post`, `@put`, `@patch`, `@del`)
81
439
 
82
- For convenience, `Ignis` provides decorator shortcuts for each HTTP method: These decorators accept the same `configs` object as `@api`, but without the `method` property.
440
+ Shorthand decorators that auto-set the HTTP method. Accept the same `configs` object as `@api` but without the `method` property.
83
441
 
84
- - `@get(opts)`
85
- - `@post(opts)`
86
- - `@put(opts)`
87
- - `@patch(opts)`
88
- - `@del(opts)`
442
+ ```typescript
443
+ import { get, post, put, patch, del } from '@venizia/ignis';
444
+ ```
445
+
446
+ Each decorator calls `@api` internally with the appropriate `HTTP.Methods.*` value.
89
447
 
90
448
  **Example using `@get` and `@post`:**
91
449
 
@@ -93,19 +451,16 @@ For convenience, `Ignis` provides decorator shortcuts for each HTTP method: Thes
93
451
  import { get, post, z, jsonContent, jsonResponse, Authentication, TRouteContext } from '@venizia/ignis';
94
452
  import { HTTP } from '@venizia/ignis-helpers';
95
453
 
96
- // Define route configs as const
97
454
  const UserRoutes = {
98
455
  LIST_USERS: {
99
456
  path: '/',
100
- method: 'get',
101
457
  responses: jsonResponse({
102
458
  description: 'A list of users',
103
459
  schema: z.array(z.object({ id: z.string(), name: z.string() })),
104
460
  }),
105
461
  },
106
462
  GET_USER: {
107
- path: '/:id',
108
- method: 'get',
463
+ path: '/{id}',
109
464
  request: {
110
465
  params: z.object({ id: z.string() }),
111
466
  },
@@ -116,8 +471,7 @@ const UserRoutes = {
116
471
  },
117
472
  CREATE_USER: {
118
473
  path: '/',
119
- method: 'post',
120
- authStrategies: [Authentication.STRATEGY_JWT], // Secure this endpoint
474
+ authenticate: { strategies: [Authentication.STRATEGY_JWT] },
121
475
  request: {
122
476
  body: jsonContent({
123
477
  schema: z.object({ name: z.string() }),
@@ -138,75 +492,77 @@ const UserRoutes = {
138
492
 
139
493
  @get({ configs: UserRoutes.GET_USER })
140
494
  getUserById(c: TRouteContext) {
141
- const { id } = c.req.valid<{ id: string }>('param'); // Explicitly typed
495
+ const { id } = c.req.valid<{ id: string }>('param');
142
496
  return c.json({ id, name: 'John Doe' }, HTTP.ResultCodes.RS_2.Ok);
143
497
  }
144
498
 
145
499
  @post({ configs: UserRoutes.CREATE_USER })
146
500
  createUser(c: TRouteContext) {
147
- const { name } = c.req.valid<{ name: string }>('json'); // Explicitly typed
501
+ const { name } = c.req.valid<{ name: string }>('json');
148
502
  const newUser = { id: '2', name };
149
503
  return c.json(newUser, HTTP.ResultCodes.RS_2.Created);
150
504
  }
151
505
  ```
152
506
 
153
- **Example using shared `RouteConfigs`:**
507
+ ### Decorator-Based Routing Notes
154
508
 
155
- For better organization, you can define all your route configurations in a constant and reference them in your decorators. This approach also allows you to get a typed context for your handler.
509
+ - The `binding()` method is not required if you use only decorator-based routing
510
+ - Routes are discovered and registered during `configure()` via `registerRoutesFromRegistry()`
511
+ - TypeScript automatically infers and validates return types against the OpenAPI response schema — no need for explicit `TRouteResponse` annotations
512
+ - Use `as const` on route config objects for strict type inference
156
513
 
157
- ```typescript
158
- import { api, BaseController, controller, TRouteContext, jsonContent, jsonResponse } from '@venizia/ignis';
159
- import { HTTP } from '@venizia/ignis-helpers';
160
- import { z } from 'hono/zod-openapi';
514
+ ## Manual Route Definition
161
515
 
162
- const RouteConfigs = {
163
- PING: {
164
- method: HTTP.Methods.POST,
165
- path: '/ping',
166
- request: {
167
- body: jsonContent({
168
- schema: z.object({ message: z.string().min(1) }),
169
- }),
170
- },
171
- responses: jsonResponse({
172
- schema: z.object({ pong: z.string() }),
173
- }),
174
- },
175
- } as const; // Use 'as const' for strict type inference
516
+ For advanced use cases — dynamic routes, feature flags, programmatic control — define routes inside `binding()`.
176
517
 
177
- @controller({ path: '/health' })
178
- export class HealthCheckController extends BaseController {
518
+ ### `defineRoute` Example
179
519
 
180
- @api({ configs: RouteConfigs.PING })
181
- ping(c: TRouteContext) {
182
- const { message } = c.req.valid<{ message: string }>('json');
183
- return c.json({ pong: message }, HTTP.ResultCodes.RS_2.Ok);
184
- }
185
- }
520
+ ```typescript
521
+ this.defineRoute({
522
+ configs: {
523
+ method: 'get',
524
+ path: '/status',
525
+ responses: jsonResponse({ schema: z.object({ ok: z.boolean() }) }),
526
+ authenticate: { strategies: ['jwt'] },
527
+ authorize: { resource: 'status', scopes: ['read'] },
528
+ },
529
+ handler: async (context) => {
530
+ return context.json({ ok: true }, 200);
531
+ },
532
+ hook: (result, context) => {
533
+ // Optional hook for post-processing
534
+ },
535
+ });
186
536
  ```
187
537
 
188
- ### Manual Route Definition Methods
189
-
190
- For advanced use cases or when you prefer a non-decorator approach, you can define routes manually using `defineRoute` and `bindRoute` methods inside the `binding()` method.
538
+ ### `bindRoute` Example
191
539
 
192
- :::tip When to Use Manual Definition
193
- Manual route definition is useful for:
194
- - Dynamically generating routes based on configuration
195
- - Conditional route registration (feature flags)
196
- - Developers who prefer non-decorator syntax (coming from Express/Fastify)
197
- - Complex routing logic that benefits from programmatic control
198
- :::
199
-
200
- #### `defineJSXRoute`
540
+ ```typescript
541
+ const { configs } = this.bindRoute({
542
+ configs: {
543
+ method: 'post',
544
+ path: '/action',
545
+ request: {
546
+ body: jsonContent({ schema: z.object({ name: z.string() }) }),
547
+ },
548
+ responses: jsonResponse({ schema: z.object({ id: z.string() }) }),
549
+ },
550
+ }).to({
551
+ handler: async (context) => {
552
+ const data = context.req.valid('json');
553
+ return context.json({ id: '123' }, 201);
554
+ },
555
+ });
556
+ ```
201
557
 
202
- Define a route that returns server-rendered JSX/HTML:
558
+ ### `defineJSXRoute` Example
203
559
 
204
560
  ```typescript
205
561
  this.defineJSXRoute({
206
562
  configs: {
207
563
  path: '/dashboard',
208
564
  method: 'get',
209
- responses: htmlResponse({ description: 'Dashboard page' }),
565
+ responses: {}, // HTML response schema is auto-merged
210
566
  },
211
567
  handler: async (c) => {
212
568
  const data = await this.dashboardService.getData();
@@ -218,53 +574,22 @@ this.defineJSXRoute({
218
574
  });
219
575
  ```
220
576
 
221
- Works the same as `defineRoute()` but typed for JSX handler return values.
222
-
223
- #### `defineRoute`
224
-
225
- This method is for creating API endpoints. It now handles both public and authenticated routes by accepting an `authStrategies` array within the `configs`.
226
-
227
- ```typescript
228
- this.defineRoute({
229
- configs: TAuthRouteConfig<RouteConfig>; // You would define this inline or via a const
230
- handler: TLazyRouteHandler<typeof configs, RouteEnv>; // Inferred from configs
231
- hook?: Hook;
232
- });
233
- ```
234
-
235
- - **`configs`**: An object that defines the route's OpenAPI specification. It now includes an optional `authStrategies` array. See the table below for details.
236
- - **`handler`**: The Hono route handler function `(c: Context) => Response`.
237
- - **`hook`**: An optional hook for processing the request or response, often used for validation error handling.
577
+ ## `IAuthRouteConfig` Options
238
578
 
239
- #### `bindRoute`
240
-
241
- This method offers a fluent API for defining routes, similar to `defineRoute`, but structured for chaining. It also supports `authStrategies`.
242
-
243
- ```typescript
244
- this.bindRoute({
245
- configs: TAuthRouteConfig<RouteConfig>; // You would define this inline or via a const
246
- }).to({
247
- handler: TLazyRouteHandler<typeof configs, RouteEnv>; // Inferred from configs
248
- });
249
- ```
250
-
251
- - **`configs`**: Same as `defineRoute`, including `authStrategies`.
252
- - **`to`**: A method that accepts an object with the `handler` function.
253
-
254
- ### `TRouteConfig` Options
255
-
256
- The `configs` object accepts properties based on the OpenAPI 3.0 specification.
579
+ The `configs` object extends the OpenAPI 3.0 `RouteConfig` from `@hono/zod-openapi`.
257
580
 
258
581
  | Property | Type | Description |
259
582
  | :--- | :--- | :--- |
260
- | `path` | `string` | The route path, relative to the controller's base path (e.g., `/:id`). |
261
- | `method` | `'get' \| 'post' \| ...` | The HTTP method for the route. |
262
- | `request` | `object` | Defines the request, including `params`, `query`, and `body`. You can use Zod schemas for validation. |
263
- | `responses`| `object` | An object mapping HTTP status codes to response descriptions and schemas. The `jsonContent` and `jsonResponse` utilities can simplify this. |
264
- | `tags` | `string[]` | An array of tags for grouping routes in the OpenAPI documentation. The controller's name is automatically added as a tag. |
265
- | `summary` | `string` | A short summary of what the operation does. |
266
- | `description`| `string` | A detailed description of the operation. |
267
- | `authStrategies`| `TAuthStrategy[]` | An optional array of authentication strategy names (e.g., `[Authentication.STRATEGY_JWT]`). If provided, the framework will automatically add the necessary middleware to enforce these strategies. |
583
+ | `path` | `string` | Route path relative to the controller's base path (e.g., `/{id}`) |
584
+ | `method` | `'get' \| 'post' \| 'put' \| 'patch' \| 'delete'` | HTTP method (auto-set by `@get`, `@post`, etc.) |
585
+ | `request` | `object` | Request definition: `params`, `query`, `body`, `headers` (Zod schemas) |
586
+ | `responses` | `object` | HTTP status code to response description/schema mapping |
587
+ | `tags` | `string[]` | OpenAPI tags. The controller's `scope` is automatically appended |
588
+ | `summary` | `string` | Short summary of the operation |
589
+ | `description` | `string` | Detailed description of the operation |
590
+ | `authenticate` | `{ strategies?: TAuthStrategy[]; mode?: TAuthMode }` | Auth strategies. If provided, framework injects auth middleware automatically |
591
+ | `authorize` | `IAuthorizationSpec \| IAuthorizationSpec[]` | Authorization spec(s). If provided, framework injects authorize middleware after authenticate |
592
+ | `middleware` | `MiddlewareHandler \| MiddlewareHandler[]` | Custom middleware(s) appended after auth middleware |
268
593
 
269
594
  ### Example of `request` Configuration
270
595
 
@@ -283,101 +608,87 @@ request: {
283
608
  // ...
284
609
  ```
285
610
 
286
- ### `defineRouteConfigs`
611
+ ## Standard Headers and Constants
287
612
 
288
- - **File:** `packages/core/src/base/controllers/factory/definition.ts`
613
+ **File:** `packages/core/src/base/controllers/common/constants.ts`
289
614
 
290
- The `defineRouteConfigs` function is a simple helper for creating a typed object containing multiple route configurations. This is particularly useful for organizing all of a controller's route definitions in a single, type-checked constant.
615
+ ### `RestPaths`
291
616
 
292
617
  ```typescript
293
- import { defineRouteConfigs, jsonResponse, jsonContent, z } from '@venizia/ignis';
294
- import { HTTP } from '@venizia/ignis-helpers';
295
-
296
- const RouteConfigs = defineRouteConfigs({
297
- ROOT: {
298
- method: HTTP.Methods.GET,
299
- path: '/',
300
- responses: jsonResponse({
301
- schema: z.object({ status: z.string() }),
302
- }),
303
- },
304
- PING: {
305
- method: HTTP.Methods.POST,
306
- path: '/ping',
307
- request: {
308
- body: jsonContent({
309
- schema: z.object({ message: z.string() }),
310
- }),
311
- },
312
- responses: jsonResponse({
313
- schema: z.object({ message: z.string() }),
314
- }),
315
- },
316
- });
618
+ class RestPaths {
619
+ static readonly ROOT = '/';
620
+ static readonly COUNT = '/count';
621
+ static readonly FIND_ONE = '/find-one';
622
+ }
317
623
  ```
318
624
 
625
+ ### Built-in Header Schemas
626
+
627
+ | Constant | Headers Included |
628
+ | :--- | :--- |
629
+ | `trackableHeaders` | `x-request-id`, `x-request-channel`, `x-request-device-info` (all optional) |
630
+ | `countableHeaders` | `x-request-count` — controls `{count, data}` vs data-only response format |
631
+ | `defaultRequestHeaders` | `trackableHeaders` + `countableHeaders` combined |
632
+ | `commonResponseHeaders` | `x-request-id` (echo), `x-response-count`, `x-response-format` |
633
+ | `findResponseHeaders` | `commonResponseHeaders` + `content-range` for pagination |
634
+
319
635
  ## `ControllerFactory`
320
636
 
321
- The `ControllerFactory` provides a static method `defineCrudController` to quickly generate a pre-configured CRUD controller for any given `BaseEntity` and its corresponding repository. This significantly reduces boilerplate for standard RESTful resources.
637
+ The `ControllerFactory` provides a static method `defineCrudController` to quickly generate a pre-configured CRUD controller for any given `BaseEntity` and its corresponding repository.
322
638
 
323
- - **File:** `packages/core/src/base/controllers/factory/controller.ts`
639
+ **File:** `packages/core/src/base/controllers/factory/controller.ts`
324
640
 
325
641
  ### `static defineCrudController<EntitySchema>(opts: ICrudControllerOptions<EntitySchema>)`
326
642
 
327
- This factory method returns a `BaseController` class that is already set up with the following standard CRUD endpoints.
328
-
329
- **Note:** The returned class is dynamically named using `controller.name` from the options. This ensures that when registered with `app.controller()`, the class has a proper name for binding keys and debugging (e.g., `ConfigurationController` instead of an anonymous class).
643
+ Returns a `BaseRestController` subclass with standard CRUD endpoints pre-configured. The returned class is dynamically named using `controller.name` from the options.
330
644
 
331
645
  | Route Name | Method | Path | Description |
332
646
  | :--- | :--- | :--- | :--- |
333
- | `count` | `GET` | `/count` | Get the number of records matching a filter. |
334
- | `find` | `GET` | `/` | Retrieve all records matching a filter. |
335
- | `findById` | `GET` | `/:id` | Retrieve a single record by its ID. |
336
- | `findOne` | `GET` | `/find-one` | Retrieve a single record matching a filter. |
337
- | `create` | `POST` | `/` | Create a new record. |
338
- | `updateById` | `PATCH` | `/:id` | Update a single record by its ID. |
339
- | `updateBy` | `PATCH` | `/` | Update multiple records matching a `where` filter. |
340
- | `deleteById` | `DELETE` | `/:id` | Delete a single record by its ID. |
341
- | `deleteBy` | `DELETE` | `/` | Delete multiple records matching a `where` filter. |
647
+ | `count` | `GET` | `/count` | Count records matching a where condition |
648
+ | `find` | `GET` | `/` | Find records with filter, pagination, sorting, and relations |
649
+ | `findById` | `GET` | `/{id}` | Find a single record by its ID |
650
+ | `findOne` | `GET` | `/find-one` | Find the first record matching a filter |
651
+ | `create` | `POST` | `/` | Create a new record |
652
+ | `updateById` | `PATCH` | `/{id}` | Partial update a record by its ID |
653
+ | `updateBy` | `PATCH` | `/` | Bulk update records matching a `where` filter |
654
+ | `deleteById` | `DELETE` | `/{id}` | Delete a record by its ID |
655
+ | `deleteBy` | `DELETE` | `/` | Bulk delete records matching a `where` filter |
342
656
 
343
657
  ### `ICrudControllerOptions<EntitySchema>`
344
658
 
345
659
  | Option | Type | Description |
346
660
  | :--- | :--- | :--- |
347
- | `entity` | `TClass<BaseEntity<EntitySchema>> \| TResolver<TClass<BaseEntity<EntitySchema>>>` | The entity class (or a resolver function returning it) that this CRUD controller manages. This is used to derive request/response schemas. |
348
- | `repository.name` | `string` | The binding key name of the repository associated with this entity (e.g., `'ConfigurationRepository'`). |
349
- | `controller.name` | `string` | A unique name for the generated controller (e.g., `'ConfigurationController'`). |
350
- | `controller.basePath`| `string` | The base path for all routes in this CRUD controller (e.g., `'/configurations'`). |
351
- | `controller.readonly` | `boolean` | If `true`, only read operations (find, findOne, findById, count) are generated. Write operations are excluded. Defaults to `false`. |
352
- | `controller.isStrict` | `boolean` | If `true`, query parameters like `where` will be strictly validated. Defaults to `true`. |
353
- | `controller.defaultLimit`| `number` | The default limit for `find` operations. Defaults to `10`. |
354
- | `authStrategies` | `Array<TAuthStrategy>` | Auth strategies applied to all routes (unless overridden per-route). |
355
- | `routes` | `TRoutesConfig` | Per-route configuration combining schema and auth. See routes configuration below. |
661
+ | `entity` | `TClass<BaseEntity<EntitySchema>> \| TResolver<TClass<BaseEntity<EntitySchema>>>` | Entity class or resolver function returning it. Used to derive request/response schemas |
662
+ | `repository.name` | `string` | Repository binding key name in the IoC container (e.g., `'ConfigurationRepository'`) |
663
+ | `controller.name` | `string` | Unique name for the generated controller (e.g., `'ConfigurationController'`) |
664
+ | `controller.basePath` | `string` | Base path for all routes (e.g., `'/configurations'`). Required |
665
+ | `controller.readonly` | `boolean` | If `true`, only read operations (count, find, findOne, findById) are generated. Defaults to `false` |
666
+ | `controller.isStrict` | `{ path?: boolean; requestSchema?: boolean }` | `path` (default `true`): strict path matching. `requestSchema` (default `true`): strict query parameter validation |
667
+ | `authenticate` | `{ strategies?: TAuthStrategy[]; mode?: TAuthMode }` | Authentication config applied to all routes (unless overridden per-route) |
668
+ | `authorize` | `IAuthorizationSpec \| IAuthorizationSpec[]` | Authorization config applied to all routes (unless overridden per-route) |
669
+ | `routes` | `ICustomizableRoutes` | Per-route configuration combining schema and auth overrides |
356
670
 
357
671
  ### Routes Configuration
358
672
 
359
- The `routes` option provides a unified way to configure request/response schemas and authentication for each endpoint:
673
+ The `routes` option provides per-route customization of request/response schemas and auth:
360
674
 
361
675
  ```typescript
362
- type TRouteAuthConfig =
363
- | { skipAuth: true }
364
- | { skipAuth?: false; authStrategies: Array<TAuthStrategy> };
365
-
366
- type TRequestConfig = {
367
- query?: z.ZodObject; // Custom query parameters
368
- headers?: z.ZodObject; // Custom headers
369
- params?: z.ZodObject; // Custom path parameters
370
- body?: z.ZodObject; // Custom request body (write routes only)
371
- };
372
-
373
- type TResponseConfig = {
374
- schema?: z.ZodObject; // Custom response body schema
375
- headers?: z.ZodObject; // Custom response headers
676
+ type TRouteAuthConfig = {
677
+ authenticate?: { skip: true } | { skip?: false; strategies?: TAuthStrategy[]; mode?: TAuthMode };
678
+ authorize?: { skip: true } | IAuthorizationSpec | IAuthorizationSpec[];
376
679
  };
377
680
 
378
- type TBaseRouteConfig = TRouteAuthConfig & {
379
- request?: TRequestConfig;
380
- response?: TResponseConfig;
681
+ type TCustomizableRouteConfig = TRouteAuthConfig & {
682
+ request?: {
683
+ params?: TAnyObjectSchema;
684
+ query?: TAnyObjectSchema;
685
+ body?: TAnyObjectSchema;
686
+ headers?: TAnyObjectSchema;
687
+ };
688
+ response?: {
689
+ schema?: z.ZodTypeAny;
690
+ headers?: TResponseHeaders;
691
+ };
381
692
  };
382
693
  ```
383
694
 
@@ -395,11 +706,18 @@ type TBaseRouteConfig = TRouteAuthConfig & {
395
706
 
396
707
  ### Auth Resolution Priority
397
708
 
398
- When resolving authentication for a route, the following priority applies:
709
+ When resolving authentication for a route:
710
+
711
+ 1. **Endpoint `authenticate: { skip: true }`** — No auth (ignores controller `authenticate`)
712
+ 2. **Endpoint `authenticate: { strategies }`** — Override controller (empty array = no auth)
713
+ 3. **Controller `authenticate`** — Default fallback
399
714
 
400
- 1. **Endpoint `skipAuth: true`** No auth (ignores controller `authStrategies`)
401
- 2. **Endpoint `authStrategies`** → Override controller (empty array = no auth)
402
- 3. **Controller `authStrategies`** Default fallback
715
+ When resolving authorization for a route:
716
+
717
+ 1. **Endpoint `authenticate: { skip: true }`** No authorize (auth skipped entirely)
718
+ 2. **Endpoint `authorize: { skip: true }`** — No authorize (explicitly skipped)
719
+ 3. **Endpoint `authorize: { ... }`** — Override controller authorize
720
+ 4. **Controller `authorize`** — Default fallback
403
721
 
404
722
  ### Authentication Examples
405
723
 
@@ -411,7 +729,7 @@ const UserController = ControllerFactory.defineCrudController({
411
729
  entity: UserEntity,
412
730
  repository: { name: 'UserRepository' },
413
731
  controller: { name: 'UserController', basePath: '/users' },
414
- authStrategies: [Authentication.STRATEGY_JWT],
732
+ authenticate: { strategies: [Authentication.STRATEGY_JWT] },
415
733
  });
416
734
 
417
735
  // 2. JWT auth on all, but skip for public read endpoints
@@ -419,11 +737,11 @@ const ProductController = ControllerFactory.defineCrudController({
419
737
  entity: ProductEntity,
420
738
  repository: { name: 'ProductRepository' },
421
739
  controller: { name: 'ProductController', basePath: '/products' },
422
- authStrategies: [Authentication.STRATEGY_JWT],
740
+ authenticate: { strategies: [Authentication.STRATEGY_JWT] },
423
741
  routes: {
424
- find: { skipAuth: true },
425
- findById: { skipAuth: true },
426
- count: { skipAuth: true },
742
+ find: { authenticate: { skip: true } },
743
+ findById: { authenticate: { skip: true } },
744
+ count: { authenticate: { skip: true } },
427
745
  },
428
746
  });
429
747
 
@@ -433,24 +751,26 @@ const ArticleController = ControllerFactory.defineCrudController({
433
751
  repository: { name: 'ArticleRepository' },
434
752
  controller: { name: 'ArticleController', basePath: '/articles' },
435
753
  routes: {
436
- create: { authStrategies: [Authentication.STRATEGY_JWT] },
437
- updateById: { authStrategies: [Authentication.STRATEGY_JWT] },
438
- deleteById: { authStrategies: [Authentication.STRATEGY_JWT] },
754
+ create: { authenticate: { strategies: [Authentication.STRATEGY_JWT] } },
755
+ updateById: { authenticate: { strategies: [Authentication.STRATEGY_JWT] } },
756
+ deleteById: { authenticate: { strategies: [Authentication.STRATEGY_JWT] } },
439
757
  },
440
758
  });
441
759
 
442
- // 4. Custom request/response schemas with auth configuration
760
+ // 4. Custom request/response schemas with auth and authorization
443
761
  const OrderController = ControllerFactory.defineCrudController({
444
762
  entity: OrderEntity,
445
763
  repository: { name: 'OrderRepository' },
446
764
  controller: { name: 'OrderController', basePath: '/orders' },
447
- authStrategies: [Authentication.STRATEGY_JWT],
765
+ authenticate: { strategies: [Authentication.STRATEGY_JWT] },
766
+ authorize: { resource: 'orders', scopes: ['read'] },
448
767
  routes: {
449
768
  find: {
450
- skipAuth: true,
769
+ authenticate: { skip: true },
451
770
  response: { schema: CustomOrderListSchema },
452
771
  },
453
772
  create: {
773
+ authorize: { resource: 'orders', scopes: ['write'] },
454
774
  request: { body: CustomOrderCreateSchema },
455
775
  response: { schema: CustomOrderResponseSchema },
456
776
  },
@@ -464,14 +784,12 @@ const OrderController = ControllerFactory.defineCrudController({
464
784
  import { Authentication, ControllerFactory } from '@venizia/ignis';
465
785
  import { z } from '@hono/zod-openapi';
466
786
 
467
- // Custom request body for create
468
787
  const CreateUserSchema = z.object({
469
788
  name: z.string().min(1).max(100),
470
789
  email: z.string().email(),
471
790
  role: z.enum(['admin', 'user']).default('user'),
472
791
  });
473
792
 
474
- // Custom response schema (omit sensitive fields)
475
793
  const PublicUserSchema = z.object({
476
794
  id: z.string(),
477
795
  name: z.string(),
@@ -483,33 +801,28 @@ const UserController = ControllerFactory.defineCrudController({
483
801
  entity: UserEntity,
484
802
  repository: { name: 'UserRepository' },
485
803
  controller: { name: 'UserController', basePath: '/users' },
486
- authStrategies: [Authentication.STRATEGY_JWT],
804
+ authenticate: { strategies: [Authentication.STRATEGY_JWT] },
487
805
  routes: {
488
- // Public read endpoints
489
806
  find: {
490
- skipAuth: true,
807
+ authenticate: { skip: true },
491
808
  response: { schema: z.array(PublicUserSchema) },
492
809
  },
493
810
  findById: {
494
- skipAuth: true,
811
+ authenticate: { skip: true },
495
812
  response: { schema: PublicUserSchema },
496
813
  },
497
-
498
- // Custom create with custom body and response
499
814
  create: {
500
815
  request: { body: CreateUserSchema },
501
816
  response: { schema: PublicUserSchema },
502
817
  },
503
-
504
- // Delete requires JWT auth (uses default schema)
505
818
  deleteById: {
506
- authStrategies: [Authentication.STRATEGY_JWT],
819
+ authenticate: { strategies: [Authentication.STRATEGY_JWT] },
507
820
  },
508
821
  },
509
822
  });
510
823
  ```
511
824
 
512
- ### Example
825
+ ### Full Example
513
826
 
514
827
  ```typescript
515
828
  // src/controllers/configuration.controller.ts
@@ -525,18 +838,16 @@ import {
525
838
 
526
839
  const BASE_PATH = '/configurations';
527
840
 
528
- // Define the CRUD controller using the factory
529
841
  const _ConfigurationController = ControllerFactory.defineCrudController({
530
842
  repository: { name: ConfigurationRepository.name },
531
843
  controller: {
532
844
  name: 'ConfigurationController',
533
845
  basePath: BASE_PATH,
534
- isStrict: true,
846
+ isStrict: { path: true, requestSchema: true },
535
847
  },
536
- entity: () => Configuration, // Provide the entity class
848
+ entity: () => Configuration,
537
849
  });
538
850
 
539
- // Extend the generated controller to add custom logic or inject dependencies
540
851
  @controller({ path: BASE_PATH })
541
852
  export class ConfigurationController extends _ConfigurationController {
542
853
  constructor(
@@ -548,19 +859,15 @@ export class ConfigurationController extends _ConfigurationController {
548
859
  })
549
860
  repository: ConfigurationRepository,
550
861
  ) {
551
- super(repository); // Pass the injected repository to the super constructor
862
+ super(repository);
552
863
  }
553
864
  }
554
865
  ```
555
866
 
556
- By leveraging these structured configuration options and the `ControllerFactory`, you ensure that your API is not only functional but also well-documented, easy to validate, and rapidly deployable for standard CRUD operations.
557
-
558
867
  ### Overriding CRUD Methods with Strong Typing
559
868
 
560
869
  When extending a generated CRUD controller, you can override methods using `TRouteContext` and explicit type arguments for validation.
561
870
 
562
- #### Example: Full Controller Override Pattern
563
-
564
871
  ```typescript
565
872
  import { Configuration } from '@/models';
566
873
  import { ConfigurationRepository } from '@/repositories';
@@ -577,31 +884,27 @@ import { z } from '@hono/zod-openapi';
577
884
 
578
885
  const BASE_PATH = '/configurations';
579
886
 
580
- // Custom request body schema
581
887
  const CreateConfigurationSchema = z.object({
582
888
  code: z.string().min(1).max(100),
583
889
  description: z.string().max(500).optional(),
584
890
  group: z.string().min(1).max(50),
585
891
  });
586
892
 
587
- // Infer type for usage
588
893
  type TCreateConfiguration = z.infer<typeof CreateConfigurationSchema>;
589
894
 
590
- // Custom response schema
591
895
  const CreateResponseSchema = z.object({
592
896
  id: z.string(),
593
897
  code: z.string(),
594
898
  message: z.string(),
595
899
  });
596
900
 
597
- // Define the CRUD controller with custom schemas
598
901
  const _Controller = ControllerFactory.defineCrudController({
599
902
  repository: { name: ConfigurationRepository.name },
600
903
  controller: { name: 'ConfigurationController', basePath: BASE_PATH },
601
- authStrategies: [Authentication.STRATEGY_JWT],
904
+ authenticate: { strategies: [Authentication.STRATEGY_JWT] },
602
905
  entity: () => Configuration,
603
906
  routes: {
604
- count: { skipAuth: true },
907
+ count: { authenticate: { skip: true } },
605
908
  create: {
606
909
  request: { body: CreateConfigurationSchema },
607
910
  response: { schema: CreateResponseSchema },
@@ -623,34 +926,25 @@ export class ConfigurationController extends _Controller {
623
926
  super(repository);
624
927
  }
625
928
 
626
- // Override with full type safety
627
929
  override async create(opts: { context: TRouteContext }) {
628
930
  const { context } = opts;
629
-
630
- // Get typed request body using generic validation
631
931
  const data = context.req.valid<TCreateConfiguration>('json');
632
932
 
633
- // Access typed properties
634
933
  this.logger.info('[create] code: %s, group: %s', data.code, data.group);
635
934
 
636
935
  // Custom business logic here...
637
-
638
- // Call parent or return custom response
639
936
  return super.create(opts);
640
937
  }
641
938
 
642
- // Override updateById
643
939
  override async updateById(opts: { context: TRouteContext }) {
644
940
  const { context } = opts;
645
- // Explicitly type parameters if needed, or rely on schema validation
646
941
  const { id } = context.req.valid<{ id: string }>('param');
647
-
942
+
648
943
  this.logger.info('[updateById] id: %s', id);
649
944
 
650
945
  return super.updateById(opts);
651
946
  }
652
947
 
653
- // Override deleteById with audit logging
654
948
  override async deleteById(opts: { context: TRouteContext }) {
655
949
  const { context } = opts;
656
950
  const { id } = context.req.valid<{ id: string }>('param');
@@ -662,10 +956,19 @@ export class ConfigurationController extends _Controller {
662
956
  }
663
957
  ```
664
958
 
959
+ ### Generated Controller Internal Methods
960
+
961
+ The factory-generated controller includes a `normalizeCountData` method that checks the `x-request-count` header to determine response format:
962
+
963
+ - When `x-request-count` is `"true"` or omitted: returns `{ count, data }`
964
+ - When `x-request-count` is `"false"`: returns data only
965
+
966
+ Bulk operations (`updateBy`, `deleteBy`) require a non-empty `where` filter and return `400 Bad Request` if omitted.
665
967
 
666
968
  ## See Also
667
969
 
668
970
  - **Related References:**
971
+ - [gRPC Controllers](./grpc-controllers.md) - gRPC controller reference with ConnectRPC integration
669
972
  - [Services](./services.md) - Business logic layer called by controllers
670
973
  - [Repositories](./repositories/) - Data access layer for CRUD operations
671
974
  - [Middlewares](./middlewares.md) - Request/response middleware
@@ -673,7 +976,7 @@ export class ConfigurationController extends _Controller {
673
976
  - [Dependency Injection](./dependency-injection.md) - DI patterns and injection
674
977
 
675
978
  - **Guides:**
676
- - [Controllers Guide](/guides/core-concepts/controllers)
979
+ - [Controllers Guide](/guides/core-concepts/rest-controllers)
677
980
  - [Building a CRUD API](/guides/tutorials/building-a-crud-api)
678
981
 
679
982
  - **Best Practices:**