@venizia/ignis-docs 0.0.1-7 → 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 +12 -12
  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
@@ -0,0 +1,278 @@
1
+ # v0.0.1-8 - Model-Repository-DataSource Architecture Refactor
2
+
3
+ **Release Date**: 2025-12-16
4
+ **Status**: Current
5
+
6
+ ## Overview
7
+
8
+ Major refactor following Loopback 4's architecture pattern where:
9
+ - **Model** is self-contained with schema and relations
10
+ - **Repository** connects Model to DataSource (defines the binding)
11
+ - **DataSource** auto-discovers schemas from registered repositories
12
+
13
+ This eliminates manual schema registration and simplifies the development workflow significantly.
14
+
15
+ ## Breaking Changes
16
+
17
+ 1. **Model static properties now require `override` keyword**
18
+ - `static override schema = pgTable(...)`
19
+ - `static override relations = () => ({...})`
20
+
21
+ 2. **Repository constructor is now optional**
22
+ - Old: Required `entityClass`, `relations`, `dataSource` parameters
23
+ - New: Auto-resolved from `@repository` decorator metadata
24
+
25
+ 3. **DataSource schema is now optional**
26
+ - Old: Required manual `schema` property with all models
27
+ - New: Auto-discovered from `@repository` bindings
28
+
29
+ ## New Features
30
+
31
+ ### Self-Contained Models
32
+
33
+ Models now define schema and relations as static properties:
34
+
35
+ ```typescript
36
+ @model({ type: 'entity' })
37
+ export class Configuration extends BaseEntity<typeof Configuration.schema> {
38
+ static override schema = pgTable('Configuration', {
39
+ ...generateIdColumnDefs({ id: { dataType: 'string' } }),
40
+ ...generateTzColumnDefs(),
41
+ code: text('code').notNull(),
42
+ group: text('group').notNull(),
43
+ });
44
+
45
+ static override relations = () => ({
46
+ creator: {
47
+ type: 'one' as const,
48
+ target: () => User,
49
+ fields: [Configuration.schema.createdBy],
50
+ references: () => [User.schema.id],
51
+ },
52
+ });
53
+ }
54
+ ```
55
+
56
+ ### Repository Auto-Resolution
57
+
58
+ Repositories now use `@repository` decorator for model-datasource binding:
59
+
60
+ ```typescript
61
+ @repository({
62
+ model: Configuration,
63
+ datasource: PostgresDataSource,
64
+ })
65
+ export class ConfigurationRepository extends DefaultCRUDRepository<typeof Configuration.schema> {
66
+ // No constructor needed!
67
+
68
+ async findByCode(code: string) {
69
+ return this.findOne({ filter: { where: { code } } });
70
+ }
71
+ }
72
+ ```
73
+
74
+ ### DataSource Auto-Discovery
75
+
76
+ DataSources automatically discover their schema from repository bindings:
77
+
78
+ ```typescript
79
+ @datasource({ driver: 'node-postgres' })
80
+ export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, IDSConfigs> {
81
+ constructor() {
82
+ super({
83
+ name: PostgresDataSource.name,
84
+ driver: 'node-postgres',
85
+ config: { /* connection config */ },
86
+ // NO schema property - auto-discovered!
87
+ });
88
+ }
89
+
90
+ override configure(): ValueOrPromise<void> {
91
+ const schema = this.getSchema(); // Auto-discovers from @repository bindings
92
+ this.connector = drizzle({ client: new Pool(this.settings), schema });
93
+ }
94
+ }
95
+ ```
96
+
97
+ ## Files Changed
98
+
99
+ ### Core Package (`packages/core`)
100
+
101
+ | File | Changes |
102
+ |------|---------|
103
+ | `src/base/models/base.ts` | Added static `schema`, `relations`, `TABLE_NAME` |
104
+ | `src/base/datasources/base.ts` | Added auto-discovery via `buildAutoDiscoveredSchema()` |
105
+ | `src/base/repositories/core/base.ts` | Added lazy resolution, static container reference |
106
+ | `src/base/repositories/core/readable.ts` | Made constructor opts optional |
107
+ | `src/base/repositories/core/persistable.ts` | Made constructor opts optional |
108
+ | `src/base/repositories/core/default-crud.ts` | Added documentation |
109
+ | `src/base/metadata/persistents.ts` | Updated decorators for auto-discovery |
110
+ | `src/base/applications/base.ts` | Added `AbstractRepository.setContainer(this)` |
111
+ | `src/components/static-asset/models/base.model.ts` | Updated to new pattern |
112
+
113
+ ### Helpers Package (`packages/helpers`)
114
+
115
+ | File | Changes |
116
+ |------|---------|
117
+ | `src/helpers/inversion/common/types.ts` | Added `IModelMetadata`, `IRelationDefinition`, `IModelStatic`, `IRepositoryMetadata`, `IRepositoryBinding` |
118
+ | `src/helpers/inversion/registry.ts` | Added `registerModel`, `registerRepositoryBinding`, `buildDataSourceSchema` |
119
+
120
+ ### Examples (`examples/vert`)
121
+
122
+ | File | Changes |
123
+ |------|---------|
124
+ | `src/models/entities/user.model.ts` | Updated to static schema pattern |
125
+ | `src/models/entities/configuration.model.ts` | Updated to static schema pattern |
126
+ | `src/datasources/postgres.datasource.ts` | Removed manual schema registration |
127
+ | `src/repositories/user.repository.ts` | Updated to use `@repository` decorator |
128
+ | `src/repositories/configuration.repository.ts` | Updated to use `@repository` decorator |
129
+
130
+ ## Migration Guide
131
+
132
+ ### Step 1: Update Models
133
+
134
+ **Before:**
135
+ ```typescript
136
+ const userTable = pgTable('User', {...});
137
+ const userRelations = createRelations({...});
138
+
139
+ @model({ type: 'entity' })
140
+ export class User extends BaseEntity<typeof userTable> {
141
+ constructor() {
142
+ super({ name: 'User', schema: userTable });
143
+ }
144
+ }
145
+ ```
146
+
147
+ **After:**
148
+ ```typescript
149
+ @model({ type: 'entity' })
150
+ export class User extends BaseEntity<typeof User.schema> {
151
+ static override schema = pgTable('User', {...});
152
+ static override relations = () => ({...});
153
+ }
154
+ ```
155
+
156
+ ### Step 2: Update Repositories
157
+
158
+ **Before:**
159
+ ```typescript
160
+ @repository({})
161
+ export class UserRepository extends DefaultCRUDRepository<typeof userTable> {
162
+ constructor(@inject({ key: 'datasources.PostgresDataSource' }) dataSource: IDataSource) {
163
+ super({ dataSource, entityClass: User, relations: userRelations.definitions });
164
+ }
165
+ }
166
+ ```
167
+
168
+ **After:**
169
+ ```typescript
170
+ @repository({ model: User, datasource: PostgresDataSource })
171
+ export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {
172
+ // No constructor needed!
173
+ }
174
+ ```
175
+
176
+ ### Step 3: Update DataSources
177
+
178
+ **Before:**
179
+ ```typescript
180
+ @datasource({})
181
+ export class PostgresDataSource extends BaseDataSource {
182
+ constructor() {
183
+ super({
184
+ name: PostgresDataSource.name,
185
+ driver: 'node-postgres',
186
+ config: {...},
187
+ schema: { User: userTable, userRelations: userRelations.relations, ... },
188
+ });
189
+ }
190
+ }
191
+ ```
192
+
193
+ **After:**
194
+ ```typescript
195
+ @datasource({ driver: 'node-postgres' })
196
+ export class PostgresDataSource extends BaseDataSource {
197
+ constructor() {
198
+ super({
199
+ name: PostgresDataSource.name,
200
+ driver: 'node-postgres',
201
+ config: {...},
202
+ // NO schema - auto-discovered!
203
+ });
204
+ }
205
+ }
206
+ ```
207
+
208
+ ## Architecture Diagram
209
+
210
+ ```
211
+ ┌─────────────────────────────────────────────────────────────────┐
212
+ │ Application │
213
+ │ ┌───────────────────────────────────────────────────────────┐ │
214
+ │ │ preConfigure() │ │
215
+ │ │ this.dataSource(PostgresDataSource) │ │
216
+ │ │ this.repository(UserRepository) ──┐ │ │
217
+ │ │ this.repository(ConfigurationRepository) │ Registers │ │
218
+ │ │ │ bindings │ │
219
+ │ └─────────────────────────────────────────────┼─────────────┘ │
220
+ │ │ │
221
+ │ ┌─────────────────────────────────────────────▼─────────────┐ │
222
+ │ │ MetadataRegistry │ │
223
+ │ │ modelRegistry: Map<tableName, {schema, relations}> │ │
224
+ │ │ repositoryBindings: Map<repoClass, {model, datasource}> │ │
225
+ │ │ datasourceModels: Map<datasource, Set<modelNames>> │ │
226
+ │ └─────────────────────────────────────────────┬─────────────┘ │
227
+ │ │ │
228
+ │ ┌─────────────────────────────────────────────▼─────────────┐ │
229
+ │ │ DataSource.configure() │ │
230
+ │ │ schema = this.getSchema() ◄── Auto-discovers from │ │
231
+ │ │ MetadataRegistry │ │
232
+ │ │ this.connector = drizzle({ schema }) │ │
233
+ │ └───────────────────────────────────────────────────────────┘ │
234
+ └─────────────────────────────────────────────────────────────────┘
235
+ ```
236
+
237
+ ## Benefits
238
+
239
+ 1. **Simplified Model Definition**: Single class with static properties
240
+ 2. **No Manual Schema Registration**: DataSource auto-discovers models
241
+ 3. **Clear Repository Role**: Explicitly binds model to datasource
242
+ 4. **Better Type Safety**: Types flow from `static schema` through the system
243
+ 5. **Reduced Boilerplate**: No constructor needed for basic repositories
244
+ 6. **Follows Loopback 4 Pattern**: Familiar architecture for developers
245
+
246
+ ## Technical Details
247
+
248
+ ### IRelationDefinition Interface
249
+
250
+ ```typescript
251
+ interface IRelationDefinition<TTarget = any> {
252
+ type: 'one' | 'many';
253
+ target: () => TClass<TTarget>; // Lazy to avoid circular imports
254
+ fields?: AnyColumn[];
255
+ references?: () => AnyColumn[]; // Lazy to avoid circular imports
256
+ relationName?: string;
257
+ }
258
+ ```
259
+
260
+ ### Repository Auto-Resolution Flow
261
+
262
+ 1. `@repository` decorator registers binding in `MetadataRegistry`
263
+ 2. When repository is instantiated, constructor checks for explicit opts
264
+ 3. If not provided, lazy getters resolve from `MetadataRegistry`:
265
+ - `entity` → resolved from `modelClass` in binding
266
+ - `relations` → built from model's `static relations()`
267
+ - `dataSource` → resolved from container using binding key
268
+
269
+ ### DataSource Auto-Discovery Flow
270
+
271
+ 1. `@repository` decorators register model-datasource bindings
272
+ 2. When `dataSource.configure()` is called, `getSchema()` is invoked
273
+ 3. `buildAutoDiscoveredSchema()` queries `MetadataRegistry` for all models bound to this datasource
274
+ 4. Schema is assembled from model schemas and relations
275
+
276
+ ---
277
+
278
+ *This refactor was inspired by Loopback 4's architecture pattern*
@@ -253,7 +253,7 @@ Open `http://localhost:3000/doc/explorer` to see interactive Swagger UI document
253
253
  })
