@venizia/ignis-docs 0.0.1-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/mcp-server/dist/common/config.d.ts +27 -0
- package/mcp-server/dist/common/config.d.ts.map +1 -0
- package/mcp-server/dist/common/config.js +27 -0
- package/mcp-server/dist/common/config.js.map +1 -0
- package/mcp-server/dist/common/index.d.ts +3 -0
- package/mcp-server/dist/common/index.d.ts.map +1 -0
- package/mcp-server/dist/common/index.js +19 -0
- package/mcp-server/dist/common/index.js.map +1 -0
- package/mcp-server/dist/common/paths.d.ts +13 -0
- package/mcp-server/dist/common/paths.d.ts.map +1 -0
- package/mcp-server/dist/common/paths.js +23 -0
- package/mcp-server/dist/common/paths.js.map +1 -0
- package/mcp-server/dist/helpers/docs.helper.d.ts +81 -0
- package/mcp-server/dist/helpers/docs.helper.d.ts.map +1 -0
- package/mcp-server/dist/helpers/docs.helper.js +171 -0
- package/mcp-server/dist/helpers/docs.helper.js.map +1 -0
- package/mcp-server/dist/helpers/index.d.ts +3 -0
- package/mcp-server/dist/helpers/index.d.ts.map +1 -0
- package/mcp-server/dist/helpers/index.js +19 -0
- package/mcp-server/dist/helpers/index.js.map +1 -0
- package/mcp-server/dist/helpers/logger.helper.d.ts +7 -0
- package/mcp-server/dist/helpers/logger.helper.d.ts.map +1 -0
- package/mcp-server/dist/helpers/logger.helper.js +22 -0
- package/mcp-server/dist/helpers/logger.helper.js.map +1 -0
- package/mcp-server/dist/index.d.ts +3 -0
- package/mcp-server/dist/index.d.ts.map +1 -0
- package/mcp-server/dist/index.js +62 -0
- package/mcp-server/dist/index.js.map +1 -0
- package/mcp-server/dist/tools/base.tool.d.ts +98 -0
- package/mcp-server/dist/tools/base.tool.d.ts.map +1 -0
- package/mcp-server/dist/tools/base.tool.js +47 -0
- package/mcp-server/dist/tools/base.tool.js.map +1 -0
- package/mcp-server/dist/tools/get-doc-content.tool.d.ts +30 -0
- package/mcp-server/dist/tools/get-doc-content.tool.d.ts.map +1 -0
- package/mcp-server/dist/tools/get-doc-content.tool.js +127 -0
- package/mcp-server/dist/tools/get-doc-content.tool.js.map +1 -0
- package/mcp-server/dist/tools/get-doc-metadata.tool.d.ts +40 -0
- package/mcp-server/dist/tools/get-doc-metadata.tool.d.ts.map +1 -0
- package/mcp-server/dist/tools/get-doc-metadata.tool.js +121 -0
- package/mcp-server/dist/tools/get-doc-metadata.tool.js.map +1 -0
- package/mcp-server/dist/tools/index.d.ts +8 -0
- package/mcp-server/dist/tools/index.d.ts.map +1 -0
- package/mcp-server/dist/tools/index.js +18 -0
- package/mcp-server/dist/tools/index.js.map +1 -0
- package/mcp-server/dist/tools/list-categories.tool.d.ts +20 -0
- package/mcp-server/dist/tools/list-categories.tool.d.ts.map +1 -0
- package/mcp-server/dist/tools/list-categories.tool.js +105 -0
- package/mcp-server/dist/tools/list-categories.tool.js.map +1 -0
- package/mcp-server/dist/tools/list-docs.tool.d.ts +32 -0
- package/mcp-server/dist/tools/list-docs.tool.d.ts.map +1 -0
- package/mcp-server/dist/tools/list-docs.tool.js +121 -0
- package/mcp-server/dist/tools/list-docs.tool.js.map +1 -0
- package/mcp-server/dist/tools/search-docs.tool.d.ts +32 -0
- package/mcp-server/dist/tools/search-docs.tool.d.ts.map +1 -0
- package/mcp-server/dist/tools/search-docs.tool.js +120 -0
- package/mcp-server/dist/tools/search-docs.tool.js.map +1 -0
- package/package.json +102 -0
- package/wiki/get-started/5-minute-quickstart.md +266 -0
- package/wiki/get-started/best-practices/api-usage-examples.md +222 -0
- package/wiki/get-started/best-practices/architectural-patterns.md +129 -0
- package/wiki/get-started/best-practices/code-style-standards.md +122 -0
- package/wiki/get-started/best-practices/common-pitfalls.md +136 -0
- package/wiki/get-started/best-practices/contribution-workflow.md +145 -0
- package/wiki/get-started/best-practices/deployment-strategies.md +121 -0
- package/wiki/get-started/best-practices/performance-optimization.md +88 -0
- package/wiki/get-started/best-practices/security-guidelines.md +97 -0
- package/wiki/get-started/best-practices/troubleshooting-tips.md +100 -0
- package/wiki/get-started/building-a-crud-api.md +717 -0
- package/wiki/get-started/core-concepts/application.md +168 -0
- package/wiki/get-started/core-concepts/components.md +96 -0
- package/wiki/get-started/core-concepts/controllers.md +441 -0
- package/wiki/get-started/core-concepts/dependency-injection.md +160 -0
- package/wiki/get-started/core-concepts/persistent.md +591 -0
- package/wiki/get-started/core-concepts/services.md +88 -0
- package/wiki/get-started/index.md +65 -0
- package/wiki/get-started/mcp-docs-server.md +840 -0
- package/wiki/get-started/philosophy.md +123 -0
- package/wiki/get-started/prerequisites.md +113 -0
- package/wiki/get-started/quickstart.md +382 -0
- package/wiki/index.md +48 -0
- package/wiki/references/base/application.md +67 -0
- package/wiki/references/base/components.md +80 -0
- package/wiki/references/base/controllers.md +361 -0
- package/wiki/references/base/datasources.md +105 -0
- package/wiki/references/base/dependency-injection.md +83 -0
- package/wiki/references/base/models.md +104 -0
- package/wiki/references/base/repositories.md +118 -0
- package/wiki/references/base/services.md +97 -0
- package/wiki/references/components/authentication.md +224 -0
- package/wiki/references/components/health-check.md +190 -0
- package/wiki/references/components/index.md +61 -0
- package/wiki/references/components/request-tracker.md +35 -0
- package/wiki/references/components/socket-io.md +127 -0
- package/wiki/references/components/swagger.md +175 -0
- package/wiki/references/helpers/cron.md +94 -0
- package/wiki/references/helpers/crypto.md +117 -0
- package/wiki/references/helpers/env.md +67 -0
- package/wiki/references/helpers/error.md +80 -0
- package/wiki/references/helpers/index.md +21 -0
- package/wiki/references/helpers/inversion.md +141 -0
- package/wiki/references/helpers/logger.md +98 -0
- package/wiki/references/helpers/network.md +143 -0
- package/wiki/references/helpers/queue.md +131 -0
- package/wiki/references/helpers/redis.md +121 -0
- package/wiki/references/helpers/socket-io.md +103 -0
- package/wiki/references/helpers/storage.md +130 -0
- package/wiki/references/helpers/testing.md +115 -0
- package/wiki/references/helpers/worker-thread.md +162 -0
- package/wiki/references/src-details/core.md +249 -0
- package/wiki/references/src-details/dev-configs.md +302 -0
- package/wiki/references/src-details/docs.md +61 -0
- package/wiki/references/src-details/helpers.md +211 -0
- package/wiki/references/src-details/inversion.md +345 -0
- package/wiki/references/src-details/mcp-server.md +726 -0
- package/wiki/references/utilities/crypto.md +39 -0
- package/wiki/references/utilities/date.md +72 -0
- package/wiki/references/utilities/index.md +12 -0
- package/wiki/references/utilities/module.md +40 -0
- package/wiki/references/utilities/parse.md +68 -0
- package/wiki/references/utilities/performance.md +64 -0
- package/wiki/references/utilities/promise.md +83 -0
- package/wiki/references/utilities/request.md +66 -0
- package/wiki/references/utilities/schema.md +88 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
# Controllers
|
|
2
|
+
|
|
3
|
+
Controllers handle HTTP requests and return responses - they're your API endpoints.
|
|
4
|
+
|
|
5
|
+
> **Deep Dive:** See [Controllers Reference](../../references/base/controllers.md) for advanced patterns.
|
|
6
|
+
|
|
7
|
+
## Creating a Controller
|
|
8
|
+
|
|
9
|
+
Extend `BaseController` and use decorators to define routes:
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { BaseController, controller, get, jsonResponse, z } from '@venizia/ignis';
|
|
13
|
+
import { Context } from 'hono';
|
|
14
|
+
|
|
15
|
+
@controller({ path: '/users' })
|
|
16
|
+
export class UserController extends BaseController {
|
|
17
|
+
constructor() {
|
|
18
|
+
// It's good practice to pass a scope for logging
|
|
19
|
+
super({ scope: UserController.name, path: '/users' });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@get({
|
|
23
|
+
configs: {
|
|
24
|
+
path: '/',
|
|
25
|
+
responses: jsonResponse({
|
|
26
|
+
description: 'A list of users',
|
|
27
|
+
schema: z.array(z.object({ id: z.string(), name: z.string() })),
|
|
28
|
+
}),
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
getAllUsers(c: Context) {
|
|
32
|
+
return c.json([{ id: '1', name: 'John Doe' }]);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
Notice that the `binding()` method is no longer needed when using decorators.
|
|
37
|
+
|
|
38
|
+
## Controller Lifecycle
|
|
39
|
+
|
|
40
|
+
Controllers have a simple and predictable lifecycle managed by the application.
|
|
41
|
+
|
|
42
|
+
| Stage | Method | Description |
|
|
43
|
+
| :--- | :--- | :--- |
|
|
44
|
+
| **1. Instantiation** | `constructor(opts)` | The controller is created by the DI container. Dependencies are injected, and you call `super()` to initialize the internal Hono router. |
|
|
45
|
+
| **2. Configuration**| `registerControllers` phase | The application automatically discovers and registers all routes defined with decorators (`@get`, `@post`, etc.) on your controller methods. If you have routes defined manually inside `binding()`, that method is also called during this phase. **Note:** If you exclusively use decorators for routing, the `binding()` method can be omitted from your controller. |
|
|
46
|
+
|
|
47
|
+
## Defining Routes with Decorators (Recommended)
|
|
48
|
+
|
|
49
|
+
The recommended way to define routes is by using decorators directly on the controller methods that handle them. This approach is more declarative, keeps your code organized, and provides **full type safety** for your request parameters, query, body, and even the response.
|
|
50
|
+
|
|
51
|
+
:::tip Type Safety without Boilerplate
|
|
52
|
+
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.
|
|
53
|
+
:::
|
|
54
|
+
|
|
55
|
+
### HTTP Method Decorators
|
|
56
|
+
|
|
57
|
+
`Ignis` provides a decorator for each common HTTP method:
|
|
58
|
+
|
|
59
|
+
- `@get(opts)`
|
|
60
|
+
- `@post(opts)`
|
|
61
|
+
- `@put(opts)`
|
|
62
|
+
- `@patch(opts)`
|
|
63
|
+
- `@del(opts)`
|
|
64
|
+
- `@api(opts)` (a generic decorator where you specify the method in the `configs`)
|
|
65
|
+
|
|
66
|
+
The `opts` object contains a `configs` property that defines the route's path, request validation, and OpenAPI response schemas.
|
|
67
|
+
|
|
68
|
+
**Example using decorators with `ROUTE_CONFIGS` and full type inference:**
|
|
69
|
+
|
|
70
|
+
For optimal organization and type safety, define your route configurations in a constant with `as const`. This allows TypeScript to precisely infer the types for your request data and expected responses within your handler methods.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { BaseController, controller, get, post, HTTP, jsonContent, jsonResponse, TRouteContext } from '@venizia/ignis';
|
|
74
|
+
import { z } from '@hono/zod-openapi';
|
|
75
|
+
|
|
76
|
+
// Define route configs as const for type inference
|
|
77
|
+
const TEST_ROUTES = {
|
|
78
|
+
getData: {
|
|
79
|
+
method: HTTP.Methods.GET,
|
|
80
|
+
path: '/',
|
|
81
|
+
responses: jsonResponse({
|
|
82
|
+
description: 'A simple message',
|
|
83
|
+
schema: z.object({ message: z.string(), method: z.string() }),
|
|
84
|
+
}),
|
|
85
|
+
},
|
|
86
|
+
createItem: {
|
|
87
|
+
method: HTTP.Methods.POST,
|
|
88
|
+
path: '/',
|
|
89
|
+
request: {
|
|
90
|
+
body: jsonContent({
|
|
91
|
+
description: 'Request body for creating an item',
|
|
92
|
+
schema: z.object({ name: z.string(), value: z.number().int().positive() }),
|
|
93
|
+
}),
|
|
94
|
+
},
|
|
95
|
+
responses: jsonResponse({
|
|
96
|
+
description: 'Created item',
|
|
97
|
+
schema: z.object({ id: z.string(), name: z.string(), value: z.number() }),
|
|
98
|
+
}),
|
|
99
|
+
},
|
|
100
|
+
} as const; // Crucial for strict type inference!
|
|
101
|
+
|
|
102
|
+
@controller({ path: '/my-items' })
|
|
103
|
+
export class MyItemsController extends BaseController {
|
|
104
|
+
constructor() {
|
|
105
|
+
super({ scope: MyItemsController.name, path: '/my-items' });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@get({ configs: TEST_ROUTES.getData })
|
|
109
|
+
getData(c: TRouteContext<typeof TEST_ROUTES.getData>) { // Return type is automatically inferred
|
|
110
|
+
// 'c' is fully typed here, including c.req.valid and c.json return type
|
|
111
|
+
return c.json({ message: 'Hello from decorator', method: 'GET' }, HTTP.ResultCodes.RS_2.Ok);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@post({ configs: TEST_ROUTES.createItem })
|
|
115
|
+
createItem(c: TRouteContext<typeof TEST_ROUTES.createItem>) { // Return type is automatically inferred
|
|
116
|
+
// c.req.valid('json') is automatically typed based on createItem.request.body.content['application/json'].schema
|
|
117
|
+
const body = c.req.valid('json');
|
|
118
|
+
|
|
119
|
+
// Return type is automatically validated against createItem.responses[200].content['application/json'].schema
|
|
120
|
+
return c.json(
|
|
121
|
+
{
|
|
122
|
+
id: 'some-uuid',
|
|
123
|
+
name: body.name,
|
|
124
|
+
value: body.value,
|
|
125
|
+
},
|
|
126
|
+
HTTP.ResultCodes.RS_2.Ok,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Manual Route Definition: An Alternative Approach
|
|
133
|
+
|
|
134
|
+
While decorators are the recommended approach for most use cases, `Ignis` also provides a manual way to define routes within the controller's `binding()` method.
|
|
135
|
+
|
|
136
|
+
### Decorator vs Manual: Quick Comparison
|
|
137
|
+
|
|
138
|
+
| Aspect | Decorators (`@get`, `@post`) | Manual (`defineRoute`, `bindRoute`) |
|
|
139
|
+
| :--- | :--- | :--- |
|
|
140
|
+
| **Syntax** | Clean, declarative | More verbose |
|
|
141
|
+
| **Organization** | Route config near the handler | Can be in separate files |
|
|
142
|
+
| **Use Case** | Most standard CRUD endpoints | Dynamic route generation |
|
|
143
|
+
| **Type Safety** | Full inference with `TRouteContext` | Full inference with `TRouteContext` |
|
|
144
|
+
| **Readability** | High - easy to scan | Medium - requires scrolling |
|
|
145
|
+
| **Best For** | 90% of use cases | Advanced scenarios |
|
|
146
|
+
|
|
147
|
+
### When to Use Manual Route Definition
|
|
148
|
+
|
|
149
|
+
Manual route definition is useful for:
|
|
150
|
+
|
|
151
|
+
- **Dynamically generating routes** based on configuration (e.g., loading routes from a database)
|
|
152
|
+
- **Organizing all routes** for a controller within a single method if you prefer centralized route declarations
|
|
153
|
+
- **Conditional route registration** (e.g., enabling/disabling routes based on feature flags)
|
|
154
|
+
- **Developers who prefer non-decorator syntax** (coming from Express/Fastify)
|
|
155
|
+
|
|
156
|
+
When using this method, you will override the `binding()` method in your controller and use `this.defineRoute` or `this.bindRoute` to register your endpoints.
|
|
157
|
+
|
|
158
|
+
### `defineRoute`
|
|
159
|
+
|
|
160
|
+
Use this method for defining a single API endpoint with all its configurations and handler. It also benefits from type inference when used with `TRouteContext`.
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { Authentication, HTTP, jsonResponse, z, TRouteContext } from '@venizia/ignis';
|
|
164
|
+
|
|
165
|
+
// ... inside the binding() method
|
|
166
|
+
|
|
167
|
+
const GetUsersRoute = {
|
|
168
|
+
path: '/',
|
|
169
|
+
method: 'get',
|
|
170
|
+
authStrategies: [Authentication.STRATEGY_JWT],
|
|
171
|
+
responses: jsonResponse({
|
|
172
|
+
description: 'List of all users',
|
|
173
|
+
schema: z.array(z.object({ id: z.number(), name: z.string() })),
|
|
174
|
+
}),
|
|
175
|
+
} as const;
|
|
176
|
+
|
|
177
|
+
this.defineRoute({
|
|
178
|
+
configs: GetUsersRoute,
|
|
179
|
+
handler: (c: TRouteContext<typeof GetUsersRoute>) => { // Return type is automatically inferred
|
|
180
|
+
return c.json([{ id: 1, name: 'John Doe' }]);
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### `bindRoute`
|
|
186
|
+
|
|
187
|
+
This method offers a fluent API for defining routes, similar to `defineRoute`, but structured for chaining. It also benefits from `TRouteContext` for type safety.
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { jsonResponse, z, TRouteContext } from '@venizia/ignis';
|
|
191
|
+
|
|
192
|
+
// ... inside the binding() method
|
|
193
|
+
|
|
194
|
+
const GetUserByIdRoute = {
|
|
195
|
+
path: '/:id',
|
|
196
|
+
method: 'get',
|
|
197
|
+
responses: jsonResponse({
|
|
198
|
+
description: 'A single user',
|
|
199
|
+
schema: z.object({ id: z.string(), name: z.string() }),
|
|
200
|
+
}),
|
|
201
|
+
} as const;
|
|
202
|
+
|
|
203
|
+
this.bindRoute({
|
|
204
|
+
configs: GetUserByIdRoute,
|
|
205
|
+
}).to({
|
|
206
|
+
handler: (c: TRouteContext<typeof GetUserByIdRoute>) => { // Return type is automatically inferred
|
|
207
|
+
const { id } = c.req.param();
|
|
208
|
+
return c.json({ id: id, name: 'John Doe' });
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## `ControllerFactory` for CRUD Operations
|
|
214
|
+
|
|
215
|
+
For standard CRUD (Create, Read, Update, Delete) operations, `Ignis` provides a `ControllerFactory` that can generate a full-featured controller for any given entity. This significantly reduces boilerplate code.
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// src/controllers/configuration.controller.ts (Example from @examples/vert)
|
|
219
|
+
import { Configuration } from '@/models';
|
|
220
|
+
import { ConfigurationRepository } from '@/repositories';
|
|
221
|
+
import {
|
|
222
|
+
BindingKeys,
|
|
223
|
+
BindingNamespaces,
|
|
224
|
+
controller,
|
|
225
|
+
ControllerFactory,
|
|
226
|
+
inject,
|
|
227
|
+
} from '@venizia/ignis';
|
|
228
|
+
|
|
229
|
+
const BASE_PATH = '/configurations';
|
|
230
|
+
|
|
231
|
+
const _Controller = ControllerFactory.defineCrudController({
|
|
232
|
+
repository: { name: ConfigurationRepository.name },
|
|
233
|
+
controller: {
|
|
234
|
+
name: 'ConfigurationController',
|
|
235
|
+
basePath: BASE_PATH,
|
|
236
|
+
isStrict: true,
|
|
237
|
+
},
|
|
238
|
+
entity: () => Configuration, // Provide a resolver for your entity class
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
@controller({ path: BASE_PATH })
|
|
242
|
+
export class ConfigurationController extends _Controller {
|
|
243
|
+
constructor(
|
|
244
|
+
@inject({
|
|
245
|
+
key: BindingKeys.build({
|
|
246
|
+
namespace: BindingNamespaces.REPOSITORY,
|
|
247
|
+
key: ConfigurationRepository.name,
|
|
248
|
+
}),
|
|
249
|
+
})
|
|
250
|
+
repository: ConfigurationRepository,
|
|
251
|
+
) {
|
|
252
|
+
super(repository); // The generated controller expects the repository in its constructor
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
The `ControllerFactory.defineCrudController` method automatically sets up the following routes based on your entity schema:
|
|
257
|
+
|
|
258
|
+
| Name | Method | Path | Description |
|
|
259
|
+
| :--- | :--- | :--- | :--- |
|
|
260
|
+
| `count` | `GET` | `/count` | Get the number of records matching a filter. |
|
|
261
|
+
| `find` | `GET` | `/` | Retrieve all records matching a filter. |
|
|
262
|
+
| `findById` | `GET` | `/:id` | Retrieve a single record by its ID. |
|
|
263
|
+
| `findOne` | `GET` | `/find-one` | Retrieve a single record matching a filter. |
|
|
264
|
+
| `create` | `POST` | `/` | Create a new record. |
|
|
265
|
+
| `updateById` | `PATCH` | `/:id` | Update a record by its ID. |
|
|
266
|
+
| `updateAll` | `PATCH` | `/` | Update multiple records matching a filter. |
|
|
267
|
+
| `deleteById` | `DELETE` | `/:id` | Delete a record by its ID. |
|
|
268
|
+
| `deleteAll` | `DELETE` | `/` | Delete multiple records matching a filter. |
|
|
269
|
+
|
|
270
|
+
:::info Customization
|
|
271
|
+
The `ControllerFactory` is highly customizable. You can override the Zod schemas for any of the generated routes to add, remove, or modify fields for request validation and response shapes. You can also configure other behaviors, like making delete operations return the deleted records.
|
|
272
|
+
|
|
273
|
+
For a full list of customization options, see the [**Deep Dive: `ControllerFactory`**](../../references/base/controllers.md#controllerfactory) documentation.
|
|
274
|
+
:::
|
|
275
|
+
|
|
276
|
+
### `defineJSXRoute` (Server-Side Rendered HTML)
|
|
277
|
+
|
|
278
|
+
Use this method for routes that render HTML pages using JSX components. This is perfect for building server-side rendered web applications.
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
import { htmlResponse } from '@venizia/ignis';
|
|
282
|
+
|
|
283
|
+
// ... inside the binding() method
|
|
284
|
+
|
|
285
|
+
this.defineJSXRoute({
|
|
286
|
+
configs: {
|
|
287
|
+
path: '/',
|
|
288
|
+
method: 'get',
|
|
289
|
+
description: 'Home page',
|
|
290
|
+
responses: htmlResponse({
|
|
291
|
+
description: 'HTML home page',
|
|
292
|
+
}),
|
|
293
|
+
},
|
|
294
|
+
handler: (c) => {
|
|
295
|
+
const user = { name: 'John Doe' };
|
|
296
|
+
return c.html(<HomePage user={user} />);
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Key Points:**
|
|
302
|
+
- The handler **must** return `c.html()` with a JSX component
|
|
303
|
+
- JSX routes automatically include HTML content-type in OpenAPI documentation
|
|
304
|
+
- Views are typically organized in `src/views/` directory
|
|
305
|
+
- Components can be reused across multiple pages
|
|
306
|
+
|
|
307
|
+
**Example View Component:**
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
// src/views/pages/home.page.tsx
|
|
311
|
+
import type { FC } from '@venizia/ignis';
|
|
312
|
+
import { MainLayout } from '../layouts/main.layout';
|
|
313
|
+
|
|
314
|
+
interface HomePageProps {
|
|
315
|
+
user: { name: string };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export const HomePage: FC<HomePageProps> = ({ user }) => {
|
|
319
|
+
return (
|
|
320
|
+
<MainLayout title="Home">
|
|
321
|
+
<h1>Welcome, {user.name}!</h1>
|
|
322
|
+
<p>This page is rendered on the server using JSX.</p>
|
|
323
|
+
</MainLayout>
|
|
324
|
+
);
|
|
325
|
+
};
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
**Example Layout Component:**
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
// src/views/layouts/main.layout.tsx
|
|
332
|
+
import type { FC, PropsWithChildren } from '@venizia/ignis';
|
|
333
|
+
|
|
334
|
+
interface MainLayoutProps {
|
|
335
|
+
title: string;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export const MainLayout: FC<PropsWithChildren<MainLayoutProps>> = ({ title, children }) => {
|
|
339
|
+
return (
|
|
340
|
+
<html lang="en">
|
|
341
|
+
<head>
|
|
342
|
+
<meta charSet="UTF-8" />
|
|
343
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
344
|
+
<title>{title}</title>
|
|
345
|
+
</head>
|
|
346
|
+
<body>
|
|
347
|
+
<header>
|
|
348
|
+
<nav>
|
|
349
|
+
<a href="/">Home</a>
|
|
350
|
+
<a href="/about">About</a>
|
|
351
|
+
</nav>
|
|
352
|
+
</header>
|
|
353
|
+
<main>{children}</main>
|
|
354
|
+
<footer>
|
|
355
|
+
<p>© 2025 My App</p>
|
|
356
|
+
</footer>
|
|
357
|
+
</body>
|
|
358
|
+
</html>
|
|
359
|
+
);
|
|
360
|
+
};
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
> **Note:** JSX support in `Ignis` uses Hono's built-in JSX runtime. Make sure your `tsconfig.json` includes the JSX configuration (this is already set up in the framework's base configuration).
|
|
364
|
+
|
|
365
|
+
## Accessing Validated Request Data
|
|
366
|
+
|
|
367
|
+
When you define Zod schemas in your route's `request` configuration (whether with decorators or manual definition), Hono's validation middleware automatically parses and validates the incoming data. You can access this validated data using `c.req.valid()`.
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { z } from '@hono/zod-openapi';
|
|
371
|
+
import { jsonContent, put } from '@venizia/ignis';
|
|
372
|
+
|
|
373
|
+
// ... inside a controller class
|
|
374
|
+
|
|
375
|
+
const UserSchema = z.object({ name: z.string(), email: z.string().email() });
|
|
376
|
+
|
|
377
|
+
@put({
|
|
378
|
+
configs: {
|
|
379
|
+
path: '/:id',
|
|
380
|
+
method: 'put',
|
|
381
|
+
request: {
|
|
382
|
+
params: z.object({ id: z.string() }),
|
|
383
|
+
query: z.object({ notify: z.string().optional() }),
|
|
384
|
+
body: jsonContent({ schema: UserSchema }),
|
|
385
|
+
},
|
|
386
|
+
// ... responses
|
|
387
|
+
},
|
|
388
|
+
})
|
|
389
|
+
updateUser(c: Context) {
|
|
390
|
+
// Access validated data from the request
|
|
391
|
+
const { id } = c.req.valid('param');
|
|
392
|
+
const { notify } = c.req.valid('query');
|
|
393
|
+
const userUpdateData = c.req.valid('json'); // for body
|
|
394
|
+
|
|
395
|
+
console.log(`Updating user ${id} with data:`, userUpdateData);
|
|
396
|
+
if (notify) {
|
|
397
|
+
console.log('Notification is enabled.');
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return c.json({ success: true, id, ...userUpdateData });
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Using `c.req.valid()` is the recommended way to access request data as it ensures that the data conforms to the schema you've defined. For even better type safety, you can use the `TRouteContext` utility type.
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
import { z } from '@hono/zod-openapi';
|
|
408
|
+
import { jsonContent, put, TRouteContext } from '@venizia/ignis';
|
|
409
|
+
|
|
410
|
+
// ... inside a controller class
|
|
411
|
+
|
|
412
|
+
const updateUserConfig = {
|
|
413
|
+
path: '/:id',
|
|
414
|
+
method: 'put',
|
|
415
|
+
request: {
|
|
416
|
+
params: z.object({ id: z.string() }),
|
|
417
|
+
query: z.object({ notify: z.string().optional() }),
|
|
418
|
+
body: jsonContent({
|
|
419
|
+
schema: z.object({ name: z.string(), email: z.string().email() }),
|
|
420
|
+
}),
|
|
421
|
+
},
|
|
422
|
+
// ... responses
|
|
423
|
+
} as const; // Use 'as const' for strict type inference
|
|
424
|
+
|
|
425
|
+
@put({ configs: updateUserConfig })
|
|
426
|
+
updateUser(c: TRouteContext<typeof updateUserConfig>) {
|
|
427
|
+
// Access validated data from the request
|
|
428
|
+
const { id } = c.req.valid('param');
|
|
429
|
+
const { notify } = c.req.valid('query');
|
|
430
|
+
const userUpdateData = c.req.valid('json'); // for body
|
|
431
|
+
|
|
432
|
+
console.log(`Updating user ${id} with data:`, userUpdateData);
|
|
433
|
+
if (notify) {
|
|
434
|
+
console.log('Notification is enabled.');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return c.json({ success: true, id, ...userUpdateData });
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Using `TRouteContext` provides full type inference for `c.req.valid()`, so your editor will know that `id` is a string, `notify` is an optional string, and `userUpdateData` matches the body schema.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Dependency Injection
|
|
2
|
+
|
|
3
|
+
Dependency Injection (DI) enables loosely coupled, testable code by automatically providing dependencies to classes.
|
|
4
|
+
|
|
5
|
+
> **Deep Dive:** See [DI Reference](../../references/base/dependency-injection.md) for technical details on Container, Binding, and `@inject`.
|
|
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](../../references/src-details/inversion.md) for details.
|
|
8
|
+
|
|
9
|
+
## Core Concepts
|
|
10
|
+
|
|
11
|
+
| Concept | Description |
|
|
12
|
+
| :--- | :--- |
|
|
13
|
+
| **Container** | The central registry for all your application's services and dependencies. The `Application` class itself acts as the container. |
|
|
14
|
+
| **Binding** | The process of registering a class or value with the container under a specific key (e.g., `'services.UserService'`). |
|
|
15
|
+
| **Injection**| The process of requesting a dependency from the container using the `@inject` decorator. |
|
|
16
|
+
|
|
17
|
+
### How It Works: The DI Flow
|
|
18
|
+
|
|
19
|
+
```mermaid
|
|
20
|
+
graph TD
|
|
21
|
+
A[1. Bind Resource] -- app.service(MyService) --> B(Container stores a Binding for 'services.MyService');
|
|
22
|
+
B -- triggers --> C[2. Instantiate Consumer];
|
|
23
|
+
C -- @inject({ key: 'services.MyService' }) --> D(Container reads injection metadata);
|
|
24
|
+
D -- finds binding --> E[3. Resolve & Inject];
|
|
25
|
+
E -- gets dependency --> F(Container creates/gets instance of MyService);
|
|
26
|
+
F -- injects into --> G(MyController instance is created with MyService);
|
|
27
|
+
|
|
28
|
+
subgraph Application Startup
|
|
29
|
+
A
|
|
30
|
+
end
|
|
31
|
+
subgraph On-Demand Instantiation
|
|
32
|
+
C
|
|
33
|
+
end
|
|
34
|
+
subgraph Container Internals
|
|
35
|
+
B
|
|
36
|
+
D
|
|
37
|
+
E
|
|
38
|
+
F
|
|
39
|
+
end
|
|
40
|
+
subgraph Result
|
|
41
|
+
G
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Binding Dependencies
|
|
46
|
+
|
|
47
|
+
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.
|
|
48
|
+
|
|
49
|
+
### Standard Resource Binding
|
|
50
|
+
|
|
51
|
+
The `Application` class provides helper methods for common resource types. These automatically create a binding with a conventional key.
|
|
52
|
+
|
|
53
|
+
| Method | Example 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
|
+
|
|
61
|
+
### Custom Bindings
|
|
62
|
+
|
|
63
|
+
For other values or more complex setups, use the `bind` method directly.
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// In your application class's preConfigure()
|
|
67
|
+
this.bind<MyCustomClass>({ key: 'MyCustomClass' }).toClass(MyCustomClass);
|
|
68
|
+
|
|
69
|
+
this.bind<string>({ key: 'API_KEY' }).toValue('my-secret-api-key');
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Binding Scopes
|
|
73
|
+
|
|
74
|
+
You can control the lifecycle of your dependencies with scopes.
|
|
75
|
+
|
|
76
|
+
- **`TRANSIENT`** (default): A new instance is created every time the dependency is injected.
|
|
77
|
+
- **`SINGLETON`**: A single instance is created once and reused for all subsequent injections.
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
this.bind({ key: 'services.MySingletonService' })
|
|
81
|
+
.toClass(MySingletonService)
|
|
82
|
+
.setScope(BindingScopes.SINGLETON); // Use SINGLETON for this service
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Injecting Dependencies
|
|
86
|
+
|
|
87
|
+
`Ignis` provides the `@inject` decorator to request dependencies from the container.
|
|
88
|
+
|
|
89
|
+
### Constructor Injection (Recommended)
|
|
90
|
+
|
|
91
|
+
This makes dependencies explicit and ensures they are available right away.
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { BaseController, controller, inject } from '@venizia/ignis';
|
|
95
|
+
import { UserService } from '../services/user.service';
|
|
96
|
+
|
|
97
|
+
@controller({ path: '/users' })
|
|
98
|
+
export class UserController extends BaseController {
|
|
99
|
+
constructor(
|
|
100
|
+
@inject({ key: 'services.UserService' })
|
|
101
|
+
private userService: UserService
|
|
102
|
+
) {
|
|
103
|
+
super({ scope: UserController.name, path: '/users' });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ... you can now use this.userService
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Property Injection
|
|
111
|
+
|
|
112
|
+
You can also inject dependencies directly as class properties.
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { inject } from '@venizia/ignis';
|
|
116
|
+
import { UserService } from '../services/user.service';
|
|
117
|
+
|
|
118
|
+
export class UserComponent {
|
|
119
|
+
@inject({ key: 'services.UserService' })
|
|
120
|
+
private userService: UserService;
|
|
121
|
+
|
|
122
|
+
// ...
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Providers
|
|
127
|
+
|
|
128
|
+
Providers are used for dependencies that require complex setup logic. A provider is a class that implements a `value()` method, which is responsible for creating and returning the dependency instance.
|
|
129
|
+
|
|
130
|
+
### Creating a Custom Provider
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { BaseProvider, inject, Container, IConfig } from '@venizia/ignis';
|
|
134
|
+
import { ThirdPartyApiClient } from '../services/api-client.service';
|
|
135
|
+
|
|
136
|
+
// Assume IConfig is a configuration object we've bound elsewhere
|
|
137
|
+
@injectable() // Make the provider itself injectable
|
|
138
|
+
export class ApiClientProvider extends BaseProvider<ThirdPartyApiClient> {
|
|
139
|
+
@inject({ key: 'configs.api' })
|
|
140
|
+
private apiConfig: IConfig;
|
|
141
|
+
|
|
142
|
+
// The container calls this method to get the instance
|
|
143
|
+
value(container: Container): ThirdPartyApiClient {
|
|
144
|
+
const client = new ThirdPartyApiClient({
|
|
145
|
+
apiKey: this.apiConfig.apiKey,
|
|
146
|
+
baseUrl: this.apiConfig.baseUrl,
|
|
147
|
+
});
|
|
148
|
+
client.connect(); // Perform initial setup
|
|
149
|
+
return client;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
You would then bind this provider in your application:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// In your application class
|
|
158
|
+
this.bind<ThirdPartyApiClient>({ key: 'services.ApiClient' })
|
|
159
|
+
.toProvider(ApiClientProvider);
|
|
160
|
+
```
|