@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
@@ -0,0 +1,984 @@
1
+ ---
2
+ title: gRPC Controllers Reference
3
+ description: Technical reference for gRPC controller classes, RPC decorators, ConnectRPC adapter, and component integration
4
+ difficulty: intermediate
5
+ ---
6
+
7
+ # Deep Dive: gRPC Controllers
8
+
9
+ Technical reference for gRPC controller classes -- the foundation for building gRPC services in Ignis, powered by [ConnectRPC](https://connectrpc.com/).
10
+
11
+ Ignis gRPC controllers follow the same patterns as REST controllers (decorator-based routing, `binding()` method, DI integration) while bridging to ConnectRPC's universal handler system. REST and gRPC controllers coexist in the same application, sharing the same DI container, middleware pipeline, and lifecycle.
12
+
13
+ **Files:**
14
+ - `packages/core/src/base/controllers/grpc/abstract.ts`
15
+ - `packages/core/src/base/controllers/grpc/base.ts`
16
+ - `packages/core/src/base/controllers/grpc/adapter.ts`
17
+ - `packages/core/src/base/controllers/grpc/common/types.ts`
18
+ - `packages/core/src/base/metadata/routes/rpc.ts`
19
+ - `packages/core/src/components/controller/grpc/grpc.component.ts`
20
+ - `packages/core/src/components/controller/grpc/common/types.ts`
21
+
22
+ ## Quick Reference
23
+
24
+ | Item | Description |
25
+ |------|-------------|
26
+ | **AbstractGrpcController** | Abstract base class with RPC registration, ConnectRPC adapter mounting, idempotent `configure()` |
27
+ | **BaseGrpcController** | Recommended concrete base class with `bindRoute()` and `defineRoute()` implementations |
28
+ | **GrpcRequestAdapter** | Internal bridge from Ignis handlers to ConnectRPC universal handlers via `AsyncLocalStorage` |
29
+ | **GrpcComponent** | Auto-discovers gRPC controllers and mounts them on the application router |
30
+ | **@controller** | Class decorator with `transport: ControllerTransports.GRPC` and `service` field |
31
+ | **@unary** | Method decorator for unary RPCs |
32
+ | **@serverStream** | Method decorator for server-streaming RPCs (**unsupported -- throws at boot**) |
33
+ | **@clientStream** | Method decorator for client-streaming RPCs (**unsupported -- throws at boot**) |
34
+ | **@bidiStream** | Method decorator for bidirectional-streaming RPCs (**unsupported -- throws at boot**) |
35
+ | **@rpc** | Generic method decorator (requires explicit `method` in configs) |
36
+
37
+ > [!WARNING]
38
+ > **Current version supports unary RPCs only.** The `@serverStream`, `@clientStream`, and `@bidiStream` decorators still exist and set metadata correctly, but `BaseGrpcController.registerRoute()` will throw a clear error at boot time if a non-unary RPC is registered. This is because the Connect protocol over HTTP/1.1 cannot support streaming. The decorators are preserved for forward compatibility.
39
+
40
+ ## Prerequisites
41
+
42
+ gRPC support requires the following peer dependencies:
43
+
44
+ ```bash
45
+ bun add @connectrpc/connect @bufbuild/protobuf
46
+ ```
47
+
48
+ | Package | Purpose |
49
+ |---------|---------|
50
+ | `@connectrpc/connect` | ConnectRPC router, universal handlers, protocol bridge |
51
+ | `@bufbuild/protobuf` | Protobuf code generation, `create()` for constructing response messages |
52
+
53
+ For client-side usage (e.g., test clients), you also need a transport package:
54
+
55
+ ```bash
56
+ bun add @connectrpc/connect-web
57
+ ```
58
+
59
+ > [!NOTE]
60
+ > These are **optional** peer dependencies. They are only loaded at runtime when a gRPC controller is configured, via `createRequire` from the application's `node_modules`. If the deps are missing, `GrpcRequestAdapter.build()` throws a clear error at startup via `validateModule()`.
61
+
62
+ ### Protobuf Code Generation
63
+
64
+ Use `buf` or `protoc-gen-es` to generate TypeScript code from `.proto` files:
65
+
66
+ ```yaml
67
+ # buf.gen.yaml
68
+ version: v2
69
+ plugins:
70
+ - local: protoc-gen-es
71
+ out: generated
72
+ opt: target=ts
73
+ ```
74
+
75
+ ```bash
76
+ buf generate proto/greeter.proto
77
+ ```
78
+
79
+ The generated output includes:
80
+ - **Service descriptors** (e.g., `GreeterService`) -- passed to `@controller({ service })`
81
+ - **Message schemas** (e.g., `SayHelloResponseSchema`) -- used with `create()` to build responses
82
+ - **TypeScript types** (e.g., `SayHelloRequest`, `SayHelloResponse`) -- for handler signatures
83
+
84
+ ## `BaseGrpcController`
85
+
86
+ The recommended base class for gRPC controllers. Extends `AbstractGrpcController` with concrete `bindRoute()` and `defineRoute()` implementations.
87
+
88
+ ### Constructor Options
89
+
90
+ ```typescript
91
+ interface IGrpcControllerOptions {
92
+ scope: string;
93
+ path?: string; // Falls back to @controller decorator path if not provided
94
+ }
95
+ ```
96
+
97
+ The `scope` is used for scoped logging (`this.logger.for('methodName')`). The `path` defines the HTTP mount point for the ConnectRPC handlers; when both the constructor and `@controller` decorator specify a path, the decorator takes precedence.
98
+
99
+ ### Generic Parameters
100
+
101
+ `BaseGrpcController` accepts five generic parameters:
102
+
103
+ ```typescript
104
+ class BaseGrpcController<
105
+ RouteEnv extends Env = Env,
106
+ RouteSchema extends Schema = {},
107
+ BasePath extends string = '/',
108
+ ServiceType = unknown,
109
+ ConfigurableOptions extends object = {},
110
+ >
111
+ ```
112
+
113
+ | Parameter | Default | Description |
114
+ |-----------|---------|-------------|
115
+ | `RouteEnv` | `Env` | Hono environment type for typed context access |
116
+ | `RouteSchema` | `{}` | Hono schema type |
117
+ | `BasePath` | `'/'` | Base path string literal type |
118
+ | `ServiceType` | `unknown` | ConnectRPC service descriptor type |
119
+ | `ConfigurableOptions` | `{}` | Extra options passed to `configure()` |
120
+
121
+ `AbstractGrpcController` differs by defaulting `ServiceType` to `Parameters<ConnectRouter['service']>[0]` (the actual ConnectRPC service descriptor type), providing stricter type checking on the `service` field.
122
+
123
+ ### The `@controller` Decorator
124
+
125
+ gRPC controllers use the same `@controller` decorator as REST controllers, with two additional fields:
126
+
127
+ ```typescript
128
+ @controller({
129
+ path: '/grpc',
130
+ transport: ControllerTransports.GRPC,
131
+ service: GreeterServiceDef, // Generated ConnectRPC service descriptor
132
+ })
133
+ export class GreeterController extends BaseGrpcController {
134
+ // ...
135
+ }
136
+ ```
137
+
138
+ | Field | Type | Required | Description |
139
+ |-------|------|----------|-------------|
140
+ | `path` | `string` | Yes | HTTP base path for this controller's RPC endpoints |
141
+ | `transport` | `ControllerTransports.GRPC` | Yes | Marks this controller for gRPC transport (picked up by `GrpcComponent`) |
142
+ | `service` | `ServiceType` | Yes | ConnectRPC service descriptor from generated protobuf code |
143
+ | `tags` | `string[]` | No | Metadata tags (inherited from base controller metadata) |
144
+ | `description` | `string` | No | Controller description (inherited from base controller metadata) |
145
+
146
+ > [!NOTE]
147
+ > If `service` is missing or falsy at configure time, the `GrpcComponent` logs a warning and skips the controller entirely -- no routes are mounted.
148
+
149
+ ### Route Definition Patterns
150
+
151
+ Like REST controllers, gRPC controllers support three route definition patterns:
152
+
153
+ #### 1. Decorator-Based (Recommended)
154
+
155
+ ```typescript
156
+ @controller({
157
+ path: '/grpc',
158
+ transport: ControllerTransports.GRPC,
159
+ service: GreeterServiceDef,
160
+ })
161
+ export class GreeterController extends BaseGrpcController {
162
+ override binding() {}
163
+
164
+ @unary({ configs: { name: 'sayHello' } })
165
+ async sayHello(opts: { request: SayHelloRequest }): Promise<SayHelloResponse> {
166
+ return create(SayHelloResponseSchema, { message: `Hello, ${opts.request.name}!` });
167
+ }
168
+ }
169
+ ```
170
+
171
+ Decorator-based RPCs are auto-discovered during `configure()` via `registerRpcsFromRegistry()`. The `binding()` method can be left empty if all routes use decorators.
172
+
173
+ #### 2. `defineRoute()` -- Imperative
174
+
175
+ ```typescript
176
+ override binding() {
177
+ this.defineRoute({
178
+ configs: { name: 'sayHello', method: GRPC.Methods.UNARY },
179
+ handler: async (opts) => {
180
+ return create(SayHelloResponseSchema, { message: `Hello!` });
181
+ },
182
+ });
183
+ }
184
+ ```
185
+
186
+ #### 3. `bindRoute().to()` -- Fluent
187
+
188
+ ```typescript
189
+ override binding() {
190
+ this.bindRoute({
191
+ configs: { name: 'sayHello', method: GRPC.Methods.UNARY },
192
+ }).to({
193
+ handler: async (opts) => {
194
+ return create(SayHelloResponseSchema, { message: `Hello!` });
195
+ },
196
+ });
197
+ }
198
+ ```
199
+
200
+ ### The `binding()` Method
201
+
202
+ An abstract method you override to register RPCs using `defineRoute()` or `bindRoute()`. Called during `configure()` before decorator-based RPCs are registered. If you only use decorators, provide an empty implementation:
203
+
204
+ ```typescript
205
+ override binding() {}
206
+ ```
207
+
208
+ ### The `definitions` Property
209
+
210
+ A `Record<string, IRpcRegistration>` that stores all registered RPC handlers keyed by their proto method name. Populated by both decorator-based and imperative registration. The `GrpcRequestAdapter` reads this to build ConnectRPC handlers.
211
+
212
+ ```typescript
213
+ interface IRpcRegistration<RouteEnv extends Env = Env> {
214
+ configs: IRpcMetadata;
215
+ handler: TRpcHandler<unknown, unknown, RouteEnv>;
216
+ middlewares: TRpcMiddleware<RouteEnv>[]; // Pre-built auth middleware
217
+ }
218
+ ```
219
+
220
+ If you register a handler with the same `name` as an existing one, it overwrites the previous handler with a warning.
221
+
222
+ ### The `configure()` Lifecycle
223
+
224
+ The `configure()` method on `AbstractGrpcController` is idempotent (guarded by `isConfigured` flag). It runs the following steps in order:
225
+
226
+ 1. **`binding()`** -- Your override, registers imperative/fluent routes
227
+ 2. **`registerRpcsFromRegistry()`** -- Discovers decorator-based RPCs from `MetadataRegistry` and calls `bindRoute()` for each
228
+ 3. **`GrpcRequestAdapter.build()`** -- Creates the ConnectRPC adapter and mounts it as Hono middleware on `this.router`
229
+
230
+ ## RPC Decorators
231
+
232
+ All RPC decorators live in `packages/core/src/base/metadata/routes/rpc.ts`. They register metadata in the `MetadataRegistry`, which is read during `configure()`.
233
+
234
+ ### `@rpc` -- Generic
235
+
236
+ The base decorator. Requires the full `IRpcMetadata` config including `method`:
237
+
238
+ ```typescript
239
+ @rpc({ configs: { name: 'sayHello', method: GRPC.Methods.UNARY } })
240
+ async sayHello(opts: { request: SayHelloRequest }): Promise<SayHelloResponse> {
241
+ // ...
242
+ }
243
+ ```
244
+
245
+ ### `@unary`
246
+
247
+ Shorthand for `@rpc` with `method: 'unary'`. Single request, single response.
248
+
249
+ ```typescript
250
+ @unary({ configs: { name: 'sayHello' } })
251
+ async sayHello(opts: { request: SayHelloRequest }): Promise<SayHelloResponse> {
252
+ return create(SayHelloResponseSchema, { message: `Hello, ${opts.request.name}!` });
253
+ }
254
+ ```
255
+
256
+ ### `@serverStream` (unsupported)
257
+
258
+ Shorthand for `@rpc` with `method: 'server_streaming'`. **Throws at boot time in the current version** -- streaming is not supported over HTTP/1.1 Connect protocol. Decorator preserved for forward compatibility.
259
+
260
+ ### `@clientStream` (unsupported)
261
+
262
+ Shorthand for `@rpc` with `method: 'client_streaming'`. **Throws at boot time in the current version.**
263
+
264
+ ### `@bidiStream` (unsupported)
265
+
266
+ Shorthand for `@rpc` with `method: 'bidi_streaming'`. **Throws at boot time in the current version.**
267
+
268
+ ### Decorator Config
269
+
270
+ All decorators accept `{ configs: ... }` where configs extends `IRpcMetadata` (with `method` omitted for the shorthand variants):
271
+
272
+ ```typescript
273
+ // @unary, @serverStream, @clientStream, @bidiStream
274
+ { configs: Omit<IRpcMetadata, 'method'> }
275
+
276
+ // @rpc (generic)
277
+ { configs: IRpcMetadata }
278
+ ```
279
+
280
+ ## Type Definitions
281
+
282
+ ### `IRpcMetadata`
283
+
284
+ Metadata stored per RPC method in the `MetadataRegistry`.
285
+
286
+ ```typescript
287
+ interface IRpcMetadata {
288
+ /** Proto method name -- must match the RPC name in your .proto service definition. */
289
+ name: string;
290
+ /** RPC method type. */
291
+ method: TGrpcMethod; // 'unary' | 'server_streaming' | 'client_streaming' | 'bidi_streaming'
292
+ /** Per-RPC authentication config. */
293
+ authenticate?: { strategies?: TAuthStrategy[]; mode?: TAuthMode };
294
+ /** Per-RPC authorization spec(s). */
295
+ authorize?: IAuthorizationSpec | IAuthorizationSpec[];
296
+ }
297
+ ```
298
+
299
+ ### `IRpcRegistration`
300
+
301
+ Unified entry stored in the controller's `definitions` map. Combines metadata, handler function, and pre-built auth middleware.
302
+
303
+ ```typescript
304
+ interface IRpcRegistration<RouteEnv extends Env = Env> {
305
+ configs: IRpcMetadata;
306
+ handler: TRpcHandler<unknown, unknown, RouteEnv>;
307
+ middlewares: TRpcMiddleware<RouteEnv>[];
308
+ }
309
+ ```
310
+
311
+ ### `TRpcMiddleware`
312
+
313
+ Pre-built middleware function for gRPC auth enforcement, created by `AbstractGrpcController.buildRpcMiddlewares()`.
314
+
315
+ ```typescript
316
+ type TRpcMiddleware<RouteEnv extends Env = Env> = (
317
+ context: TRouteContext<RouteEnv>,
318
+ next: Next,
319
+ ) => ValueOrPromise<void | Response>;
320
+ ```
321
+
322
+ ### `TRpcHandler`
323
+
324
+ The handler signature for gRPC RPC methods. Receives the deserialized protobuf request and the Hono context (via `AsyncLocalStorage`).
325
+
326
+ ```typescript
327
+ type TRpcHandler<
328
+ RequestType = unknown,
329
+ ResponseType = unknown,
330
+ RouteEnv extends Env = Env,
331
+ > = (opts: {
332
+ request: RequestType;
333
+ context: TRouteContext<RouteEnv>;
334
+ }) => ValueOrPromise<ResponseType>;
335
+ ```
336
+
337
+ > [!NOTE]
338
+ > When using decorator-based RPCs, the handler method signature is `(opts: { request: RequestType }) => Promise<ResponseType>`. The `context` parameter is injected internally by the adapter and is not passed to the decorator-based handler method directly. The full `TRpcHandler` signature (with `context`) applies when using `defineRoute()` or `bindRoute()`.
339
+
340
+ ### `IGrpcControllerOptions`
341
+
342
+ Constructor options for gRPC controllers.
343
+
344
+ ```typescript
345
+ interface IGrpcControllerOptions {
346
+ scope: string;
347
+ path?: string;
348
+ }
349
+ ```
350
+
351
+ ### `IGrpcBindRouteOptions`
352
+
353
+ Fluent binding returned by `bindRoute()`.
354
+
355
+ ```typescript
356
+ interface IGrpcBindRouteOptions<RouteEnv extends Env = Env> {
357
+ configs: IRpcMetadata;
358
+ to: (opts: { handler: TRpcHandler<unknown, unknown, RouteEnv> }) => IGrpcDefineRouteOptions;
359
+ }
360
+ ```
361
+
362
+ ### `IGrpcDefineRouteOptions`
363
+
364
+ Return type from both `defineRoute()` and `bindRoute().to()`.
365
+
366
+ ```typescript
367
+ interface IGrpcDefineRouteOptions {
368
+ configs: IRpcMetadata;
369
+ }
370
+ ```
371
+
372
+ ### `IGrpcController`
373
+
374
+ The full interface that gRPC controllers implement. Extends `IConfigurable`.
375
+
376
+ ```typescript
377
+ interface IGrpcController<
378
+ RouteEnv extends Env = Env,
379
+ RouteSchema extends Schema = {},
380
+ BasePath extends string = '/',
381
+ ServiceType = unknown,
382
+ ConfigurableOptions extends object = {},
383
+ > extends IConfigurable<ConfigurableOptions> {
384
+ service: ServiceType;
385
+ router: Hono<RouteEnv, RouteSchema, BasePath>;
386
+ definitions: Record<string, IRpcRegistration<RouteEnv>>;
387
+
388
+ getRouter(): Hono<RouteEnv, RouteSchema, BasePath>;
389
+ bindRoute(opts: { configs: IRpcMetadata }): IGrpcBindRouteOptions<RouteEnv>;
390
+ defineRoute(opts: {
391
+ configs: IRpcMetadata;
392
+ handler: TRpcHandler<unknown, unknown, RouteEnv>;
393
+ }): IGrpcDefineRouteOptions;
394
+ }
395
+ ```
396
+
397
+ ### `IConnectAdapterResult`
398
+
399
+ Return type from `GrpcRequestAdapter.build()`.
400
+
401
+ ```typescript
402
+ interface IConnectAdapterResult<
403
+ RouteEnv extends Env = Env,
404
+ BasePath extends string = '/',
405
+ RouteInput extends Input = {},
406
+ > {
407
+ paths: string[];
408
+ middleware: MiddlewareHandler<RouteEnv, BasePath, RouteInput>;
409
+ }
410
+ ```
411
+
412
+ ## `GrpcRequestAdapter`
413
+
414
+ Internal bridge between Ignis gRPC controllers and ConnectRPC's universal handler system. You do not interact with this class directly -- it is created automatically during `configure()`.
415
+
416
+ ### Architecture
417
+
418
+ The adapter solves a key challenge: ConnectRPC handlers have their own `(request, context) => response` signature, but Ignis controllers need access to the Hono `Context` for middleware, auth, and request-scoped state. The adapter uses `AsyncLocalStorage` to provide request-scoped context isolation, ensuring concurrent requests never share state.
419
+
420
+ ```
421
+ Hono Request
422
+ -> GrpcRequestAdapter middleware (path matching via basePath + controllerPath)
423
+ -> AsyncLocalStorage.run(honoContext, ...)
424
+ -> Pre-built auth middlewares (authenticate -> authorize)
425
+ -> ConnectRPC universal handler
426
+ -> Ignis TRpcHandler (reads context from AsyncLocalStorage)
427
+ -> Response
428
+ ```
429
+
430
+ ### Static `build()` Method
431
+
432
+ The only public API. Validates peer deps via `validateModule()`, creates the adapter, and returns the middleware + registered paths:
433
+
434
+ ```typescript
435
+ static async build(opts: {
436
+ controller: AbstractGrpcController<...>;
437
+ interceptors?: unknown[];
438
+ }): Promise<IConnectAdapterResult<RouteEnv, BasePath>>
439
+ ```
440
+
441
+ Called internally by `AbstractGrpcController.configure()`:
442
+
443
+ ```typescript
444
+ const adapter = await GrpcRequestAdapter.build({ controller: this });
445
+ this.router.use('*', adapter.middleware);
446
+ ```
447
+
448
+ The optional `interceptors` array is passed to ConnectRPC's `createConnectRouter()` for request/response interception at the protocol level.
449
+
450
+ ### Internal Flow
451
+
452
+ 1. **`buildConnectHandlers()`** -- Wraps each Ignis `TRpcHandler` into ConnectRPC's `(request, context) => response` signature. The wrapper reads the Hono context from `AsyncLocalStorage`, runs pre-built auth middlewares (built by `AbstractGrpcController.buildRpcMiddlewares()`), then passes `{ request, context }` to the Ignis handler.
453
+
454
+ 2. **`registerService()`** -- Bridges the opaque `ServiceType` from `@controller` metadata to ConnectRPC's `router.service()` call, registering all handlers for the service.
455
+
456
+ 3. **`buildMiddleware()`** -- Creates a Hono middleware that:
457
+ - Strips the full mount prefix (`basePath + controllerPath`) from the request URL to derive the ConnectRPC handler path (e.g., `/package.Service/Method`)
458
+ - Looks up the ConnectRPC handler by path from the handler map
459
+ - Runs the handler inside `AsyncLocalStorage.run()` with the current Hono context
460
+ - Converts between Fetch API `Request`/`Response` and ConnectRPC's `UniversalServerRequest`/`UniversalServerResponse` formats
461
+ - Returns proper gRPC error responses on failure (with `grpc-status` and `grpc-message` headers)
462
+
463
+ ### Peer Dependency Loading
464
+
465
+ The adapter loads ConnectRPC modules at runtime using `createRequire` from the application's `node_modules`:
466
+
467
+ - `@connectrpc/connect` -- for `createConnectRouter`
468
+ - `@connectrpc/connect/protocol` -- for `universalServerRequestFromFetch` and `universalServerResponseToFetch`
469
+
470
+ This approach supports single-file builds where the peer deps may not be resolvable via standard `import`.
471
+
472
+ ### Error Handling
473
+
474
+ On handler errors, the adapter returns a JSON response with:
475
+ - HTTP status: `200` if gRPC status is `OK`, `500` otherwise
476
+ - `grpc-status` header: Preserved from `ConnectError.code` if available (duck-type check on `error.code` being a number), otherwise `13` (INTERNAL)
477
+ - `grpc-message` header: URL-encoded error message
478
+ - Body: JSON `{ message, code }`
479
+
480
+ The adapter uses a duck-type check on `error.code` to preserve gRPC status codes from ConnectRPC errors without importing `ConnectError` directly, avoiding tight coupling to the peer dependency.
481
+
482
+ ## `GrpcComponent`
483
+
484
+ Auto-discovers and configures gRPC controllers during the application lifecycle.
485
+
486
+ ### Configuration
487
+
488
+ ```typescript
489
+ interface IGrpcComponentConfig {
490
+ interceptors?: unknown[];
491
+ }
492
+ ```
493
+
494
+ The component registers a default (empty) config binding under the key `'@app/grpc/options'` (`GrpcBindingKeys.GRPC_COMPONENT_OPTIONS`).
495
+
496
+ ### Behavior
497
+
498
+ 1. Finds all bindings tagged with the `controllers` namespace
499
+ 2. Filters to controllers whose metadata has `transport: 'grpc'`
500
+ 3. Validates each gRPC controller:
501
+ - If `path` is missing, throws an error
502
+ - If `service` is missing, logs a warning and skips the controller
503
+ 4. Sets `instance.basePath` from the application's `path.base` config (needed for correct path stripping in the adapter)
504
+ 5. Calls `configure()` on each controller instance
505
+ 6. Mounts the controller's router on the application's root router at the controller's path via `router.route(metadata.path, instance.getRouter())`
506
+
507
+ ### Dynamic Discovery
508
+
509
+ The component uses a re-fetch loop with `Set` tracking. After configuring each controller, it re-queries the container for new controller bindings (excluding already-configured ones). This handles controllers registered dynamically during component composition (e.g., a component that registers another component that registers a gRPC controller).
510
+
511
+ ### Automatic Registration
512
+
513
+ `GrpcComponent` is instantiated and configured automatically by `BaseApplication` when `appConfigs.transports` includes `ControllerTransports.GRPC`. You do not need to register it manually. The relevant code in `BaseApplication`:
514
+
515
+ ```typescript
516
+ case ControllerTransports.GRPC: {
517
+ const grpcComponent = new GrpcComponent(this);
518
+ await grpcComponent.configure();
519
+ break;
520
+ }
521
+ ```
522
+
523
+ ## `GRPC` Constants
524
+
525
+ The `GRPC` class from `@venizia/ignis-helpers` provides all gRPC protocol constants:
526
+
527
+ ### Methods
528
+
529
+ ```typescript
530
+ GRPC.Methods.UNARY // 'unary'
531
+ GRPC.Methods.SERVER_STREAMING // 'server_streaming'
532
+ GRPC.Methods.CLIENT_STREAMING // 'client_streaming'
533
+ GRPC.Methods.BIDI_STREAMING // 'bidi_streaming'
534
+ ```
535
+
536
+ ### Result Codes
537
+
538
+ Standard gRPC status codes (matching `google.rpc.Code`):
539
+
540
+ ```typescript
541
+ GRPC.ResultCodes.OK // 0
542
+ GRPC.ResultCodes.CANCELLED // 1
543
+ GRPC.ResultCodes.UNKNOWN // 2
544
+ GRPC.ResultCodes.INVALID_ARGUMENT // 3
545
+ GRPC.ResultCodes.DEADLINE_EXCEEDED // 4
546
+ GRPC.ResultCodes.NOT_FOUND // 5
547
+ GRPC.ResultCodes.ALREADY_EXISTS // 6
548
+ GRPC.ResultCodes.PERMISSION_DENIED // 7
549
+ GRPC.ResultCodes.RESOURCE_EXHAUSTED // 8
550
+ GRPC.ResultCodes.FAILED_PRECONDITION // 9
551
+ GRPC.ResultCodes.ABORTED // 10
552
+ GRPC.ResultCodes.OUT_OF_RANGE // 11
553
+ GRPC.ResultCodes.UNIMPLEMENTED // 12
554
+ GRPC.ResultCodes.INTERNAL // 13
555
+ GRPC.ResultCodes.UNAVAILABLE // 14
556
+ GRPC.ResultCodes.DATA_LOSS // 15
557
+ GRPC.ResultCodes.UNAUTHENTICATED // 16
558
+ ```
559
+
560
+ ### Headers
561
+
562
+ Standard gRPC protocol headers:
563
+
564
+ ```typescript
565
+ GRPC.Headers.GRPC_STATUS // 'grpc-status'
566
+ GRPC.Headers.GRPC_MESSAGE // 'grpc-message'
567
+ GRPC.Headers.GRPC_TIMEOUT // 'grpc-timeout'
568
+ GRPC.Headers.GRPC_ENCODING // 'grpc-encoding'
569
+ // ... and more (see packages/helpers/src/common/constants/grpc.ts)
570
+ ```
571
+
572
+ ### Content Types
573
+
574
+ ```typescript
575
+ GRPC.HeaderValues.GRPC // 'application/grpc'
576
+ GRPC.HeaderValues.GRPC_PROTO // 'application/grpc+proto'
577
+ GRPC.HeaderValues.GRPC_JSON // 'application/grpc+json'
578
+ GRPC.HeaderValues.GRPC_WEB // 'application/grpc-web'
579
+ GRPC.HeaderValues.GRPC_WEB_PROTO // 'application/grpc-web+proto'
580
+ GRPC.HeaderValues.GRPC_WEB_JSON // 'application/grpc-web+json'
581
+ GRPC.HeaderValues.GRPC_WEB_TEXT // 'application/grpc-web-text'
582
+ ```
583
+
584
+ ## Application Setup
585
+
586
+ ### Enabling gRPC Transport
587
+
588
+ Add `ControllerTransports.GRPC` to the `transports` array in your application configs:
589
+
590
+ ```typescript
591
+ import {
592
+ BaseApplication,
593
+ ControllerTransports,
594
+ IApplicationConfigs,
595
+ IApplicationInfo,
596
+ } from '@venizia/ignis';
597
+ import { ValueOrPromise } from '@venizia/ignis-helpers';
598
+ import { GreeterController } from './controllers/greeter';
599
+ import { GreeterService } from './services/greeter.service';
600
+
601
+ export const appConfigs: IApplicationConfigs = {
602
+ host: '0.0.0.0',
603
+ port: 3000,
604
+ path: { base: '/', isStrict: false },
605
+ transports: [ControllerTransports.REST, ControllerTransports.GRPC],
606
+ };
607
+
608
+ export class Application extends BaseApplication {
609
+ getAppInfo(): ValueOrPromise<IApplicationInfo> {
610
+ return { name: 'my-app', version: '1.0.0', description: 'gRPC + REST app' };
611
+ }
612
+
613
+ staticConfigure() {}
614
+
615
+ preConfigure() {
616
+ this.service(GreeterService);
617
+ this.controller(GreeterController);
618
+ }
619
+
620
+ postConfigure() {}
621
+ setupMiddlewares() {}
622
+ }
623
+ ```
624
+
625
+ > [!WARNING]
626
+ > If `transports` does not include `ControllerTransports.GRPC`, gRPC controllers are still registered in the DI container but the `GrpcComponent` is never mounted -- their `configure()` is never called and no routes are served.
627
+
628
+ ### Dual Transport
629
+
630
+ REST and gRPC controllers coexist in the same application. Each controller declares its own transport via the `@controller` decorator. A single application can serve both:
631
+
632
+ ```typescript
633
+ preConfigure() {
634
+ // gRPC controller
635
+ this.controller(GreeterController);
636
+
637
+ // REST controller
638
+ this.controller(StatusController);
639
+ }
640
+ ```
641
+
642
+ REST controllers are handled by the `RestComponent` (active when transports includes `ControllerTransports.REST`, which is the default); gRPC controllers are handled by the `GrpcComponent` (active when transport is enabled). They share the same DI container and lifecycle.
643
+
644
+ ## Complete Example
645
+
646
+ ### 1. Proto File
647
+
648
+ ```protobuf
649
+ // proto/greeter.proto
650
+ syntax = "proto3";
651
+ package greeter.v1;
652
+
653
+ message SayHelloRequest {
654
+ string name = 1;
655
+ }
656
+
657
+ message SayHelloResponse {
658
+ string message = 1;
659
+ }
660
+
661
+ message ListUsersRequest {}
662
+
663
+ message ListUsersResponse {
664
+ repeated string users = 1;
665
+ }
666
+
667
+ service GreeterService {
668
+ rpc SayHello (SayHelloRequest) returns (SayHelloResponse);
669
+ rpc ListUsers (ListUsersRequest) returns (ListUsersResponse);
670
+ }
671
+ ```
672
+
673
+ ### 2. Generate TypeScript Code
674
+
675
+ ```bash
676
+ buf generate proto/greeter.proto
677
+ ```
678
+
679
+ ### 3. Definition File (Stable Import Boundary)
680
+
681
+ ```typescript
682
+ // controllers/greeter/definition.ts
683
+ export {
684
+ GreeterService,
685
+ ListUsersResponseSchema,
686
+ SayHelloResponseSchema,
687
+ type ListUsersRequest,
688
+ type ListUsersResponse,
689
+ type SayHelloRequest,
690
+ type SayHelloResponse,
691
+ } from './generated/greeter_pb';
692
+ ```
693
+
694
+ > [!TIP]
695
+ > Always re-export generated code through a `definition.ts` file. This acts as a stable import boundary -- controller code and external consumers import from here, never from the `generated/` directory directly. When you regenerate protos, only this file needs updating.
696
+
697
+ ### 4. Controller
698
+
699
+ ```typescript
700
+ // controllers/greeter/controller.ts
701
+ import { GreeterService } from '@/services';
702
+ import { create } from '@bufbuild/protobuf';
703
+ import {
704
+ BaseGrpcController,
705
+ ControllerTransports,
706
+ controller,
707
+ inject,
708
+ unary,
709
+ } from '@venizia/ignis';
710
+ import {
711
+ GreeterService as GreeterServiceDef,
712
+ ListUsersResponseSchema,
713
+ SayHelloResponseSchema,
714
+ type ListUsersRequest,
715
+ type ListUsersResponse,
716
+ type SayHelloRequest,
717
+ type SayHelloResponse,
718
+ } from './definition';
719
+
720
+ @controller({
721
+ path: '/grpc',
722
+ transport: ControllerTransports.GRPC,
723
+ service: GreeterServiceDef,
724
+ })
725
+ export class GreeterController extends BaseGrpcController {
726
+ constructor(
727
+ @inject({ key: 'services.GreeterService' })
728
+ private readonly greeterService: GreeterService,
729
+ ) {
730
+ super({ scope: 'GreeterController', path: '/grpc' });
731
+ }
732
+
733
+ override binding() {}
734
+
735
+ @unary({ configs: { name: 'sayHello' } })
736
+ async sayHello(opts: { request: SayHelloRequest }): Promise<SayHelloResponse> {
737
+ const message = await this.greeterService.sayHello(opts);
738
+ return create(SayHelloResponseSchema, { message });
739
+ }
740
+
741
+ @unary({ configs: { name: 'listUsers' } })
742
+ async listUsers(opts: { request: ListUsersRequest }): Promise<ListUsersResponse> {
743
+ const users = await this.greeterService.listUsers(opts);
744
+ return create(ListUsersResponseSchema, { users });
745
+ }
746
+ }
747
+ ```
748
+
749
+ ### 5. Minimal Controller (No DI)
750
+
751
+ A controller with no injected dependencies:
752
+
753
+ ```typescript
754
+ // controllers/echo/controller.ts
755
+ import { create } from '@bufbuild/protobuf';
756
+ import {
757
+ BaseGrpcController,
758
+ ControllerTransports,
759
+ controller,
760
+ unary,
761
+ } from '@venizia/ignis';
762
+ import {
763
+ EchoResponseSchema,
764
+ EchoService as EchoServiceDef,
765
+ type EchoRequest,
766
+ type EchoResponse,
767
+ } from './definition';
768
+
769
+ @controller({
770
+ path: '/grpc',
771
+ transport: ControllerTransports.GRPC,
772
+ service: EchoServiceDef,
773
+ })
774
+ export class EchoController extends BaseGrpcController {
775
+ constructor() {
776
+ super({ scope: 'EchoController', path: '/grpc' });
777
+ }
778
+
779
+ override binding() {}
780
+
781
+ @unary({ configs: { name: 'echo' } })
782
+ async echo(opts: { request: EchoRequest }): Promise<EchoResponse> {
783
+ return create(EchoResponseSchema, {
784
+ message: `Echo: ${opts.request.message}`,
785
+ });
786
+ }
787
+ }
788
+ ```
789
+
790
+ ### 6. Application
791
+
792
+ ```typescript
793
+ // application.ts
794
+ import {
795
+ BaseApplication,
796
+ ControllerTransports,
797
+ IApplicationConfigs,
798
+ IApplicationInfo,
799
+ } from '@venizia/ignis';
800
+ import { ValueOrPromise } from '@venizia/ignis-helpers';
801
+ import { GreeterController } from './controllers/greeter';
802
+ import { EchoController } from './controllers/echo';
803
+ import { GreeterService } from './services/greeter.service';
804
+
805
+ export const appConfigs: IApplicationConfigs = {
806
+ host: '0.0.0.0',
807
+ port: 3000,
808
+ path: { base: '/', isStrict: false },
809
+ transports: [ControllerTransports.REST, ControllerTransports.GRPC],
810
+ };
811
+
812
+ export class Application extends BaseApplication {
813
+ getAppInfo(): ValueOrPromise<IApplicationInfo> {
814
+ return { name: 'greeter-app', version: '1.0.0', description: 'gRPC greeter' };
815
+ }
816
+
817
+ staticConfigure() {}
818
+
819
+ preConfigure() {
820
+ this.service(GreeterService);
821
+ this.controller(GreeterController);
822
+ this.controller(EchoController);
823
+ }
824
+
825
+ postConfigure() {}
826
+ setupMiddlewares() {}
827
+ }
828
+ ```
829
+
830
+ ### 7. Client (Testing)
831
+
832
+ ```typescript
833
+ // client.ts
834
+ import { create } from '@bufbuild/protobuf';
835
+ import { createClient } from '@connectrpc/connect';
836
+ import { createConnectTransport } from '@connectrpc/connect-web';
837
+ import { GreeterService, SayHelloRequestSchema } from './controllers/greeter/definition';
838
+
839
+ const transport = createConnectTransport({ baseUrl: 'http://localhost:3000/grpc' });
840
+ const client = createClient(GreeterService, transport);
841
+
842
+ const response = await client.sayHello(create(SayHelloRequestSchema, { name: 'Ignis' }));
843
+ console.log(response.message);
844
+ ```
845
+
846
+ ## Component-Based Registration
847
+
848
+ gRPC controllers can be registered through components, following the same pattern as REST controllers. This enables modular composition and late registration.
849
+
850
+ ### Basic Component
851
+
852
+ ```typescript
853
+ import {
854
+ BaseApplication,
855
+ BaseComponent,
856
+ CoreBindings,
857
+ inject,
858
+ } from '@venizia/ignis';
859
+ import { ValueOrPromise } from '@venizia/ignis-helpers';
860
+ import { EchoController } from '../controllers/echo';
861
+
862
+ export class EchoComponent extends BaseComponent {
863
+ constructor(
864
+ @inject({ key: CoreBindings.APPLICATION_INSTANCE })
865
+ private application: BaseApplication,
866
+ ) {
867
+ super({ scope: 'EchoComponent' });
868
+ }
869
+
870
+ override binding(): ValueOrPromise<void> {
871
+ this.application.controller(EchoController);
872
+ }
873
+ }
874
+ ```
875
+
876
+ ### Component Composition
877
+
878
+ Components can compose other components, building a dependency graph of controllers:
879
+
880
+ ```typescript
881
+ import {
882
+ BaseApplication,
883
+ BaseComponent,
884
+ CoreBindings,
885
+ inject,
886
+ } from '@venizia/ignis';
887
+ import { TimeController } from '../controllers/time';
888
+ import { EchoComponent } from './echo.component';
889
+
890
+ export class TimeComponent extends BaseComponent {
891
+ constructor(
892
+ @inject({ key: CoreBindings.APPLICATION_INSTANCE })
893
+ private application: BaseApplication,
894
+ ) {
895
+ super({ scope: 'TimeComponent' });
896
+ }
897
+
898
+ override async binding(): Promise<void> {
899
+ // Compose EchoComponent -- registers EchoController
900
+ this.application.component(EchoComponent);
901
+
902
+ // Register this component's own controller
903
+ this.application.controller(TimeController);
904
+ }
905
+ }
906
+ ```
907
+
908
+ The `GrpcComponent` handles these dynamically-registered controllers through its re-fetch loop -- after configuring each controller, it re-queries the container for newly added bindings.
909
+
910
+ ### Registration in Application
911
+
912
+ ```typescript
913
+ preConfigure() {
914
+ // TimeComponent composes EchoComponent internally
915
+ this.component(TimeComponent);
916
+ }
917
+ ```
918
+
919
+ ## Authentication and Authorization
920
+
921
+ Per-RPC authentication and authorization are configured via the `authenticate` and `authorize` fields in `IRpcMetadata`. Auth middlewares are pre-built during route registration by `AbstractGrpcController.buildRpcMiddlewares()` and executed before the handler inside the `AsyncLocalStorage` context.
922
+
923
+ ### Per-RPC Authentication
924
+
925
+ ```typescript
926
+ @unary({
927
+ configs: {
928
+ name: 'sayHello',
929
+ authenticate: {
930
+ strategies: ['jwt'],
931
+ mode: 'required',
932
+ },
933
+ },
934
+ })
935
+ async sayHello(opts: { request: SayHelloRequest }): Promise<SayHelloResponse> {
936
+ // Only accessible with a valid JWT token
937
+ return create(SayHelloResponseSchema, { message: 'Hello!' });
938
+ }
939
+ ```
940
+
941
+ | Field | Type | Default | Description |
942
+ |-------|------|---------|-------------|
943
+ | `strategies` | `TAuthStrategy[]` | `[]` | Authentication strategies to apply (e.g., `['jwt']`, `['basic']`) |
944
+ | `mode` | `TAuthMode` | `'any'` | `'required'` \| `'optional'` \| `'any'` \| `'all'` (defaults to `AuthenticationModes.ANY`) |
945
+
946
+ ### Per-RPC Authorization
947
+
948
+ ```typescript
949
+ @unary({
950
+ configs: {
951
+ name: 'deleteUser',
952
+ authenticate: { strategies: ['jwt'], mode: 'required' },
953
+ authorize: { action: 'delete', resource: 'user' },
954
+ },
955
+ })
956
+ async deleteUser(opts: { request: DeleteUserRequest }): Promise<DeleteUserResponse> {
957
+ // Requires JWT + delete permission on user resource
958
+ // ...
959
+ }
960
+ ```
961
+
962
+ Multiple authorization specs can be provided as an array:
963
+
964
+ ```typescript
965
+ authorize: [
966
+ { action: 'read', resource: 'user' },
967
+ { action: 'read', resource: 'profile' },
968
+ ]
969
+ ```
970
+
971
+ ### Middleware Execution Order
972
+
973
+ Auth middlewares run in the following order inside the ConnectRPC handler wrapper:
974
+
975
+ 1. **Authenticate** middlewares (if `configs.authenticate.strategies` has entries)
976
+ 2. **Authorize** middlewares (if `configs.authorize` is present), one per spec
977
+ 3. **Handler** execution
978
+
979
+ ## See Also
980
+
981
+ - [Controllers Reference](./controllers.md) -- REST controller classes and API endpoint patterns
982
+ - [Components Reference](./components.md) -- Component system and built-in components
983
+ - [Dependency Injection](./dependency-injection.md) -- IoC container, `@inject`, binding keys
984
+ - [Services](./services.md) -- Business logic layer