@venizia/ignis-docs 0.0.1-8 → 0.0.1-9

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 (27) hide show
  1. package/package.json +1 -1
  2. package/wiki/changelogs/2025-12-17-refactor.md +22 -0
  3. package/wiki/changelogs/2025-12-18-performance-optimizations.md +192 -0
  4. package/wiki/changelogs/2025-12-18-repository-validation-security.md +445 -0
  5. package/wiki/changelogs/index.md +22 -0
  6. package/wiki/changelogs/v0.0.1-7-initial-architecture.md +137 -0
  7. package/wiki/changelogs/v0.0.1-8-model-repo-datasource-refactor.md +278 -0
  8. package/wiki/get-started/5-minute-quickstart.md +1 -1
  9. package/wiki/get-started/best-practices/api-usage-examples.md +12 -8
  10. package/wiki/get-started/best-practices/common-pitfalls.md +2 -2
  11. package/wiki/get-started/best-practices/data-modeling.md +14 -20
  12. package/wiki/get-started/building-a-crud-api.md +60 -75
  13. package/wiki/get-started/core-concepts/controllers.md +14 -14
  14. package/wiki/get-started/core-concepts/persistent.md +110 -130
  15. package/wiki/get-started/quickstart.md +1 -1
  16. package/wiki/references/base/controllers.md +40 -16
  17. package/wiki/references/base/datasources.md +195 -33
  18. package/wiki/references/base/dependency-injection.md +5 -5
  19. package/wiki/references/base/models.md +398 -28
  20. package/wiki/references/base/repositories.md +475 -22
  21. package/wiki/references/components/authentication.md +224 -7
  22. package/wiki/references/components/health-check.md +1 -1
  23. package/wiki/references/components/swagger.md +1 -1
  24. package/wiki/references/helpers/inversion.md +8 -3
  25. package/wiki/references/src-details/core.md +6 -5
  26. package/wiki/references/src-details/inversion.md +4 -4
  27. package/wiki/references/utilities/request.md +16 -7
@@ -65,7 +65,7 @@ export class MyFeatureController extends BaseController {
65
65
 
66
66
  @api({ configs: MyRouteConfig })
67
67
  getData(c: TRouteContext<typeof MyRouteConfig>) { // Return type is automatically inferred and validated
68
- return c.json({ success: true });
68
+ return c.json({ success: true }, HTTP.ResultCodes.RS_2.Ok);
69
69
  }
70
70
  }
71
71
  ```
@@ -83,7 +83,7 @@ For convenience, `Ignis` provides decorator shortcuts for each HTTP method: Thes
83
83
  **Example using `@get` and `@post` with type inference:**
84
84
 
85
85
  ```typescript
86
- import { get, post, z, jsonContent, jsonResponse, Authentication, TRouteContext } from '@venizia/ignis';
86
+ import { get, post, z, jsonContent, jsonResponse, Authentication, TRouteContext, HTTP } from '@venizia/ignis';
87
87
 
88
88
  // Define route configs as const for full type inference
