@venizia/ignis-docs 0.0.3 → 0.0.4-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/README.md +1 -1
- package/package.json +4 -2
- package/wiki/best-practices/api-usage-examples.md +591 -0
- package/wiki/best-practices/architectural-patterns.md +415 -0
- package/wiki/best-practices/architecture-decisions.md +488 -0
- package/wiki/{get-started/best-practices → best-practices}/code-style-standards.md +406 -17
- package/wiki/{get-started/best-practices → best-practices}/common-pitfalls.md +109 -4
- package/wiki/{get-started/best-practices → best-practices}/contribution-workflow.md +34 -7
- package/wiki/best-practices/data-modeling.md +376 -0
- package/wiki/best-practices/deployment-strategies.md +698 -0
- package/wiki/best-practices/index.md +27 -0
- package/wiki/best-practices/performance-optimization.md +196 -0
- package/wiki/best-practices/security-guidelines.md +218 -0
- package/wiki/{get-started/best-practices → best-practices}/troubleshooting-tips.md +97 -1
- package/wiki/changelogs/2025-12-16-initial-architecture.md +1 -1
- package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +1 -1
- package/wiki/changelogs/2025-12-17-refactor.md +1 -1
- package/wiki/changelogs/2025-12-18-performance-optimizations.md +5 -5
- package/wiki/changelogs/2025-12-18-repository-validation-security.md +13 -7
- package/wiki/changelogs/2025-12-26-nested-relations-and-generics.md +2 -2
- package/wiki/changelogs/2025-12-29-dynamic-binding-registration.md +104 -0
- package/wiki/changelogs/2025-12-29-snowflake-uid-helper.md +100 -0
- package/wiki/changelogs/2025-12-30-repository-enhancements.md +214 -0
- package/wiki/changelogs/2025-12-31-json-path-filtering-array-operators.md +214 -0
- package/wiki/changelogs/2025-12-31-string-id-custom-generator.md +137 -0
- package/wiki/changelogs/2026-01-02-default-filter-and-repository-mixins.md +418 -0
- package/wiki/changelogs/index.md +6 -0
- package/wiki/changelogs/planned-schema-migrator.md +0 -8
- package/wiki/{get-started/core-concepts → guides/core-concepts/application}/bootstrapping.md +18 -5
- package/wiki/{get-started/core-concepts/application.md → guides/core-concepts/application/index.md} +47 -104
- package/wiki/guides/core-concepts/components-guide.md +509 -0
- package/wiki/{get-started → guides}/core-concepts/components.md +24 -17
- package/wiki/{get-started → guides}/core-concepts/controllers.md +30 -13
- package/wiki/{get-started → guides}/core-concepts/dependency-injection.md +97 -0
- package/wiki/guides/core-concepts/persistent/datasources.md +179 -0
- package/wiki/guides/core-concepts/persistent/index.md +119 -0
- package/wiki/guides/core-concepts/persistent/models.md +241 -0
- package/wiki/guides/core-concepts/persistent/repositories.md +219 -0
- package/wiki/guides/core-concepts/persistent/transactions.md +170 -0
- package/wiki/{get-started → guides}/core-concepts/services.md +26 -3
- package/wiki/{get-started → guides/get-started}/5-minute-quickstart.md +59 -14
- package/wiki/guides/get-started/philosophy.md +682 -0
- package/wiki/guides/get-started/setup.md +157 -0
- package/wiki/guides/index.md +89 -0
- package/wiki/guides/reference/glossary.md +243 -0
- package/wiki/{get-started → guides/reference}/mcp-docs-server.md +0 -10
- package/wiki/{get-started → guides/tutorials}/building-a-crud-api.md +134 -132
- package/wiki/{get-started/quickstart.md → guides/tutorials/complete-installation.md} +107 -71
- package/wiki/guides/tutorials/ecommerce-api.md +1399 -0
- package/wiki/guides/tutorials/realtime-chat.md +1261 -0
- package/wiki/guides/tutorials/testing.md +723 -0
- package/wiki/index.md +176 -37
- package/wiki/references/base/application.md +27 -0
- package/wiki/references/base/bootstrapping.md +31 -26
- package/wiki/references/base/components.md +24 -7
- package/wiki/references/base/controllers.md +50 -20
- package/wiki/references/base/datasources.md +30 -0
- package/wiki/references/base/dependency-injection.md +39 -3
- package/wiki/references/base/filter-system/application-usage.md +224 -0
- package/wiki/references/base/filter-system/array-operators.md +132 -0
- package/wiki/references/base/filter-system/comparison-operators.md +109 -0
- package/wiki/references/base/filter-system/default-filter.md +428 -0
- package/wiki/references/base/filter-system/fields-order-pagination.md +155 -0
- package/wiki/references/base/filter-system/index.md +127 -0
- package/wiki/references/base/filter-system/json-filtering.md +197 -0
- package/wiki/references/base/filter-system/list-operators.md +71 -0
- package/wiki/references/base/filter-system/logical-operators.md +156 -0
- package/wiki/references/base/filter-system/null-operators.md +58 -0
- package/wiki/references/base/filter-system/pattern-matching.md +108 -0
- package/wiki/references/base/filter-system/quick-reference.md +431 -0
- package/wiki/references/base/filter-system/range-operators.md +63 -0
- package/wiki/references/base/filter-system/tips.md +190 -0
- package/wiki/references/base/filter-system/use-cases.md +452 -0
- package/wiki/references/base/index.md +90 -0
- package/wiki/references/base/middlewares.md +604 -0
- package/wiki/references/base/models.md +215 -23
- package/wiki/references/base/providers.md +731 -0
- package/wiki/references/base/repositories/advanced.md +555 -0
- package/wiki/references/base/repositories/index.md +228 -0
- package/wiki/references/base/repositories/mixins.md +331 -0
- package/wiki/references/base/repositories/relations.md +486 -0
- package/wiki/references/base/repositories.md +40 -635
- package/wiki/references/base/services.md +28 -4
- package/wiki/references/components/authentication.md +22 -2
- package/wiki/references/components/health-check.md +12 -0
- package/wiki/references/components/index.md +23 -0
- package/wiki/references/components/mail.md +687 -0
- package/wiki/references/components/request-tracker.md +16 -0
- package/wiki/references/components/socket-io.md +18 -0
- package/wiki/references/components/static-asset.md +14 -26
- package/wiki/references/components/swagger.md +17 -0
- package/wiki/references/configuration/environment-variables.md +427 -0
- package/wiki/references/configuration/index.md +73 -0
- package/wiki/references/helpers/cron.md +14 -0
- package/wiki/references/helpers/crypto.md +15 -0
- package/wiki/references/helpers/env.md +16 -0
- package/wiki/references/helpers/error.md +17 -0
- package/wiki/references/helpers/index.md +14 -0
- package/wiki/references/helpers/inversion.md +24 -4
- package/wiki/references/helpers/logger.md +19 -0
- package/wiki/references/helpers/network.md +11 -0
- package/wiki/references/helpers/queue.md +19 -0
- package/wiki/references/helpers/redis.md +21 -0
- package/wiki/references/helpers/socket-io.md +24 -5
- package/wiki/references/helpers/storage.md +18 -10
- package/wiki/references/helpers/testing.md +18 -0
- package/wiki/references/helpers/types.md +16 -0
- package/wiki/references/helpers/uid.md +167 -0
- package/wiki/references/helpers/worker-thread.md +16 -0
- package/wiki/references/index.md +177 -0
- package/wiki/references/quick-reference.md +634 -0
- package/wiki/references/src-details/boot.md +3 -3
- package/wiki/references/src-details/dev-configs.md +0 -4
- package/wiki/references/src-details/docs.md +2 -2
- package/wiki/references/src-details/index.md +86 -0
- package/wiki/references/src-details/inversion.md +1 -6
- package/wiki/references/src-details/mcp-server.md +3 -15
- package/wiki/references/utilities/index.md +86 -10
- package/wiki/references/utilities/jsx.md +577 -0
- package/wiki/references/utilities/request.md +0 -2
- package/wiki/references/utilities/statuses.md +740 -0
- package/wiki/get-started/best-practices/api-usage-examples.md +0 -266
- package/wiki/get-started/best-practices/architectural-patterns.md +0 -170
- package/wiki/get-started/best-practices/data-modeling.md +0 -177
- package/wiki/get-started/best-practices/deployment-strategies.md +0 -121
- package/wiki/get-started/best-practices/performance-optimization.md +0 -97
- package/wiki/get-started/best-practices/security-guidelines.md +0 -99
- package/wiki/get-started/core-concepts/persistent.md +0 -539
- package/wiki/get-started/index.md +0 -65
- package/wiki/get-started/philosophy.md +0 -296
- package/wiki/get-started/prerequisites.md +0 -113
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
# Relations & Includes
|
|
2
|
+
|
|
3
|
+
Fetch related data using `include` for eager loading. This guide covers one-to-one, one-to-many, and many-to-many relationships.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Basic Include
|
|
7
|
+
|
|
8
|
+
### One-to-Many: User with Posts
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
// Fetch user with their posts
|
|
12
|
+
const user = await userRepo.findOne({
|
|
13
|
+
filter: {
|
|
14
|
+
where: { id: '123' },
|
|
15
|
+
include: [{ relation: 'posts' }]
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Result:
|
|
20
|
+
// {
|
|
21
|
+
// id: '123',
|
|
22
|
+
// name: 'John',
|
|
23
|
+
// posts: [
|
|
24
|
+
// { id: 'p1', title: 'First Post', authorId: '123' },
|
|
25
|
+
// { id: 'p2', title: 'Second Post', authorId: '123' }
|
|
26
|
+
// ]
|
|
27
|
+
// }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### One-to-One: Post with Author
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
// Fetch post with its author
|
|
34
|
+
const post = await postRepo.findOne({
|
|
35
|
+
filter: {
|
|
36
|
+
where: { id: 'p1' },
|
|
37
|
+
include: [{ relation: 'author' }]
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Result:
|
|
42
|
+
// {
|
|
43
|
+
// id: 'p1',
|
|
44
|
+
// title: 'First Post',
|
|
45
|
+
// authorId: '123',
|
|
46
|
+
// author: { id: '123', name: 'John', email: 'john@example.com' }
|
|
47
|
+
// }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Multiple Relations
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// Fetch post with author AND comments
|
|
54
|
+
const post = await postRepo.findOne({
|
|
55
|
+
filter: {
|
|
56
|
+
where: { id: 'p1' },
|
|
57
|
+
include: [
|
|
58
|
+
{ relation: 'author' },
|
|
59
|
+
{ relation: 'comments' }
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
## Scoped Includes
|
|
67
|
+
|
|
68
|
+
Apply filters, ordering, and limits to included relations using `scope`:
|
|
69
|
+
|
|
70
|
+
### Filter Related Data
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// User with only published posts
|
|
74
|
+
const user = await userRepo.findOne({
|
|
75
|
+
filter: {
|
|
76
|
+
where: { id: '123' },
|
|
77
|
+
include: [{
|
|
78
|
+
relation: 'posts',
|
|
79
|
+
scope: {
|
|
80
|
+
where: { status: 'published' }
|
|
81
|
+
}
|
|
82
|
+
}]
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Order Related Data
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// User with posts ordered by date
|
|
91
|
+
const user = await userRepo.findOne({
|
|
92
|
+
filter: {
|
|
93
|
+
where: { id: '123' },
|
|
94
|
+
include: [{
|
|
95
|
+
relation: 'posts',
|
|
96
|
+
scope: {
|
|
97
|
+
order: ['createdAt DESC']
|
|
98
|
+
}
|
|
99
|
+
}]
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Limit Related Data
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// User with their 5 most recent posts
|
|
108
|
+
const user = await userRepo.findOne({
|
|
109
|
+
filter: {
|
|
110
|
+
where: { id: '123' },
|
|
111
|
+
include: [{
|
|
112
|
+
relation: 'posts',
|
|
113
|
+
scope: {
|
|
114
|
+
order: ['createdAt DESC'],
|
|
115
|
+
limit: 5
|
|
116
|
+
}
|
|
117
|
+
}]
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Combined Scope Options
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const user = await userRepo.findOne({
|
|
126
|
+
filter: {
|
|
127
|
+
where: { id: '123' },
|
|
128
|
+
include: [{
|
|
129
|
+
relation: 'posts',
|
|
130
|
+
scope: {
|
|
131
|
+
where: { status: 'published' },
|
|
132
|
+
order: ['createdAt DESC'],
|
|
133
|
+
limit: 10,
|
|
134
|
+
fields: ['id', 'title', 'createdAt']
|
|
135
|
+
}
|
|
136
|
+
}]
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
## Nested Includes
|
|
143
|
+
|
|
144
|
+
Include relations of relations (up to 2 levels recommended):
|
|
145
|
+
|
|
146
|
+
### Two-Level Nesting
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
// User → Posts → Comments
|
|
150
|
+
const user = await userRepo.findOne({
|
|
151
|
+
filter: {
|
|
152
|
+
where: { id: '123' },
|
|
153
|
+
include: [{
|
|
154
|
+
relation: 'posts',
|
|
155
|
+
scope: {
|
|
156
|
+
include: [{ relation: 'comments' }]
|
|
157
|
+
}
|
|
158
|
+
}]
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Result:
|
|
163
|
+
// {
|
|
164
|
+
// id: '123',
|
|
165
|
+
// name: 'John',
|
|
166
|
+
// posts: [
|
|
167
|
+
// {
|
|
168
|
+
// id: 'p1',
|
|
169
|
+
// title: 'First Post',
|
|
170
|
+
// comments: [
|
|
171
|
+
// { id: 'c1', text: 'Great post!' },
|
|
172
|
+
// { id: 'c2', text: 'Thanks for sharing' }
|
|
173
|
+
// ]
|
|
174
|
+
// }
|
|
175
|
+
// ]
|
|
176
|
+
// }
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Many-to-Many Through Junction
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// Product → SaleChannelProduct (junction) → SaleChannel
|
|
183
|
+
const product = await productRepo.findOne({
|
|
184
|
+
filter: {
|
|
185
|
+
where: { id: 'prod1' },
|
|
186
|
+
include: [{
|
|
187
|
+
relation: 'saleChannelProducts',
|
|
188
|
+
scope: {
|
|
189
|
+
include: [{ relation: 'saleChannel' }]
|
|
190
|
+
}
|
|
191
|
+
}]
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Result:
|
|
196
|
+
// {
|
|
197
|
+
// id: 'prod1',
|
|
198
|
+
// name: 'Widget',
|
|
199
|
+
// saleChannelProducts: [
|
|
200
|
+
// {
|
|
201
|
+
// productId: 'prod1',
|
|
202
|
+
// saleChannelId: 'ch1',
|
|
203
|
+
// saleChannel: { id: 'ch1', name: 'Online Store' }
|
|
204
|
+
// },
|
|
205
|
+
// {
|
|
206
|
+
// productId: 'prod1',
|
|
207
|
+
// saleChannelId: 'ch2',
|
|
208
|
+
// saleChannel: { id: 'ch2', name: 'Retail' }
|
|
209
|
+
// }
|
|
210
|
+
// ]
|
|
211
|
+
// }
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
> **Performance Warning:** Each nested `include` adds SQL complexity. **Maximum 2 levels recommended.** For deeper relationships, use multiple queries.
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
## Defining Relations
|
|
218
|
+
|
|
219
|
+
Relations must be defined in your model before you can `include` them.
|
|
220
|
+
|
|
221
|
+
### In Your Model
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
// src/models/user.model.ts
|
|
225
|
+
import { createRelations } from '@venizia/ignis';
|
|
226
|
+
|
|
227
|
+
export const userTable = pgTable('User', {
|
|
228
|
+
id: text('id').primaryKey(),
|
|
229
|
+
name: text('name').notNull(),
|
|
230
|
+
email: text('email').notNull(),
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
export const userRelations = createRelations({
|
|
234
|
+
source: userTable,
|
|
235
|
+
relations: [
|
|
236
|
+
{
|
|
237
|
+
type: 'hasMany',
|
|
238
|
+
model: () => Post, // Target model
|
|
239
|
+
foreignKey: 'authorId', // FK in Post table
|
|
240
|
+
name: 'posts', // Relation name for includes
|
|
241
|
+
},
|
|
242
|
+
],
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
@model({ type: 'entity' })
|
|
246
|
+
export class User extends BaseEntity<typeof User.schema> {
|
|
247
|
+
static override schema = userTable;
|
|
248
|
+
static override relations = () => userRelations.definitions;
|
|
249
|
+
static override TABLE_NAME = 'User';
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Relation Types
|
|
254
|
+
|
|
255
|
+
| Type | Description | Example |
|
|
256
|
+
|------|-------------|---------|
|
|
257
|
+
| `hasMany` | One-to-many | User has many Posts |
|
|
258
|
+
| `hasOne` | One-to-one | User has one Profile |
|
|
259
|
+
| `belongsTo` | Inverse of hasMany/hasOne | Post belongs to User |
|
|
260
|
+
|
|
261
|
+
### Example: Post Model
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
export const postRelations = createRelations({
|
|
265
|
+
source: postTable,
|
|
266
|
+
relations: [
|
|
267
|
+
{
|
|
268
|
+
type: 'belongsTo',
|
|
269
|
+
model: () => User,
|
|
270
|
+
foreignKey: 'authorId',
|
|
271
|
+
name: 'author',
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
type: 'hasMany',
|
|
275
|
+
model: () => Comment,
|
|
276
|
+
foreignKey: 'postId',
|
|
277
|
+
name: 'comments',
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
});
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
## Auto-Resolution
|
|
285
|
+
|
|
286
|
+
Relations are automatically resolved from the entity's static `relations` property. No need to pass them in the repository constructor:
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
@repository({ model: User, dataSource: PostgresDataSource })
|
|
290
|
+
export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {
|
|
291
|
+
// Relations auto-resolved from User.relations!
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
## Type Safety with Generics
|
|
297
|
+
|
|
298
|
+
For queries with `include`, use generic type overrides for full type safety:
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// Define the expected return type
|
|
302
|
+
type UserWithPosts = User & {
|
|
303
|
+
posts: Post[];
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// Use generic override
|
|
307
|
+
const user = await userRepo.findOne<UserWithPosts>({
|
|
308
|
+
filter: {
|
|
309
|
+
where: { id: '123' },
|
|
310
|
+
include: [{ relation: 'posts' }]
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// TypeScript knows the structure!
|
|
315
|
+
if (user) {
|
|
316
|
+
console.log(user.posts[0].title); // Fully typed
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Nested Relations Type
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
type ProductWithChannels = Product & {
|
|
324
|
+
saleChannelProducts: (SaleChannelProduct & {
|
|
325
|
+
saleChannel: SaleChannel;
|
|
326
|
+
})[];
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const product = await productRepo.findOne<ProductWithChannels>({
|
|
330
|
+
filter: {
|
|
331
|
+
where: { id: 'prod1' },
|
|
332
|
+
include: [{
|
|
333
|
+
relation: 'saleChannelProducts',
|
|
334
|
+
scope: {
|
|
335
|
+
include: [{ relation: 'saleChannel' }]
|
|
336
|
+
}
|
|
337
|
+
}]
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Fully typed access
|
|
342
|
+
product?.saleChannelProducts[0].saleChannel.name;
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
## Common Patterns
|
|
347
|
+
|
|
348
|
+
### Find All with Count of Relations
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
// Get users with post count
|
|
352
|
+
const users = await userRepo.find({
|
|
353
|
+
filter: {
|
|
354
|
+
include: [{
|
|
355
|
+
relation: 'posts',
|
|
356
|
+
scope: { fields: ['id'] } // Only fetch IDs to minimize data
|
|
357
|
+
}]
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Calculate counts
|
|
362
|
+
const usersWithCounts = users.map(user => ({
|
|
363
|
+
...user,
|
|
364
|
+
postCount: (user as any).posts?.length ?? 0
|
|
365
|
+
}));
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Conditional Include
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
async function getUser(id: string, includePosts: boolean) {
|
|
372
|
+
const include = includePosts
|
|
373
|
+
? [{ relation: 'posts' }]
|
|
374
|
+
: [];
|
|
375
|
+
|
|
376
|
+
return userRepo.findOne({
|
|
377
|
+
filter: {
|
|
378
|
+
where: { id },
|
|
379
|
+
include
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Include with Hidden Properties
|
|
386
|
+
|
|
387
|
+
Hidden properties (like `password`) are automatically excluded from included relations:
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
// User model has hiddenProperties: ['password']
|
|
391
|
+
const post = await postRepo.findOne({
|
|
392
|
+
filter: {
|
|
393
|
+
include: [{ relation: 'author' }]
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// post.author will NOT include password
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
## Error Handling
|
|
402
|
+
|
|
403
|
+
### Relation Not Found
|
|
404
|
+
|
|
405
|
+
If you try to include a relation that doesn't exist:
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
// Error: Relation 'nonExistent' not found in User relations
|
|
409
|
+
await userRepo.find({
|
|
410
|
+
filter: {
|
|
411
|
+
include: [{ relation: 'nonExistent' }]
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**Fix:** Check your model's `relations` definition.
|
|
417
|
+
|
|
418
|
+
### Schema Key Mismatch
|
|
419
|
+
|
|
420
|
+
```
|
|
421
|
+
Error: [UserRepository] Schema key mismatch | Entity name 'User' not found
|
|
422
|
+
in connector.query | Available keys: [Post, Comment]
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
**Fix:** Ensure your model's `TABLE_NAME` matches the schema registration.
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
## Performance Tips
|
|
429
|
+
|
|
430
|
+
1. **Limit nesting depth** - Max 2 levels recommended
|
|
431
|
+
2. **Use `fields` in scope** - Only fetch needed columns
|
|
432
|
+
3. **Use `limit` in scope** - Don't fetch unbounded related data
|
|
433
|
+
4. **Consider separate queries** - For complex data needs, multiple simple queries often outperform one complex nested query
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
// Instead of deep nesting, use separate queries
|
|
437
|
+
const user = await userRepo.findById({ id: '123' });
|
|
438
|
+
const posts = await postRepo.find({
|
|
439
|
+
filter: {
|
|
440
|
+
where: { authorId: '123' },
|
|
441
|
+
limit: 10
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
const comments = await commentRepo.find({
|
|
445
|
+
filter: {
|
|
446
|
+
where: { postId: { in: posts.map(p => p.id) } }
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
## Quick Reference
|
|
453
|
+
|
|
454
|
+
| Want to... | Code |
|
|
455
|
+
|------------|------|
|
|
456
|
+
| Include one relation | `include: [{ relation: 'posts' }]` |
|
|
457
|
+
| Include multiple | `include: [{ relation: 'posts' }, { relation: 'profile' }]` |
|
|
458
|
+
| Filter included | `include: [{ relation: 'posts', scope: { where: { status: 'active' } } }]` |
|
|
459
|
+
| Order included | `include: [{ relation: 'posts', scope: { order: ['createdAt DESC'] } }]` |
|
|
460
|
+
| Limit included | `include: [{ relation: 'posts', scope: { limit: 5 } }]` |
|
|
461
|
+
| Nested include | `include: [{ relation: 'posts', scope: { include: [{ relation: 'comments' }] } }]` |
|
|
462
|
+
| Select fields | `include: [{ relation: 'posts', scope: { fields: ['id', 'title'] } }]` |
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
## Next Steps
|
|
466
|
+
|
|
467
|
+
- [JSON Path Filtering](../filter-system/json-filtering) - Query JSONB columns
|
|
468
|
+
- [Array Operators](../filter-system/array-operators) - PostgreSQL array queries
|
|
469
|
+
- [Advanced Features](./advanced.md) - Transactions, hidden props
|
|
470
|
+
|
|
471
|
+
## See Also
|
|
472
|
+
|
|
473
|
+
- **Related Concepts:**
|
|
474
|
+
- [Repositories Overview](./index) - Core repository operations
|
|
475
|
+
- [Models](/guides/core-concepts/persistent/models) - Defining model relationships
|
|
476
|
+
|
|
477
|
+
- **Related Topics:**
|
|
478
|
+
- [Advanced Features](./advanced) - Hidden properties, transactions
|
|
479
|
+
- [Repository Mixins](./mixins) - Soft delete and auditing
|
|
480
|
+
- [Filter System](/references/base/filter-system/) - Query operators
|
|
481
|
+
|
|
482
|
+
- **External Resources:**
|
|
483
|
+
- [Drizzle ORM Relations](https://orm.drizzle.team/docs/rqb#relations) - Relation definition guide
|
|
484
|
+
|
|
485
|
+
- **Tutorials:**
|
|
486
|
+
- [E-commerce API](/guides/tutorials/ecommerce-api) - Relations in practice
|