@venizia/ignis-docs 0.2.0 → 0.2.1-0
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 +14 -14
- package/content/best-practices/api-usage-examples.md +39 -19
- package/content/best-practices/architectural-patterns.md +22 -11
- package/content/best-practices/architecture-decisions.md +29 -16
- package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
- package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
- package/content/best-practices/code-style-standards/control-flow.md +33 -1
- package/content/best-practices/code-style-standards/documentation.md +28 -8
- package/content/best-practices/code-style-standards/function-patterns.md +15 -4
- package/content/best-practices/code-style-standards/index.md +7 -2
- package/content/best-practices/code-style-standards/naming-conventions.md +26 -2
- package/content/best-practices/code-style-standards/route-definitions.md +9 -5
- package/content/best-practices/code-style-standards/tooling.md +14 -1
- package/content/best-practices/code-style-standards/type-safety.md +39 -6
- package/content/best-practices/common-pitfalls.md +59 -35
- package/content/best-practices/contribution-workflow.md +6 -2
- package/content/best-practices/data-modeling.md +78 -62
- package/content/best-practices/deployment-strategies.md +56 -19
- package/content/best-practices/error-handling.md +182 -93
- package/content/best-practices/index.md +6 -0
- package/content/best-practices/performance-optimization.md +26 -13
- package/content/best-practices/security-guidelines.md +35 -9
- package/content/best-practices/testing-strategies.md +7 -2
- package/content/best-practices/troubleshooting-tips.md +27 -17
- package/content/extensions/components/api-reference.md +107 -322
- package/content/extensions/components/authentication/api.md +454 -603
- package/content/extensions/components/authentication/errors.md +121 -498
- package/content/extensions/components/authentication/index.md +88 -801
- package/content/extensions/components/authentication/usage.md +207 -956
- package/content/extensions/components/authorization/api.md +736 -656
- package/content/extensions/components/authorization/errors.md +168 -206
- package/content/extensions/components/authorization/index.md +82 -797
- package/content/extensions/components/authorization/usage.md +194 -527
- package/content/extensions/components/health-check.md +71 -243
- package/content/extensions/components/mail/api.md +504 -287
- package/content/extensions/components/mail/errors.md +73 -61
- package/content/extensions/components/mail/index.md +96 -467
- package/content/extensions/components/mail/usage.md +130 -172
- package/content/extensions/components/request-tracker.md +66 -173
- package/content/extensions/components/socket-io/api.md +195 -13
- package/content/extensions/components/socket-io/errors.md +3 -3
- package/content/extensions/components/socket-io/index.md +50 -337
- package/content/extensions/components/socket-io/usage.md +143 -26
- package/content/extensions/components/static-asset/api.md +410 -142
- package/content/extensions/components/static-asset/errors.md +110 -53
- package/content/extensions/components/static-asset/index.md +79 -608
- package/content/extensions/components/static-asset/usage.md +180 -300
- package/content/extensions/components/websocket/api.md +275 -399
- package/content/extensions/components/websocket/errors.md +47 -56
- package/content/extensions/components/websocket/index.md +74 -407
- package/content/extensions/components/websocket/usage.md +110 -341
- package/content/extensions/helpers/cron/index.md +51 -160
- package/content/extensions/helpers/crypto/index.md +62 -483
- package/content/extensions/helpers/crypto/reference.md +456 -0
- package/content/extensions/helpers/env/index.md +60 -178
- package/content/extensions/helpers/error/index.md +221 -207
- package/content/extensions/helpers/inversion/index.md +65 -556
- package/content/extensions/helpers/inversion/reference.md +522 -0
- package/content/extensions/helpers/kafka/admin.md +20 -1
- package/content/extensions/helpers/kafka/compile-binary.md +41 -35
- package/content/extensions/helpers/kafka/consumer.md +54 -20
- package/content/extensions/helpers/kafka/examples.md +21 -16
- package/content/extensions/helpers/kafka/index.md +80 -610
- package/content/extensions/helpers/kafka/producer.md +134 -5
- package/content/extensions/helpers/kafka/schema-registry.md +45 -70
- package/content/extensions/helpers/logger/hf-logger.md +193 -0
- package/content/extensions/helpers/logger/index.md +64 -563
- package/content/extensions/helpers/logger/pino.md +85 -0
- package/content/extensions/helpers/logger/reference.md +746 -0
- package/content/extensions/helpers/network/api.md +241 -195
- package/content/extensions/helpers/network/index.md +72 -530
- package/content/extensions/helpers/queue/index.md +72 -900
- package/content/extensions/helpers/queue/reference.md +467 -0
- package/content/extensions/helpers/redis/index.md +73 -645
- package/content/extensions/helpers/redis/reference.md +727 -0
- package/content/extensions/helpers/secrets/index.md +66 -0
- package/content/extensions/helpers/socket-io/api.md +305 -203
- package/content/extensions/helpers/socket-io/index.md +66 -432
- package/content/extensions/helpers/storage/api.md +564 -462
- package/content/extensions/helpers/storage/index.md +77 -573
- package/content/extensions/helpers/types/index.md +66 -499
- package/content/extensions/helpers/types/reference.md +650 -0
- package/content/extensions/helpers/uid/index.md +58 -227
- package/content/extensions/helpers/websocket/api.md +329 -216
- package/content/extensions/helpers/websocket/index.md +65 -503
- package/content/extensions/helpers/worker-thread/index.md +58 -396
- package/content/extensions/helpers/worker-thread/reference.md +428 -0
- package/content/guides/core-concepts/persistent/models.md +1 -1
- package/content/guides/core-concepts/persistent/search-typesense.md +3 -3
- package/content/guides/core-concepts/persistent/transactions.md +1 -1
- package/content/guides/core-concepts/secrets-vault.md +177 -0
- package/content/guides/core-concepts/services.md +1 -1
- package/content/guides/migrations/redis-helpers-migration.md +1 -1
- package/content/guides/migrations/unified-connectors-migration.md +2 -2
- package/content/guides/tutorials/ecommerce-api.md +3 -8
- package/content/references/base/application.md +1 -1
- package/content/references/base/connectors.md +79 -136
- package/content/references/base/datasources-reference.md +599 -0
- package/content/references/base/datasources.md +84 -444
- package/content/references/base/dependency-injection.md +17 -39
- package/content/references/base/filter-system/application-usage.md +69 -121
- package/content/references/base/filter-system/array-operators.md +12 -0
- package/content/references/base/filter-system/comparison-operators.md +12 -0
- package/content/references/base/filter-system/default-filter.md +136 -348
- package/content/references/base/filter-system/fields-order-pagination.md +38 -16
- package/content/references/base/filter-system/index.md +106 -257
- package/content/references/base/filter-system/json-filtering.md +12 -2
- package/content/references/base/filter-system/list-operators.md +16 -2
- package/content/references/base/filter-system/logical-operators.md +13 -0
- package/content/references/base/filter-system/null-operators.md +13 -0
- package/content/references/base/filter-system/pattern-matching.md +12 -0
- package/content/references/base/filter-system/quick-reference.md +11 -2
- package/content/references/base/filter-system/range-operators.md +12 -0
- package/content/references/base/filter-system/tips.md +70 -133
- package/content/references/base/filter-system/use-cases.md +156 -233
- package/content/references/base/middlewares.md +35 -21
- package/content/references/base/models-reference.md +886 -0
- package/content/references/base/models.md +80 -1452
- package/content/references/base/repositories/advanced.md +156 -192
- package/content/references/base/repositories/index.md +77 -650
- package/content/references/base/repositories/mixins.md +22 -18
- package/content/references/base/repositories/relations.md +123 -171
- package/content/references/base/repositories/soft-deletable.md +58 -56
- package/content/references/base/secrets.md +263 -0
- package/content/references/base/services.md +2 -2
- package/content/references/configuration/environment-variables.md +48 -4
- package/content/references/configuration/index.md +49 -31
- package/content/references/quick-reference.md +3 -16
- package/content/references/utilities/crypto.md +35 -76
- package/content/references/utilities/date.md +33 -73
- package/content/references/utilities/index.md +1 -1
- package/content/references/utilities/jsx-reference.md +298 -0
- package/content/references/utilities/jsx.md +82 -525
- package/content/references/utilities/module.md +29 -62
- package/content/references/utilities/parse.md +34 -64
- package/content/references/utilities/performance.md +33 -58
- package/content/references/utilities/promise.md +28 -62
- package/content/references/utilities/request.md +57 -218
- package/content/references/utilities/schema.md +43 -137
- package/content/references/utilities/statuses-reference.md +361 -0
- package/content/references/utilities/statuses.md +63 -667
- package/package.json +8 -8
|
@@ -1,19 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Relations & Includes
|
|
3
|
+
description: Declaring model relations and eager-loading them with include
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
---
|
|
6
|
+
|
|
1
7
|
# Relations & Includes
|
|
2
8
|
|
|
3
|
-
|
|
9
|
+
Exhaustive reference for declaring `one`/`many` relations on a model and eager-loading them via `include` - one-to-one, one-to-many, and many-to-many. For the common tasks, start with the [Repositories overview](/references/base/repositories/).
|
|
10
|
+
|
|
11
|
+
**Files:**
|
|
4
12
|
|
|
13
|
+
- [`packages/core/src/base/repositories/query-schemas/filter.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/repositories/query-schemas/filter.ts) - `TFilter`, `TInclusion`
|
|
14
|
+
- [`packages/core/src/base/repositories/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/repositories/common/constants.ts) - `RelationTypes`
|
|
15
|
+
- [`packages/core/src/connectors/postgres/repositories/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/common/types.ts) - `TRelationConfig`
|
|
16
|
+
- [`packages/core/src/connectors/postgres/repositories/dialect/filter.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/dialect/filter.ts) - `FilterBuilder` (`resolveRelations`, `toInclude`)
|
|
17
|
+
- [`packages/core/src/connectors/postgres/repositories/dialect/relation.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/dialect/relation.ts) - `createRelations` (config -> Drizzle `relations()`)
|
|
5
18
|
|
|
6
19
|
## Basic Include
|
|
7
20
|
|
|
8
|
-
### One-to-
|
|
21
|
+
### One-to-many: user with posts
|
|
9
22
|
|
|
10
23
|
```typescript
|
|
11
24
|
// Fetch user with their posts
|
|
12
25
|
const user = await userRepository.findOne({
|
|
13
26
|
filter: {
|
|
14
27
|
where: { id: '123' },
|
|
15
|
-
include: [{ relation: 'posts' }]
|
|
16
|
-
}
|
|
28
|
+
include: [{ relation: 'posts' }],
|
|
29
|
+
},
|
|
17
30
|
});
|
|
18
31
|
|
|
19
32
|
// Result:
|
|
@@ -27,15 +40,15 @@ const user = await userRepository.findOne({
|
|
|
27
40
|
// }
|
|
28
41
|
```
|
|
29
42
|
|
|
30
|
-
### One-to-
|
|
43
|
+
### One-to-one: post with author
|
|
31
44
|
|
|
32
45
|
```typescript
|
|
33
46
|
// Fetch post with its author
|
|
34
47
|
const post = await postRepository.findOne({
|
|
35
48
|
filter: {
|
|
36
49
|
where: { id: 'p1' },
|
|
37
|
-
include: [{ relation: 'author' }]
|
|
38
|
-
}
|
|
50
|
+
include: [{ relation: 'author' }],
|
|
51
|
+
},
|
|
39
52
|
});
|
|
40
53
|
|
|
41
54
|
// Result:
|
|
@@ -47,82 +60,62 @@ const post = await postRepository.findOne({
|
|
|
47
60
|
// }
|
|
48
61
|
```
|
|
49
62
|
|
|
50
|
-
### Multiple
|
|
63
|
+
### Multiple relations
|
|
51
64
|
|
|
52
65
|
```typescript
|
|
53
66
|
// Fetch post with author AND comments
|
|
54
67
|
const post = await postRepository.findOne({
|
|
55
68
|
filter: {
|
|
56
69
|
where: { id: 'p1' },
|
|
57
|
-
include: [
|
|
58
|
-
|
|
59
|
-
{ relation: 'comments' }
|
|
60
|
-
]
|
|
61
|
-
}
|
|
70
|
+
include: [{ relation: 'author' }, { relation: 'comments' }],
|
|
71
|
+
},
|
|
62
72
|
});
|
|
63
73
|
```
|
|
64
74
|
|
|
65
75
|
> [!NOTE]
|
|
66
|
-
> When `include` is present in the filter, the repository uses the **Query API** (`connector.query`) instead of the Core API. This is handled automatically by the `canUseCoreAPI` check in `
|
|
67
|
-
|
|
76
|
+
> When `include` is present in the filter, the repository uses the **Query API** (`connector.query`) instead of the Core API. This is handled automatically by the `canUseCoreAPI` check in `ReadableRelationalRepository` - see [Performance Optimization](./advanced#performance-optimization).
|
|
68
77
|
|
|
69
78
|
## Scoped Includes
|
|
70
79
|
|
|
71
|
-
Apply filters, ordering, and limits to included relations using `scope
|
|
80
|
+
Apply filters, ordering, and limits to included relations using `scope` (itself a `TFilter`).
|
|
72
81
|
|
|
73
|
-
### Filter
|
|
82
|
+
### Filter related data
|
|
74
83
|
|
|
75
84
|
```typescript
|
|
76
85
|
// User with only published posts
|
|
77
86
|
const user = await userRepository.findOne({
|
|
78
87
|
filter: {
|
|
79
88
|
where: { id: '123' },
|
|
80
|
-
include: [{
|
|
81
|
-
|
|
82
|
-
scope: {
|
|
83
|
-
where: { status: 'published' }
|
|
84
|
-
}
|
|
85
|
-
}]
|
|
86
|
-
}
|
|
89
|
+
include: [{ relation: 'posts', scope: { where: { status: 'published' } } }],
|
|
90
|
+
},
|
|
87
91
|
});
|
|
88
92
|
```
|
|
89
93
|
|
|
90
|
-
### Order
|
|
94
|
+
### Order related data
|
|
91
95
|
|
|
92
96
|
```typescript
|
|
93
97
|
// User with posts ordered by date
|
|
94
98
|
const user = await userRepository.findOne({
|
|
95
99
|
filter: {
|
|
96
100
|
where: { id: '123' },
|
|
97
|
-
include: [{
|
|
98
|
-
|
|
99
|
-
scope: {
|
|
100
|
-
order: ['createdAt DESC']
|
|
101
|
-
}
|
|
102
|
-
}]
|
|
103
|
-
}
|
|
101
|
+
include: [{ relation: 'posts', scope: { order: ['createdAt DESC'] } }],
|
|
102
|
+
},
|
|
104
103
|
});
|
|
105
104
|
```
|
|
106
105
|
|
|
107
|
-
### Limit
|
|
106
|
+
### Limit related data
|
|
108
107
|
|
|
109
108
|
```typescript
|
|
110
109
|
// User with their 5 most recent posts
|
|
111
110
|
const user = await userRepository.findOne({
|
|
112
111
|
filter: {
|
|
113
112
|
where: { id: '123' },
|
|
114
|
-
include: [{
|
|
115
|
-
|
|
116
|
-
scope: {
|
|
117
|
-
order: ['createdAt DESC'],
|
|
118
|
-
limit: 5
|
|
119
|
-
}
|
|
120
|
-
}]
|
|
121
|
-
}
|
|
113
|
+
include: [{ relation: 'posts', scope: { order: ['createdAt DESC'], limit: 5 } }],
|
|
114
|
+
},
|
|
122
115
|
});
|
|
123
116
|
```
|
|
124
117
|
|
|
125
|
-
### Combined
|
|
118
|
+
### Combined scope options
|
|
126
119
|
|
|
127
120
|
```typescript
|
|
128
121
|
const user = await userRepository.findOne({
|
|
@@ -134,14 +127,14 @@ const user = await userRepository.findOne({
|
|
|
134
127
|
where: { status: 'published' },
|
|
135
128
|
order: ['createdAt DESC'],
|
|
136
129
|
limit: 10,
|
|
137
|
-
fields: ['id', 'title', 'createdAt']
|
|
138
|
-
}
|
|
139
|
-
}]
|
|
140
|
-
}
|
|
130
|
+
fields: ['id', 'title', 'createdAt'],
|
|
131
|
+
},
|
|
132
|
+
}],
|
|
133
|
+
},
|
|
141
134
|
});
|
|
142
135
|
```
|
|
143
136
|
|
|
144
|
-
### Skip
|
|
137
|
+
### Skip default filter on includes
|
|
145
138
|
|
|
146
139
|
Each inclusion can independently bypass the related model's default filter:
|
|
147
140
|
|
|
@@ -150,20 +143,16 @@ Each inclusion can independently bypass the related model's default filter:
|
|
|
150
143
|
const user = await userRepository.findOne({
|
|
151
144
|
filter: {
|
|
152
145
|
where: { id: '123' },
|
|
153
|
-
include: [{
|
|
154
|
-
|
|
155
|
-
shouldSkipDefaultFilter: true
|
|
156
|
-
}]
|
|
157
|
-
}
|
|
146
|
+
include: [{ relation: 'posts', shouldSkipDefaultFilter: true }],
|
|
147
|
+
},
|
|
158
148
|
});
|
|
159
149
|
```
|
|
160
150
|
|
|
161
|
-
|
|
162
151
|
## Nested Includes
|
|
163
152
|
|
|
164
|
-
Include relations of relations
|
|
153
|
+
Include relations of relations by nesting `include` inside `scope` (2 levels recommended, see [Performance Tips](#performance-tips)).
|
|
165
154
|
|
|
166
|
-
### Two-
|
|
155
|
+
### Two-level nesting
|
|
167
156
|
|
|
168
157
|
```typescript
|
|
169
158
|
// User -> Posts -> Comments
|
|
@@ -172,11 +161,9 @@ const user = await userRepository.findOne({
|
|
|
172
161
|
where: { id: '123' },
|
|
173
162
|
include: [{
|
|
174
163
|
relation: 'posts',
|
|
175
|
-
scope: {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}]
|
|
179
|
-
}
|
|
164
|
+
scope: { include: [{ relation: 'comments' }] },
|
|
165
|
+
}],
|
|
166
|
+
},
|
|
180
167
|
});
|
|
181
168
|
|
|
182
169
|
// Result:
|
|
@@ -196,7 +183,9 @@ const user = await userRepository.findOne({
|
|
|
196
183
|
// }
|
|
197
184
|
```
|
|
198
185
|
|
|
199
|
-
### Many-to-
|
|
186
|
+
### Many-to-many through junction
|
|
187
|
+
|
|
188
|
+
This is the pattern `examples/vert` uses for `Product` <-> `SaleChannel` through the `SaleChannelProduct` junction table:
|
|
200
189
|
|
|
201
190
|
```typescript
|
|
202
191
|
// Product -> SaleChannelProduct (junction) -> SaleChannel
|
|
@@ -205,11 +194,9 @@ const product = await productRepository.findOne({
|
|
|
205
194
|
where: { id: 'prod1' },
|
|
206
195
|
include: [{
|
|
207
196
|
relation: 'saleChannelProducts',
|
|
208
|
-
scope: {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}]
|
|
212
|
-
}
|
|
197
|
+
scope: { include: [{ relation: 'saleChannel' }] },
|
|
198
|
+
}],
|
|
199
|
+
},
|
|
213
200
|
});
|
|
214
201
|
|
|
215
202
|
// Result:
|
|
@@ -231,43 +218,45 @@ const product = await productRepository.findOne({
|
|
|
231
218
|
// }
|
|
232
219
|
```
|
|
233
220
|
|
|
234
|
-
>
|
|
235
|
-
|
|
221
|
+
> [!WARNING] Performance
|
|
222
|
+
> Each nested `include` adds SQL complexity. **Maximum 2 levels recommended.** For deeper relationships, use multiple queries - see [Performance Tips](#performance-tips).
|
|
236
223
|
|
|
237
224
|
## Defining Relations
|
|
238
225
|
|
|
239
226
|
Relations are declared on the model as a static `relations` resolver returning an array of `TRelationConfig`. The framework translates them to Drizzle ORM relations internally during schema discovery.
|
|
240
227
|
|
|
241
|
-
### Relation
|
|
228
|
+
### Relation config type
|
|
242
229
|
|
|
243
230
|
```typescript
|
|
244
231
|
type TRelationConfig = {
|
|
245
|
-
name: string;
|
|
232
|
+
name: string; // Relation name used in includes
|
|
246
233
|
} & (
|
|
247
234
|
| {
|
|
248
235
|
type: 'one'; // one-to-one or many-to-one
|
|
249
236
|
schema: TTableSchemaWithId;
|
|
250
|
-
metadata: { fields, references, relationName? };
|
|
237
|
+
metadata: { fields, references, relationName? }; // Drizzle one() params
|
|
251
238
|
}
|
|
252
239
|
| {
|
|
253
240
|
type: 'many'; // one-to-many
|
|
254
241
|
schema: TTableSchemaWithId;
|
|
255
|
-
metadata: { relationName? };
|
|
242
|
+
metadata: { relationName? }; // Drizzle many() params
|
|
256
243
|
}
|
|
257
244
|
);
|
|
258
245
|
```
|
|
259
246
|
|
|
260
|
-
|
|
247
|
+
`metadata`'s shape is inferred directly from Drizzle's own `one()`/`many()` relation-helper parameter types, not hand-duplicated.
|
|
248
|
+
|
|
249
|
+
### In your model
|
|
261
250
|
|
|
262
251
|
```typescript
|
|
263
252
|
// src/models/user.model.ts
|
|
264
253
|
import { model, RelationTypes } from '@venizia/ignis';
|
|
265
|
-
import {
|
|
254
|
+
import { BaseEntity, TRelationConfig } from '@venizia/ignis/postgres';
|
|
266
255
|
import { pgTable, text } from 'drizzle-orm/pg-core';
|
|
267
256
|
import { Post } from './post.model';
|
|
268
257
|
|
|
269
258
|
@model({ type: 'entity' })
|
|
270
|
-
export class User extends
|
|
259
|
+
export class User extends BaseEntity<typeof User.schema> {
|
|
271
260
|
static override schema = pgTable('User', {
|
|
272
261
|
id: text('id').primaryKey(),
|
|
273
262
|
name: text('name').notNull(),
|
|
@@ -287,21 +276,23 @@ export class User extends BasePostgresEntity<typeof User.schema> {
|
|
|
287
276
|
|
|
288
277
|
The resolver form (`() => [...]`) defers evaluation until all `@model` classes are registered, avoiding circular-import ordering issues between related models.
|
|
289
278
|
|
|
290
|
-
### Relation
|
|
279
|
+
### Relation types
|
|
291
280
|
|
|
292
|
-
| Type | Drizzle
|
|
281
|
+
| Type | Drizzle function | Description | Example |
|
|
293
282
|
|------|------------------|-------------|---------|
|
|
294
|
-
| `'one'` | `one()` | One-to-one or many-to-one | Post has one Author, User has one Profile |
|
|
295
|
-
| `'many'` | `many()` | One-to-many | User has many Posts |
|
|
283
|
+
| `RelationTypes.ONE` (`'one'`) | `one()` | One-to-one or many-to-one | Post has one Author, User has one Profile |
|
|
284
|
+
| `RelationTypes.MANY` (`'many'`) | `many()` | One-to-many | User has many Posts |
|
|
296
285
|
|
|
297
286
|
> [!NOTE]
|
|
298
|
-
> Unlike LoopBack 4's `hasMany`/`hasOne`/`belongsTo` terminology, IGNIS uses Drizzle ORM's relation model which has only `one` and `many` types. A "belongsTo" relationship is expressed as `type:
|
|
287
|
+
> Unlike LoopBack 4's `hasMany`/`hasOne`/`belongsTo` terminology, IGNIS uses Drizzle ORM's relation model, which has only `one` and `many` types. A "belongsTo" relationship is expressed as `type: RelationTypes.ONE` with `fields` (local FK) and `references` (remote PK) in `metadata`.
|
|
299
288
|
|
|
300
|
-
### Example:
|
|
289
|
+
### Example: model with both types
|
|
290
|
+
|
|
291
|
+
This mirrors `examples/vert`'s `SaleChannelProduct` junction model - one `ONE` relation per foreign key, plus a `MANY` relation elsewhere:
|
|
301
292
|
|
|
302
293
|
```typescript
|
|
303
294
|
@model({ type: 'entity' })
|
|
304
|
-
export class Post extends
|
|
295
|
+
export class Post extends BaseEntity<typeof Post.schema> {
|
|
305
296
|
static override schema = postTable;
|
|
306
297
|
|
|
307
298
|
static override relations = (): TRelationConfig[] => [
|
|
@@ -324,14 +315,16 @@ export class Post extends BasePostgresEntity<typeof Post.schema> {
|
|
|
324
315
|
}
|
|
325
316
|
```
|
|
326
317
|
|
|
327
|
-
### How
|
|
328
|
-
|
|
329
|
-
During schema discovery, `MetadataRegistry` resolves each model's `relations` array and passes it to the `createRelations` helper (`packages/core/src/connectors/postgres/repositories/operators/relation.ts`), which builds the actual Drizzle `relations()` definition registered on the DataSource schema. You do not call `createRelations` yourself in application code.
|
|
318
|
+
### How configs become Drizzle relations
|
|
330
319
|
|
|
320
|
+
- **`MetadataRegistry` resolves the configs.** During schema discovery, it resolves each model's `relations` array and passes it to the `createRelations` helper ([`packages/core/src/connectors/postgres/repositories/dialect/relation.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/dialect/relation.ts)).
|
|
321
|
+
- **`createRelations` builds the Drizzle definition.** It produces the actual Drizzle `relations()` definition, registered on the DataSource schema.
|
|
322
|
+
- **Application code never calls it.** `createRelations` is internal - you only declare the `relations` resolver on the model.
|
|
331
323
|
|
|
332
324
|
## Auto-Resolution
|
|
333
325
|
|
|
334
|
-
Relations are automatically resolved from the entity's static `relations` property via `MetadataRegistry
|
|
326
|
+
- **Resolved from the model, not the constructor.** Relations are automatically resolved from the entity's static `relations` property via `MetadataRegistry` - no need to pass them in the repository constructor.
|
|
327
|
+
- **Memoized per schema.** `FilterBuilder.resolveRelations()` reads and caches them in a `WeakMap` when building include queries.
|
|
335
328
|
|
|
336
329
|
```typescript
|
|
337
330
|
@repository({ model: User, dataSource: PostgresDataSource })
|
|
@@ -340,53 +333,48 @@ export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {
|
|
|
340
333
|
}
|
|
341
334
|
```
|
|
342
335
|
|
|
343
|
-
|
|
344
336
|
## Hidden Properties in Relations
|
|
345
337
|
|
|
346
|
-
When building include queries,
|
|
338
|
+
When building include queries, `FilterBuilder.toInclude()` automatically:
|
|
347
339
|
|
|
348
340
|
1. Resolves hidden properties for each related model via `resolveHiddenProperties()`.
|
|
349
|
-
2. Resolves the default filter for each related model via `resolveDefaultFilter()
|
|
350
|
-
3. Merges the default filter with any user-provided `scope`.
|
|
341
|
+
2. Resolves the default filter for each related model via `resolveDefaultFilter()` (unless `shouldSkipDefaultFilter` is set on that inclusion).
|
|
342
|
+
3. Merges the default filter with any user-provided `scope` via `mergeFilter()`.
|
|
351
343
|
4. Excludes hidden columns from the nested query's `columns` selection.
|
|
352
344
|
|
|
353
345
|
```typescript
|
|
354
346
|
// User model has hiddenProperties: ['password']
|
|
355
347
|
const post = await postRepository.findOne({
|
|
356
|
-
filter: {
|
|
357
|
-
include: [{ relation: 'author' }]
|
|
358
|
-
}
|
|
348
|
+
filter: { include: [{ relation: 'author' }] },
|
|
359
349
|
});
|
|
360
350
|
|
|
361
351
|
// post.author will NOT include password - excluded at SQL level
|
|
362
352
|
```
|
|
363
353
|
|
|
354
|
+
See [Hidden Properties](./advanced#hidden-properties) for the top-level equivalent.
|
|
364
355
|
|
|
365
356
|
## Type Safety with Generics
|
|
366
357
|
|
|
367
358
|
For queries with `include`, use generic type overrides for full type safety:
|
|
368
359
|
|
|
369
360
|
```typescript
|
|
370
|
-
// Define the expected return type
|
|
371
361
|
type UserWithPosts = User & {
|
|
372
362
|
posts: Post[];
|
|
373
363
|
};
|
|
374
364
|
|
|
375
|
-
// Use generic override
|
|
376
365
|
const user = await userRepository.findOne<UserWithPosts>({
|
|
377
366
|
filter: {
|
|
378
367
|
where: { id: '123' },
|
|
379
|
-
include: [{ relation: 'posts' }]
|
|
380
|
-
}
|
|
368
|
+
include: [{ relation: 'posts' }],
|
|
369
|
+
},
|
|
381
370
|
});
|
|
382
371
|
|
|
383
|
-
// TypeScript knows the structure!
|
|
384
372
|
if (user) {
|
|
385
|
-
console.log(user.posts[0].title);
|
|
373
|
+
console.log(user.posts[0].title); // Fully typed
|
|
386
374
|
}
|
|
387
375
|
```
|
|
388
376
|
|
|
389
|
-
### Nested
|
|
377
|
+
### Nested relations type
|
|
390
378
|
|
|
391
379
|
```typescript
|
|
392
380
|
type ProductWithChannels = Product & {
|
|
@@ -400,131 +388,107 @@ const product = await productRepository.findOne<ProductWithChannels>({
|
|
|
400
388
|
where: { id: 'prod1' },
|
|
401
389
|
include: [{
|
|
402
390
|
relation: 'saleChannelProducts',
|
|
403
|
-
scope: {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
}]
|
|
407
|
-
}
|
|
391
|
+
scope: { include: [{ relation: 'saleChannel' }] },
|
|
392
|
+
}],
|
|
393
|
+
},
|
|
408
394
|
});
|
|
409
395
|
|
|
410
|
-
// Fully typed access
|
|
411
|
-
product?.saleChannelProducts[0].saleChannel.name;
|
|
396
|
+
product?.saleChannelProducts[0].saleChannel.name; // Fully typed access
|
|
412
397
|
```
|
|
413
398
|
|
|
414
|
-
|
|
415
399
|
## TInclusion Type Reference
|
|
416
400
|
|
|
417
401
|
Each element in the `include` array has this shape:
|
|
418
402
|
|
|
419
403
|
```typescript
|
|
420
404
|
type TInclusion = {
|
|
421
|
-
relation: string;
|
|
422
|
-
scope?: TFilter;
|
|
423
|
-
shouldSkipDefaultFilter?: boolean;
|
|
405
|
+
relation: string; // Name of the relation to include
|
|
406
|
+
scope?: TFilter; // Optional nested filter (where, order, limit, fields, include)
|
|
407
|
+
shouldSkipDefaultFilter?: boolean; // Skip the related model's default filter
|
|
424
408
|
};
|
|
425
409
|
```
|
|
426
410
|
|
|
427
|
-
|
|
428
411
|
## Common Patterns
|
|
429
412
|
|
|
430
|
-
### Find
|
|
413
|
+
### Find all with count of relations
|
|
431
414
|
|
|
432
415
|
```typescript
|
|
433
416
|
// Get users with post count
|
|
434
417
|
const users = await userRepository.find({
|
|
435
418
|
filter: {
|
|
436
|
-
include: [{
|
|
437
|
-
|
|
438
|
-
scope: { fields: ['id'] } // Only fetch IDs to minimize data
|
|
439
|
-
}]
|
|
440
|
-
}
|
|
419
|
+
include: [{ relation: 'posts', scope: { fields: ['id'] } }], // Only fetch IDs to minimize data
|
|
420
|
+
},
|
|
441
421
|
});
|
|
442
422
|
|
|
443
|
-
// Calculate counts
|
|
444
423
|
const usersWithCounts = users.map(user => ({
|
|
445
424
|
...user,
|
|
446
|
-
postCount: (user as any).posts?.length ?? 0
|
|
425
|
+
postCount: (user as any).posts?.length ?? 0,
|
|
447
426
|
}));
|
|
448
427
|
```
|
|
449
428
|
|
|
450
|
-
### Conditional
|
|
429
|
+
### Conditional include
|
|
451
430
|
|
|
452
431
|
```typescript
|
|
453
432
|
async function getUser(id: string, includePosts: boolean) {
|
|
454
|
-
const include = includePosts
|
|
455
|
-
? [{ relation: 'posts' }]
|
|
456
|
-
: [];
|
|
433
|
+
const include = includePosts ? [{ relation: 'posts' }] : [];
|
|
457
434
|
|
|
458
435
|
return userRepository.findOne({
|
|
459
|
-
filter: {
|
|
460
|
-
where: { id },
|
|
461
|
-
include
|
|
462
|
-
}
|
|
436
|
+
filter: { where: { id }, include },
|
|
463
437
|
});
|
|
464
438
|
}
|
|
465
439
|
```
|
|
466
440
|
|
|
467
|
-
|
|
468
441
|
## Error Handling
|
|
469
442
|
|
|
470
|
-
### Relation
|
|
443
|
+
### Relation not found
|
|
471
444
|
|
|
472
445
|
If you try to include a relation that doesn't exist:
|
|
473
446
|
|
|
474
447
|
```typescript
|
|
475
448
|
// Error: [FilterBuilder][toInclude] Relation NOT FOUND | relation: 'nonExistent'
|
|
476
449
|
await userRepository.find({
|
|
477
|
-
filter: {
|
|
478
|
-
include: [{ relation: 'nonExistent' }]
|
|
479
|
-
}
|
|
450
|
+
filter: { include: [{ relation: 'nonExistent' }] },
|
|
480
451
|
});
|
|
481
452
|
```
|
|
482
453
|
|
|
483
|
-
**Fix:**
|
|
454
|
+
**Fix:** check your model's `relations` definition and ensure the relation name matches.
|
|
484
455
|
|
|
485
|
-
### Invalid
|
|
456
|
+
### Invalid include format
|
|
486
457
|
|
|
487
458
|
```typescript
|
|
488
459
|
// Error: [FilterBuilder][toInclude] Invalid include format | include: ...
|
|
489
460
|
```
|
|
490
461
|
|
|
491
|
-
**Fix:**
|
|
462
|
+
**Fix:** ensure each include element has a `relation` string property.
|
|
492
463
|
|
|
493
|
-
### Schema
|
|
464
|
+
### Schema key mismatch
|
|
494
465
|
|
|
495
466
|
```
|
|
496
467
|
Error: [UserRepository] Schema key mismatch | Entity name 'User' not found
|
|
497
468
|
in connector.query | Available keys: [Post, Comment]
|
|
498
469
|
```
|
|
499
470
|
|
|
500
|
-
**Fix:**
|
|
501
|
-
|
|
471
|
+
**Fix:** ensure your model's `TABLE_NAME` matches the schema registration - see [Query Interface Validation](./advanced#query-interface-validation).
|
|
502
472
|
|
|
503
473
|
## Performance Tips
|
|
504
474
|
|
|
505
|
-
1. **Limit nesting depth** -
|
|
506
|
-
2. **Use `fields` in scope** -
|
|
507
|
-
3. **Use `limit` in scope** -
|
|
508
|
-
4. **Consider separate queries** -
|
|
509
|
-
5. **Use `shouldSkipDefaultFilter` sparingly** -
|
|
475
|
+
1. **Limit nesting depth** - max 2 levels recommended.
|
|
476
|
+
2. **Use `fields` in scope** - only fetch needed columns.
|
|
477
|
+
3. **Use `limit` in scope** - don't fetch unbounded related data.
|
|
478
|
+
4. **Consider separate queries** - for complex data needs, multiple simple queries often outperform one complex nested query.
|
|
479
|
+
5. **Use `shouldSkipDefaultFilter` sparingly** - only when you explicitly need filtered-out records.
|
|
510
480
|
|
|
511
481
|
```typescript
|
|
512
482
|
// Instead of deep nesting, use separate queries
|
|
513
483
|
const user = await userRepository.findById({ id: '123' });
|
|
514
484
|
const posts = await postRepository.find({
|
|
515
|
-
filter: {
|
|
516
|
-
where: { authorId: '123' },
|
|
517
|
-
limit: 10
|
|
518
|
-
}
|
|
485
|
+
filter: { where: { authorId: '123' }, limit: 10 },
|
|
519
486
|
});
|
|
520
487
|
const comments = await commentRepository.find({
|
|
521
|
-
filter: {
|
|
522
|
-
where: { postId: { inq: posts.map(p => p.id) } }
|
|
523
|
-
}
|
|
488
|
+
filter: { where: { postId: { inq: posts.map(p => p.id) } } },
|
|
524
489
|
});
|
|
525
490
|
```
|
|
526
491
|
|
|
527
|
-
|
|
528
492
|
## Quick Reference
|
|
529
493
|
|
|
530
494
|
| Want to... | Code |
|
|
@@ -538,23 +502,11 @@ const comments = await commentRepository.find({
|
|
|
538
502
|
| Select fields | `include: [{ relation: 'posts', scope: { fields: ['id', 'title'] } }]` |
|
|
539
503
|
| Skip default filter | `include: [{ relation: 'posts', shouldSkipDefaultFilter: true }]` |
|
|
540
504
|
|
|
505
|
+
## See also
|
|
541
506
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
- [
|
|
545
|
-
- [
|
|
546
|
-
- [
|
|
547
|
-
|
|
548
|
-
## See Also
|
|
549
|
-
|
|
550
|
-
- **Related Concepts:**
|
|
551
|
-
- [Repositories Overview](./index) - Core repository operations
|
|
552
|
-
- [Models](/guides/core-concepts/persistent/models) - Defining model relationships
|
|
553
|
-
|
|
554
|
-
- **Related Topics:**
|
|
555
|
-
- [Advanced Features](./advanced) - Hidden properties, transactions
|
|
556
|
-
- [Repository Mixins (Removed)](./mixins) - Where default-filter and fields-visibility behavior lives now
|
|
557
|
-
- [Filter System](/references/base/filter-system/) - Query operators
|
|
558
|
-
|
|
559
|
-
- **External Resources:**
|
|
560
|
-
- [Drizzle ORM Relations](https://orm.drizzle.team/docs/rqb#relations) - Relation definition guide
|
|
507
|
+
- [Repositories overview](/references/base/repositories/) - CRUD basics, common tasks
|
|
508
|
+
- [Advanced Features](./advanced) - transactions, hidden properties, performance
|
|
509
|
+
- [Repository Mixins (Removed)](./mixins) - where default-filter and fields-visibility behavior lives now
|
|
510
|
+
- [Filter System](/references/base/filter-system/) - every `where` operator, ordering, pagination
|
|
511
|
+
- [Models - Full Reference](/references/base/models-reference) - `@model`, entity hierarchy, schema enrichers
|
|
512
|
+
- [Drizzle ORM Relations](https://orm.drizzle.team/docs/rqb#relations) - relation definition guide
|