@venizia/ignis-docs 0.0.7-2 → 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.
- package/dist/mcp-server/common/paths.d.ts +4 -2
- package/dist/mcp-server/common/paths.d.ts.map +1 -1
- package/dist/mcp-server/common/paths.js +8 -6
- package/dist/mcp-server/common/paths.js.map +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.js +7 -7
- package/dist/mcp-server/tools/docs/get-document-metadata.tool.js +3 -3
- package/dist/mcp-server/tools/docs/get-package-overview.tool.d.ts +1 -1
- package/dist/mcp-server/tools/docs/get-package-overview.tool.js +1 -1
- package/package.json +1 -1
- package/wiki/best-practices/api-usage-examples.md +9 -9
- package/wiki/best-practices/architectural-patterns.md +19 -3
- package/wiki/best-practices/architecture-decisions.md +6 -6
- package/wiki/best-practices/code-style-standards/advanced-patterns.md +1 -1
- package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
- package/wiki/best-practices/code-style-standards/function-patterns.md +2 -2
- package/wiki/best-practices/code-style-standards/index.md +2 -2
- package/wiki/best-practices/code-style-standards/naming-conventions.md +1 -1
- package/wiki/best-practices/code-style-standards/route-definitions.md +4 -4
- package/wiki/best-practices/data-modeling.md +1 -1
- package/wiki/best-practices/deployment-strategies.md +1 -1
- package/wiki/best-practices/error-handling.md +2 -2
- package/wiki/best-practices/performance-optimization.md +3 -3
- package/wiki/best-practices/security-guidelines.md +2 -2
- package/wiki/best-practices/troubleshooting-tips.md +1 -1
- package/wiki/{references → extensions}/components/authentication/api.md +12 -20
- package/wiki/{references → extensions}/components/authentication/errors.md +1 -1
- package/wiki/{references → extensions}/components/authentication/index.md +5 -8
- package/wiki/{references → extensions}/components/authentication/usage.md +20 -36
- package/wiki/{references → extensions}/components/authorization/api.md +62 -13
- package/wiki/{references → extensions}/components/authorization/errors.md +12 -7
- package/wiki/{references → extensions}/components/authorization/index.md +93 -6
- package/wiki/{references → extensions}/components/authorization/usage.md +42 -4
- package/wiki/{references → extensions}/components/health-check.md +5 -4
- package/wiki/{references → extensions}/components/index.md +2 -0
- package/wiki/{references → extensions}/components/mail/index.md +1 -1
- package/wiki/{references → extensions}/components/request-tracker.md +1 -1
- package/wiki/{references → extensions}/components/socket-io/api.md +2 -2
- package/wiki/{references → extensions}/components/socket-io/errors.md +2 -0
- package/wiki/{references → extensions}/components/socket-io/index.md +24 -20
- package/wiki/{references → extensions}/components/socket-io/usage.md +2 -2
- package/wiki/{references → extensions}/components/static-asset/api.md +14 -15
- package/wiki/{references → extensions}/components/static-asset/errors.md +3 -1
- package/wiki/{references → extensions}/components/static-asset/index.md +158 -89
- package/wiki/{references → extensions}/components/static-asset/usage.md +8 -5
- package/wiki/{references → extensions}/components/swagger.md +3 -3
- package/wiki/{references → extensions}/components/template/index.md +4 -4
- package/wiki/{references → extensions}/components/template/setup-page.md +1 -1
- package/wiki/{references → extensions}/components/template/single-page.md +1 -1
- package/wiki/{references → extensions}/components/websocket/api.md +7 -6
- package/wiki/{references → extensions}/components/websocket/errors.md +17 -3
- package/wiki/{references → extensions}/components/websocket/index.md +17 -11
- package/wiki/{references → extensions}/components/websocket/usage.md +2 -2
- package/wiki/{references → extensions}/helpers/crypto/index.md +1 -1
- package/wiki/{references → extensions}/helpers/env/index.md +9 -5
- package/wiki/{references → extensions}/helpers/error/index.md +2 -7
- package/wiki/{references → extensions}/helpers/index.md +18 -6
- package/wiki/{references → extensions}/helpers/kafka/admin.md +33 -16
- package/wiki/extensions/helpers/kafka/consumer.md +384 -0
- package/wiki/extensions/helpers/kafka/examples.md +361 -0
- package/wiki/extensions/helpers/kafka/index.md +639 -0
- package/wiki/{references → extensions}/helpers/kafka/producer.md +100 -96
- package/wiki/extensions/helpers/kafka/schema-registry.md +214 -0
- package/wiki/{references → extensions}/helpers/logger/index.md +2 -2
- package/wiki/{references → extensions}/helpers/queue/index.md +400 -4
- package/wiki/{references → extensions}/helpers/storage/api.md +170 -10
- package/wiki/{references → extensions}/helpers/storage/index.md +44 -8
- package/wiki/{references → extensions}/helpers/template/index.md +1 -1
- package/wiki/{references → extensions}/helpers/testing/index.md +4 -4
- package/wiki/{references → extensions}/helpers/types/index.md +63 -16
- package/wiki/{references → extensions}/helpers/websocket/index.md +1 -1
- package/wiki/extensions/index.md +48 -0
- package/wiki/guides/core-concepts/application/bootstrapping.md +55 -37
- package/wiki/guides/core-concepts/application/index.md +95 -35
- package/wiki/guides/core-concepts/components-guide.md +23 -19
- package/wiki/guides/core-concepts/components.md +34 -10
- package/wiki/guides/core-concepts/dependency-injection.md +99 -34
- package/wiki/guides/core-concepts/grpc-controllers.md +295 -0
- package/wiki/guides/core-concepts/persistent/datasources.md +27 -8
- package/wiki/guides/core-concepts/persistent/models.md +43 -1
- package/wiki/guides/core-concepts/persistent/repositories.md +75 -8
- package/wiki/guides/core-concepts/persistent/transactions.md +38 -8
- package/wiki/guides/core-concepts/{controllers.md → rest-controllers.md} +30 -33
- package/wiki/guides/core-concepts/services.md +19 -5
- package/wiki/guides/get-started/5-minute-quickstart.md +6 -7
- package/wiki/guides/get-started/philosophy.md +1 -1
- package/wiki/guides/index.md +2 -2
- package/wiki/guides/reference/glossary.md +7 -7
- package/wiki/guides/reference/mcp-docs-server.md +1 -1
- package/wiki/guides/tutorials/building-a-crud-api.md +2 -2
- package/wiki/guides/tutorials/complete-installation.md +17 -14
- package/wiki/guides/tutorials/ecommerce-api.md +18 -18
- package/wiki/guides/tutorials/realtime-chat.md +8 -8
- package/wiki/guides/tutorials/testing.md +2 -2
- package/wiki/index.md +4 -3
- package/wiki/references/base/application.md +341 -21
- package/wiki/references/base/bootstrapping.md +43 -13
- package/wiki/references/base/components.md +259 -8
- package/wiki/references/base/controllers.md +556 -253
- package/wiki/references/base/datasources.md +159 -79
- package/wiki/references/base/dependency-injection.md +299 -48
- package/wiki/references/base/filter-system/application-usage.md +18 -2
- package/wiki/references/base/filter-system/array-operators.md +14 -6
- package/wiki/references/base/filter-system/comparison-operators.md +9 -3
- package/wiki/references/base/filter-system/default-filter.md +28 -3
- package/wiki/references/base/filter-system/fields-order-pagination.md +17 -13
- package/wiki/references/base/filter-system/index.md +169 -11
- package/wiki/references/base/filter-system/json-filtering.md +51 -18
- package/wiki/references/base/filter-system/list-operators.md +4 -3
- package/wiki/references/base/filter-system/logical-operators.md +7 -2
- package/wiki/references/base/filter-system/null-operators.md +50 -0
- package/wiki/references/base/filter-system/quick-reference.md +82 -243
- package/wiki/references/base/filter-system/range-operators.md +7 -1
- package/wiki/references/base/filter-system/tips.md +34 -7
- package/wiki/references/base/filter-system/use-cases.md +6 -5
- package/wiki/references/base/grpc-controllers.md +984 -0
- package/wiki/references/base/index.md +32 -24
- package/wiki/references/base/middleware.md +347 -0
- package/wiki/references/base/models.md +390 -46
- package/wiki/references/base/providers.md +14 -14
- package/wiki/references/base/repositories/advanced.md +84 -69
- package/wiki/references/base/repositories/index.md +447 -12
- package/wiki/references/base/repositories/mixins.md +103 -98
- package/wiki/references/base/repositories/relations.md +129 -45
- package/wiki/references/base/repositories/soft-deletable.md +104 -23
- package/wiki/references/base/services.md +94 -14
- package/wiki/references/index.md +12 -10
- package/wiki/references/quick-reference.md +98 -65
- package/wiki/references/utilities/crypto.md +21 -4
- package/wiki/references/utilities/date.md +25 -7
- package/wiki/references/utilities/index.md +26 -24
- package/wiki/references/utilities/jsx.md +54 -54
- package/wiki/references/utilities/module.md +8 -6
- package/wiki/references/utilities/parse.md +16 -9
- package/wiki/references/utilities/performance.md +22 -7
- package/wiki/references/utilities/promise.md +19 -16
- package/wiki/references/utilities/request.md +48 -26
- package/wiki/references/utilities/schema.md +69 -6
- package/wiki/references/utilities/statuses.md +131 -140
- package/wiki/references/helpers/kafka/consumer.md +0 -473
- package/wiki/references/helpers/kafka/examples.md +0 -234
- package/wiki/references/helpers/kafka/index.md +0 -482
- /package/wiki/{references → extensions}/components/mail/api.md +0 -0
- /package/wiki/{references → extensions}/components/mail/errors.md +0 -0
- /package/wiki/{references → extensions}/components/mail/usage.md +0 -0
- /package/wiki/{references → extensions}/components/template/api-page.md +0 -0
- /package/wiki/{references → extensions}/components/template/errors-page.md +0 -0
- /package/wiki/{references → extensions}/components/template/usage-page.md +0 -0
- /package/wiki/{references → extensions}/helpers/cron/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/inversion/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/network/api.md +0 -0
- /package/wiki/{references → extensions}/helpers/network/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/redis/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/socket-io/api.md +0 -0
- /package/wiki/{references → extensions}/helpers/socket-io/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/template/single-page.md +0 -0
- /package/wiki/{references → extensions}/helpers/uid/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/websocket/api.md +0 -0
- /package/wiki/{references → extensions}/helpers/worker-thread/index.md +0 -0
- /package/wiki/{references → extensions}/src-details/mcp-server.md +0 -0
|
@@ -4,13 +4,13 @@ Dependency Injection (DI) enables loosely coupled, testable code by automaticall
|
|
|
4
4
|
|
|
5
5
|
> **Deep Dive:** See [DI Reference](../../references/base/dependency-injection.md) for technical details on Container, Binding, and `@inject`.
|
|
6
6
|
|
|
7
|
-
> **Standalone Package:** The core DI container is available as the standalone `@venizia/ignis-inversion` package for use outside the Ignis framework. See [Inversion Package Reference](/
|
|
7
|
+
> **Standalone Package:** The core DI container is available as the standalone `@venizia/ignis-inversion` package for use outside the Ignis framework. See [Inversion Package Reference](/extensions/helpers/inversion/) for details.
|
|
8
8
|
|
|
9
9
|
## Core Concepts
|
|
10
10
|
|
|
11
11
|
| Concept | Description |
|
|
12
12
|
| :--- | :--- |
|
|
13
|
-
| **Container** | The central registry for all your application's services and dependencies. The `Application` class itself
|
|
13
|
+
| **Container** | The central registry for all your application's services and dependencies. The `Application` class itself extends `Container`. |
|
|
14
14
|
| **Binding** | The process of registering a class or value with the container under a specific key (e.g., `'services.UserService'`). |
|
|
15
15
|
| **Injection**| The process of requesting a dependency from the container using the `@inject` decorator. |
|
|
16
16
|
|
|
@@ -42,6 +42,13 @@ graph TD
|
|
|
42
42
|
end
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
### Instantiation Algorithm (Two-Phase)
|
|
46
|
+
|
|
47
|
+
When the container instantiates a class, it follows a two-phase process:
|
|
48
|
+
|
|
49
|
+
1. **Constructor injection** -- Reads `@inject` metadata from the class, sorts by parameter index, resolves each dependency from the container, and passes them as constructor arguments.
|
|
50
|
+
2. **Property injection** -- After construction, reads property metadata and assigns each dependency to the decorated properties.
|
|
51
|
+
|
|
45
52
|
## Binding Dependencies
|
|
46
53
|
|
|
47
54
|
Before a dependency can be injected, it must be **bound** to the container. This is typically done in the `preConfigure` method of your `Application` class.
|
|
@@ -50,13 +57,13 @@ Before a dependency can be injected, it must be **bound** to the container. This
|
|
|
50
57
|
|
|
51
58
|
The `Application` class provides helper methods for common resource types. These automatically create a binding with a conventional key.
|
|
52
59
|
|
|
53
|
-
| Method | Default Key |
|
|
54
|
-
| :--- | :--- |
|
|
55
|
-
| `app.service(UserService)` | `services.UserService` |
|
|
56
|
-
| `app.repository(UserRepository)` | `repositories.UserRepository` |
|
|
57
|
-
| `app.dataSource(PostgresDataSource)` | `datasources.PostgresDataSource` |
|
|
58
|
-
| `app.controller(UserController)` | `controllers.UserController` |
|
|
59
|
-
| `app.component(MyComponent)` | `components.MyComponent` |
|
|
60
|
+
| Method | Default Key | Default Scope |
|
|
61
|
+
| :--- | :--- | :--- |
|
|
62
|
+
| `app.service(UserService)` | `services.UserService` | Transient |
|
|
63
|
+
| `app.repository(UserRepository)` | `repositories.UserRepository` | Transient |
|
|
64
|
+
| `app.dataSource(PostgresDataSource)` | `datasources.PostgresDataSource` | **Singleton** |
|
|
65
|
+
| `app.controller(UserController)` | `controllers.UserController` | Transient |
|
|
66
|
+
| `app.component(MyComponent)` | `components.MyComponent` | **Singleton** |
|
|
60
67
|
|
|
61
68
|
All these methods accept an optional second parameter to customize the binding key:
|
|
62
69
|
|
|
@@ -85,15 +92,29 @@ this.bind<string>({ key: 'API_KEY' }).toValue('my-secret-api-key');
|
|
|
85
92
|
|
|
86
93
|
You can control the lifecycle of your dependencies with scopes.
|
|
87
94
|
|
|
88
|
-
- **`TRANSIENT`** (default): A new instance is created every time the dependency is
|
|
89
|
-
- **`SINGLETON`**: A single instance is created once and
|
|
95
|
+
- **`TRANSIENT`** (default): A new instance is created every time the dependency is resolved.
|
|
96
|
+
- **`SINGLETON`**: A single instance is created once and cached. All subsequent resolutions return the same instance.
|
|
90
97
|
|
|
91
98
|
```typescript
|
|
99
|
+
import { BindingScopes } from '@venizia/ignis-inversion';
|
|
100
|
+
|
|
92
101
|
this.bind({ key: 'services.MySingletonService' })
|
|
93
102
|
.toClass(MySingletonService)
|
|
94
103
|
.setScope(BindingScopes.SINGLETON); // Use SINGLETON for this service
|
|
95
104
|
```
|
|
96
105
|
|
|
106
|
+
### Binding Tags
|
|
107
|
+
|
|
108
|
+
Bindings are automatically tagged with their namespace prefix. For example, a binding with key `'services.UserService'` is auto-tagged with `'services'`. You can also add custom tags:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
this.bind({ key: 'services.UserService' })
|
|
112
|
+
.toClass(UserService)
|
|
113
|
+
.setTags('critical', 'user-domain');
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Tags are used by the container's `findByTag()` method to discover bindings by category.
|
|
117
|
+
|
|
97
118
|
## Injecting Dependencies
|
|
98
119
|
|
|
99
120
|
`Ignis` provides the `@inject` decorator to request dependencies from the container.
|
|
@@ -103,16 +124,16 @@ this.bind({ key: 'services.MySingletonService' })
|
|
|
103
124
|
This makes dependencies explicit and ensures they are available right away.
|
|
104
125
|
|
|
105
126
|
```typescript
|
|
106
|
-
import {
|
|
127
|
+
import { BaseRestController, controller, inject } from '@venizia/ignis';
|
|
107
128
|
import { UserService } from '../services/user.service';
|
|
108
129
|
|
|
109
130
|
@controller({ path: '/users' })
|
|
110
|
-
export class UserController extends
|
|
131
|
+
export class UserController extends BaseRestController {
|
|
111
132
|
constructor(
|
|
112
133
|
@inject({ key: 'services.UserService' })
|
|
113
134
|
private userService: UserService
|
|
114
135
|
) {
|
|
115
|
-
super({ scope: UserController.name
|
|
136
|
+
super({ scope: UserController.name });
|
|
116
137
|
}
|
|
117
138
|
|
|
118
139
|
// ... you can now use this.userService
|
|
@@ -130,34 +151,56 @@ import { UserService } from '../services/user.service';
|
|
|
130
151
|
export class UserComponent {
|
|
131
152
|
@inject({ key: 'services.UserService' })
|
|
132
153
|
private userService: UserService;
|
|
133
|
-
|
|
154
|
+
|
|
134
155
|
// ...
|
|
135
156
|
}
|
|
136
157
|
```
|
|
137
158
|
|
|
159
|
+
### Optional Dependencies
|
|
160
|
+
|
|
161
|
+
Mark a dependency as optional to avoid errors when it's not bound:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
constructor(
|
|
165
|
+
@inject({ key: 'services.CacheService', isOptional: true })
|
|
166
|
+
private cache?: CacheService
|
|
167
|
+
) {
|
|
168
|
+
super({ scope: MyService.name });
|
|
169
|
+
// cache will be undefined if not bound
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
138
173
|
## Providers
|
|
139
174
|
|
|
140
|
-
Providers are used for dependencies that require complex setup logic. A provider
|
|
175
|
+
Providers are used for dependencies that require complex setup logic. A provider can be either a factory function or a class that implements the `IProvider<T>` interface with a `value()` method.
|
|
141
176
|
|
|
142
|
-
###
|
|
177
|
+
### Factory Function Provider
|
|
143
178
|
|
|
144
179
|
```typescript
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
180
|
+
// In your application class
|
|
181
|
+
this.bind<ThirdPartyApiClient>({ key: 'services.ApiClient' })
|
|
182
|
+
.toProvider((container) => {
|
|
183
|
+
const config = container.get<IConfig>({ key: 'configs.api' });
|
|
184
|
+
return new ThirdPartyApiClient({
|
|
185
|
+
apiKey: config.apiKey,
|
|
186
|
+
baseUrl: config.baseUrl,
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Class-Based Provider
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import { IProvider, Container } from '@venizia/ignis-inversion';
|
|
195
|
+
|
|
196
|
+
export class ApiClientProvider implements IProvider<ThirdPartyApiClient> {
|
|
155
197
|
value(container: Container): ThirdPartyApiClient {
|
|
198
|
+
const config = container.get<IConfig>({ key: 'configs.api' });
|
|
156
199
|
const client = new ThirdPartyApiClient({
|
|
157
|
-
apiKey:
|
|
158
|
-
baseUrl:
|
|
200
|
+
apiKey: config.apiKey,
|
|
201
|
+
baseUrl: config.baseUrl,
|
|
159
202
|
});
|
|
160
|
-
client.connect();
|
|
203
|
+
client.connect();
|
|
161
204
|
return client;
|
|
162
205
|
}
|
|
163
206
|
}
|
|
@@ -192,7 +235,15 @@ container.bind({ key: 'services.Cache' })
|
|
|
192
235
|
|
|
193
236
|
// Resolve dependencies
|
|
194
237
|
const logger = container.get<LoggerService>({ key: 'services.Logger' });
|
|
195
|
-
const apiKey = container.
|
|
238
|
+
const apiKey = container.get<string>({ key: 'config.apiKey' });
|
|
239
|
+
|
|
240
|
+
// Resolve multiple at once
|
|
241
|
+
const [svcA, svcB] = container.gets<[ServiceA, ServiceB]>({
|
|
242
|
+
bindings: [
|
|
243
|
+
{ key: 'services.A' },
|
|
244
|
+
{ key: 'services.B', isOptional: true },
|
|
245
|
+
],
|
|
246
|
+
});
|
|
196
247
|
```
|
|
197
248
|
|
|
198
249
|
### Use Cases
|
|
@@ -245,19 +296,33 @@ describe('UserService', () => {
|
|
|
245
296
|
| **Use Case** | Main application | Testing, isolated modules, workers |
|
|
246
297
|
|
|
247
298
|
> [!TIP]
|
|
248
|
-
> The `Application` class extends `Container`, so all container methods (`bind`, `get`, `
|
|
299
|
+
> The `Application` class extends `Container`, so all container methods (`bind`, `get`, `gets`, `resolve`, `findByTag`, `isBound`, `unbind`) are available on your application instance. Standalone containers are useful when you need isolation from the main application context.
|
|
300
|
+
|
|
301
|
+
### Container API Summary
|
|
302
|
+
|
|
303
|
+
| Method | Description |
|
|
304
|
+
| :--- | :--- |
|
|
305
|
+
| `bind<T>({ key })` | Create a new binding |
|
|
306
|
+
| `get<T>({ key, isOptional? })` | Resolve a single dependency |
|
|
307
|
+
| `gets<T>({ bindings })` | Resolve multiple dependencies at once |
|
|
308
|
+
| `resolve<T>(cls)` | Instantiate a class with DI (alias for `instantiate`) |
|
|
309
|
+
| `isBound({ key })` | Check if a key is bound |
|
|
310
|
+
| `unbind({ key })` | Remove a binding |
|
|
311
|
+
| `findByTag({ tag, exclude? })` | Find bindings by tag |
|
|
312
|
+
| `clear()` | Clear all cached singleton instances |
|
|
313
|
+
| `reset()` | Remove all bindings entirely |
|
|
249
314
|
|
|
250
315
|
## See Also
|
|
251
316
|
|
|
252
317
|
- **Related Concepts:**
|
|
253
318
|
- [Application](/guides/core-concepts/application/) - Application extends Container
|
|
254
|
-
- [Controllers](/guides/core-concepts/controllers) - Use DI for injecting services
|
|
319
|
+
- [Controllers](/guides/core-concepts/rest-controllers) - Use DI for injecting services
|
|
255
320
|
- [Services](/guides/core-concepts/services) - Use DI for injecting repositories
|
|
256
321
|
- [Providers](/references/base/providers) - Factory pattern for dynamic injection
|
|
257
322
|
|
|
258
323
|
- **References:**
|
|
259
324
|
- [Dependency Injection API](/references/base/dependency-injection) - Complete API reference
|
|
260
|
-
- [Inversion Helper](/
|
|
325
|
+
- [Inversion Helper](/extensions/helpers/inversion/) - DI container utilities
|
|
261
326
|
- [Glossary](/guides/reference/glossary#dependency-injection-di) - DI concepts explained
|
|
262
327
|
|
|
263
328
|
- **Tutorials:**
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# gRPC Controllers
|
|
2
|
+
|
|
3
|
+
Ignis provides first-class support for gRPC via the [ConnectRPC](https://connectrpc.com/) protocol. gRPC controllers use Protobuf service definitions for strongly-typed RPC methods, and are served over the same Hono HTTP server as REST controllers.
|
|
4
|
+
|
|
5
|
+
> **Deep Dive:** See [gRPC Controllers Reference](../../references/base/grpc-controllers.md) for the complete API.
|
|
6
|
+
|
|
7
|
+
> [!IMPORTANT]
|
|
8
|
+
> The current version only supports **unary** RPCs (single request, single response) over HTTP/1.1 Connect protocol. Streaming methods (`@serverStream`, `@clientStream`, `@bidiStream`) have decorators defined for metadata registration, but will throw an error at runtime if used.
|
|
9
|
+
|
|
10
|
+
## Peer Dependencies
|
|
11
|
+
|
|
12
|
+
gRPC support requires the following packages to be installed:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bun add @connectrpc/connect @bufbuild/protobuf
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Enabling gRPC Transport
|
|
19
|
+
|
|
20
|
+
To use gRPC controllers, you must enable the gRPC transport in your application configuration:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { ControllerTransports } from '@venizia/ignis';
|
|
24
|
+
|
|
25
|
+
export const appConfigs: IApplicationConfigs = {
|
|
26
|
+
// ... other config
|
|
27
|
+
transports: [ControllerTransports.REST, ControllerTransports.GRPC],
|
|
28
|
+
};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
If `transports` is not specified, only `REST` is enabled by default. If gRPC controllers are discovered but the gRPC transport is not enabled, the framework will log an error warning.
|
|
32
|
+
|
|
33
|
+
## Creating a gRPC Controller
|
|
34
|
+
|
|
35
|
+
Extend `BaseGrpcController` and use the `@controller` decorator with `transport: ControllerTransports.GRPC` and a `service` reference to your generated Protobuf service definition.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { create } from '@bufbuild/protobuf';
|
|
39
|
+
import {
|
|
40
|
+
BaseGrpcController,
|
|
41
|
+
ControllerTransports,
|
|
42
|
+
controller,
|
|
43
|
+
inject,
|
|
44
|
+
unary,
|
|
45
|
+
TRouteContext,
|
|
46
|
+
} from '@venizia/ignis';
|
|
47
|
+
import {
|
|
48
|
+
GreeterService,
|
|
49
|
+
SayHelloResponseSchema,
|
|
50
|
+
type SayHelloRequest,
|
|
51
|
+
type SayHelloResponse,
|
|
52
|
+
} from './generated/greeter_pb';
|
|
53
|
+
|
|
54
|
+
@controller({
|
|
55
|
+
path: '/grpc',
|
|
56
|
+
transport: ControllerTransports.GRPC,
|
|
57
|
+
service: GreeterService,
|
|
58
|
+
})
|
|
59
|
+
export class GreeterController extends BaseGrpcController {
|
|
60
|
+
constructor(
|
|
61
|
+
@inject({ key: 'services.GreeterService' })
|
|
62
|
+
private readonly greeterService: GreeterService,
|
|
63
|
+
) {
|
|
64
|
+
super({ scope: 'GreeterController' });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
override binding() {}
|
|
68
|
+
|
|
69
|
+
@unary({ configs: { name: 'sayHello' } })
|
|
70
|
+
async sayHello(opts: { request: SayHelloRequest; context: TRouteContext }): Promise<SayHelloResponse> {
|
|
71
|
+
const message = await this.greeterService.greet({ name: opts.request.name });
|
|
72
|
+
return create(SayHelloResponseSchema, { message });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Key Points
|
|
78
|
+
|
|
79
|
+
- The `service` field in `@controller` must reference the generated ConnectRPC service definition (e.g., `GreeterService`)
|
|
80
|
+
- The `path` determines the URL prefix where the gRPC service is mounted
|
|
81
|
+
- `binding()` must be implemented (even if empty) -- it is called during `configure()`
|
|
82
|
+
- Handler methods receive `{ request, context }` and return a Protobuf message object
|
|
83
|
+
|
|
84
|
+
## RPC Method Decorators
|
|
85
|
+
|
|
86
|
+
Ignis provides a decorator for each gRPC method type:
|
|
87
|
+
|
|
88
|
+
- `@unary(opts)` -- Single request, single response. **This is the only supported method type** in the current version.
|
|
89
|
+
- `@serverStream(opts)` -- Decorator exists for metadata, but throws at runtime.
|
|
90
|
+
- `@clientStream(opts)` -- Decorator exists for metadata, but throws at runtime.
|
|
91
|
+
- `@bidiStream(opts)` -- Decorator exists for metadata, but throws at runtime.
|
|
92
|
+
- `@rpc(opts)` -- Generic RPC decorator where you specify the `method` in the configs.
|
|
93
|
+
|
|
94
|
+
The `opts` object contains a `configs` property with at minimum a `name` field that matches the RPC method name in your Protobuf service definition. You can also specify `authenticate` and `authorize` options, just like REST routes.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// Unary (supported)
|
|
98
|
+
@unary({ configs: { name: 'sayHello' } })
|
|
99
|
+
async sayHello(opts: { request: SayHelloRequest; context: TRouteContext }): Promise<SayHelloResponse> { ... }
|
|
100
|
+
|
|
101
|
+
// With authentication
|
|
102
|
+
@unary({
|
|
103
|
+
configs: {
|
|
104
|
+
name: 'getUser',
|
|
105
|
+
authenticate: { strategies: ['jwt'], mode: 'required' },
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
async getUser(opts: { request: GetUserRequest; context: TRouteContext }): Promise<GetUserResponse> { ... }
|
|
109
|
+
|
|
110
|
+
// With authorization
|
|
111
|
+
@unary({
|
|
112
|
+
configs: {
|
|
113
|
+
name: 'deleteUser',
|
|
114
|
+
authenticate: { strategies: ['jwt'] },
|
|
115
|
+
authorize: { resource: 'user', scopes: ['delete'] },
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
async deleteUser(opts: { request: DeleteUserRequest; context: TRouteContext }): Promise<DeleteUserResponse> { ... }
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Transport Configuration
|
|
122
|
+
|
|
123
|
+
The transport type is set via the `transport` field in the `@controller` decorator:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// REST controller (default -- transport can be omitted)
|
|
127
|
+
@controller({ path: '/users' })
|
|
128
|
+
|
|
129
|
+
// gRPC controller
|
|
130
|
+
@controller({ path: '/grpc', transport: ControllerTransports.GRPC, service: MyService })
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The `ControllerTransports` class provides the available transport constants:
|
|
134
|
+
|
|
135
|
+
- `ControllerTransports.REST` -- Default HTTP/JSON transport
|
|
136
|
+
- `ControllerTransports.GRPC` -- ConnectRPC transport
|
|
137
|
+
|
|
138
|
+
## Manual Route Definition
|
|
139
|
+
|
|
140
|
+
Just like REST controllers, gRPC controllers support manual route definition via `defineRoute` and `bindRoute`:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { GRPC } from '@venizia/ignis-helpers';
|
|
144
|
+
|
|
145
|
+
override binding() {
|
|
146
|
+
// Using defineRoute
|
|
147
|
+
this.defineRoute({
|
|
148
|
+
configs: { name: 'sayHello', method: GRPC.Methods.UNARY },
|
|
149
|
+
handler: async (opts) => {
|
|
150
|
+
return create(SayHelloResponseSchema, { message: `Hello ${opts.request.name}` });
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Using bindRoute (fluent API)
|
|
155
|
+
this.bindRoute({
|
|
156
|
+
configs: { name: 'getUser', method: GRPC.Methods.UNARY },
|
|
157
|
+
}).to({
|
|
158
|
+
handler: async (opts) => {
|
|
159
|
+
return create(GetUserResponseSchema, { name: 'John' });
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Minimal Controller (No DI)
|
|
166
|
+
|
|
167
|
+
For simple services that don't need injected dependencies:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { create } from '@bufbuild/protobuf';
|
|
171
|
+
import { BaseGrpcController, ControllerTransports, controller, unary } from '@venizia/ignis';
|
|
172
|
+
import { EchoService, EchoResponseSchema, type EchoRequest, type EchoResponse } from './generated/echo_pb';
|
|
173
|
+
|
|
174
|
+
@controller({
|
|
175
|
+
path: '/echo',
|
|
176
|
+
transport: ControllerTransports.GRPC,
|
|
177
|
+
service: EchoService,
|
|
178
|
+
})
|
|
179
|
+
export class EchoController extends BaseGrpcController {
|
|
180
|
+
constructor() {
|
|
181
|
+
super({ scope: 'EchoController' });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
override binding() {}
|
|
185
|
+
|
|
186
|
+
@unary({ configs: { name: 'echo' } })
|
|
187
|
+
async echo(opts: { request: EchoRequest }): Promise<EchoResponse> {
|
|
188
|
+
return create(EchoResponseSchema, { message: opts.request.message });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Key Differences from REST Controllers
|
|
194
|
+
|
|
195
|
+
| Aspect | REST Controller | gRPC Controller |
|
|
196
|
+
| :--- | :--- | :--- |
|
|
197
|
+
| **Base class** | `BaseRestController` | `BaseGrpcController` |
|
|
198
|
+
| **Router** | `OpenAPIHono` | `Hono` (plain) |
|
|
199
|
+
| **Route identifier** | `path` + HTTP method | `name` (Protobuf method name) |
|
|
200
|
+
| **Handler signature** | `(c: TRouteContext) => Response` | `(opts: { request, context }) => ResponseMessage` |
|
|
201
|
+
| **Response format** | `c.json()` calls | Protobuf message objects via `create()` |
|
|
202
|
+
| **Schema** | Zod + OpenAPI | Protobuf (`@bufbuild/protobuf`) |
|
|
203
|
+
| **`binding()` method** | Optional (can use decorators only) | Must be implemented (even if empty) |
|
|
204
|
+
| **`configure()` return** | `OpenAPIHono` router | `void` (adapter mounted internally) |
|
|
205
|
+
| **Peer dependencies** | None | `@connectrpc/connect`, `@bufbuild/protobuf` |
|
|
206
|
+
|
|
207
|
+
## Protobuf Setup
|
|
208
|
+
|
|
209
|
+
gRPC controllers require Protobuf service definitions. The typical workflow:
|
|
210
|
+
|
|
211
|
+
### 1. Define your `.proto` file
|
|
212
|
+
|
|
213
|
+
```protobuf
|
|
214
|
+
// proto/greeter.proto
|
|
215
|
+
syntax = "proto3";
|
|
216
|
+
|
|
217
|
+
package greeter;
|
|
218
|
+
|
|
219
|
+
service GreeterService {
|
|
220
|
+
rpc SayHello (SayHelloRequest) returns (SayHelloResponse);
|
|
221
|
+
rpc ListUsers (ListUsersRequest) returns (ListUsersResponse);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
message SayHelloRequest {
|
|
225
|
+
string name = 1;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
message SayHelloResponse {
|
|
229
|
+
string message = 1;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
message ListUsersRequest {}
|
|
233
|
+
|
|
234
|
+
message ListUsersResponse {
|
|
235
|
+
repeated User users = 1;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
message User {
|
|
239
|
+
string id = 1;
|
|
240
|
+
string name = 2;
|
|
241
|
+
string email = 3;
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### 2. Generate TypeScript code
|
|
246
|
+
|
|
247
|
+
Use [Buf](https://buf.build/) to generate TypeScript from your `.proto` files:
|
|
248
|
+
|
|
249
|
+
```yaml
|
|
250
|
+
# buf.gen.yaml
|
|
251
|
+
version: v2
|
|
252
|
+
plugins:
|
|
253
|
+
- remote: buf.build/bufbuild/es
|
|
254
|
+
out: src/controllers/greeter/generated
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
npx buf generate src/controllers/greeter/proto
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### 3. Use the generated types in your controller
|
|
262
|
+
|
|
263
|
+
The generated `*_pb.ts` files export service definitions, message schemas, and TypeScript types that you use directly in your controller (as shown in the examples above).
|
|
264
|
+
|
|
265
|
+
## Client Usage
|
|
266
|
+
|
|
267
|
+
You can test gRPC controllers using the ConnectRPC client:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
import { createClient } from '@connectrpc/connect';
|
|
271
|
+
import { createConnectTransport } from '@connectrpc/connect-web';
|
|
272
|
+
import { GreeterService } from './controllers/greeter/generated/greeter_pb';
|
|
273
|
+
|
|
274
|
+
const transport = createConnectTransport({
|
|
275
|
+
baseUrl: 'http://localhost:3000',
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const client = createClient(GreeterService, transport);
|
|
279
|
+
|
|
280
|
+
const response = await client.sayHello({ name: 'World' });
|
|
281
|
+
console.log(response.message); // "Hello, World!"
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## See Also
|
|
285
|
+
|
|
286
|
+
- **Related Concepts:**
|
|
287
|
+
- [Application](/guides/core-concepts/application/) - Registering controllers and enabling transports
|
|
288
|
+
- [REST Controllers](/guides/core-concepts/rest-controllers) - HTTP/JSON controllers
|
|
289
|
+
- [Services](/guides/core-concepts/services) - Business logic layer called by controllers
|
|
290
|
+
- [Dependency Injection](/guides/core-concepts/dependency-injection) - Injecting services into controllers
|
|
291
|
+
|
|
292
|
+
- **References:**
|
|
293
|
+
- [BaseGrpcController API](/references/base/grpc-controllers) - Complete gRPC controller API reference
|
|
294
|
+
- [Authentication](/extensions/components/authentication/) - Securing gRPC endpoints
|
|
295
|
+
- [Authorization](/extensions/components/authorization/) - Role-based access control for RPCs
|
|
@@ -27,7 +27,7 @@ interface IDSConfigs {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
@datasource({ driver: 'node-postgres' })
|
|
30
|
-
export class PostgresDataSource extends BaseDataSource<
|
|
30
|
+
export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
|
|
31
31
|
constructor() {
|
|
32
32
|
super({
|
|
33
33
|
name: PostgresDataSource.name,
|
|
@@ -64,9 +64,11 @@ export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, I
|
|
|
64
64
|
|
|
65
65
|
**How auto-discovery works:**
|
|
66
66
|
|
|
67
|
-
1. `@repository` decorators register model-datasource bindings
|
|
68
|
-
2. When `configure()` is called, `getSchema()`
|
|
69
|
-
3. Drizzle is initialized with the complete schema
|
|
67
|
+
1. `@repository` decorators register model-datasource bindings in the `MetadataRegistry`
|
|
68
|
+
2. When `configure()` is called, `getSchema()` invokes `discoverSchema()` which calls `MetadataRegistry.buildSchema({ dataSource })` to collect all bound models and their relations
|
|
69
|
+
3. Drizzle is initialized with the complete schema (tables + Drizzle relations)
|
|
70
|
+
|
|
71
|
+
You can disable auto-discovery per datasource via `@datasource({ driver: 'node-postgres', autoDiscovery: false })`.
|
|
70
72
|
|
|
71
73
|
## Manual Schema (Optional)
|
|
72
74
|
|
|
@@ -74,7 +76,7 @@ If you need explicit control, you can still provide schema manually:
|
|
|
74
76
|
|
|
75
77
|
```typescript
|
|
76
78
|
@datasource({ driver: 'node-postgres' })
|
|
77
|
-
export class PostgresDataSource extends BaseDataSource<
|
|
79
|
+
export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
|
|
78
80
|
constructor() {
|
|
79
81
|
super({
|
|
80
82
|
name: PostgresDataSource.name,
|
|
@@ -89,6 +91,21 @@ export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, I
|
|
|
89
91
|
}
|
|
90
92
|
```
|
|
91
93
|
|
|
94
|
+
## DataSource Hierarchy
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
AbstractDataSource extends BaseHelper
|
|
98
|
+
└── BaseDataSource
|
|
99
|
+
├── configure() # Setup pool + Drizzle connector (abstract)
|
|
100
|
+
├── getConnectionString() # Build connection URL (abstract)
|
|
101
|
+
├── getSchema() # Auto-discover from @repository bindings
|
|
102
|
+
├── discoverSchema() # Internal: reads MetadataRegistry
|
|
103
|
+
├── hasDiscoverableModels() # Check if any repos reference this DS
|
|
104
|
+
├── beginTransaction(opts?) # Start transaction with isolation level
|
|
105
|
+
├── getConnector() # Get Drizzle connector
|
|
106
|
+
└── getSettings() # Get connection config
|
|
107
|
+
```
|
|
108
|
+
|
|
92
109
|
## Registering a DataSource
|
|
93
110
|
|
|
94
111
|
```typescript
|
|
@@ -100,18 +117,20 @@ export class Application extends BaseApplication {
|
|
|
100
117
|
}
|
|
101
118
|
```
|
|
102
119
|
|
|
120
|
+
DataSources are bound as **singletons** to ensure connection pool sharing across the application.
|
|
121
|
+
|
|
103
122
|
## Supported Drivers
|
|
104
123
|
|
|
105
124
|
| Driver | Package | Status |
|
|
106
125
|
|--------|---------|--------|
|
|
107
|
-
| `node-postgres` | `pg` |
|
|
126
|
+
| `node-postgres` | `pg` | Supported |
|
|
108
127
|
| `mysql2` | `mysql2` | Planned |
|
|
109
128
|
| `better-sqlite3` | `better-sqlite3` | Planned |
|
|
110
129
|
|
|
111
130
|
## DataSource Template
|
|
112
131
|
|
|
113
132
|
```typescript
|
|
114
|
-
import { BaseDataSource, datasource,
|
|
133
|
+
import { BaseDataSource, datasource, ValueOrPromise } from '@venizia/ignis';
|
|
115
134
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
116
135
|
import { Pool } from 'pg';
|
|
117
136
|
|
|
@@ -124,7 +143,7 @@ interface IDSConfigs {
|
|
|
124
143
|
}
|
|
125
144
|
|
|
126
145
|
@datasource({ driver: 'node-postgres' })
|
|
127
|
-
export class PostgresDataSource extends BaseDataSource<
|
|
146
|
+
export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
|
|
128
147
|
constructor() {
|
|
129
148
|
super({
|
|
130
149
|
name: PostgresDataSource.name,
|
|
@@ -208,6 +208,35 @@ const [fullUser] = await connector
|
|
|
208
208
|
For complete hidden properties documentation, see the [Models Reference](../../../references/base/models.md#hidden-properties).
|
|
209
209
|
:::
|
|
210
210
|
|
|
211
|
+
## Default Filter
|
|
212
|
+
|
|
213
|
+
Apply automatic filters to all repository queries. This is commonly used for soft-delete patterns:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
@model({
|
|
217
|
+
type: 'entity',
|
|
218
|
+
settings: {
|
|
219
|
+
defaultFilter: { where: { isDeleted: false } },
|
|
220
|
+
hiddenProperties: ['deletedAt'],
|
|
221
|
+
},
|
|
222
|
+
})
|
|
223
|
+
export class Article extends BaseEntity<typeof Article.schema> {
|
|
224
|
+
// ...
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The default filter is applied automatically to all read operations. Bypass it with `shouldSkipDefaultFilter: true` in the options:
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
// Normal query - auto-filters out soft-deleted records
|
|
232
|
+
const articles = await articleRepo.find({});
|
|
233
|
+
|
|
234
|
+
// Include deleted records
|
|
235
|
+
const allArticles = await articleRepo.find({
|
|
236
|
+
options: { shouldSkipDefaultFilter: true },
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
211
240
|
## Authorization Settings
|
|
212
241
|
|
|
213
242
|
Declare your model's authorization principal directly in `@model` settings. The decorator auto-populates `AUTHORIZATION_SUBJECT` for type-safe references in route configs:
|
|
@@ -237,9 +266,22 @@ authorize: {
|
|
|
237
266
|
```
|
|
238
267
|
|
|
239
268
|
:::tip
|
|
240
|
-
For full authorization integration details, see the [Authorization Usage Reference](../../../
|
|
269
|
+
For full authorization integration details, see the [Authorization Usage Reference](../../../extensions/components/authorization/usage#model-based-resource-references).
|
|
241
270
|
:::
|
|
242
271
|
|
|
272
|
+
## Model Metadata Types
|
|
273
|
+
|
|
274
|
+
The `@model` decorator accepts the following metadata:
|
|
275
|
+
|
|
276
|
+
| Field | Type | Description |
|
|
277
|
+
| :--- | :--- | :--- |
|
|
278
|
+
| `type` | `'entity' \| 'view'` | Whether this is a table or a database view |
|
|
279
|
+
| `tableName` | `string` | Optional explicit table name |
|
|
280
|
+
| `skipMigrate` | `boolean` | Skip this model during migrations |
|
|
281
|
+
| `settings.hiddenProperties` | `string[]` | Properties excluded from all query results |
|
|
282
|
+
| `settings.defaultFilter` | `TFilter` | Default filter auto-applied to all queries |
|
|
283
|
+
| `settings.authorize.principal` | `string` | Authorization subject name for this model |
|
|
284
|
+
|
|
243
285
|
## Model Template
|
|
244
286
|
|
|
245
287
|
```typescript
|