@venizia/ignis-docs 0.0.1-8 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +1 -0
- package/package.json +2 -2
- package/wiki/changelogs/2025-12-16-initial-architecture.md +145 -0
- package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +300 -0
- package/wiki/changelogs/2025-12-17-refactor.md +90 -0
- package/wiki/changelogs/2025-12-18-performance-optimizations.md +130 -0
- package/wiki/changelogs/2025-12-18-repository-validation-security.md +249 -0
- package/wiki/changelogs/index.md +33 -0
- package/wiki/changelogs/planned-transaction-support.md +216 -0
- package/wiki/changelogs/template.md +123 -0
- package/wiki/get-started/5-minute-quickstart.md +1 -1
- package/wiki/get-started/best-practices/api-usage-examples.md +12 -10
- package/wiki/get-started/best-practices/architectural-patterns.md +2 -2
- package/wiki/get-started/best-practices/common-pitfalls.md +7 -5
- package/wiki/get-started/best-practices/contribution-workflow.md +2 -0
- package/wiki/get-started/best-practices/data-modeling.md +91 -40
- package/wiki/get-started/best-practices/security-guidelines.md +3 -1
- package/wiki/get-started/building-a-crud-api.md +63 -78
- package/wiki/get-started/core-concepts/application.md +72 -3
- package/wiki/get-started/core-concepts/bootstrapping.md +566 -0
- package/wiki/get-started/core-concepts/components.md +4 -2
- package/wiki/get-started/core-concepts/controllers.md +14 -14
- package/wiki/get-started/core-concepts/persistent.md +383 -431
- package/wiki/get-started/core-concepts/services.md +21 -27
- package/wiki/get-started/quickstart.md +1 -1
- package/wiki/references/base/bootstrapping.md +789 -0
- package/wiki/references/base/components.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 +98 -5
- package/wiki/references/base/models.md +398 -28
- package/wiki/references/base/repositories.md +475 -22
- package/wiki/references/base/services.md +2 -2
- package/wiki/references/components/authentication.md +228 -10
- package/wiki/references/components/health-check.md +1 -1
- package/wiki/references/components/index.md +1 -1
- package/wiki/references/components/swagger.md +1 -1
- package/wiki/references/helpers/error.md +2 -2
- package/wiki/references/helpers/inversion.md +8 -3
- package/wiki/references/src-details/boot.md +379 -0
- package/wiki/references/src-details/core.md +8 -7
- package/wiki/references/src-details/inversion.md +4 -4
- package/wiki/references/utilities/request.md +16 -7
|
@@ -1,342 +1,175 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Persistent Layer
|
|
3
|
+
description: Models, DataSources, and Repositories in Ignis
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# Persistent Layer: Models, DataSources, and Repositories
|
|
2
7
|
|
|
3
8
|
The persistent layer manages data using [Drizzle ORM](https://orm.drizzle.team/) for type-safe database access and the Repository pattern for data abstraction.
|
|
4
9
|
|
|
5
10
|
**Three main components:**
|
|
6
|
-
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
11
|
+
|
|
12
|
+
- **Models** - Define data structure (static schema + relations on Entity class)
|
|
13
|
+
- **DataSources** - Manage database connections with auto-discovery
|
|
14
|
+
- **Repositories** - Provide CRUD operations with zero boilerplate
|
|
9
15
|
|
|
10
16
|
## 1. Models: Defining Your Data Structure
|
|
11
17
|
|
|
12
|
-
A model in
|
|
18
|
+
A model in Ignis is a single class with static properties for schema and relations. No separate variables needed.
|
|
13
19
|
|
|
14
20
|
### Creating a Basic Model
|
|
15
21
|
|
|
16
|
-
Here's how to create a simple `User` model.
|
|
17
|
-
|
|
18
22
|
```typescript
|
|
19
23
|
// src/models/entities/user.model.ts
|
|
20
|
-
import {
|
|
21
|
-
BaseEntity,
|
|
22
|
-
createRelations,
|
|
23
|
-
extraUserColumns,
|
|
24
|
-
generateIdColumnDefs,
|
|
25
|
-
model,
|
|
26
|
-
TTableObject,
|
|
27
|
-
} from '@venizia/ignis';
|
|
24
|
+
import { BaseEntity, extraUserColumns, generateIdColumnDefs, model } from '@venizia/ignis';
|
|
28
25
|
import { pgTable } from 'drizzle-orm/pg-core';
|
|
29
26
|
|
|
30
|
-
// 1. Define the Drizzle schema for the 'User' table
|
|
31
|
-
export const userTable = pgTable(User.name, {
|
|
32
|
-
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
33
|
-
...extraUserColumns({ idType: 'string' }),
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// 2. Define relations (empty for now, but required)
|
|
37
|
-
export const userRelations = createRelations({
|
|
38
|
-
source: userTable,
|
|
39
|
-
relations: [],
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// 3. Define the TypeScript type for a User object
|
|
43
|
-
export type TUserSchema = typeof userTable;
|
|
44
|
-
export type TUser = TTableObject<TUserSchema>;
|
|
45
|
-
|
|
46
|
-
// 4. Create the Entity class, decorated with @model
|
|
47
27
|
@model({ type: 'entity' })
|
|
48
|
-
export class User extends BaseEntity<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### Understanding Enrichers: The Smart Column Generators
|
|
58
|
-
|
|
59
|
-
You might have noticed functions like `generateIdColumnDefs()` and `extraUserColumns()` in the model definition. These are **Enrichers**—powerful helper functions that generate common database columns automatically.
|
|
60
|
-
|
|
61
|
-
#### Why Enrichers Exist
|
|
62
|
-
|
|
63
|
-
**Without enrichers (the hard way):**
|
|
64
|
-
```typescript
|
|
65
|
-
export const userTable = pgTable('User', {
|
|
66
|
-
// Manually define every common column in every table
|
|
67
|
-
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
68
|
-
status: text('status').notNull().default('ACTIVE'),
|
|
69
|
-
type: text('type'),
|
|
70
|
-
createdBy: text('created_by'),
|
|
71
|
-
modifiedBy: text('modified_by'),
|
|
72
|
-
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
73
|
-
modifiedAt: timestamp('modified_at', { withTimezone: true }).notNull().defaultNow(),
|
|
74
|
-
// ... your actual user-specific fields
|
|
75
|
-
email: text('email').notNull(),
|
|
76
|
-
name: text('name'),
|
|
77
|
-
});
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
**With enrichers (the smart way):**
|
|
81
|
-
```typescript
|
|
82
|
-
export const userTable = pgTable('User', {
|
|
83
|
-
...generateIdColumnDefs({ id: { dataType: 'string' } }), // Adds: id (UUID)
|
|
84
|
-
...extraUserColumns({ idType: 'string' }), // Adds: status, type, createdBy, modifiedBy, createdAt, modifiedAt
|
|
85
|
-
// Just your actual user-specific fields
|
|
86
|
-
email: text('email').notNull(),
|
|
87
|
-
name: text('name'),
|
|
88
|
-
});
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
**Result:** Same table structure, but with:
|
|
92
|
-
- 7 fewer lines of code
|
|
93
|
-
- Guaranteed consistency across all tables
|
|
94
|
-
- Less chance of typos or mistakes
|
|
95
|
-
- Easier to maintain
|
|
96
|
-
|
|
97
|
-
#### Common Enrichers
|
|
98
|
-
|
|
99
|
-
| Enricher | What It Adds | Use Case |
|
|
100
|
-
| :--- | :--- | :--- |
|
|
101
|
-
| `generateIdColumnDefs()` | Primary key `id` column (UUID or number) | Every table needs an ID |
|
|
102
|
-
| `generateTzColumnDefs()` | `createdAt` and `modifiedAt` timestamps | Track when records are created/updated |
|
|
103
|
-
| `generateUserAuditColumnDefs()` | `createdBy` and `modifiedBy` foreign keys | Track which user created/updated records |
|
|
104
|
-
| `generateDataTypeColumnDefs()` | `dataType` and type-specific value columns (`tValue`, `nValue`, etc.) | Configuration tables with mixed data types |
|
|
105
|
-
| `extraUserColumns()` | Combination of audit + status + type fields | Full-featured entity tables |
|
|
106
|
-
|
|
107
|
-
#### Practical Example: Building a Post Model
|
|
108
|
-
|
|
109
|
-
Let's create a blog post model using enrichers:
|
|
110
|
-
|
|
111
|
-
```typescript
|
|
112
|
-
// src/models/post.model.ts
|
|
113
|
-
import {
|
|
114
|
-
BaseEntity,
|
|
115
|
-
createRelations,
|
|
116
|
-
generateIdColumnDefs,
|
|
117
|
-
generateTzColumnDefs,
|
|
118
|
-
generateUserAuditColumnDefs,
|
|
119
|
-
model,
|
|
120
|
-
RelationTypes,
|
|
121
|
-
TTableObject,
|
|
122
|
-
} from '@venizia/ignis';
|
|
123
|
-
import { pgTable, text, boolean } from 'drizzle-orm/pg-core';
|
|
124
|
-
import { userTable } from './user.model';
|
|
125
|
-
|
|
126
|
-
export const postTable = pgTable('Post', {
|
|
127
|
-
// Use enrichers for common columns
|
|
128
|
-
...generateIdColumnDefs({ id: { dataType: 'string' } }), // id: UUID primary key
|
|
129
|
-
...generateTzColumnDefs(), // createdAt, modifiedAt
|
|
130
|
-
...generateUserAuditColumnDefs({ // createdBy, modifiedBy
|
|
131
|
-
created: { dataType: 'string', columnName: 'created_by' },
|
|
132
|
-
modified: { dataType: 'string', columnName: 'modified_by' },
|
|
133
|
-
}),
|
|
134
|
-
|
|
135
|
-
// Your post-specific fields
|
|
136
|
-
title: text('title').notNull(),
|
|
137
|
-
content: text('content').notNull(),
|
|
138
|
-
isPublished: boolean('is_published').default(false),
|
|
139
|
-
slug: text('slug').notNull().unique(),
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
export const postRelations = createRelations({
|
|
143
|
-
source: postTable,
|
|
144
|
-
relations: [
|
|
145
|
-
{
|
|
146
|
-
name: 'author',
|
|
147
|
-
type: RelationTypes.ONE,
|
|
148
|
-
schema: userTable,
|
|
149
|
-
metadata: {
|
|
150
|
-
fields: [postTable.createdBy],
|
|
151
|
-
references: [userTable.id],
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
],
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
export type TPostSchema = typeof postTable;
|
|
158
|
-
export type TPost = TTableObject<TPostSchema>;
|
|
159
|
-
|
|
160
|
-
@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
|
-
}
|
|
167
|
-
}
|
|
168
|
-
```
|
|
28
|
+
export class User extends BaseEntity<typeof User.schema> {
|
|
29
|
+
// Define schema as static property
|
|
30
|
+
static override schema = pgTable('User', {
|
|
31
|
+
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
32
|
+
...extraUserColumns({ idType: 'string' }),
|
|
33
|
+
});
|
|
169
34
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
interface Post {
|
|
173
|
-
id: string; // From generateIdColumnDefs
|
|
174
|
-
createdAt: Date; // From generateTzColumnDefs
|
|
175
|
-
modifiedAt: Date; // From generateTzColumnDefs
|
|
176
|
-
createdBy: string; // From generateUserAuditColumnDefs
|
|
177
|
-
modifiedBy: string; // From generateUserAuditColumnDefs
|
|
178
|
-
title: string; // Your field
|
|
179
|
-
content: string; // Your field
|
|
180
|
-
isPublished: boolean; // Your field
|
|
181
|
-
slug: string; // Your field
|
|
35
|
+
// Relations (empty array if none)
|
|
36
|
+
static override relations = () => [];
|
|
182
37
|
}
|
|
183
38
|
```
|
|
184
39
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
You can always define columns manually if:
|
|
188
|
-
- You need a custom ID strategy (e.g., integer auto-increment)
|
|
189
|
-
- You don't need audit fields for a specific table
|
|
190
|
-
- You have very specific timestamp requirements
|
|
191
|
-
|
|
192
|
-
```typescript
|
|
193
|
-
// Mixing enrichers with manual columns is perfectly fine
|
|
194
|
-
export const simpleTable = pgTable('Simple', {
|
|
195
|
-
...generateIdColumnDefs({ id: { dataType: 'number' } }), // Use enricher for ID
|
|
196
|
-
// But manually define everything else
|
|
197
|
-
name: text('name').notNull(),
|
|
198
|
-
value: integer('value'),
|
|
199
|
-
});
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
:::tip
|
|
203
|
-
For a complete list of available enrichers and their options, see the [**Schema Enrichers Reference**](../../references/base/models.md#schema-enrichers).
|
|
204
|
-
:::
|
|
40
|
+
**Key points:**
|
|
205
41
|
|
|
206
|
-
|
|
207
|
-
-
|
|
208
|
-
-
|
|
209
|
-
-
|
|
210
|
-
- **`BaseEntity`**: The class your model extends. It wraps the Drizzle schema and provides utilities for the framework.
|
|
211
|
-
- **`@model`**: A decorator that registers the class with the framework as a database model.
|
|
42
|
+
- Schema is defined inline as `static override schema`
|
|
43
|
+
- Relations are defined as `static override relations`
|
|
44
|
+
- No constructor needed - BaseEntity auto-discovers from static properties
|
|
45
|
+
- Type parameter uses `typeof User.schema` (self-referencing)
|
|
212
46
|
|
|
213
47
|
### Creating a Model with Relations
|
|
214
48
|
|
|
215
|
-
Now, let's create a `Configuration` model that has a relationship with the `User` model.
|
|
216
|
-
|
|
217
49
|
```typescript
|
|
218
50
|
// src/models/entities/configuration.model.ts
|
|
219
51
|
import {
|
|
220
52
|
BaseEntity,
|
|
221
|
-
createRelations, // Import createRelations
|
|
222
53
|
generateDataTypeColumnDefs,
|
|
223
54
|
generateIdColumnDefs,
|
|
224
55
|
generateTzColumnDefs,
|
|
225
56
|
generateUserAuditColumnDefs,
|
|
226
57
|
model,
|
|
227
58
|
RelationTypes,
|
|
228
|
-
|
|
59
|
+
TRelationConfig,
|
|
229
60
|
} from '@venizia/ignis';
|
|
230
61
|
import { foreignKey, index, pgTable, text, unique } from 'drizzle-orm/pg-core';
|
|
231
|
-
import { User
|
|
62
|
+
import { User } from './user.model';
|
|
232
63
|
|
|
233
|
-
|
|
234
|
-
export
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
relations: [
|
|
64
|
+
@model({ type: 'entity' })
|
|
65
|
+
export class Configuration extends BaseEntity<typeof Configuration.schema> {
|
|
66
|
+
static override schema = pgTable(
|
|
67
|
+
'Configuration',
|
|
68
|
+
{
|
|
69
|
+
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
70
|
+
...generateTzColumnDefs(),
|
|
71
|
+
...generateDataTypeColumnDefs(),
|
|
72
|
+
...generateUserAuditColumnDefs({
|
|
73
|
+
created: { dataType: 'string', columnName: 'created_by' },
|
|
74
|
+
modified: { dataType: 'string', columnName: 'modified_by' },
|
|
75
|
+
}),
|
|
76
|
+
code: text('code').notNull(),
|
|
77
|
+
description: text('description'),
|
|
78
|
+
group: text('group').notNull(),
|
|
79
|
+
},
|
|
80
|
+
def => [
|
|
81
|
+
unique('UQ_Configuration_code').on(def.code),
|
|
82
|
+
index('IDX_Configuration_group').on(def.group),
|
|
83
|
+
foreignKey({
|
|
84
|
+
columns: [def.createdBy],
|
|
85
|
+
foreignColumns: [User.schema.id], // Reference User.schema, not a separate variable
|
|
86
|
+
name: 'FK_Configuration_createdBy_User_id',
|
|
87
|
+
}),
|
|
88
|
+
],
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Define relations using TRelationConfig array
|
|
92
|
+
static override relations = (): TRelationConfig[] => [
|
|
263
93
|
{
|
|
264
94
|
name: 'creator',
|
|
265
95
|
type: RelationTypes.ONE,
|
|
266
|
-
schema:
|
|
96
|
+
schema: User.schema,
|
|
267
97
|
metadata: {
|
|
268
|
-
fields: [
|
|
269
|
-
references: [
|
|
98
|
+
fields: [Configuration.schema.createdBy],
|
|
99
|
+
references: [User.schema.id],
|
|
270
100
|
},
|
|
271
101
|
},
|
|
272
102
|
{
|
|
273
103
|
name: 'modifier',
|
|
274
104
|
type: RelationTypes.ONE,
|
|
275
|
-
schema:
|
|
105
|
+
schema: User.schema,
|
|
276
106
|
metadata: {
|
|
277
|
-
fields: [
|
|
278
|
-
references: [
|
|
107
|
+
fields: [Configuration.schema.modifiedBy],
|
|
108
|
+
references: [User.schema.id],
|
|
279
109
|
},
|
|
280
110
|
},
|
|
281
|
-
]
|
|
282
|
-
}
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
```
|
|
283
114
|
|
|
284
|
-
|
|
285
|
-
export type TConfigurationSchema = typeof configurationTable;
|
|
286
|
-
export type TConfiguration = TTableObject<TConfigurationSchema>;
|
|
115
|
+
**Key points:**
|
|
287
116
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
117
|
+
- Relations use `TRelationConfig[]` format directly
|
|
118
|
+
- Reference other models via `Model.schema` (e.g., `User.schema.id`)
|
|
119
|
+
- Relation names (`creator`, `modifier`) are used in queries with `include`
|
|
291
120
|
|
|
292
|
-
|
|
293
|
-
super({ name: Configuration.TABLE_NAME, schema: configurationTable });
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
```
|
|
297
|
-
**Key Concepts:**
|
|
298
|
-
- **`createRelations`**: This helper function from `Ignis` simplifies defining Drizzle ORM relations. It creates both a Drizzle `relations` object (for querying) and a `definitions` object (for repository configuration). Here, we define `creator` and `modifier` relations from `Configuration` to `User`. The names (`creator`, `modifier`) are important, as they will be used when querying.
|
|
121
|
+
### Understanding Enrichers
|
|
299
122
|
|
|
300
|
-
|
|
301
|
-
> - Explore the [**`BaseEntity`**](../../references/base/models.md#baseentity-class) class.
|
|
302
|
-
> - See all available [**Enrichers**](../../references/base/models.md#schema-enrichers) for schema generation.
|
|
123
|
+
Enrichers are helper functions that generate common database columns automatically.
|
|
303
124
|
|
|
304
|
-
|
|
125
|
+
**Without enrichers:**
|
|
305
126
|
|
|
306
|
-
|
|
127
|
+
```typescript
|
|
128
|
+
static override schema = pgTable('User', {
|
|
129
|
+
id: uuid('id').defaultRandom().primaryKey(),
|
|
130
|
+
status: text('status').notNull().default('ACTIVE'),
|
|
131
|
+
createdBy: text('created_by'),
|
|
132
|
+
modifiedBy: text('modified_by'),
|
|
133
|
+
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
134
|
+
modifiedAt: timestamp('modified_at', { withTimezone: true }).notNull().defaultNow(),
|
|
135
|
+
// ... your fields
|
|
136
|
+
});
|
|
137
|
+
```
|
|
307
138
|
|
|
308
|
-
|
|
139
|
+
**With enrichers:**
|
|
309
140
|
|
|
310
|
-
|
|
141
|
+
```typescript
|
|
142
|
+
static override schema = pgTable('User', {
|
|
143
|
+
...generateIdColumnDefs({ id: { dataType: 'string' } }), // id (UUID)
|
|
144
|
+
...extraUserColumns({ idType: 'string' }), // status, audit fields, timestamps
|
|
145
|
+
// ... your fields
|
|
146
|
+
});
|
|
147
|
+
```
|
|
311
148
|
|
|
312
|
-
|
|
149
|
+
#### Available Enrichers
|
|
313
150
|
|
|
314
|
-
|
|
151
|
+
| Enricher | Columns Added | Use Case |
|
|
152
|
+
|----------|---------------|----------|
|
|
153
|
+
| `generateIdColumnDefs()` | `id` (UUID or number) | Every table |
|
|
154
|
+
| `generateTzColumnDefs()` | `createdAt`, `modifiedAt` | Track timestamps |
|
|
155
|
+
| `generateUserAuditColumnDefs()` | `createdBy`, `modifiedBy` | Track who created/updated |
|
|
156
|
+
| `generateDataTypeColumnDefs()` | `dataType`, `tValue`, `nValue`, etc. | Configuration tables |
|
|
157
|
+
| `extraUserColumns()` | Combines audit + status + type | Full-featured entities |
|
|
315
158
|
|
|
316
|
-
|
|
159
|
+
:::tip
|
|
160
|
+
For a complete list of enrichers and options, see the [Schema Enrichers Reference](../../references/base/models.md#schema-enrichers).
|
|
161
|
+
:::
|
|
317
162
|
|
|
318
|
-
|
|
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")
|
|
163
|
+
---
|
|
322
164
|
|
|
323
|
-
|
|
324
|
-
You must merge both into a single `schema` object in your DataSource constructor.
|
|
165
|
+
## 2. DataSources: Connecting to Your Database
|
|
325
166
|
|
|
326
|
-
|
|
167
|
+
A DataSource manages database connections and supports **schema auto-discovery** from repositories.
|
|
327
168
|
|
|
328
|
-
|
|
169
|
+
### Creating a DataSource
|
|
329
170
|
|
|
330
171
|
```typescript
|
|
331
172
|
// 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
173
|
import {
|
|
341
174
|
BaseDataSource,
|
|
342
175
|
datasource,
|
|
@@ -346,215 +179,178 @@ import {
|
|
|
346
179
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
347
180
|
import { Pool } from 'pg';
|
|
348
181
|
|
|
349
|
-
// Configuration interface for database connection
|
|
350
182
|
interface IDSConfigs {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
database?: string;
|
|
357
|
-
};
|
|
183
|
+
host: string;
|
|
184
|
+
port: number;
|
|
185
|
+
database: string;
|
|
186
|
+
user: string;
|
|
187
|
+
password: string;
|
|
358
188
|
}
|
|
359
189
|
|
|
360
|
-
@datasource()
|
|
190
|
+
@datasource({ driver: 'node-postgres' })
|
|
361
191
|
export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, IDSConfigs> {
|
|
362
192
|
constructor() {
|
|
363
193
|
super({
|
|
364
194
|
name: PostgresDataSource.name,
|
|
365
|
-
driver: 'node-postgres',
|
|
366
195
|
config: {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
database: process.env.APP_ENV_POSTGRES_DATABASE,
|
|
373
|
-
},
|
|
196
|
+
host: process.env.POSTGRES_HOST ?? 'localhost',
|
|
197
|
+
port: +(process.env.POSTGRES_PORT ?? 5432),
|
|
198
|
+
database: process.env.POSTGRES_DATABASE ?? 'mydb',
|
|
199
|
+
user: process.env.POSTGRES_USER ?? 'postgres',
|
|
200
|
+
password: process.env.POSTGRES_PASSWORD ?? '',
|
|
374
201
|
},
|
|
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
|
-
),
|
|
202
|
+
// No schema needed - auto-discovered from @repository bindings!
|
|
388
203
|
});
|
|
389
204
|
}
|
|
390
205
|
|
|
391
206
|
override configure(): ValueOrPromise<void> {
|
|
392
|
-
//
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
207
|
+
// getSchema() auto-discovers models from @repository bindings
|
|
208
|
+
const schema = this.getSchema();
|
|
209
|
+
|
|
210
|
+
this.logger.debug(
|
|
211
|
+
'[configure] Auto-discovered schema | Keys: %o',
|
|
212
|
+
Object.keys(schema),
|
|
213
|
+
);
|
|
398
214
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
return this.connector;
|
|
215
|
+
const client = new Pool(this.settings);
|
|
216
|
+
this.connector = drizzle({ client, schema });
|
|
402
217
|
}
|
|
403
218
|
|
|
404
|
-
override
|
|
405
|
-
|
|
219
|
+
override getConnectionString(): ValueOrPromise<string> {
|
|
220
|
+
const { host, port, user, password, database } = this.settings;
|
|
221
|
+
return `postgresql://${user}:${password}@${host}:${port}/${database}`;
|
|
406
222
|
}
|
|
407
223
|
}
|
|
408
224
|
```
|
|
409
225
|
|
|
410
|
-
|
|
226
|
+
**How auto-discovery works:**
|
|
411
227
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
schema: {
|
|
416
|
-
[User.TABLE_NAME]: userTable,
|
|
417
|
-
[Configuration.TABLE_NAME]: configurationTable,
|
|
418
|
-
}
|
|
419
|
-
```
|
|
228
|
+
1. `@repository` decorators register model-datasource bindings
|
|
229
|
+
2. When `configure()` is called, `getSchema()` collects all bound models
|
|
230
|
+
3. Drizzle is initialized with the complete schema
|
|
420
231
|
|
|
421
|
-
|
|
232
|
+
### Manual Schema (Optional)
|
|
422
233
|
|
|
423
|
-
|
|
424
|
-
```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
|
-
)
|
|
435
|
-
```
|
|
234
|
+
If you need explicit control, you can still provide schema manually:
|
|
436
235
|
|
|
437
|
-
**Result:** You can now do:
|
|
438
236
|
```typescript
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
1. Import its table and relations
|
|
453
|
-
2. Add them to the schema object
|
|
454
|
-
|
|
455
|
-
**Example - Adding a `Post` model:**
|
|
456
|
-
```typescript
|
|
457
|
-
import {
|
|
458
|
-
Post,
|
|
459
|
-
postTable,
|
|
460
|
-
postRelations,
|
|
461
|
-
// ... other models
|
|
462
|
-
} from '@/models/entities';
|
|
463
|
-
|
|
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
|
-
),
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
**Pro tip:** As your app grows, consider using a helper function to reduce boilerplate:
|
|
479
|
-
|
|
480
|
-
```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);
|
|
237
|
+
@datasource({ driver: 'node-postgres' })
|
|
238
|
+
export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, IDSConfigs> {
|
|
239
|
+
constructor() {
|
|
240
|
+
super({
|
|
241
|
+
name: PostgresDataSource.name,
|
|
242
|
+
config: { /* ... */ },
|
|
243
|
+
schema: {
|
|
244
|
+
User: User.schema,
|
|
245
|
+
Configuration: Configuration.schema,
|
|
246
|
+
// Add relations if using Drizzle's relational queries
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
}
|
|
485
250
|
}
|
|
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
|
-
])
|
|
493
251
|
```
|
|
494
252
|
|
|
495
253
|
### Registering a DataSource
|
|
496
254
|
|
|
497
|
-
Finally, register your `DataSource` with the application in `src/application.ts`.
|
|
498
|
-
|
|
499
255
|
```typescript
|
|
500
256
|
// src/application.ts
|
|
501
|
-
import { PostgresDataSource } from './datasources';
|
|
502
|
-
|
|
503
257
|
export class Application extends BaseApplication {
|
|
504
|
-
// ...
|
|
505
258
|
preConfigure(): ValueOrPromise<void> {
|
|
506
259
|
this.dataSource(PostgresDataSource);
|
|
507
|
-
// ...
|
|
508
260
|
}
|
|
509
261
|
}
|
|
510
262
|
```
|
|
511
263
|
|
|
512
|
-
> **Deep Dive:**
|
|
513
|
-
> - Explore the [**`BaseDataSource`**](../../references/base/datasources.md) class.
|
|
514
|
-
|
|
515
264
|
---
|
|
516
265
|
|
|
517
266
|
## 3. Repositories: The Data Access Layer
|
|
518
267
|
|
|
519
|
-
Repositories
|
|
268
|
+
Repositories provide type-safe CRUD operations. Use `@repository` decorator with both `model` and `dataSource` for auto-discovery.
|
|
520
269
|
|
|
521
|
-
###
|
|
270
|
+
### Pattern 1: Zero Boilerplate (Recommended)
|
|
522
271
|
|
|
523
|
-
|
|
272
|
+
The simplest approach - everything is auto-resolved:
|
|
524
273
|
|
|
525
274
|
```typescript
|
|
526
275
|
// src/repositories/configuration.repository.ts
|
|
527
|
-
import {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
276
|
+
import { Configuration } from '@/models/entities';
|
|
277
|
+
import { PostgresDataSource } from '@/datasources/postgres.datasource';
|
|
278
|
+
import { DefaultCRUDRepository, repository } from '@venizia/ignis';
|
|
279
|
+
|
|
280
|
+
@repository({
|
|
281
|
+
model: Configuration,
|
|
282
|
+
dataSource: PostgresDataSource,
|
|
283
|
+
})
|
|
284
|
+
export class ConfigurationRepository extends DefaultCRUDRepository<typeof Configuration.schema> {
|
|
285
|
+
// No constructor needed!
|
|
286
|
+
|
|
287
|
+
async findByCode(code: string) {
|
|
288
|
+
return this.findOne({ filter: { where: { code } } });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async findByGroup(group: string) {
|
|
292
|
+
return this.find({ filter: { where: { group } } });
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Pattern 2: Explicit @inject
|
|
298
|
+
|
|
299
|
+
When you need constructor control (e.g., read-only repository or additional dependencies):
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
// src/repositories/user.repository.ts
|
|
303
|
+
import { User } from '@/models/entities';
|
|
304
|
+
import { PostgresDataSource } from '@/datasources/postgres.datasource';
|
|
305
|
+
import { inject, ReadableRepository, repository } from '@venizia/ignis';
|
|
306
|
+
import { CacheService } from '@/services/cache.service';
|
|
307
|
+
|
|
308
|
+
@repository({ model: User, dataSource: PostgresDataSource })
|
|
309
|
+
export class UserRepository extends ReadableRepository<typeof User.schema> {
|
|
537
310
|
constructor(
|
|
538
|
-
//
|
|
539
|
-
@inject({ key: 'datasources.PostgresDataSource' })
|
|
311
|
+
// First parameter MUST be DataSource injection
|
|
312
|
+
@inject({ key: 'datasources.PostgresDataSource' })
|
|
313
|
+
dataSource: PostgresDataSource, // Must be concrete type, not 'any'
|
|
314
|
+
|
|
315
|
+
// After first arg, you can inject any additional dependencies
|
|
316
|
+
@inject({ key: 'some.cache' })
|
|
317
|
+
private cache: SomeCache,
|
|
540
318
|
) {
|
|
541
|
-
|
|
542
|
-
|
|
319
|
+
super(dataSource);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async findByRealm(realm: string) {
|
|
323
|
+
// Use injected dependencies
|
|
324
|
+
const cached = await this.cacheService.get(`user:realm:${realm}`);
|
|
325
|
+
if (cached) {
|
|
326
|
+
return cached;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return this.findOne({ filter: { where: { realm } } });
|
|
543
330
|
}
|
|
544
331
|
}
|
|
545
332
|
```
|
|
546
|
-
You would then register this repository in your `application.ts`: `this.repository(ConfigurationRepository);`
|
|
547
333
|
|
|
548
|
-
|
|
334
|
+
> **Important:**
|
|
335
|
+
> - First constructor parameter **MUST** be the DataSource injection
|
|
336
|
+
> - After the first argument, you can inject any additional dependencies you need
|
|
337
|
+
> - When `@inject` is at param index 0, auto-injection is skipped
|
|
338
|
+
|
|
339
|
+
### Repository Types
|
|
549
340
|
|
|
550
|
-
|
|
341
|
+
| Type | Description |
|
|
342
|
+
|------|-------------|
|
|
343
|
+
| `DefaultCRUDRepository` | Full read/write operations |
|
|
344
|
+
| `ReadableRepository` | Read-only operations |
|
|
345
|
+
| `PersistableRepository` | Write operations only |
|
|
346
|
+
|
|
347
|
+
### Querying Data
|
|
551
348
|
|
|
552
349
|
```typescript
|
|
553
|
-
// Example usage in application.ts or a service
|
|
554
350
|
const repo = this.get<ConfigurationRepository>({ key: 'repositories.ConfigurationRepository' });
|
|
555
351
|
|
|
556
352
|
// Find multiple records
|
|
557
|
-
const
|
|
353
|
+
const configs = await repo.find({
|
|
558
354
|
filter: {
|
|
559
355
|
where: { group: 'SYSTEM' },
|
|
560
356
|
limit: 10,
|
|
@@ -562,30 +358,186 @@ const someConfigs = await repo.find({
|
|
|
562
358
|
}
|
|
563
359
|
});
|
|
564
360
|
|
|
565
|
-
//
|
|
361
|
+
// Find one record
|
|
362
|
+
const config = await repo.findOne({
|
|
363
|
+
filter: { where: { code: 'APP_NAME' } }
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// Create a record
|
|
566
367
|
const newConfig = await repo.create({
|
|
567
368
|
data: {
|
|
568
|
-
code: '
|
|
369
|
+
code: 'NEW_SETTING',
|
|
569
370
|
group: 'SYSTEM',
|
|
570
|
-
|
|
371
|
+
description: 'A new setting',
|
|
571
372
|
}
|
|
572
373
|
});
|
|
374
|
+
|
|
375
|
+
// Update by ID
|
|
376
|
+
await repo.updateById({
|
|
377
|
+
id: 'uuid-here',
|
|
378
|
+
data: { description: 'Updated description' }
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// Delete by ID
|
|
382
|
+
await repo.deleteById({ id: 'uuid-here' });
|
|
573
383
|
```
|
|
574
384
|
|
|
575
385
|
### Querying with Relations
|
|
576
386
|
|
|
577
|
-
|
|
387
|
+
Use `include` to fetch related data. The relation name must match what you defined in `static relations`:
|
|
578
388
|
|
|
579
389
|
```typescript
|
|
580
|
-
const
|
|
390
|
+
const configWithCreator = await repo.findOne({
|
|
581
391
|
filter: {
|
|
582
|
-
where: { code: '
|
|
583
|
-
include: [{ relation: 'creator' }],
|
|
392
|
+
where: { code: 'APP_NAME' },
|
|
393
|
+
include: [{ relation: 'creator' }],
|
|
584
394
|
},
|
|
585
395
|
});
|
|
586
396
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
397
|
+
console.log('Created by:', configWithCreator.creator.name);
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Registering Repositories
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
// src/application.ts
|
|
404
|
+
export class Application extends BaseApplication {
|
|
405
|
+
preConfigure(): ValueOrPromise<void> {
|
|
406
|
+
this.dataSource(PostgresDataSource);
|
|
407
|
+
this.repository(UserRepository);
|
|
408
|
+
this.repository(ConfigurationRepository);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## 4. Advanced Topics
|
|
416
|
+
|
|
417
|
+
### Performance: Core API Optimization
|
|
418
|
+
|
|
419
|
+
Ignis automatically optimizes "flat" queries (no relations, no field selection) by using Drizzle's Core API. This provides **~15-20% faster** queries for simple reads.
|
|
420
|
+
|
|
421
|
+
### Transactions (Current)
|
|
422
|
+
|
|
423
|
+
Currently, use Drizzle's callback-based `connector.transaction` for atomic operations:
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
const ds = this.get<PostgresDataSource>({ key: 'datasources.PostgresDataSource' });
|
|
427
|
+
|
|
428
|
+
await ds.connector.transaction(async (tx) => {
|
|
429
|
+
await tx.insert(User.schema).values({ /* ... */ });
|
|
430
|
+
await tx.insert(Configuration.schema).values({ /* ... */ });
|
|
431
|
+
});
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
> **Note:** This callback-based approach requires all transaction logic to be in one callback. See [Section 5](#5-transactions-planned) for the planned improvement.
|
|
435
|
+
|
|
436
|
+
### Modular Persistence with Components
|
|
437
|
+
|
|
438
|
+
Bundle related persistence resources into Components for better organization:
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
export class UserManagementComponent extends BaseComponent {
|
|
442
|
+
override binding() {
|
|
443
|
+
this.application.dataSource(PostgresDataSource);
|
|
444
|
+
this.application.repository(UserRepository);
|
|
445
|
+
this.application.repository(ProfileRepository);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## 5. Transactions (Planned)
|
|
453
|
+
|
|
454
|
+
> **Status:** Planned - Not yet implemented. See [full plan](../../changelogs/planned-transaction-support).
|
|
455
|
+
|
|
456
|
+
### The Problem
|
|
457
|
+
|
|
458
|
+
Drizzle's callback-based transactions make it hard to pass transactions across services:
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
// Current: Everything must be inside the callback
|
|
462
|
+
await ds.connector.transaction(async (tx) => {
|
|
463
|
+
// Can't easily call other services with this tx
|
|
464
|
+
});
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Planned Solution
|
|
468
|
+
|
|
469
|
+
Loopback 4-style explicit transaction objects that can be passed anywhere:
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
// Start transaction from repository
|
|
473
|
+
const tx = await userRepo.beginTransaction({
|
|
474
|
+
isolationLevel: 'SERIALIZABLE' // Optional, defaults to 'READ COMMITTED'
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
// Pass tx to multiple services/repositories
|
|
479
|
+
const user = await userRepo.create({ data, options: { transaction: tx } });
|
|
480
|
+
await profileRepo.create({ data: { userId: user.id }, options: { transaction: tx } });
|
|
481
|
+
await orderService.createInitialOrder(user.id, { transaction: tx });
|
|
482
|
+
|
|
483
|
+
await tx.commit();
|
|
484
|
+
} catch (err) {
|
|
485
|
+
await tx.rollback();
|
|
486
|
+
throw err;
|
|
487
|
+
}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Isolation Levels
|
|
491
|
+
|
|
492
|
+
| Level | Description | Use Case |
|
|
493
|
+
|-------|-------------|----------|
|
|
494
|
+
| `READ COMMITTED` | Default. Sees only committed data | General use |
|
|
495
|
+
| `REPEATABLE READ` | Snapshot from transaction start | Reports, consistent reads |
|
|
496
|
+
| `SERIALIZABLE` | Full isolation, may throw errors | Financial, critical data |
|
|
497
|
+
|
|
498
|
+
### Benefits
|
|
499
|
+
|
|
500
|
+
| Aspect | Current (Callback) | Planned (Pass-through) |
|
|
501
|
+
|--------|-------------------|------------------------|
|
|
502
|
+
| Service composition | Hard | Easy - pass tx anywhere |
|
|
503
|
+
| Separation of concerns | Services coupled | Services independent |
|
|
504
|
+
| Testing | Complex mocking | Easy to mock tx |
|
|
505
|
+
| Code organization | Nested callbacks | Flat, sequential |
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## Quick Reference
|
|
510
|
+
|
|
511
|
+
### Model Template
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
import { BaseEntity, generateIdColumnDefs, model, TRelationConfig } from '@venizia/ignis';
|
|
515
|
+
import { pgTable, text } from 'drizzle-orm/pg-core';
|
|
516
|
+
|
|
517
|
+
@model({ type: 'entity' })
|
|
518
|
+
export class MyModel extends BaseEntity<typeof MyModel.schema> {
|
|
519
|
+
static override schema = pgTable('MyModel', {
|
|
520
|
+
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
521
|
+
name: text('name').notNull(),
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
static override relations = (): TRelationConfig[] => [];
|
|
590
525
|
}
|
|
591
526
|
```
|
|
527
|
+
|
|
528
|
+
### Repository Template
|
|
529
|
+
|
|
530
|
+
```typescript
|
|
531
|
+
import { DefaultCRUDRepository, repository } from '@venizia/ignis';
|
|
532
|
+
import { MyModel } from '@/models/entities';
|
|
533
|
+
import { PostgresDataSource } from '@/datasources/postgres.datasource';
|
|
534
|
+
|
|
535
|
+
@repository({ model: MyModel, dataSource: PostgresDataSource })
|
|
536
|
+
export class MyModelRepository extends DefaultCRUDRepository<typeof MyModel.schema> {}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
> **Deep Dive:**
|
|
540
|
+
> - [BaseEntity Reference](../../references/base/models.md#baseentity-class)
|
|
541
|
+
> - [Schema Enrichers](../../references/base/models.md#schema-enrichers)
|
|
542
|
+
> - [BaseDataSource Reference](../../references/base/datasources.md)
|
|
543
|
+
> - [Repository Reference](../../references/base/repositories.md)
|