@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.
Files changed (131) hide show
  1. package/README.md +1 -1
  2. package/package.json +4 -2
  3. package/wiki/best-practices/api-usage-examples.md +591 -0
  4. package/wiki/best-practices/architectural-patterns.md +415 -0
  5. package/wiki/best-practices/architecture-decisions.md +488 -0
  6. package/wiki/{get-started/best-practices → best-practices}/code-style-standards.md +406 -17
  7. package/wiki/{get-started/best-practices → best-practices}/common-pitfalls.md +109 -4
  8. package/wiki/{get-started/best-practices → best-practices}/contribution-workflow.md +34 -7
  9. package/wiki/best-practices/data-modeling.md +376 -0
  10. package/wiki/best-practices/deployment-strategies.md +698 -0
  11. package/wiki/best-practices/index.md +27 -0
  12. package/wiki/best-practices/performance-optimization.md +196 -0
  13. package/wiki/best-practices/security-guidelines.md +218 -0
  14. package/wiki/{get-started/best-practices → best-practices}/troubleshooting-tips.md +97 -1
  15. package/wiki/changelogs/2025-12-16-initial-architecture.md +1 -1
  16. package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +1 -1
  17. package/wiki/changelogs/2025-12-17-refactor.md +1 -1
  18. package/wiki/changelogs/2025-12-18-performance-optimizations.md +5 -5
  19. package/wiki/changelogs/2025-12-18-repository-validation-security.md +13 -7
  20. package/wiki/changelogs/2025-12-26-nested-relations-and-generics.md +2 -2
  21. package/wiki/changelogs/2025-12-29-dynamic-binding-registration.md +104 -0
  22. package/wiki/changelogs/2025-12-29-snowflake-uid-helper.md +100 -0
  23. package/wiki/changelogs/2025-12-30-repository-enhancements.md +214 -0
  24. package/wiki/changelogs/2025-12-31-json-path-filtering-array-operators.md +214 -0
  25. package/wiki/changelogs/2025-12-31-string-id-custom-generator.md +137 -0
  26. package/wiki/changelogs/2026-01-02-default-filter-and-repository-mixins.md +418 -0
  27. package/wiki/changelogs/index.md +6 -0
  28. package/wiki/changelogs/planned-schema-migrator.md +0 -8
  29. package/wiki/{get-started/core-concepts → guides/core-concepts/application}/bootstrapping.md +18 -5
  30. package/wiki/{get-started/core-concepts/application.md → guides/core-concepts/application/index.md} +47 -104
  31. package/wiki/guides/core-concepts/components-guide.md +509 -0
  32. package/wiki/{get-started → guides}/core-concepts/components.md +24 -17
  33. package/wiki/{get-started → guides}/core-concepts/controllers.md +30 -13
  34. package/wiki/{get-started → guides}/core-concepts/dependency-injection.md +97 -0
  35. package/wiki/guides/core-concepts/persistent/datasources.md +179 -0
  36. package/wiki/guides/core-concepts/persistent/index.md +119 -0
  37. package/wiki/guides/core-concepts/persistent/models.md +241 -0
  38. package/wiki/guides/core-concepts/persistent/repositories.md +219 -0
  39. package/wiki/guides/core-concepts/persistent/transactions.md +170 -0
  40. package/wiki/{get-started → guides}/core-concepts/services.md +26 -3
  41. package/wiki/{get-started → guides/get-started}/5-minute-quickstart.md +59 -14
  42. package/wiki/guides/get-started/philosophy.md +682 -0
  43. package/wiki/guides/get-started/setup.md +157 -0
  44. package/wiki/guides/index.md +89 -0
  45. package/wiki/guides/reference/glossary.md +243 -0
  46. package/wiki/{get-started → guides/reference}/mcp-docs-server.md +0 -10
  47. package/wiki/{get-started → guides/tutorials}/building-a-crud-api.md +134 -132
  48. package/wiki/{get-started/quickstart.md → guides/tutorials/complete-installation.md} +107 -71
  49. package/wiki/guides/tutorials/ecommerce-api.md +1399 -0
  50. package/wiki/guides/tutorials/realtime-chat.md +1261 -0
  51. package/wiki/guides/tutorials/testing.md +723 -0
  52. package/wiki/index.md +176 -37
  53. package/wiki/references/base/application.md +27 -0
  54. package/wiki/references/base/bootstrapping.md +31 -26
  55. package/wiki/references/base/components.md +24 -7
  56. package/wiki/references/base/controllers.md +50 -20
  57. package/wiki/references/base/datasources.md +30 -0
  58. package/wiki/references/base/dependency-injection.md +39 -3
  59. package/wiki/references/base/filter-system/application-usage.md +224 -0
  60. package/wiki/references/base/filter-system/array-operators.md +132 -0
  61. package/wiki/references/base/filter-system/comparison-operators.md +109 -0
  62. package/wiki/references/base/filter-system/default-filter.md +428 -0
  63. package/wiki/references/base/filter-system/fields-order-pagination.md +155 -0
  64. package/wiki/references/base/filter-system/index.md +127 -0
  65. package/wiki/references/base/filter-system/json-filtering.md +197 -0
  66. package/wiki/references/base/filter-system/list-operators.md +71 -0
  67. package/wiki/references/base/filter-system/logical-operators.md +156 -0
  68. package/wiki/references/base/filter-system/null-operators.md +58 -0
  69. package/wiki/references/base/filter-system/pattern-matching.md +108 -0
  70. package/wiki/references/base/filter-system/quick-reference.md +431 -0
  71. package/wiki/references/base/filter-system/range-operators.md +63 -0
  72. package/wiki/references/base/filter-system/tips.md +190 -0
  73. package/wiki/references/base/filter-system/use-cases.md +452 -0
  74. package/wiki/references/base/index.md +90 -0
  75. package/wiki/references/base/middlewares.md +604 -0
  76. package/wiki/references/base/models.md +215 -23
  77. package/wiki/references/base/providers.md +731 -0
  78. package/wiki/references/base/repositories/advanced.md +555 -0
  79. package/wiki/references/base/repositories/index.md +228 -0
  80. package/wiki/references/base/repositories/mixins.md +331 -0
  81. package/wiki/references/base/repositories/relations.md +486 -0
  82. package/wiki/references/base/repositories.md +40 -635
  83. package/wiki/references/base/services.md +28 -4
  84. package/wiki/references/components/authentication.md +22 -2
  85. package/wiki/references/components/health-check.md +12 -0
  86. package/wiki/references/components/index.md +23 -0
  87. package/wiki/references/components/mail.md +687 -0
  88. package/wiki/references/components/request-tracker.md +16 -0
  89. package/wiki/references/components/socket-io.md +18 -0
  90. package/wiki/references/components/static-asset.md +14 -26
  91. package/wiki/references/components/swagger.md +17 -0
  92. package/wiki/references/configuration/environment-variables.md +427 -0
  93. package/wiki/references/configuration/index.md +73 -0
  94. package/wiki/references/helpers/cron.md +14 -0
  95. package/wiki/references/helpers/crypto.md +15 -0
  96. package/wiki/references/helpers/env.md +16 -0
  97. package/wiki/references/helpers/error.md +17 -0
  98. package/wiki/references/helpers/index.md +14 -0
  99. package/wiki/references/helpers/inversion.md +24 -4
  100. package/wiki/references/helpers/logger.md +19 -0
  101. package/wiki/references/helpers/network.md +11 -0
  102. package/wiki/references/helpers/queue.md +19 -0
  103. package/wiki/references/helpers/redis.md +21 -0
  104. package/wiki/references/helpers/socket-io.md +24 -5
  105. package/wiki/references/helpers/storage.md +18 -10
  106. package/wiki/references/helpers/testing.md +18 -0
  107. package/wiki/references/helpers/types.md +16 -0
  108. package/wiki/references/helpers/uid.md +167 -0
  109. package/wiki/references/helpers/worker-thread.md +16 -0
  110. package/wiki/references/index.md +177 -0
  111. package/wiki/references/quick-reference.md +634 -0
  112. package/wiki/references/src-details/boot.md +3 -3
  113. package/wiki/references/src-details/dev-configs.md +0 -4
  114. package/wiki/references/src-details/docs.md +2 -2
  115. package/wiki/references/src-details/index.md +86 -0
  116. package/wiki/references/src-details/inversion.md +1 -6
  117. package/wiki/references/src-details/mcp-server.md +3 -15
  118. package/wiki/references/utilities/index.md +86 -10
  119. package/wiki/references/utilities/jsx.md +577 -0
  120. package/wiki/references/utilities/request.md +0 -2
  121. package/wiki/references/utilities/statuses.md +740 -0
  122. package/wiki/get-started/best-practices/api-usage-examples.md +0 -266
  123. package/wiki/get-started/best-practices/architectural-patterns.md +0 -170
  124. package/wiki/get-started/best-practices/data-modeling.md +0 -177
  125. package/wiki/get-started/best-practices/deployment-strategies.md +0 -121
  126. package/wiki/get-started/best-practices/performance-optimization.md +0 -97
  127. package/wiki/get-started/best-practices/security-guidelines.md +0 -99
  128. package/wiki/get-started/core-concepts/persistent.md +0 -539
  129. package/wiki/get-started/index.md +0 -65
  130. package/wiki/get-started/philosophy.md +0 -296
  131. 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