@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
@@ -134,12 +134,10 @@ export type TTodo = TTableObject<TTodoSchema>;
134
134
 
135
135
  // 4. Create the Entity class, decorated with @model
136
136
  @model({ type: 'entity' })
137
- export class Todo extends BaseEntity<TTodoSchema> {
138
- static readonly TABLE_NAME = 'Todo';
139
-
140
- constructor() {
141
- super({ name: Todo.TABLE_NAME, schema: todoTable });
142
- }
137
+ export class Todo extends BaseEntity<typeof Todo.schema> {
138
+ static override schema = todoTable;
139
+ static override relations = () => todoRelations.definitions;
140
+ static override TABLE_NAME = 'Todo';
143
141
  }
144
142
  ```
145
143
 
@@ -203,7 +201,6 @@ Create `src/datasources/postgres.datasource.ts`:
203
201
 
204
202
  ```typescript
205
203
  // src/datasources/postgres.datasource.ts
206
- import { Todo, todoRelations, todoTable } from '@/models/todo.model';
207
204
  import {
208
205
  BaseDataSource,
209
206
  datasource,
@@ -214,61 +211,61 @@ import { drizzle } from 'drizzle-orm/node-postgres';
214
211
  import { Pool } from 'pg';
215
212
 
216
213
  interface IDSConfigs {
217
- connection: {
218
- host?: string;
219
- port?: number;
220
- user?: string;
221
- password?: string;
222
- database?: string;
223
- };
214
+ host: string;
215
+ port: number;
216
+ database: string;
217
+ user: string;
218
+ password: string;
224
219
  }
225
220
 
226
- @datasource()
221
+ /**
222
+ * PostgresDataSource with auto-discovery support.
223
+ *
224
+ * How it works:
225
+ * 1. @repository decorator binds model to datasource
226
+ * 2. When configure() is called, getSchema() auto-discovers all bound models
227
+ * 3. Drizzle is initialized with the auto-discovered schema
228
+ */
229
+ @datasource({ driver: 'node-postgres' })
227
230
  export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, IDSConfigs> {
228
231
  constructor() {
229
232
  super({
230
233
  name: PostgresDataSource.name,
231
- driver: 'node-postgres',
234
+ // Driver is read from @datasource decorator - no need to pass here!
232
235
  config: {
233
- connection: {
234
- host: process.env.APP_ENV_POSTGRES_HOST,
235
- port: +(process.env.APP_ENV_POSTGRES_PORT ?? 5432),
236
- user: process.env.APP_ENV_POSTGRES_USERNAME,
237
- password: process.env.APP_ENV_POSTGRES_PASSWORD,
238
- database: process.env.APP_ENV_POSTGRES_DATABASE,
239
- },
236
+ host: process.env.APP_ENV_POSTGRES_HOST ?? 'localhost',
237
+ port: +(process.env.APP_ENV_POSTGRES_PORT ?? 5432),
238
+ database: process.env.APP_ENV_POSTGRES_DATABASE ?? 'todo_db',
239
+ user: process.env.APP_ENV_POSTGRES_USERNAME ?? 'postgres',
240
+ password: process.env.APP_ENV_POSTGRES_PASSWORD ?? '',
240
241
  },
241
- // Register all your models and their relations here
242
- schema: Object.assign(
243
- {},
244
- { [Todo.TABLE_NAME]: todoTable },
245
- todoRelations.relations,
246
- ),
242
+ // NO schema property - auto-discovered from @repository bindings!
247
243
  });
248
244
  }
249
245
 
250
246
  override configure(): ValueOrPromise<void> {
251
- this.connector = drizzle({
252
- client: new Pool(this.settings.connection),
253
- schema: this.schema,
254
- });
255
- }
256
-
257
- override async connect(): Promise<TNodePostgresConnector | undefined> {
258
- await (this.connector.client as Pool).connect();
259
- return this.connector;
260
- }
261
-
262
- override async disconnect(): Promise<void> {
263
- await (this.connector.client as Pool).end();
247
+ // getSchema() auto-discovers models from @repository bindings
248
+ const schema = this.getSchema();
249
+
250
+ // Log discovered schema for debugging
251
+ const schemaKeys = Object.keys(schema);
252
+ this.logger.debug(
253
+ '[configure] Auto-discovered schema | Schema + Relations (%s): %o',
254
+ schemaKeys.length,
255
+ schemaKeys,
256
+ );
257
+
258
+ const client = new Pool(this.settings);
259
+ this.connector = drizzle({ client, schema });
264
260
  }
265
261
  }
266
262
  ```
267
263
 
268
264
  **Key Points:**
269
- - Registers `todoTable` and `todoRelations` in the schema
265
+ - Schema is auto-discovered from `@repository` decorators - no manual registration needed
266
+ - Uses `getSchema()` for lazy schema resolution (resolves when all models are loaded)
270
267
  - Uses environment variables for connection config
271
- - Implements connection lifecycle methods
268
+ - Implements connection lifecycle methods (`connect()`, `disconnect()`)
272
269
 
273
270
  > **Deep Dive:** See [DataSources Reference](../references/base/datasources.md) for advanced configuration and multiple database support.
274
271
 
@@ -280,26 +277,15 @@ Create `src/repositories/todo.repository.ts`:
280
277
 
281
278
  ```typescript
282
279
  // src/repositories/todo.repository.ts
283
- import { Todo, todoRelations, TTodoSchema } from '@/models/todo.model';
284
- import {
285
- DefaultCRUDRepository,
286
- IDataSource,
287
- inject,
288
- repository,
289
- } from '@venizia/ignis';
290
-
291
- @repository()
292
- export class TodoRepository extends DefaultCRUDRepository<TTodoSchema> {
293
- constructor(
294
- @inject({ key: 'datasources.PostgresDataSource' })
295
- dataSource: IDataSource,
296
- ) {
297
- super({
298
- dataSource,
299
- entityClass: Todo,
300
- relations: todoRelations.definitions,
301
- });
302
- }
280
+ import { Todo } from '@/models/todo.model';
281
+ import { PostgresDataSource } from '@/datasources/postgres.datasource';
282
+ import { DefaultCRUDRepository, repository } from '@venizia/ignis';
283
+
284
+ // Both 'model' and 'dataSource' are required for schema auto-discovery
285
+ @repository({ model: Todo, dataSource: PostgresDataSource })
286
+ export class TodoRepository extends DefaultCRUDRepository<typeof Todo.schema> {
287
+ // No constructor needed! DataSource and relations are auto-resolved
288
+ // from the @repository decorator and entity's static properties
303
289
  }
304
290
  ```
305
291
 
@@ -358,15 +344,15 @@ export class TodoController extends _Controller {
358
344
  **Auto-generated Endpoints:**
359
345
  | Method | Path | Description |
360
346
  |--------|------|-------------|
361
- | GET | `/todos` | List all todos |
362
- | GET | `/todos/:id` | Get todo by ID |
363
- | GET | `/todos/find-one` | Find one todo by filter |
364
- | GET | `/todos/count` | Count todos |
365
- | POST | `/todos` | Create todo |
366
- | PATCH | `/todos/:id` | Update todo by ID |
367
- | PATCH | `/todos` | Update multiple todos |
368
- | DELETE | `/todos/:id` | Delete todo by ID |
369
- | DELETE | `/todos` | Delete multiple todos |
347
+ | GET | `/todos` | List all todos (find) |
348
+ | GET | `/todos/:id` | Get todo by ID (findById) |
349
+ | GET | `/todos/find-one` | Find one todo by filter (findOne) |
350
+ | GET | `/todos/count` | Count todos (count) |
351
+ | POST | `/todos` | Create todo (create) |
352
+ | PATCH | `/todos/:id` | Update todo by ID (updateById) |
353
+ | PATCH | `/todos` | Update multiple todos by filter (updateBy) |
354
+ | DELETE | `/todos/:id` | Delete todo by ID (deleteById) |
355
+ | DELETE | `/todos` | Delete multiple todos by filter (deleteBy) |
370
356
 
371
357
  > **Deep Dive:** See [ControllerFactory Reference](../references/base/controllers.md#controllerfactory) for customization options.
372
358
 
@@ -639,8 +625,7 @@ Now that you've built the Todo API, try building a **User** feature on your own!
639
625
 
640
626
  **Challenge checklist:**
641
627
  - [ ] Create `src/models/user.model.ts`
642
- - [ ] Update `PostgresDataSource` to include User schema
643
- - [ ] Create `src/repositories/user.repository.ts`
628
+ - [ ] Create `src/repositories/user.repository.ts` (this auto-registers User with PostgresDataSource)
644
629
  - [ ] Create `src/controllers/user.controller.ts`
645
630
  - [ ] Register repository and controller in `application.ts`
646
631
  - [ ] Run migration: `bun run migrate:dev`
@@ -9,7 +9,7 @@ Controllers handle HTTP requests and return responses - they're your API endpoin
9
9
  Extend `BaseController` and use decorators to define routes:
10
10
 
11
11
  ```typescript
12
- import { BaseController, controller, get, jsonResponse, z } from '@venizia/ignis';
12
+ import { BaseController, controller, get, jsonResponse, z, HTTP } from '@venizia/ignis';
13
13
  import { Context } from 'hono';
14
14
 
15
15
  @controller({ path: '/users' })
@@ -29,7 +29,7 @@ export class UserController extends BaseController {
29
29
  },
30
30
  })
31
31
  getAllUsers(c: Context) {
32
- return c.json([{ id: '1', name: 'John Doe' }]);
32
+ return c.json([{ id: '1', name: 'John Doe' }], HTTP.ResultCodes.RS_2.Ok);
33
33
  }
34
34
  }