254
254
  async greet(c: Context) {
255
255
  const { name } = await c.req.json();
256
- return c.json({ greeting: `Hello, ${name}!` });
256
+ return c.json({ greeting: `Hello, ${name}!` }, HTTP.ResultCodes.RS_2.Ok);
257
257
  }
258
258
  ```
259
259
 
@@ -52,6 +52,7 @@ import {
52
52
  get,
53
53
  post,
54
54
  TRouteContext,
55
+ HTTP,
55
56
  } from '@venizia/ignis';
56
57
  import { ROUTE_CONFIGS } from './definitions';
57
58
 
@@ -62,7 +63,7 @@ export class TestController extends BaseController {
62
63
  @get({ configs: ROUTE_CONFIGS['/4'] })
63
64
  getWithDecorator(context: TRouteContext<(typeof ROUTE_CONFIGS)['/4']>) {
64
65
  // context is fully typed!
65
- return context.json({ message: 'Hello from decorator', method: 'GET' });
66
+ return context.json({ message: 'Hello from decorator', method: 'GET' }, HTTP.ResultCodes.RS_2.Ok);
66
67
  }
67
68
 
68
69
  @post({ configs: ROUTE_CONFIGS['/5'] })
@@ -71,11 +72,14 @@ export class TestController extends BaseController {
71
72
  const body = context.req.valid('json');
72
73
 
73
74
  // The response is validated against the schema
74
- return context.json({
75
- id: crypto.randomUUID(),
76
- name: body.name,
77
- age: body.age,
78
- });
75
+ return context.json(
76
+ {
77
+ id: crypto.randomUUID(),
78
+ name: body.name,
79
+ age: body.age,
80
+ },
81
+ HTTP.ResultCodes.RS_2.Ok,
82
+ );
79
83
  }
80
84
  }
81
85
  ```
@@ -97,7 +101,7 @@ export class TestController extends BaseController {
97
101
  this.defineRoute({
98
102
  configs: ROUTE_CONFIGS['/1'],
99
103
  handler: context => {
100
- return context.json({ message: 'Hello' });
104
+ return context.json({ message: 'Hello' }, HTTP.ResultCodes.RS_2.Ok);
101
105
  },
102
106
  });
103
107
 
@@ -106,7 +110,7 @@ export class TestController extends BaseController {
106
110
  configs: ROUTE_CONFIGS['/3'],
107
111
  }).to({
108
112
  handler: context => {
109
- return context.json({ message: 'Hello 3' });
113
+ return context.json({ message: 'Hello 3' }, HTTP.ResultCodes.RS_2.Ok);
110
114
  },
111
115
  });
112
116
  }
@@ -79,7 +79,7 @@ export class Application extends BaseApplication {
79
79
  const company = await this.companyRepository.findOrCreate(companyName);
80
80
  const user = await this.userRepository.create({ name, email, companyId: company.id });
81
81
 
82
- return c.json(user);
82
+ return c.json(user, HTTP.ResultCodes.RS_2.Ok);
83
83
  }
84
84
  ```
85
85
  - **Good:**
@@ -89,7 +89,7 @@ export class Application extends BaseApplication {
89
89
  const userData = c.req.valid('json');
90
90
  // Delegate to the service
91
91
  const newUser = await this.userService.createUser(userData);
92
- return c.json(newUser);
92
+ return c.json(newUser, HTTP.ResultCodes.RS_2.Ok);
93
93
  }
94
94
 
95
95
  // In UserService
@@ -14,22 +14,18 @@ import {
14
14
  model,
15
15
  TTableObject,
16
16
  } from '@venizia/ignis';
17
- import { configurationTable } from './schema'; // Your Drizzle schema
17
+ import { configurationTable, configurationRelations } from './schema'; // Your Drizzle schema
18
18
 
19
19
  // Define types for TypeScript inference
20
20
  export type TConfigurationSchema = typeof configurationTable;
21
21
  export type TConfiguration = TTableObject<TConfigurationSchema>;
22
22
 
23
23
  @model({ type: 'entity', skipMigrate: false })
24
- export class Configuration extends BaseEntity<TConfigurationSchema> {
25
- static readonly TABLE_NAME = Configuration.name;
26
-
27
- constructor() {
28
- super({
29
- name: Configuration.TABLE_NAME,
30
- schema: configurationTable,
31
- });
32
- }
24
+ export class Configuration extends BaseEntity<typeof Configuration.schema> {
25
+ // Use static properties (recommended pattern)
26
+ static override schema = configurationTable;
27
+ static override relations = () => configurationRelations.definitions;
28
+ static override TABLE_NAME = 'Configuration';
33
29
  }
34
30
  ```
35
31
 
@@ -110,17 +106,15 @@ export const configurationRelations = createRelations({
110
106
  });
111
107
  ```
112
108
 
113
- This configuration is then passed directly to your Repository:
109
+ This configuration is automatically used when you define your Repository with the `@repository` decorator:
114
110
 
115
111
  ```typescript
116
- @repository({})
117
- export class ConfigurationRepository extends DefaultCRUDRepository<TConfigurationSchema> {
118
- constructor(@inject({ key: 'datasources.PostgresDataSource' }) dataSource: IDataSource) {
119
- super({
120
- dataSource,
121
- entityClass: Configuration,
122
- relations: configurationRelations.definitions, // <-- Injected here
123
- });
124
- }
112
+ import { PostgresDataSource } from '@/datasources';
113
+
114
+ // Both 'model' and 'dataSource' are required for schema auto-discovery
115
+ @repository({ model: Configuration, dataSource: PostgresDataSource })
116
+ export class ConfigurationRepository extends DefaultCRUDRepository<typeof Configuration.schema> {
117
+ // No constructor needed! DataSource and relations are auto-resolved
118
+ // from the @repository decorator and entity's static properties
125
119
  }
126
120
  ```
@@ -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`