89
89
  const USER_ROUTES = {
@@ -125,20 +125,20 @@ const USER_ROUTES = {
125
125
 
126
126
  @get({ configs: USER_ROUTES.listUsers })
127
127
  getAllUsers(c: TRouteContext<typeof USER_ROUTES.listUsers>) { // Return type is automatically inferred
128
- return c.json([{ id: '1', name: 'John Doe' }]);
128
+ return c.json([{ id: '1', name: 'John Doe' }], HTTP.ResultCodes.RS_2.Ok);
129
129
  }
130
130
 
131
131
  @get({ configs: USER_ROUTES.getUser })
132
132
  getUserById(c: TRouteContext<typeof USER_ROUTES.getUser>) { // Return type is automatically inferred
133
133
  const { id } = c.req.valid('param'); // id is typed as string
134
- return c.json({ id, name: 'John Doe' });
134
+ return c.json({ id, name: 'John Doe' }, HTTP.ResultCodes.RS_2.Ok);
135
135
  }
136
136
 
137
137
  @post({ configs: USER_ROUTES.createUser })
138
138
  createUser(c: TRouteContext<typeof USER_ROUTES.createUser>) { // Return type is automatically inferred
139
139
  const { name } = c.req.valid('json'); // name is typed as string
140
140
  const newUser = { id: '2', name };
141
- return c.json(newUser, 201); // Return type is validated
141
+ return c.json(newUser, HTTP.ResultCodes.RS_2.Created); // Return type is validated
142
142
  }
143
143
  ```
144
144
 
@@ -171,17 +171,21 @@ export class HealthCheckController extends BaseController {
171
171
  @api({ configs: HEALTH_CHECK_ROUTES['/ping'] })
172
172
  ping(c: TRouteContext<typeof HEALTH_CHECK_ROUTES['/ping']>) { // Return type is automatically inferred
173
173
  const { message } = c.req.valid('json');
174
- return c.json({ pong: message });
174
+ return c.json({ pong: message }, HTTP.ResultCodes.RS_2.Ok);
175
175
  }
176
176
  }
177
177
  ```
178
178
 
179
179
  ### Manual Route Definition Methods
180
180
 
181
- While decorator-based routing is available, the recommended way to define routes is by using the `defineRoute` and `bindRoute` methods inside the `binding()` method. This approach offers a clear and declarative syntax that keeps your route definitions organized and easy to manage.
181
+ For advanced use cases or when you prefer a non-decorator approach, you can define routes manually using `defineRoute` and `bindRoute` methods inside the `binding()` method.
182
182
 
183
- :::tip Recommendation
184
- For better organization and a more declarative approach, we strongly recommend using `defineRoute` or `bindRoute` within the `binding()` method to define your controller's routes. This keeps all route definitions in one place, making your controller easier to read and maintain.
183
+ :::tip When to Use Manual Definition
184
+ Manual route definition is useful for:
185
+ - Dynamically generating routes based on configuration
186
+ - Conditional route registration (feature flags)
187
+ - Developers who prefer non-decorator syntax (coming from Express/Fastify)
188
+ - Complex routing logic that benefits from programmatic control
185
189
  :::
186
190
 
187
191
  #### `defineRoute`
@@ -291,17 +295,17 @@ This factory method returns a `BaseController` class that is already set up with
291
295
 
292
296
  **Note:** The returned class is dynamically named using `controller.name` from the options. This ensures that when registered with `app.controller()`, the class has a proper name for binding keys and debugging (e.g., `ConfigurationController` instead of an anonymous class).
293
297
 
294
- | Name | Method | Path | Description |
298
+ | Route Name | Method | Path | Description |
295
299
  | :--- | :--- | :--- | :--- |
296
300
  | `count` | `GET` | `/count` | Get the number of records matching a filter. |
297
301
  | `find` | `GET` | `/` | Retrieve all records matching a filter. |
298
302
  | `findById` | `GET` | `/:id` | Retrieve a single record by its ID. |
299
303
  | `findOne` | `GET` | `/find-one` | Retrieve a single record matching a filter. |
300
304
  | `create` | `POST` | `/` | Create a new record. |
301
- | `updateById` | `PATCH` | `/:id` | Update a record by its ID. |
302
- | `updateAll` | `PATCH` | `/` | Update multiple records matching a filter. |
303
- | `deleteById` | `DELETE` | `/:id` | Delete a record by its ID. |
304
- | `deleteAll` | `DELETE` | `/` | Delete multiple records matching a filter. |
305
+ | `updateById` | `PATCH` | `/:id` | Update a single record by its ID. |
306
+ | `updateBy` | `PATCH` | `/` | Update multiple records matching a `where` filter. |
307
+ | `deleteById` | `DELETE` | `/:id` | Delete a single record by its ID. |
308
+ | `deleteBy` | `DELETE` | `/` | Delete multiple records matching a `where` filter. |
305
309
 
306
310
  ### `ICrudControllerOptions<EntitySchema>`
307
311
 
@@ -311,10 +315,30 @@ This factory method returns a `BaseController` class that is already set up with
311
315
  | `repository.name` | `string` | The binding key name of the repository associated with this entity (e.g., `'ConfigurationRepository'`). |
312
316
  | `controller.name` | `string` | A unique name for the generated controller (e.g., `'ConfigurationController'`). |
313
317
  | `controller.basePath`| `string` | The base path for all routes in this CRUD controller (e.g., `'/configurations'`). |
318
+ | `controller.readonly` | `boolean` | If `true`, only read operations (find, findOne, findById, count) are generated. Write operations are excluded. Defaults to `false`. |
314
319
  | `controller.isStrict` | `boolean` | If `true`, query parameters like `where` will be strictly validated. Defaults to `true`. |
315
320
  | `controller.defaultLimit`| `number` | The default limit for `find` operations. Defaults to `10`. |
316
- | `schema` | `object` | An optional object to override the default Zod schemas for specific CRUD endpoints (e.g., `find`, `create`, `updateByIdRequestBody`). This allows for fine-grained control over the request and response validation and OpenAPI documentation. |
317
- | `doDeleteWithReturn` | `boolean` | If `true`, the `deleteById` and `deleteAll` endpoints will return the deleted record(s) in the response body. Defaults to `false`. |
321
+ | `schema` | `object` | An optional object to override the default Zod schemas for specific CRUD endpoints. See schema options below. |
322
+ | `doDeleteWithReturn` | `boolean` | If `true`, the `deleteById` and `deleteBy` endpoints will return the deleted record(s) in the response body. Defaults to `false`. |
323
+
324
+ ### Schema Override Options
325
+
326
+ The `schema` option allows fine-grained control over request/response validation and OpenAPI documentation:
327
+
328
+ | Schema Option | Description |
329
+ | :--- | :--- |
330
+ | `count` | Override response schema for count endpoint |
331
+ | `find` | Override response schema for find endpoint |
332
+ | `findOne` | Override response schema for findOne endpoint |
333
+ | `findById` | Override response schema for findById endpoint |
334
+ | `create` | Override response schema for create endpoint |
335
+ | `createRequestBody` | Override request body schema for create endpoint |
336
+ | `updateById` | Override response schema for updateById endpoint |
337
+ | `updateByIdRequestBody` | Override request body schema for updateById endpoint |
338
+ | `updateBy` | Override response schema for updateBy (bulk update) endpoint |
339
+ | `updateByRequestBody` | Override request body schema for updateBy endpoint |
340
+ | `deleteById` | Override response schema for deleteById endpoint |
341
+ | `deleteBy` | Override response schema for deleteBy (bulk delete) endpoint |
318
342
 
319
343
  ### Example
320
344
 
@@ -8,9 +8,9 @@ Technical reference for DataSource classes - managing database connections in Ig
8
8
 
9
9
  | Class/Interface | Purpose | Key Members |
10
10
  |-----------------|---------|-------------|
11
- | **IDataSource** | Contract for all datasources | `name`, `settings`, `connector`, `schema`, `configure()` |
11
+ | **IDataSource** | Contract for all datasources | `name`, `settings`, `connector`, `getSchema()`, `configure()` |
12
12
  | **AbstractDataSource** | Base implementation with logging | Extends `BaseHelper` |
13
- | **BaseDataSource** | Concrete class to extend | Constructor accepts `name`, `driver`, `config`, `schema` |
13
+ | **BaseDataSource** | Concrete class to extend | Auto-discovery, driver from decorator |
14
14
 
15
15
  ## `IDataSource` Interface
16
16
 
@@ -25,7 +25,7 @@ Contract for all datasource classes in the framework.
25
25
  | `name` | `string` | Datasource name |
26
26
  | `settings` | `object` | Configuration object |
27
27
  | `connector` | `TDatabaseConnector` | Database connector instance (e.g., Drizzle) |
28
- | `schema` | `object` | Combined Drizzle schema (tables + relations) |
28
+ | `getSchema()` | Method | Returns combined Drizzle schema (auto-discovered or manual) |
29
29
  | `configure()` | Method | Initializes the `connector` |
30
30
  | `getConnectionString()` | Method | Returns connection string |
31
31
 
@@ -39,29 +39,60 @@ This is the top-level abstract class that implements the `IDataSource` interface
39
39
 
40
40
  ### `BaseDataSource`
41
41
 
42
- This class extends `AbstractDataSource` and provides a constructor that standardizes how datasources are created. When you create your own datasource, you extend `BaseDataSource`.
42
+ This class extends `AbstractDataSource` and provides a constructor with **auto-discovery** support. When you create your own datasource, you extend `BaseDataSource`.
43
43
 
44
- ### Constructor and Configuration Flow
44
+ #### Key Features
45
+
46
+ | Feature | Description |
47
+ |---------|-------------|
48
+ | **Driver Auto-Read** | Driver is read from `@datasource` decorator - no need to pass in constructor |
49
+ | **Schema Auto-Discovery** | Schema is automatically built from registered `@repository` decorators |
50
+ | **Manual Override** | You can still manually provide schema in constructor for full control |
51
+
52
+ ### Constructor Options
53
+
54
+ ```typescript
55
+ constructor(opts: {
56
+ name: string; // DataSource name (usually class name)
57
+ config: Settings; // Database connection settings
58
+ driver?: TDataSourceDriver; // Optional - read from @datasource if not provided
59
+ schema?: Schema; // Optional - auto-discovered if not provided
60
+ })
61
+ ```
62
+
63
+ ### Schema Auto-Discovery
64
+
65
+ When you use `@repository({ model: YourModel, dataSource: YourDataSource })`, the framework automatically:
66
+
67
+ 1. Registers the model-datasource binding in the MetadataRegistry
68
+ 2. When `getSchema()` is called, discovers all models bound to this datasource
69
+ 3. Builds the combined schema (tables + relations) automatically
70
+
71
+ **This means you no longer need to manually merge tables and relations in the DataSource constructor!**
72
+
73
+ ### Configuration Flow
45
74
 
46
75
  1. **Your DataSource's `constructor` is called**:
47
- - You call `super()` with the `name`, `driver`, `config` (connection settings), and the crucial `schema` object.
48
- - The `schema` object **must** contain all your Drizzle tables and `relations` definitions.
76
+ - You call `super()` with `name` and `config`
77
+ - Driver is automatically read from `@datasource` decorator
78
+ - Schema is auto-discovered from `@repository` bindings (or manually provided)
49
79
 
50
80
  2. **`Application.registerDataSources()` is called during startup**:
51
- - The application gets your `DataSource` instance from the DI container.
52
- - It calls the `configure()` method on your instance.
81
+ - The application gets your `DataSource` instance from the DI container
82
+ - It calls the `configure()` method on your instance
53
83
 
54
84
  3. **Your `configure()` method runs**:
55
- - This is where you instantiate the Drizzle ORM.
56
- - You pass the `this.settings` (your config) and `this.schema` to Drizzle, creating the `this.connector`.
85
+ - This is where you instantiate the Drizzle ORM
86
+ - Use `this.getSchema()` to get the auto-discovered schema and pass to Drizzle
87
+
88
+ ### Example Implementations
57
89
 
58
- ### Example Implementation
90
+ #### Pattern 1: Auto-Discovery (Recommended)
91
+
92
+ Simplest approach - schema is auto-discovered from repositories:
59
93
 
60
94
  ```typescript
61
95
  // src/datasources/postgres.datasource.ts
62
- import {
63
- // ... import your models and relations
64
- } from '@/models/entities';
65
96
  import {
66
97
  BaseDataSource,
67
98
  datasource,
@@ -71,35 +102,166 @@ import {
71
102
  import { drizzle } from 'drizzle-orm/node-postgres';
72
103
  import { Pool } from 'pg';
73
104
 
74
- // Decorator to mark this class as a datasource for DI
75
- @datasource()
76
- export class PostgresDataSource extends BaseDataSource<
77
- TNodePostgresConnector, // Type of the connector
78
- IDSConfigs // Type of the config object
79
- > {
105
+ interface IDSConfigs {
106
+ host: string;
107
+ port: number;
108
+ database: string;
109
+ user: string;
110
+ password: string;
111
+ }
112
+
113
+ /**
114
+ * PostgresDataSource with auto-discovery support.
115
+ *
116
+ * How it works:
117
+ * 1. @repository decorator binds model to datasource
118
+ * 2. When configure() is called, getSchema() auto-discovers all bound models
119
+ * 3. Drizzle is initialized with the auto-discovered schema
120
+ */
121
+ @datasource({ driver: 'node-postgres' })
122
+ export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, IDSConfigs> {
80
123
  constructor() {
81
124
  super({
82
125
  name: PostgresDataSource.name,
83
- driver: 'node-postgres',
84
- config: { /* ... connection details from environment ... */ },
85
- schema: {
86
- // Register all tables and relations here
87
- usersTable,
88
- configurationTable,
89
- ...configurationRelations,
126
+ // Driver is read from @datasource decorator - no need to pass here!
127
+ config: {
128
+ host: process.env.APP_ENV_POSTGRES_HOST ?? 'localhost',
129
+ port: +(process.env.APP_ENV_POSTGRES_PORT ?? 5432),
130
+ database: process.env.APP_ENV_POSTGRES_DATABASE ?? 'mydb',
131
+ user: process.env.APP_ENV_POSTGRES_USERNAME ?? 'postgres',
132
+ password: process.env.APP_ENV_POSTGRES_PASSWORD ?? '',
90
133
  },
134
+ // NO schema property - auto-discovered from @repository bindings!
91
135
  });
92
136
  }
93
137
 
94
- // This method is called by the application at startup
95
138
  override configure(): ValueOrPromise<void> {
96
- this.connector = drizzle({
97
- client: new Pool(this.settings),
98
- schema: this.schema,
139
+ // getSchema() auto-discovers models from @repository bindings
140
+ const schema = this.getSchema();
141
+
142
+ // Log discovered schema for debugging
143
+ const schemaKeys = Object.keys(schema);
144
+ this.logger.debug(
145
+ '[configure] Auto-discovered schema | Schema + Relations (%s): %o',
146
+ schemaKeys.length,
147
+ schemaKeys,
148
+ );
149
+
150
+ const client = new Pool(this.settings);
151
+ this.connector = drizzle({ client, schema });
152
+ }
153
+ }
154
+ ```
155
+
156
+ With this pattern, when you define repositories:
157
+
158
+ ```typescript
159
+ @repository({ model: User, dataSource: PostgresDataSource })
160
+ export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {}
161
+
162
+ @repository({ model: Configuration, dataSource: PostgresDataSource })
163
+ export class ConfigurationRepository extends DefaultCRUDRepository<typeof Configuration.schema> {}
164
+ ```
165
+
166
+ The `PostgresDataSource.schema` will automatically include User and Configuration tables and their relations.
167
+
168
+ #### Pattern 2: Manual Schema (Full Control)
169
+
170
+ When you need explicit control over schema (e.g., subset of models, custom ordering):
171
+
172
+ ```typescript
173
+ import {
174
+ User, userTable, userRelations,
175
+ Configuration, configurationTable, configurationRelations,
176
+ } from '@/models/entities';
177
+
178
+ @datasource({ driver: 'node-postgres' })
179
+ export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, IDSConfigs> {
180
+ constructor() {
181
+ super({
182
+ name: PostgresDataSource.name,
183
+ config: {
184
+ host: process.env.APP_ENV_POSTGRES_HOST ?? 'localhost',
185
+ port: +(process.env.APP_ENV_POSTGRES_PORT ?? 5432),
186
+ database: process.env.APP_ENV_POSTGRES_DATABASE ?? 'mydb',
187
+ user: process.env.APP_ENV_POSTGRES_USERNAME ?? 'postgres',
188
+ password: process.env.APP_ENV_POSTGRES_PASSWORD ?? '',
189
+ },
190
+ // Manually provide schema using spread syntax
191
+ schema: {
192
+ [User.TABLE_NAME]: userTable,
193
+ [Configuration.TABLE_NAME]: configurationTable,
194
+ ...userRelations.relations,
195
+ ...configurationRelations.relations,
196
+ },
99
197
  });
100
198
  }
101
- // ...
199
+
200
+ override configure(): ValueOrPromise<void> {
201
+ // When schema is manually provided, getSchema() returns it directly
202
+ const client = new Pool(this.settings);
203
+ this.connector = drizzle({ client, schema: this.getSchema() });
204
+ }
102
205
  }
103
206
  ```
104
207
 
208
+ ### @datasource Decorator
209
+
210
+ The `@datasource` decorator registers datasource metadata:
211
+
212
+ ```typescript
213
+ @datasource({
214
+ driver: 'node-postgres', // Required - database driver
215
+ autoDiscovery?: true // Optional - defaults to true
216
+ })
217
+ ```
218
+
219
+ | Option | Type | Default | Description |
220
+ |--------|------|---------|-------------|
221
+ | `driver` | `TDataSourceDriver` | - | Database driver name |
222
+ | `autoDiscovery` | `boolean` | `true` | Enable/disable schema auto-discovery |
223
+
224
+ ### Abstract Methods
225
+
226
+ These methods must be implemented in your datasource class:
227
+
228
+ | Method | Return Type | Description |
229
+ |--------|-------------|-------------|
230
+ | `configure(opts?)` | `ValueOrPromise<void>` | Initialize the Drizzle ORM connector. Called during application startup. |
231
+ | `getConnectionString()` | `ValueOrPromise<string>` | Return the database connection string. |
232
+
233
+ ### Optional Override Methods
234
+
235
+ These methods can be optionally overridden for connection lifecycle management:
236
+
237
+ | Method | Return Type | Description |
238
+ |--------|-------------|-------------|
239
+ | `connect()` | `Promise<Connector \| undefined>` | Establish database connection. Useful for connection pooling. |
240
+ | `disconnect()` | `Promise<void>` | Close database connection gracefully. |
241
+
242
+ ```typescript
243
+ @datasource({ driver: 'node-postgres' })
244
+ export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, IDSConfigs> {
245
+ // ... constructor and configure() ...
246
+
247
+ override async connect(): Promise<TNodePostgresConnector | undefined> {
248
+ await (this.connector.client as Pool).connect();
249
+ return this.connector;
250
+ }
251
+
252
+ override async disconnect(): Promise<void> {
253
+ await (this.connector.client as Pool).end();
254
+ }
255
+ }
256
+ ```
257
+
258
+ ### Helper Methods
259
+
260
+ | Method | Description |
261
+ |--------|-------------|
262
+ | `getSchema()` | Returns the schema (auto-discovers if not manually provided) |
263
+ | `getSettings()` | Returns connection settings |
264
+ | `getConnector()` | Returns the Drizzle connector |
265
+ | `hasDiscoverableModels()` | Returns `true` if there are models registered for this datasource |
266
+
105
267
  This architecture ensures that datasources are configured consistently and that the fully-initialized Drizzle connector, aware of all schemas and relations, is available to repositories for querying.
@@ -4,9 +4,9 @@ Technical reference for the DI system in Ignis - managing resource lifecycles an
4
4
 
5
5
  **Files:**
6
6
  - `packages/inversion/src/container.ts` (base Container and Binding classes)
7
- - `packages/helpers/src/helpers/inversion/container.ts` (extended Container with ApplicationLogger)
7
+ - `packages/core/src/helpers/inversion/container.ts` (extended Container with ApplicationLogger)
8
8
  - `packages/core/src/base/metadata/injectors.ts` (@inject, @injectable decorators)
9
- - `packages/helpers/src/helpers/inversion/registry.ts` (MetadataRegistry)
9
+ - `packages/core/src/helpers/inversion/registry.ts` (MetadataRegistry)
10
10
 
11
11
  ## Quick Reference
12
12
 
@@ -21,7 +21,7 @@ Technical reference for the DI system in Ignis - managing resource lifecycles an
21
21
 
22
22
  Heart of the DI system - registry managing all application resources.
23
23
 
24
- **File:** `packages/inversion/src/container.ts`
24
+ **File:** `packages/inversion/src/container.ts` (Base) & `packages/core/src/helpers/inversion/container.ts` (Extended)
25
25
 
26
26
  ### Key Methods
27
27
 
@@ -74,11 +74,11 @@ This entire process is managed by the framework when your application starts up,
74
74
 
75
75
  The `MetadataRegistry` is a crucial part of the DI and routing systems. It's a singleton class responsible for storing and retrieving all the metadata attached by decorators like `@inject`, `@controller`, `@get`, etc.
76
76
 
77
- - **File:** `packages/helpers/src/helpers/inversion/registry.ts`
77
+ - **File:** `packages/core/src/helpers/inversion/registry.ts`
78
78
 
79
79
  ### Role in DI
80
80
 
81
81
  - When you use a decorator (e.g., `@inject`), it calls a method on the `MetadataRegistry.getInstance()` to store information about the injection (like the binding key and target property/parameter).
82
82
  - When the `Container` instantiates a class, it queries the `MetadataRegistry` to find out which dependencies need to be injected and where.
83
83
 
84
- You typically won't interact with the `MetadataRegistry` directly, but it's the underlying mechanism that makes the decorator-based DI and routing systems work seamlessly.
84
+ You typically won't interact with the `MetadataRegistry` directly, but it's the underlying mechanism that makes the decorator-based DI and routing systems work seamlessly.