35
35
  ```
@@ -177,7 +177,7 @@ const GetUsersRoute = {
177
177
  this.defineRoute({
178
178
  configs: GetUsersRoute,
179
179
  handler: (c: TRouteContext<typeof GetUsersRoute>) => { // Return type is automatically inferred
180
- return c.json([{ id: 1, name: 'John Doe' }]);
180
+ return c.json([{ id: 1, name: 'John Doe' }], HTTP.ResultCodes.RS_2.Ok);
181
181
  },
182
182
  });
183
183
  ```
@@ -187,7 +187,7 @@ this.defineRoute({
187
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
188
 
189
189
  ```typescript
190
- import { jsonResponse, z, TRouteContext } from '@venizia/ignis';
190
+ import { jsonResponse, z, TRouteContext, HTTP } from '@venizia/ignis';
191
191
 
192
192
  // ... inside the binding() method
193
193
 
@@ -205,7 +205,7 @@ this.bindRoute({
205
205
  }).to({
206
206
  handler: (c: TRouteContext<typeof GetUserByIdRoute>) => { // Return type is automatically inferred
207
207
  const { id } = c.req.param();
208
- return c.json({ id: id, name: 'John Doe' });
208
+ return c.json({ id: id, name: 'John Doe' }, HTTP.ResultCodes.RS_2.Ok);
209
209
  },
210
210
  });
211
211
  ```
@@ -255,17 +255,17 @@ export class ConfigurationController extends _Controller {
255
255
  ```
256
256
  The `ControllerFactory.defineCrudController` method automatically sets up the following routes based on your entity schema:
257
257
 
258
- | Name | Method | Path | Description |
258
+ | Route Name | Method | Path | Description |
259
259
  | :--- | :--- | :--- | :--- |
260
260
  | `count` | `GET` | `/count` | Get the number of records matching a filter. |
261
261
  | `find` | `GET` | `/` | Retrieve all records matching a filter. |
262
262
  | `findById` | `GET` | `/:id` | Retrieve a single record by its ID. |
263
263
  | `findOne` | `GET` | `/find-one` | Retrieve a single record matching a filter. |
264
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. |
265
+ | `updateById` | `PATCH` | `/:id` | Update a single record by its ID. |
266
+ | `updateBy` | `PATCH` | `/` | Update multiple records matching a `where` filter. |
267
+ | `deleteById` | `DELETE` | `/:id` | Delete a single record by its ID. |
268
+ | `deleteBy` | `DELETE` | `/` | Delete multiple records matching a `where` filter. |
269
269
 
270
270
  :::info Customization
271
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.
@@ -368,7 +368,7 @@ When you define Zod schemas in your route's `request` configuration (whether wit
368
368
 
369
369
  ```typescript
370
370
  import { z } from '@hono/zod-openapi';
371
- import { jsonContent, put } from '@venizia/ignis';
371
+ import { jsonContent, put, HTTP } from '@venizia/ignis';
372
372
 
373
373
  // ... inside a controller class
374
374
 
@@ -397,7 +397,7 @@ updateUser(c: Context) {
397
397
  console.log('Notification is enabled.');
398
398
  }
399
399
 
400
- return c.json({ success: true, id, ...userUpdateData });
400
+ return c.json({ success: true, id, ...userUpdateData }, HTTP.ResultCodes.RS_2.Ok);
401
401
  }
402
402
  ```
403
403
 
@@ -405,7 +405,7 @@ Using `c.req.valid()` is the recommended way to access request data as it ensure
405
405
 
406
406
  ```typescript
407
407
  import { z } from '@hono/zod-openapi';
408
- import { jsonContent, put, TRouteContext } from '@venizia/ignis';
408
+ import { jsonContent, put, TRouteContext, HTTP } from '@venizia/ignis';
409
409
 
410
410
  // ... inside a controller class
411
411
 
@@ -434,7 +434,7 @@ updateUser(c: TRouteContext<typeof updateUserConfig>) {
434
434
  console.log('Notification is enabled.');
435
435
  }
436
436
 
437
- return c.json({ success: true, id, ...userUpdateData });
437
+ return c.json({ success: true, id, ...userUpdateData }, HTTP.ResultCodes.RS_2.Ok);
438
438
  }
439
439
  ```
440
440
 
@@ -45,12 +45,10 @@ export type TUser = TTableObject<TUserSchema>;
45
45
 
46
46
  // 4. Create the Entity class, decorated with @model
47
47
  @model({ type: 'entity' })
48
- export class User extends BaseEntity<TUserSchema> {
49
- static readonly TABLE_NAME = User.name;
50
-
51
- constructor() {
52
- super({ name: User.name, schema: userTable });
53
- }
48
+ export class User extends BaseEntity<typeof User.schema> {
49
+ static override schema = userTable;
50
+ static override relations = () => userRelations.definitions;
51
+ static override TABLE_NAME = 'User';
54
52
  }
55
53
  ```
56
54
 
@@ -158,12 +156,10 @@ export type TPostSchema = typeof postTable;
158
156
  export type TPost = TTableObject<TPostSchema>;
159
157
 
160
158
  @model({ type: 'entity' })
161
- export class Post extends BaseEntity<TPostSchema> {
162
- static readonly TABLE_NAME = 'Post';
163
-
164
- constructor() {
165
- super({ name: Post.TABLE_NAME, schema: postTable });
166
- }
159
+ export class Post extends BaseEntity<typeof Post.schema> {
160
+ static override schema = postTable;
161
+ static override relations = () => postRelations.definitions;
162
+ static override TABLE_NAME = 'Post';
167
163
  }
168
164
  ```
169
165
 
@@ -286,12 +282,10 @@ export type TConfigurationSchema = typeof configurationTable;
286
282
  export type TConfiguration = TTableObject<TConfigurationSchema>;
287
283
 
288
284
  @model({ type: 'entity' })
289
- export class Configuration extends BaseEntity<TConfigurationSchema> {
290
- static readonly TABLE_NAME = Configuration.name;
291
-
292
- constructor() {
293
- super({ name: Configuration.TABLE_NAME, schema: configurationTable });
294
- }
285
+ export class Configuration extends BaseEntity<typeof Configuration.schema> {
286
+ static override schema = configurationTable;
287
+ static override relations = () => configurationRelations.definitions;
288
+ static override TABLE_NAME = 'Configuration';
295
289
  }
296
290
  ```
297
291
  **Key Concepts:**
@@ -309,34 +303,14 @@ A DataSource is a class responsible for managing the connection to your database
309
303
 
310
304
  ### Creating and Configuring a DataSource
311
305
 
312
- A `DataSource` must be decorated with `@datasource`. **The most critical part** is correctly merging your table schemas and relations into a single object that Drizzle ORM can understand.
313
-
314
- #### ⚠️ Understanding Schema Merging (CRITICAL CONCEPT)
315
-
316
- This is one of the most important concepts in Ignis. If you don't get this right, your relations won't work.
317
-
318
- **The Problem:**
319
- Drizzle ORM needs to know about:
320
- 1. Your table structures (e.g., `userTable`, `configurationTable`)
321
- 2. The relationships between them (e.g., "Configuration belongs to User")
306
+ A `DataSource` must be decorated with `@datasource`. The framework now supports **schema auto-discovery**, which means you no longer need to manually merge tables and relations!
322
307
 
323
- **The Solution:**
324
- You must merge both into a single `schema` object in your DataSource constructor.
308
+ ### Pattern 1: Auto-Discovery (Recommended)
325
309
 
326
- #### Step-by-Step Example
327
-
328
- Let's say you have two models: `User` and `Configuration`. Here's how to set up the DataSource:
310
+ With auto-discovery, the schema is automatically built from your `@repository` decorators:
329
311
 
330
312
  ```typescript
331
313
  // src/datasources/postgres.datasource.ts
332
- import {
333
- Configuration,
334
- configurationTable, // The table structure
335
- configurationRelations, // The relationships
336
- User,
337
- userTable, // The table structure
338
- userRelations, // The relationships
339
- } from '@/models/entities';
340
314
  import {
341
315
  BaseDataSource,
342
316
  datasource,
@@ -346,7 +320,6 @@ import {
346
320
  import { drizzle } from 'drizzle-orm/node-postgres';
347
321
  import { Pool } from 'pg';
348
322
 
349
- // Configuration interface for database connection
350
323
  interface IDSConfigs {
351
324
  connection: {
352
325
  host?: string;
@@ -357,12 +330,11 @@ interface IDSConfigs {
357
330
  };
358
331
  }
359
332
 
360
- @datasource()
333
+ @datasource({ driver: 'node-postgres' })
361
334
  export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, IDSConfigs> {
362
335
  constructor() {
363
336
  super({
364
337
  name: PostgresDataSource.name,
365
- driver: 'node-postgres',
366
338
  config: {
367
339
  connection: {
368
340
  host: process.env.APP_ENV_POSTGRES_HOST,
@@ -372,27 +344,14 @@ export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, I
372
344
  database: process.env.APP_ENV_POSTGRES_DATABASE,
373
345
  },
374
346
  },
375
-
376
- // 🔥 CRITICAL: This is where you merge tables and relations
377
- schema: Object.assign(
378
- {},
379
- // Step 1: Add your table schemas
380
- {
381
- [User.TABLE_NAME]: userTable,
382
- [Configuration.TABLE_NAME]: configurationTable,
383
- },
384
- // Step 2: Spread the relations objects
385
- userRelations.relations,
386
- configurationRelations.relations,
387
- ),
347
+ // No schema needed - auto-discovered from @repository decorators!
388
348
  });
389
349
  }
390
350
 
391
351
  override configure(): ValueOrPromise<void> {
392
- // Pass the merged schema to Drizzle
393
352
  this.connector = drizzle({
394
353
  client: new Pool(this.settings.connection),
395
- schema: this.schema, // This now contains both tables AND relations
354
+ schema: this.schema, // Auto-discovered schema
396
355
  });
397
356
  }
398
357
 
@@ -407,34 +366,24 @@ export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, I
407
366
  }
408
367
  ```
409
368
 
410
- #### Why This Pattern?
411
-
412
- **Without the relations merged in:**
413
- ```typescript
414
- // ❌ WRONG - Relations won't work!
415
- schema: {
416
- [User.TABLE_NAME]: userTable,
417
- [Configuration.TABLE_NAME]: configurationTable,
418
- }
419
- ```
369
+ **How auto-discovery works:**
420
370
 
421
- **Result:** Your repository's `include` queries will fail. You won't be able to fetch related data.
371
+ When you define repositories with both `model` and `dataSource`:
422
372
 
423
- **With tables and relations merged:**
424
373
  ```typescript
425
- // CORRECT - Relations work perfectly!
426
- schema: Object.assign(
427
- {},
428
- {
429
- [User.TABLE_NAME]: userTable,
430
- [Configuration.TABLE_NAME]: configurationTable,
431
- },
432
- userRelations.relations,
433
- configurationRelations.relations,
434
- )
374
+ @repository({ model: User, dataSource: PostgresDataSource })
375
+ export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {}
376
+
377
+ @repository({ model: Configuration, dataSource: PostgresDataSource })
378
+ export class ConfigurationRepository extends DefaultCRUDRepository<typeof Configuration.schema> {}
435
379
  ```
436
380
 
437
- **Result:** You can now do:
381
+ The framework automatically:
382
+ 1. Registers each model-datasource binding
383
+ 2. Builds the combined schema (tables + relations) when `getSchema()` is called
384
+ 3. Makes all registered models available for relational queries
385
+
386
+ **Result:** You can use `include` queries without any manual schema configuration:
438
387
  ```typescript
439
388
  const config = await configRepo.findOne({
440
389
  filter: {
@@ -445,53 +394,55 @@ const config = await configRepo.findOne({
445
394
  console.log(config.creator.name); // Access related User data
446
395
  ```
447
396
 
448
- #### Adding New Models to Your DataSource
449
-
450
- Every time you create a new model, you need to:
397
+ ### Pattern 2: Manual Schema (Full Control)
451
398
 
452
- 1. Import its table and relations
453
- 2. Add them to the schema object
399
+ If you need explicit control over the schema, you can still provide it manually:
454
400
 
455
- **Example - Adding a `Post` model:**
456
401
  ```typescript
457
402
  import {
458
- Post,
459
- postTable,
460
- postRelations,
461
- // ... other models
403
+ Configuration, configurationTable, configurationRelations,
404
+ User, userTable, userRelations,
462
405
  } from '@/models/entities';
463
406
 
464
- // In your constructor:
465
- schema: Object.assign(
466
- {},
467
- {
468
- [User.TABLE_NAME]: userTable,
469
- [Configuration.TABLE_NAME]: configurationTable,
470
- [Post.TABLE_NAME]: postTable, // Add the table
471
- },
472
- userRelations.relations,
473
- configurationRelations.relations,
474
- postRelations.relations, // Add the relations
475
- ),
407
+ @datasource({ driver: 'node-postgres' })
408
+ export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, IDSConfigs> {
409
+ constructor() {
410
+ super({
411
+ name: PostgresDataSource.name,
412
+ config: { /* ... */ },
413
+ // Manually merge tables and relations using spread syntax
414
+ schema: {
415
+ [User.TABLE_NAME]: userTable,
416
+ [Configuration.TABLE_NAME]: configurationTable,
417
+ ...userRelations.relations,
418
+ ...configurationRelations.relations,
419
+ },
420
+ });
421
+ }
422
+
423
+ override configure(): ValueOrPromise<void> {
424
+ this.connector = drizzle({
425
+ client: new Pool(this.settings.connection),
426
+ schema: this.schema,
427
+ });
428
+ }
429
+ }
476
430
  ```
477
431
 
478
- **Pro tip:** As your app grows, consider using a helper function to reduce boilerplate:
432
+ ### @datasource Decorator
479
433
 
480
434
  ```typescript
481
- function buildSchema(models: Array<{ table: any; relations: any; name: string }>) {
482
- const tables = models.reduce((acc, m) => ({ ...acc, [m.name]: m.table }), {});
483
- const relations = models.map(m => m.relations.relations);
484
- return Object.assign({}, tables, ...relations);
485
- }
486
-
487
- // Usage:
488
- schema: buildSchema([
489
- { name: User.TABLE_NAME, table: userTable, relations: userRelations },
490
- { name: Configuration.TABLE_NAME, table: configurationTable, relations: configurationRelations },
491
- { name: Post.TABLE_NAME, table: postTable, relations: postRelations },
492
- ])
435
+ @datasource({
436
+ driver: 'node-postgres', // Required - database driver
437
+ autoDiscovery?: true // Optional - defaults to true
438
+ })
493
439
  ```
494
440
 
441
+ | Option | Type | Default | Description |
442
+ |--------|------|---------|-------------|
443
+ | `driver` | `TDataSourceDriver` | - | Database driver name |
444
+ | `autoDiscovery` | `boolean` | `true` | Enable/disable schema auto-discovery |
445
+
495
446
  ### Registering a DataSource
496
447
 
497
448
  Finally, register your `DataSource` with the application in `src/application.ts`.
@@ -520,29 +471,58 @@ Repositories abstract the data access logic. They use the configured `DataSource
520
471
 
521
472
  ### Creating a Repository
522
473
 
523
- A repository extends `DefaultCRUDRepository` (for full read/write operations), is decorated with `@repository`, and injects the `DataSource`.
474
+ A repository extends `DefaultCRUDRepository` (for full read/write operations) and is decorated with `@repository`.
475
+
476
+ **IMPORTANT:** Both `model` AND `dataSource` are required in `@repository` for schema auto-discovery. Without both, the model won't be registered and relational queries will fail.
477
+
478
+ #### Pattern 1: Zero Boilerplate (Recommended)
479
+
480
+ The simplest approach - dataSource is auto-injected from metadata:
524
481
 
525
482
  ```typescript
526
483
  // src/repositories/configuration.repository.ts
527
- import {
528
- Configuration,
529
- configurationRelations, // Import configurationRelations
530
- TConfigurationSchema,
531
- } from '@/models/entities';
532
- import { IDataSource, inject, repository, DefaultCRUDRepository } from '@venizia/ignis';
484
+ import { Configuration, TConfigurationSchema } from '@/models/entities';
485
+ import { PostgresDataSource } from '@/datasources';
486
+ import { repository, DefaultCRUDRepository } from '@venizia/ignis';
533
487
 
534
- // Decorator to mark this class as a repository for DI
535
- @repository({})
488
+ @repository({ model: Configuration, dataSource: PostgresDataSource })
536
489
  export class ConfigurationRepository extends DefaultCRUDRepository<TConfigurationSchema> {
490
+ // No constructor needed - datasource auto-injected!
491
+
492
+ // Add custom methods as needed
493
+ async findByCode(code: string) {
494
+ return this.findOne({ filter: { where: { code } } });
495
+ }
496
+ }
497
+ ```
498
+
499
+ #### Pattern 2: Explicit @inject
500
+
501
+ When you need constructor control (e.g., for read-only repositories or custom initialization):
502
+
503
+ ```typescript
504
+ // src/repositories/user.repository.ts
505
+ import { User } from '@/models/entities';
506
+ import { PostgresDataSource } from '@/datasources';
507
+ import { inject, repository, ReadableRepository } from '@venizia/ignis';
508
+
509
+ @repository({ model: User, dataSource: PostgresDataSource })
510
+ export class UserRepository extends ReadableRepository<typeof User.schema> {
537
511
  constructor(
538
- // Inject the configured datasource
539
- @inject({ key: 'datasources.PostgresDataSource' }) dataSource: IDataSource,
512
+ @inject({ key: 'datasources.PostgresDataSource' })
513
+ dataSource: PostgresDataSource, // Must be concrete DataSource type, NOT 'any'
540
514
  ) {
541
- // Pass the datasource, the model's Entity class, AND the relations definitions to the super constructor
542
- super({ dataSource, entityClass: Configuration, relations: configurationRelations.definitions });
515
+ super(dataSource);
516
+ }
517
+
518
+ async findByRealm(realm: string) {
519
+ return this.findOne({ filter: { where: { realm } } });
543
520
  }
544
521
  }
545
522
  ```
523
+
524
+ **Note:** When `@inject` is at param index 0, auto-injection is skipped (your `@inject` takes precedence).
525
+
546
526
  You would then register this repository in your `application.ts`: `this.repository(ConfigurationRepository);`
547
527
 
548
528
  ### Querying Data
@@ -265,7 +265,7 @@ export class HelloController extends BaseController {
265
265
  })
266
266
  sayHello(c: Context) {
267
267
  // This looks just like Hono! Because it IS Hono under the hood.
268
- return c.json({ message: 'Hello, World!' });
268
+ return c.json({ message: 'Hello, World!' }, HTTP.ResultCodes.RS_2.Ok);
269
269
  }
270
270
 
271
271
  // You can add more routes here: