@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.
Files changed (142) hide show
  1. package/README.md +14 -14
  2. package/content/best-practices/api-usage-examples.md +39 -19
  3. package/content/best-practices/architectural-patterns.md +22 -11
  4. package/content/best-practices/architecture-decisions.md +29 -16
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
  6. package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
  7. package/content/best-practices/code-style-standards/control-flow.md +33 -1
  8. package/content/best-practices/code-style-standards/documentation.md +28 -8
  9. package/content/best-practices/code-style-standards/function-patterns.md +15 -4
  10. package/content/best-practices/code-style-standards/index.md +7 -2
  11. package/content/best-practices/code-style-standards/naming-conventions.md +26 -2
  12. package/content/best-practices/code-style-standards/route-definitions.md +9 -5
  13. package/content/best-practices/code-style-standards/tooling.md +14 -1
  14. package/content/best-practices/code-style-standards/type-safety.md +39 -6
  15. package/content/best-practices/common-pitfalls.md +59 -35
  16. package/content/best-practices/contribution-workflow.md +6 -2
  17. package/content/best-practices/data-modeling.md +78 -62
  18. package/content/best-practices/deployment-strategies.md +56 -19
  19. package/content/best-practices/error-handling.md +182 -93
  20. package/content/best-practices/index.md +6 -0
  21. package/content/best-practices/performance-optimization.md +26 -13
  22. package/content/best-practices/security-guidelines.md +35 -9
  23. package/content/best-practices/testing-strategies.md +7 -2
  24. package/content/best-practices/troubleshooting-tips.md +27 -17
  25. package/content/extensions/components/api-reference.md +107 -322
  26. package/content/extensions/components/authentication/api.md +454 -603
  27. package/content/extensions/components/authentication/errors.md +121 -498
  28. package/content/extensions/components/authentication/index.md +88 -801
  29. package/content/extensions/components/authentication/usage.md +207 -956
  30. package/content/extensions/components/authorization/api.md +736 -656
  31. package/content/extensions/components/authorization/errors.md +168 -206
  32. package/content/extensions/components/authorization/index.md +82 -797
  33. package/content/extensions/components/authorization/usage.md +194 -527
  34. package/content/extensions/components/health-check.md +71 -243
  35. package/content/extensions/components/mail/api.md +504 -287
  36. package/content/extensions/components/mail/errors.md +73 -61
  37. package/content/extensions/components/mail/index.md +96 -467
  38. package/content/extensions/components/mail/usage.md +130 -172
  39. package/content/extensions/components/request-tracker.md +66 -173
  40. package/content/extensions/components/socket-io/api.md +195 -13
  41. package/content/extensions/components/socket-io/errors.md +3 -3
  42. package/content/extensions/components/socket-io/index.md +50 -337
  43. package/content/extensions/components/socket-io/usage.md +143 -26
  44. package/content/extensions/components/static-asset/api.md +410 -142
  45. package/content/extensions/components/static-asset/errors.md +110 -53
  46. package/content/extensions/components/static-asset/index.md +79 -608
  47. package/content/extensions/components/static-asset/usage.md +180 -300
  48. package/content/extensions/components/websocket/api.md +275 -399
  49. package/content/extensions/components/websocket/errors.md +47 -56
  50. package/content/extensions/components/websocket/index.md +74 -407
  51. package/content/extensions/components/websocket/usage.md +110 -341
  52. package/content/extensions/helpers/cron/index.md +51 -160
  53. package/content/extensions/helpers/crypto/index.md +62 -483
  54. package/content/extensions/helpers/crypto/reference.md +456 -0
  55. package/content/extensions/helpers/env/index.md +60 -178
  56. package/content/extensions/helpers/error/index.md +221 -207
  57. package/content/extensions/helpers/inversion/index.md +65 -556
  58. package/content/extensions/helpers/inversion/reference.md +522 -0
  59. package/content/extensions/helpers/kafka/admin.md +20 -1
  60. package/content/extensions/helpers/kafka/compile-binary.md +41 -35
  61. package/content/extensions/helpers/kafka/consumer.md +54 -20
  62. package/content/extensions/helpers/kafka/examples.md +21 -16
  63. package/content/extensions/helpers/kafka/index.md +80 -610
  64. package/content/extensions/helpers/kafka/producer.md +134 -5
  65. package/content/extensions/helpers/kafka/schema-registry.md +45 -70
  66. package/content/extensions/helpers/logger/hf-logger.md +193 -0
  67. package/content/extensions/helpers/logger/index.md +64 -563
  68. package/content/extensions/helpers/logger/pino.md +85 -0
  69. package/content/extensions/helpers/logger/reference.md +746 -0
  70. package/content/extensions/helpers/network/api.md +241 -195
  71. package/content/extensions/helpers/network/index.md +72 -530
  72. package/content/extensions/helpers/queue/index.md +72 -900
  73. package/content/extensions/helpers/queue/reference.md +467 -0
  74. package/content/extensions/helpers/redis/index.md +73 -645
  75. package/content/extensions/helpers/redis/reference.md +727 -0
  76. package/content/extensions/helpers/secrets/index.md +66 -0
  77. package/content/extensions/helpers/socket-io/api.md +305 -203
  78. package/content/extensions/helpers/socket-io/index.md +66 -432
  79. package/content/extensions/helpers/storage/api.md +564 -462
  80. package/content/extensions/helpers/storage/index.md +77 -573
  81. package/content/extensions/helpers/types/index.md +66 -499
  82. package/content/extensions/helpers/types/reference.md +650 -0
  83. package/content/extensions/helpers/uid/index.md +58 -227
  84. package/content/extensions/helpers/websocket/api.md +329 -216
  85. package/content/extensions/helpers/websocket/index.md +65 -503
  86. package/content/extensions/helpers/worker-thread/index.md +58 -396
  87. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  88. package/content/guides/core-concepts/persistent/models.md +1 -1
  89. package/content/guides/core-concepts/persistent/search-typesense.md +3 -3
  90. package/content/guides/core-concepts/persistent/transactions.md +1 -1
  91. package/content/guides/core-concepts/secrets-vault.md +177 -0
  92. package/content/guides/core-concepts/services.md +1 -1
  93. package/content/guides/migrations/redis-helpers-migration.md +1 -1
  94. package/content/guides/migrations/unified-connectors-migration.md +2 -2
  95. package/content/guides/tutorials/ecommerce-api.md +3 -8
  96. package/content/references/base/application.md +1 -1
  97. package/content/references/base/connectors.md +79 -136
  98. package/content/references/base/datasources-reference.md +599 -0
  99. package/content/references/base/datasources.md +84 -444
  100. package/content/references/base/dependency-injection.md +17 -39
  101. package/content/references/base/filter-system/application-usage.md +69 -121
  102. package/content/references/base/filter-system/array-operators.md +12 -0
  103. package/content/references/base/filter-system/comparison-operators.md +12 -0
  104. package/content/references/base/filter-system/default-filter.md +136 -348
  105. package/content/references/base/filter-system/fields-order-pagination.md +38 -16
  106. package/content/references/base/filter-system/index.md +106 -257
  107. package/content/references/base/filter-system/json-filtering.md +12 -2
  108. package/content/references/base/filter-system/list-operators.md +16 -2
  109. package/content/references/base/filter-system/logical-operators.md +13 -0
  110. package/content/references/base/filter-system/null-operators.md +13 -0
  111. package/content/references/base/filter-system/pattern-matching.md +12 -0
  112. package/content/references/base/filter-system/quick-reference.md +11 -2
  113. package/content/references/base/filter-system/range-operators.md +12 -0
  114. package/content/references/base/filter-system/tips.md +70 -133
  115. package/content/references/base/filter-system/use-cases.md +156 -233
  116. package/content/references/base/middlewares.md +35 -21
  117. package/content/references/base/models-reference.md +886 -0
  118. package/content/references/base/models.md +80 -1452
  119. package/content/references/base/repositories/advanced.md +156 -192
  120. package/content/references/base/repositories/index.md +77 -650
  121. package/content/references/base/repositories/mixins.md +22 -18
  122. package/content/references/base/repositories/relations.md +123 -171
  123. package/content/references/base/repositories/soft-deletable.md +58 -56
  124. package/content/references/base/secrets.md +263 -0
  125. package/content/references/base/services.md +2 -2
  126. package/content/references/configuration/environment-variables.md +48 -4
  127. package/content/references/configuration/index.md +49 -31
  128. package/content/references/quick-reference.md +3 -16
  129. package/content/references/utilities/crypto.md +35 -76
  130. package/content/references/utilities/date.md +33 -73
  131. package/content/references/utilities/index.md +1 -1
  132. package/content/references/utilities/jsx-reference.md +298 -0
  133. package/content/references/utilities/jsx.md +82 -525
  134. package/content/references/utilities/module.md +29 -62
  135. package/content/references/utilities/parse.md +34 -64
  136. package/content/references/utilities/performance.md +33 -58
  137. package/content/references/utilities/promise.md +28 -62
  138. package/content/references/utilities/request.md +57 -218
  139. package/content/references/utilities/schema.md +43 -137
  140. package/content/references/utilities/statuses-reference.md +361 -0
  141. package/content/references/utilities/statuses.md +63 -667
  142. 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
