@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.
- package/package.json +12 -12
- package/wiki/changelogs/2025-12-17-refactor.md +22 -0
- package/wiki/changelogs/2025-12-18-performance-optimizations.md +192 -0
- package/wiki/changelogs/2025-12-18-repository-validation-security.md +445 -0
- package/wiki/changelogs/index.md +22 -0
- package/wiki/changelogs/v0.0.1-7-initial-architecture.md +137 -0
- package/wiki/changelogs/v0.0.1-8-model-repo-datasource-refactor.md +278 -0
- package/wiki/get-started/5-minute-quickstart.md +1 -1
- package/wiki/get-started/best-practices/api-usage-examples.md +12 -8
- package/wiki/get-started/best-practices/common-pitfalls.md +2 -2
- package/wiki/get-started/best-practices/data-modeling.md +14 -20
- package/wiki/get-started/building-a-crud-api.md +60 -75
- package/wiki/get-started/core-concepts/controllers.md +14 -14
- package/wiki/get-started/core-concepts/persistent.md +110 -130
- package/wiki/get-started/quickstart.md +1 -1
- package/wiki/references/base/controllers.md +40 -16
- package/wiki/references/base/datasources.md +195 -33
- package/wiki/references/base/dependency-injection.md +5 -5
- package/wiki/references/base/models.md +398 -28
- package/wiki/references/base/repositories.md +475 -22
- package/wiki/references/components/authentication.md +224 -7
- package/wiki/references/components/health-check.md +1 -1
- package/wiki/references/components/swagger.md +1 -1
- package/wiki/references/helpers/inversion.md +8 -3
- package/wiki/references/src-details/core.md +6 -5
- package/wiki/references/src-details/inversion.md +4 -4
- 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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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<
|
|
25
|
-
static
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
109
|
+
This configuration is automatically used when you define your Repository with the `@repository` decorator:
|
|
114
110
|
|
|
115
111
|
```typescript
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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<
|
|
138
|
-
static
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
database?: string;
|
|
223
|
-
};
|
|
214
|
+
host: string;
|
|
215
|
+
port: number;
|
|
216
|
+
database: string;
|
|
217
|
+
user: string;
|
|
218
|
+
password: string;
|
|
224
219
|
}
|
|
225
220
|
|
|
226
|
-
|
|
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
|
-
|
|
234
|
+
// Driver is read from @datasource decorator - no need to pass here!
|
|
232
235
|
config: {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
-
|
|
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
|
|
284
|
-
import {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
- [ ]
|
|
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`
|