- Fetch related data using `include` for eager loading. This guide covers one-to-one, one-to-many, and many-to-many relationships.
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-Many: User with Posts
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-One: Post with Author
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 Relations
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
- { relation: 'author' },
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 `ReadableRepository`.
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 Related Data
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
- relation: 'posts',
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 Related Data
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
- relation: 'posts',
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 Related Data
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
- relation: 'posts',
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 Scope Options
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 Default Filter on Includes
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
- relation: 'posts',
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 (up to 2 levels recommended):
153
+ Include relations of relations by nesting `include` inside `scope` (2 levels recommended, see [Performance Tips](#performance-tips)).
165
154
 
166
- ### Two-Level Nesting
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
- include: [{ relation: 'comments' }]
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-Many Through Junction
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
- include: [{ relation: 'saleChannel' }]
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
- > **Performance Warning:** Each nested `include` adds SQL complexity. **Maximum 2 levels recommended.** For deeper relationships, use multiple queries.
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 Config Type
228
+ ### Relation config type
242
229
 
243
230
  ```typescript
244
231
  type TRelationConfig = {
245
- name: string; // Relation name used in includes
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
- ### In Your Model
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 { BasePostgresEntity, TRelationConfig } from '@venizia/ignis/postgres';
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 BasePostgresEntity<typeof User.schema> {
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 Types
279
+ ### Relation types
291
280
 
292
- | Type | Drizzle Function | Description | Example |
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: 'one'` with `fields` (local FK) and `references` (remote PK) in the metadata.
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: Post Model with Both Types
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 BasePostgresEntity<typeof Post.schema> {
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 Configs Become Drizzle Relations
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`. The `FilterBuilder.resolveRelations()` method reads them when building include queries. No need to pass them in the repository constructor:
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, the `FilterBuilder.toInclude()` method automatically:
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); // Fully typed
373
+ console.log(user.posts[0].title); // Fully typed
386
374
  }
387
375
  ```
388
376
 
389
- ### Nested Relations Type
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
- include: [{ relation: 'saleChannel' }]
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; // Name of the relation to include
422
- scope?: TFilter; // Optional nested filter (where, order, limit, fields, include)
423
- shouldSkipDefaultFilter?: boolean; // Skip the related model's default filter
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 All with Count of Relations
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
- relation: 'posts',
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 Include
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 Not Found
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:** Check your model's `relations` definition and ensure the relation name matches.
454
+ **Fix:** check your model's `relations` definition and ensure the relation name matches.
484
455
 
485
- ### Invalid Include Format
456
+ ### Invalid include format
486
457
 
487
458
  ```typescript
488
459
  // Error: [FilterBuilder][toInclude] Invalid include format | include: ...
489
460
  ```
490
461
 
491
- **Fix:** Ensure each include element has a `relation` string property.
462
+ **Fix:** ensure each include element has a `relation` string property.
492
463
 
493
- ### Schema Key Mismatch
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:** Ensure your model's `TABLE_NAME` matches the schema registration.
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** - Max 2 levels recommended
506
- 2. **Use `fields` in scope** - Only fetch needed columns
507
- 3. **Use `limit` in scope** - Don't fetch unbounded related data
508
- 4. **Consider separate queries** - For complex data needs, multiple simple queries often outperform one complex nested query
509
- 5. **Use `shouldSkipDefaultFilter` sparingly** - Only when you explicitly need filtered-out records
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
- ## Next Steps
543
-
544
- - [JSON Path Filtering](../filter-system/json-filtering) - Query JSONB columns
545
- - [Array Operators](../filter-system/array-operators) - PostgreSQL array queries
546
- - [Advanced Features](./advanced.md) - Transactions, hidden props
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