@venizia/ignis-docs 0.0.8 → 0.2.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 (180) hide show
  1. package/README.md +7 -7
  2. package/content/best-practices/api-usage-examples.md +15 -12
  3. package/content/best-practices/architectural-patterns.md +70 -78
  4. package/content/best-practices/architecture-decisions.md +91 -60
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +56 -44
  6. package/content/best-practices/code-style-standards/constants-configuration.md +11 -11
  7. package/content/best-practices/code-style-standards/control-flow.md +5 -2
  8. package/content/best-practices/code-style-standards/documentation.md +13 -13
  9. package/content/best-practices/code-style-standards/function-patterns.md +9 -10
  10. package/content/best-practices/code-style-standards/index.md +1 -1
  11. package/content/best-practices/code-style-standards/naming-conventions.md +10 -8
  12. package/content/best-practices/code-style-standards/route-definitions.md +30 -12
  13. package/content/best-practices/code-style-standards/tooling.md +8 -5
  14. package/content/best-practices/code-style-standards/type-safety.md +13 -12
  15. package/content/best-practices/common-pitfalls.md +56 -37
  16. package/content/best-practices/contribution-workflow.md +13 -14
  17. package/content/best-practices/data-modeling.md +46 -22
  18. package/content/best-practices/deployment-strategies.md +28 -27
  19. package/content/best-practices/error-handling.md +48 -24
  20. package/content/best-practices/index.md +5 -5
  21. package/content/best-practices/performance-optimization.md +40 -31
  22. package/content/best-practices/security-guidelines.md +52 -23
  23. package/content/best-practices/testing-strategies.md +65 -51
  24. package/content/best-practices/troubleshooting-tips.md +24 -24
  25. package/content/extensions/components/{swagger.md → api-reference.md} +40 -31
  26. package/content/extensions/components/authentication/api.md +19 -19
  27. package/content/extensions/components/authentication/errors.md +7 -7
  28. package/content/extensions/components/authentication/index.md +10 -8
  29. package/content/extensions/components/authentication/usage.md +101 -6
  30. package/content/extensions/components/authorization/api.md +45 -25
  31. package/content/extensions/components/authorization/errors.md +6 -6
  32. package/content/extensions/components/authorization/index.md +11 -10
  33. package/content/extensions/components/authorization/usage.md +21 -21
  34. package/content/extensions/components/health-check.md +1 -1
  35. package/content/extensions/components/index.md +5 -5
  36. package/content/extensions/components/mail/errors.md +15 -15
  37. package/content/extensions/components/mail/index.md +1 -2
  38. package/content/extensions/components/mail/usage.md +1 -1
  39. package/content/extensions/components/request-tracker.md +1 -1
  40. package/content/extensions/components/socket-io/api.md +9 -9
  41. package/content/extensions/components/socket-io/errors.md +5 -5
  42. package/content/extensions/components/socket-io/index.md +8 -8
  43. package/content/extensions/components/socket-io/usage.md +1 -1
  44. package/content/extensions/components/static-asset/api.md +17 -4
  45. package/content/extensions/components/static-asset/errors.md +4 -4
  46. package/content/extensions/components/static-asset/index.md +26 -28
  47. package/content/extensions/components/static-asset/usage.md +13 -12
  48. package/content/extensions/components/template/index.md +2 -2
  49. package/content/extensions/components/template/setup-page.md +1 -1
  50. package/content/extensions/components/websocket/api.md +3 -3
  51. package/content/extensions/components/websocket/errors.md +5 -5
  52. package/content/extensions/components/websocket/index.md +5 -5
  53. package/content/extensions/components/websocket/usage.md +3 -3
  54. package/content/extensions/helpers/cron/index.md +2 -2
  55. package/content/extensions/helpers/crypto/index.md +1 -1
  56. package/content/extensions/helpers/env/index.md +27 -12
  57. package/content/extensions/helpers/error/index.md +81 -25
  58. package/content/extensions/helpers/index.md +2 -3
  59. package/content/extensions/helpers/inversion/index.md +15 -7
  60. package/content/extensions/helpers/kafka/compile-binary.md +92 -0
  61. package/content/extensions/helpers/kafka/examples.md +1 -1
  62. package/content/extensions/helpers/kafka/index.md +3 -0
  63. package/content/extensions/helpers/logger/index.md +32 -2
  64. package/content/extensions/helpers/network/index.md +6 -0
  65. package/content/extensions/helpers/queue/index.md +14 -17
  66. package/content/extensions/helpers/redis/index.md +548 -323
  67. package/content/extensions/helpers/socket-io/index.md +14 -10
  68. package/content/extensions/helpers/storage/api.md +44 -8
  69. package/content/extensions/helpers/storage/index.md +43 -7
  70. package/content/extensions/helpers/template/index.md +6 -3
  71. package/content/extensions/helpers/types/index.md +11 -8
  72. package/content/extensions/helpers/websocket/api.md +9 -9
  73. package/content/extensions/helpers/websocket/index.md +7 -7
  74. package/content/extensions/helpers/worker-thread/index.md +2 -2
  75. package/content/extensions/index.md +3 -4
  76. package/content/extensions/src-details/mcp-server.md +18 -24
  77. package/content/guides/core-concepts/application/bootstrapping.md +11 -14
  78. package/content/guides/core-concepts/application/index.md +3 -3
  79. package/content/guides/core-concepts/components.md +19 -10
  80. package/content/guides/core-concepts/dependency-injection.md +6 -3
  81. package/content/guides/core-concepts/grpc-controllers.md +6 -5
  82. package/content/guides/core-concepts/persistent/datasources.md +42 -43
  83. package/content/guides/core-concepts/persistent/index.md +16 -7
  84. package/content/guides/core-concepts/persistent/models.md +24 -20
  85. package/content/guides/core-concepts/persistent/postgres-drivers.md +201 -0
  86. package/content/guides/core-concepts/persistent/repositories.md +40 -23
  87. package/content/guides/core-concepts/persistent/search-meilisearch.md +185 -0
  88. package/content/guides/core-concepts/persistent/search-typesense.md +431 -0
  89. package/content/guides/core-concepts/persistent/transactions.md +61 -25
  90. package/content/guides/core-concepts/rest-controllers.md +12 -9
  91. package/content/guides/core-concepts/services.md +330 -60
  92. package/content/guides/get-started/5-minute-quickstart.md +15 -15
  93. package/content/guides/get-started/philosophy.md +36 -36
  94. package/content/guides/get-started/setup.md +3 -3
  95. package/content/guides/index.md +3 -3
  96. package/content/guides/migrations/redis-helpers-migration.md +177 -0
  97. package/content/guides/migrations/scoped-rbac-migration.md +17 -17
  98. package/content/guides/migrations/unified-connectors-migration.md +113 -0
  99. package/content/guides/reference/glossary.md +19 -12
  100. package/content/guides/reference/mcp-docs-server.md +22 -18
  101. package/content/guides/tutorials/building-a-crud-api.md +37 -44
  102. package/content/guides/tutorials/complete-installation.md +17 -17
  103. package/content/guides/tutorials/ecommerce-api.md +163 -124
  104. package/content/guides/tutorials/realtime-chat.md +181 -135
  105. package/content/guides/tutorials/testing.md +65 -523
  106. package/content/index.md +2 -180
  107. package/content/public/apple-touch-icon.png +0 -0
  108. package/content/public/og-image.png +0 -0
  109. package/content/public/site.webmanifest +11 -0
  110. package/content/references/base/application.md +4 -5
  111. package/content/references/base/bootstrapping.md +18 -5
  112. package/content/references/base/components.md +149 -120
  113. package/content/references/base/connectors.md +178 -0
  114. package/content/references/base/controllers.md +41 -30
  115. package/content/references/base/datasources.md +163 -92
  116. package/content/references/base/dependency-injection.md +34 -22
  117. package/content/references/base/filter-system/application-usage.md +17 -14
  118. package/content/references/base/filter-system/array-operators.md +7 -2
  119. package/content/references/base/filter-system/comparison-operators.md +3 -0
  120. package/content/references/base/filter-system/default-filter.md +89 -71
  121. package/content/references/base/filter-system/fields-order-pagination.md +22 -22
  122. package/content/references/base/filter-system/index.md +6 -3
  123. package/content/references/base/filter-system/json-filtering.md +20 -1
  124. package/content/references/base/filter-system/list-operators.md +1 -1
  125. package/content/references/base/filter-system/logical-operators.md +33 -1
  126. package/content/references/base/filter-system/null-operators.md +30 -1
  127. package/content/references/base/filter-system/quick-reference.md +23 -4
  128. package/content/references/base/filter-system/tips.md +5 -5
  129. package/content/references/base/filter-system/use-cases.md +12 -12
  130. package/content/references/base/grpc-controllers.md +13 -13
  131. package/content/references/base/index.md +24 -12
  132. package/content/references/base/middlewares.md +265 -327
  133. package/content/references/base/models.md +63 -49
  134. package/content/references/base/providers.md +136 -130
  135. package/content/references/base/repositories/advanced.md +59 -58
  136. package/content/references/base/repositories/index.md +115 -91
  137. package/content/references/base/repositories/mixins.md +55 -291
  138. package/content/references/base/repositories/relations.md +54 -64
  139. package/content/references/base/repositories/soft-deletable.md +31 -30
  140. package/content/references/base/services.md +296 -93
  141. package/content/references/configuration/environment-variables.md +49 -31
  142. package/content/references/configuration/index.md +6 -6
  143. package/content/references/index.md +17 -12
  144. package/content/references/quick-reference.md +65 -106
  145. package/content/references/utilities/crypto.md +65 -23
  146. package/content/references/utilities/index.md +3 -3
  147. package/content/references/utilities/jsx.md +6 -4
  148. package/content/references/utilities/module.md +68 -20
  149. package/content/references/utilities/parse.md +4 -14
  150. package/content/references/utilities/promise.md +9 -7
  151. package/content/references/utilities/schema.md +5 -3
  152. package/dist/mcp-server/common/guards.d.ts +8 -0
  153. package/dist/mcp-server/common/guards.d.ts.map +1 -0
  154. package/dist/mcp-server/common/guards.js +14 -0
  155. package/dist/mcp-server/common/guards.js.map +1 -0
  156. package/dist/mcp-server/common/index.d.ts +1 -0
  157. package/dist/mcp-server/common/index.d.ts.map +1 -1
  158. package/dist/mcp-server/common/index.js +1 -0
  159. package/dist/mcp-server/common/index.js.map +1 -1
  160. package/dist/mcp-server/helpers/docs.helper.d.ts.map +1 -1
  161. package/dist/mcp-server/helpers/docs.helper.js +4 -2
  162. package/dist/mcp-server/helpers/docs.helper.js.map +1 -1
  163. package/dist/mcp-server/helpers/github.helper.js +1 -1
  164. package/dist/mcp-server/index.js +7 -2
  165. package/dist/mcp-server/index.js.map +1 -1
  166. package/dist/mcp-server/tools/base.tool.d.ts +6 -2
  167. package/dist/mcp-server/tools/base.tool.d.ts.map +1 -1
  168. package/dist/mcp-server/tools/base.tool.js.map +1 -1
  169. package/dist/mcp-server/tools/docs/search-documents.tool.d.ts +1 -1
  170. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  171. package/dist/mcp-server/tools/github/search-code.tool.d.ts +1 -1
  172. package/dist/mcp-server/tools/github/search-code.tool.d.ts.map +1 -1
  173. package/dist/mcp-server/tools/github/search-code.tool.js +4 -1
  174. package/dist/mcp-server/tools/github/search-code.tool.js.map +1 -1
  175. package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts.map +1 -1
  176. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +3 -1
  177. package/dist/mcp-server/tools/github/verify-dependencies.tool.js.map +1 -1
  178. package/package.json +9 -9
  179. package/content/extensions/helpers/testing/index.md +0 -510
  180. package/content/references/base/middleware.md +0 -347
@@ -1,335 +1,99 @@
1
1
  ---
2
- title: Repository Mixins
3
- description: Composable mixins for repository functionality
2
+ title: Repository Mixins (Removed)
3
+ description: FieldsVisibilityMixin and DefaultFilterMixin were removed - the behavior now lives on AbstractRepository and PostgresBaseRepository
4
4
  difficulty: intermediate
5
- lastUpdated: 2026-01-02
5
+ lastUpdated: 2026-07-06
6
6
  ---
7
7
 
8
- # Repository Mixins <Badge type="tip" text="v0.0.5+" />
8
+ # Repository Mixins <Badge type="danger" text="removed" />
9
9
 
10
- Composable mixins that provide reusable functionality for repository classes.
10
+ > [!WARNING] Removed
11
+ > `FieldsVisibilityMixin` and `DefaultFilterMixin` have been **removed** from IGNIS. They are no longer exported and must not be imported or composed in new code. This page remains as a tombstone documenting where the equivalent behavior now lives.
11
12
 
12
- > [!NOTE] Refactored in v0.0.5
13
- > Repository mixins were extracted and refactored in v0.0.5 to provide better composition and reusability.
13
+ ## What They Were
14
14
 
15
- **Files:** `packages/core/src/base/repositories/mixins/`
16
-
17
-
18
- ## Overview
19
-
20
- Ignis uses the mixin pattern to compose repository features. This enables:
21
-
22
- - **Separation of concerns** - Each mixin handles one responsibility
23
- - **Reusability** - Mixins can be applied to different base classes
24
- - **Testability** - Individual features can be tested in isolation
25
- - **Flexibility** - Create custom repositories with only needed features
26
-
27
-
28
- ## Available Mixins
15
+ Before the connectors restructure, `AbstractRepository` composed two mixins onto `BaseHelper`:
29
16
 
30
17
  | Mixin | Responsibility |
31
18
  |-------|----------------|
32
- | `FieldsVisibilityMixin` | Hidden properties exclusion at SQL level |
33
- | `DefaultFilterMixin` | Automatic filter application from model settings |
19
+ | `FieldsVisibilityMixin` | Hidden-properties exclusion at SQL level (reads `hiddenProperties` from `@model` settings) |
20
+ | `DefaultFilterMixin` | Automatic default-filter merging (reads `defaultFilter` from `@model` settings) |
34
21
 
22
+ ## Where the Behavior Lives Now
35
23
 
36
- ## FieldsVisibilityMixin
24
+ The functionality was not dropped - it was folded directly into the repository hierarchy.
37
25
 
38
- Provides hidden properties management for SQL-level field exclusion. Reads `hiddenProperties` from `@model` metadata settings and builds a visible columns map for Drizzle's `select()` and `returning()` calls.
26
+ ### Engine-neutral: `AbstractRepository`
39
27
 
40
- **File:** `packages/core/src/base/repositories/mixins/fields-visibility.ts`
28
+ **File:** `packages/core/src/base/repositories/core/abstract.ts`
41
29
 
42
- ### Abstract Requirements
43
-
44
- Classes using this mixin must implement:
30
+ `AbstractRepository` resolves `@model` settings by class (Reflect target) via `MetadataRegistry` and exposes them as protected getters, memoized after first access:
45
31
 
46
32
  ```typescript
47
- abstract getEntity(): BaseEntity<TTableSchemaWithId>;
33
+ protected get modelSettings(): IModelMetadata['settings']; // full @model settings
34
+ protected get hiddenFields(): string[]; // settings.hiddenProperties ?? []
35
+ protected get defaultWhere(): TWhere | undefined; // settings.defaultFilter?.where
36
+ protected get defaultLimit(): number | undefined; // settings.defaultLimit
48
37
  ```
49
38
 
50
- ### Properties
51
-
52
- | Property | Type | Description |
53
- |----------|------|-------------|
54
- | `_hiddenProperties` | `Set<string> \| null` | Cached hidden property names (`null` = not yet computed) |
55
- | `_visibleProperties` | `Record<string, any> \| null \| undefined` | Cached visible columns (`null` = not yet computed, `undefined` = computed with no hidden props) |
56
-
57
- ### Methods
39
+ ### PostgreSQL: `PostgresBaseRepository`
58
40
 
59
- | Method | Returns | Description |
60
- |--------|---------|-------------|
61
- | `get hiddenProperties` | `Set<string>` | Getter that delegates to `getHiddenProperties()` |
62
- | `set hiddenProperties` | `void` | Override hidden properties set |
63
- | `getHiddenProperties()` | `Set<string>` | Get hidden properties from model metadata (cached) |
64
- | `hasHiddenProperties()` | `boolean` | Check if model has any hidden properties |
65
- | `get visibleProperties` | `Record<string, any> \| undefined` | Getter that delegates to `getVisibleProperties()` |
66
- | `set visibleProperties` | `void` | Override visible properties |
67
- | `getVisibleProperties()` | `Record<string, any> \| undefined` | Build visible columns object for Drizzle (cached). Returns `undefined` if no hidden props. |
41
+ **File:** `packages/core/src/connectors/postgres/repositories/core/base.ts`
68
42
 
69
- ### Usage
43
+ Builds on those getters to implement SQL-level column exclusion and full-filter merging for Drizzle:
70
44
 
71
45
  ```typescript
72
- import { FieldsVisibilityMixin } from '@venizia/ignis';
73
- import { BaseHelper } from '@venizia/ignis-helpers';
74
-
75
- class MyRepository extends FieldsVisibilityMixin(BaseHelper) {
76
- // Required abstract implementation
77
- abstract getEntity(): BaseEntity;
78
-
79
- // Now has access to:
80
- // - hiddenProperties (getter/setter)
81
- // - visibleProperties (getter/setter)
82
- // - getHiddenProperties()
83
- // - hasHiddenProperties()
84
- // - getVisibleProperties()
85
- }
46
+ getHiddenProperties(): Set<string>; // memoized Set of hiddenFields
47
+ hasHiddenProperties(): boolean;
48
+ getVisibleProperties(): Record<string, any> | undefined; // memoized Drizzle column-selection map
49
+
50
+ getDefaultFilter(): TFilter | undefined; // full settings.defaultFilter (where/order/limit/...)
51
+ getDefaultLimit(): number | undefined;
52
+ hasDefaultFilter(): boolean;
53
+ applyDefaultFilter(opts: {
54
+ userFilter?: TFilter;
55
+ shouldSkipDefaultFilter?: boolean;
56
+ }): TFilter; // merges via FilterBuilder.mergeFilter
86
57
  ```
87
58
 
88
- ### Visible Properties for Drizzle
59
+ Hidden columns are excluded from `select()` and `returning()` calls at query time - the same SQL-level guarantee the mixins provided. The typesense connector implements its own equivalent natively since it is not Drizzle-aware.
89
60
 
90
- The `getVisibleProperties()` method returns a columns object for Drizzle's `select()` or `returning()`:
61
+ ## Migration
91
62
 
92
- ```typescript
93
- // Model with hiddenProperties: ['password', 'apiKey']
94
- // Schema columns: { id, email, password, apiKey, createdAt }
63
+ | Old (mixin) | New |
64
+ |-------------|-----|
65
+ | `FieldsVisibilityMixin` -> `getHiddenProperties()` | `PostgresBaseRepository.getHiddenProperties()` |
66
+ | `FieldsVisibilityMixin` -> `getVisibleProperties()` | `PostgresBaseRepository.getVisibleProperties()` |
67
+ | `FieldsVisibilityMixin` -> `hasHiddenProperties()` | `PostgresBaseRepository.hasHiddenProperties()` |
68
+ | `DefaultFilterMixin` -> `getDefaultFilter()` | `PostgresBaseRepository.getDefaultFilter()` |
69
+ | `DefaultFilterMixin` -> `hasDefaultFilter()` | `PostgresBaseRepository.hasDefaultFilter()` |
70
+ | `DefaultFilterMixin` -> `applyDefaultFilter()` | `PostgresBaseRepository.applyDefaultFilter()` |
95
71
 
96
- const visibleProps = this.getVisibleProperties();
97
- // Result: { id: column, email: column, createdAt: column }
98
- // (password and apiKey excluded)
72
+ If you extended `DefaultCRUDRepository` (or any class in the PostgreSQL hierarchy), no change is needed - these methods have always been available on your repository instances; only the internal composition changed.
99
73
 
100
- // Used in Drizzle queries
101
- await connector.select(visibleProps).from(schema);
102
- // SELECT id, email, created_at FROM users
103
- ```
104
-
105
- ### How It Resolves Hidden Properties
106
-
107
- 1. Checks the cache (`_hiddenProperties`). If not `null`, returns cached value.
108
- 2. Looks up the entity name in `MetadataRegistry.getModelEntry()`.
109
- 3. Reads `metadata.settings.hiddenProperties` (array of field names).
110
- 4. Converts to a `Set<string>` and caches.
111
-
112
-
113
- ## DefaultFilterMixin
114
-
115
- Provides automatic default filter application for all repository queries. Reads `defaultFilter` from `@model` metadata settings and merges it with user-provided filters.
74
+ ## Custom Mixins Still Work
116
75
 
117
- **File:** `packages/core/src/base/repositories/mixins/default-filter.ts`
118
-
119
- ### Abstract Requirements
120
-
121
- Classes using this mixin must implement:
122
-
123
- ```typescript
124
- abstract getEntity(): BaseEntity<TTableSchemaWithId>;
125
- abstract get filterBuilder(): FilterBuilder;
126
- ```
127
-
128
- ### Properties
129
-
130
- | Property | Type | Description |
131
- |----------|------|-------------|
132
- | `_defaultFilter` | `TFilter \| null \| undefined` | Cached default filter (`null` = not yet computed, `undefined` = computed with no default filter) |
133
-
134
- ### Methods
135
-
136
- | Method | Returns | Description |
137
- |--------|---------|-------------|
138
- | `getDefaultFilter()` | `TFilter \| undefined` | Get default filter from model metadata (cached) |
139
- | `hasDefaultFilter()` | `boolean` | Check if model has a default filter configured |
140
- | `applyDefaultFilter(opts)` | `TFilter` | Merge default filter with user filter |
141
-
142
- ### Usage
143
-
144
- ```typescript
145
- import { DefaultFilterMixin } from '@venizia/ignis';
146
- import { BaseHelper } from '@venizia/ignis-helpers';
147
-
148
- class MyRepository extends DefaultFilterMixin(BaseHelper) {
149
- // Required abstract implementations
150
- abstract getEntity(): BaseEntity;
151
- abstract get filterBuilder(): FilterBuilder;
152
-
153
- // Now has access to:
154
- // - getDefaultFilter()
155
- // - hasDefaultFilter()
156
- // - applyDefaultFilter()
157
- }
158
- ```
159
-
160
- ### applyDefaultFilter Options
161
-
162
- ```typescript
163
- applyDefaultFilter<DataObject = any>(opts: {
164
- userFilter?: TFilter<DataObject>; // User-provided filter
165
- shouldSkipDefaultFilter?: boolean; // If true, bypass default filter
166
- }): TFilter<DataObject>
167
- ```
168
-
169
- **Behavior:**
170
-
171
- 1. If `shouldSkipDefaultFilter` is `true`, returns the user filter as-is (or `{}` if none).
172
- 2. If no default filter is configured, returns the user filter as-is (or `{}` if none).
173
- 3. Otherwise, delegates to `filterBuilder.mergeFilter({ defaultFilter, userFilter })` which deep-merges `where` conditions and uses user values for other filter properties (`order`, `limit`, `offset`, `skip`, `fields`, `include`).
174
-
175
-
176
- ## Mixin Composition
177
-
178
- The `AbstractRepository` composes both mixins:
179
-
180
- ```typescript
181
- export abstract class AbstractRepository<...>
182
- extends DefaultFilterMixin(FieldsVisibilityMixin(BaseHelper))
183
- implements IPersistableRepository<...>
184
- {
185
- // Inherits from both mixins:
186
- // From FieldsVisibilityMixin:
187
- // - hiddenProperties (getter/setter)
188
- // - visibleProperties (getter/setter)
189
- // - getHiddenProperties()
190
- // - hasHiddenProperties()
191
- // - getVisibleProperties()
192
- //
193
- // From DefaultFilterMixin:
194
- // - getDefaultFilter()
195
- // - hasDefaultFilter()
196
- // - applyDefaultFilter()
197
- }
198
- ```
199
-
200
- ### Composition Order
201
-
202
- Mixins are applied right-to-left:
203
-
204
- ```typescript
205
- // FieldsVisibilityMixin applied first (to BaseHelper)
206
- // DefaultFilterMixin applied second (to the result)
207
- DefaultFilterMixin(FieldsVisibilityMixin(BaseHelper))
208
- ```
209
-
210
-
211
- ## Creating Custom Mixins
212
-
213
- Follow the TypeScript mixin pattern using `TMixinTarget`:
76
+ The mixin *pattern* remains a valid technique for your own repository code, via `TMixinTarget` from `@venizia/ignis-helpers`:
214
77
 
215
78
  ```typescript
216
79
  import { TMixinTarget } from '@venizia/ignis-helpers';
217
80
 
218
81
  export const AuditLogMixin = <T extends TMixinTarget<object>>(baseClass: T) => {
219
- abstract class Mixed extends baseClass {
220
- // Properties
221
- private _auditEnabled: boolean = true;
222
-
223
- // Abstract dependencies (if needed)
224
- abstract getEntity(): BaseEntity;
225
-
226
- // Public methods
227
- enableAudit(): void {
228
- this._auditEnabled = true;
229
- }
230
-
231
- disableAudit(): void {
232
- this._auditEnabled = false;
233
- }
234
-
235
- isAuditEnabled(): boolean {
236
- return this._auditEnabled;
237
- }
238
-
239
- logOperation(operation: string, data: any): void {
240
- if (this._auditEnabled) {
241
- console.log(`[${this.getEntity().name}] ${operation}:`, data);
242
- }
82
+ class Mixed extends baseClass {
83
+ logOperation(opts: { operation: string; data: unknown }): void {
84
+ // custom behavior
243
85
  }
244
86
  }
245
-
246
87
  return Mixed;
247
88
  };
248
- ```
249
-
250
- ### Using Custom Mixins
251
-
252
- ```typescript
253
- // Compose with existing mixins
254
- class MyRepository extends AuditLogMixin(DefaultFilterMixin(BaseHelper)) {
255
- getEntity() {
256
- return this._entity;
257
- }
258
89
 
259
- get filterBuilder() {
260
- return this._filterBuilder;
261
- }
262
- }
263
-
264
- // Or create a composed base
265
- const AuditableRepository = AuditLogMixin(DefaultFilterMixin(FieldsVisibilityMixin(BaseHelper)));
266
-
267
- class ProductRepository extends AuditableRepository {
268
- // Has all mixin functionality
269
- }
270
- ```
271
-
272
-
273
- ## Caching Behavior
274
-
275
- Both mixins use a three-state caching pattern for performance:
276
-
277
- ```typescript
278
- // DefaultFilterMixin caching
279
- // null = not computed yet
280
- // undefined = computed, no default filter exists
281
- // TFilter = computed, has default filter
282
- _defaultFilter: TFilter | null | undefined = null;
283
-
284
- getDefaultFilter() {
285
- if (this._defaultFilter !== null) {
286
- return this._defaultFilter; // Return cached value (either TFilter or undefined)
287
- }
288
- // Compute from MetadataRegistry and cache...
289
- }
290
-
291
- // FieldsVisibilityMixin caching
292
- // null = not computed yet
293
- // Set<string> = computed (may be empty)
294
- _hiddenProperties: Set<string> | null = null;
295
-
296
- // null = not computed yet
297
- // undefined = computed, no hidden properties exist
298
- // Record<string, any> = computed, has visible column map
299
- _visibleProperties: Record<string, any> | null | undefined = null;
90
+ export class ProductRepository extends AuditLogMixin(
91
+ DefaultCRUDRepository<typeof Product.schema>,
92
+ ) {}
300
93
  ```
301
94
 
302
- This ensures metadata lookups happen only once per repository instance, with subsequent calls returning the cached value.
303
-
304
-
305
- ## Quick Reference
306
-
307
- | Mixin | Method | Purpose |
308
- |-------|--------|---------|
309
- | `FieldsVisibilityMixin` | `hasHiddenProperties()` | Check if hidden props exist |
310
- | `FieldsVisibilityMixin` | `getHiddenProperties()` | Get hidden property names as `Set<string>` |
311
- | `FieldsVisibilityMixin` | `getVisibleProperties()` | Get Drizzle columns object (excludes hidden) |
312
- | `DefaultFilterMixin` | `hasDefaultFilter()` | Check if default filter exists |
313
- | `DefaultFilterMixin` | `getDefaultFilter()` | Get raw default filter from model metadata |
314
- | `DefaultFilterMixin` | `applyDefaultFilter()` | Merge default filter with user filter |
315
-
316
-
317
- ## Next Steps
318
-
319
- - [Default Filter](../filter-system/default-filter.md) - Full default filter documentation
320
- - [Advanced Features](./advanced.md) - Hidden properties usage
321
- - [Repository Overview](./index.md) - Repository basics
322
-
323
95
  ## See Also
324
96
 
325
- - **Related Concepts:**
326
- - [Repositories Overview](./index) - Core repository operations
327
- - [Models](/guides/core-concepts/persistent/models) - Entity definitions
328
-
329
- - **Related Topics:**
330
- - [Default Filter](../filter-system/default-filter) - Automatic filtering
331
- - [Advanced Features](./advanced) - Hidden properties and transactions
332
- - [Relations & Includes](./relations) - Loading related data
333
-
334
- - **Best Practices:**
335
- - [Data Modeling](/best-practices/data-modeling) - Soft delete patterns
97
+ - [Repository Overview](./index.md) - Current class hierarchy
98
+ - [Advanced Features](./advanced.md) - Hidden properties and default-filter bypass in practice
99
+ - [Default Filter](../filter-system/default-filter.md) - Configuring `@model` default filters
@@ -9,7 +9,7 @@ Fetch related data using `include` for eager loading. This guide covers one-to-o
9
9
 
10
10
  ```typescript
11
11
  // Fetch user with their posts
12
- const user = await userRepo.findOne({
12
+ const user = await userRepository.findOne({
13
13
  filter: {
14
14
  where: { id: '123' },
15
15
  include: [{ relation: 'posts' }]
@@ -31,7 +31,7 @@ const user = await userRepo.findOne({
31
31
 
32
32
  ```typescript
33
33
  // Fetch post with its author
34
- const post = await postRepo.findOne({
34
+ const post = await postRepository.findOne({
35
35
  filter: {
36
36
  where: { id: 'p1' },
37
37
  include: [{ relation: 'author' }]
@@ -51,7 +51,7 @@ const post = await postRepo.findOne({
51
51
 
52
52
  ```typescript
53
53
  // Fetch post with author AND comments
54
- const post = await postRepo.findOne({
54
+ const post = await postRepository.findOne({
55
55
  filter: {
56
56
  where: { id: 'p1' },
57
57
  include: [
@@ -74,7 +74,7 @@ Apply filters, ordering, and limits to included relations using `scope`:
74
74
 
75
75
  ```typescript
76
76
  // User with only published posts
77
- const user = await userRepo.findOne({
77
+ const user = await userRepository.findOne({
78
78
  filter: {
79
79
  where: { id: '123' },
80
80
  include: [{
@@ -91,7 +91,7 @@ const user = await userRepo.findOne({
91
91
 
92
92
  ```typescript
93
93
  // User with posts ordered by date
94
- const user = await userRepo.findOne({
94
+ const user = await userRepository.findOne({
95
95
  filter: {
96
96
  where: { id: '123' },
97
97
  include: [{
@@ -108,7 +108,7 @@ const user = await userRepo.findOne({
108
108
 
109
109
  ```typescript
110
110
  // User with their 5 most recent posts
111
- const user = await userRepo.findOne({
111
+ const user = await userRepository.findOne({
112
112
  filter: {
113
113
  where: { id: '123' },
114
114
  include: [{
@@ -125,7 +125,7 @@ const user = await userRepo.findOne({
125
125
  ### Combined Scope Options
126
126
 
127
127
  ```typescript
128
- const user = await userRepo.findOne({
128
+ const user = await userRepository.findOne({
129
129
  filter: {
130
130
  where: { id: '123' },
131
131
  include: [{
@@ -147,7 +147,7 @@ Each inclusion can independently bypass the related model's default filter:
147
147
 
148
148
  ```typescript
149
149
  // Include soft-deleted posts that would normally be filtered out
150
- const user = await userRepo.findOne({
150
+ const user = await userRepository.findOne({
151
151
  filter: {
152
152
  where: { id: '123' },
153
153
  include: [{
@@ -167,7 +167,7 @@ Include relations of relations (up to 2 levels recommended):
167
167
 
168
168
  ```typescript
169
169
  // User -> Posts -> Comments
170
- const user = await userRepo.findOne({
170
+ const user = await userRepository.findOne({
171
171
  filter: {
172
172
  where: { id: '123' },
173
173
  include: [{
@@ -200,7 +200,7 @@ const user = await userRepo.findOne({
200
200
 
201
201
  ```typescript
202
202
  // Product -> SaleChannelProduct (junction) -> SaleChannel
203
- const product = await productRepo.findOne({
203
+ const product = await productRepository.findOne({
204
204
  filter: {
205
205
  where: { id: 'prod1' },
206
206
  include: [{
@@ -236,7 +236,7 @@ const product = await productRepo.findOne({
236
236
 
237
237
  ## Defining Relations
238
238
 
239
- Relations are defined using the `createRelations` helper and the `TRelationConfig` type. These use Drizzle ORM's relation system under the hood.
239
+ 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
240
 
241
241
  ### Relation Config Type
242
242
 
@@ -261,34 +261,32 @@ type TRelationConfig = {
261
261
 
262
262
  ```typescript
263
263
  // src/models/user.model.ts
264
- import { createRelations } from '@venizia/ignis';
264
+ import { model, RelationTypes } from '@venizia/ignis';
265
+ import { BasePostgresEntity, TRelationConfig } from '@venizia/ignis/postgres';
266
+ import { pgTable, text } from 'drizzle-orm/pg-core';
267
+ import { Post } from './post.model';
265
268
 
266
- export const userTable = pgTable('User', {
267
- id: text('id').primaryKey(),
268
- name: text('name').notNull(),
269
- email: text('email').notNull(),
270
- });
269
+ @model({ type: 'entity' })
270
+ export class User extends BasePostgresEntity<typeof User.schema> {
271
+ static override schema = pgTable('User', {
272
+ id: text('id').primaryKey(),
273
+ name: text('name').notNull(),
274
+ email: text('email').notNull(),
275
+ });
271
276
 
272
- const userRelationsConfig = createRelations({
273
- source: userTable,
274
- relations: [
277
+ static override relations = (): TRelationConfig[] => [
275
278
  {
276
- type: 'many',
277
- schema: postTable,
278
279
  name: 'posts',
280
+ type: RelationTypes.MANY,
281
+ schema: Post.schema,
279
282
  metadata: { relationName: 'posts' },
280
283
  },
281
- ],
282
- });
283
-
284
- @model({ type: 'entity' })
285
- export class User extends BaseEntity<typeof User.schema> {
286
- static override schema = userTable;
287
- static override relations = () => userRelationsConfig.definitions;
288
- static override TABLE_NAME = 'User';
284
+ ];
289
285
  }
290
286
  ```
291
287
 
288
+ The resolver form (`() => [...]`) defers evaluation until all `@model` classes are registered, avoiding circular-import ordering issues between related models.
289
+
292
290
  ### Relation Types
293
291
 
294
292
  | Type | Drizzle Function | Description | Example |
@@ -297,46 +295,38 @@ export class User extends BaseEntity<typeof User.schema> {
297
295
  | `'many'` | `many()` | One-to-many | User has many Posts |
298
296
 
299
297
  > [!NOTE]
300
- > 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.
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.
301
299
 
302
300
  ### Example: Post Model with Both Types
303
301
 
304
302
  ```typescript
305
- const postRelationsConfig = createRelations({
306
- source: postTable,
307
- relations: [
303
+ @model({ type: 'entity' })
304
+ export class Post extends BasePostgresEntity<typeof Post.schema> {
305
+ static override schema = postTable;
306
+
307
+ static override relations = (): TRelationConfig[] => [
308
308
  {
309
- type: 'one',
310
- schema: userTable,
311
309
  name: 'author',
310
+ type: RelationTypes.ONE,
311
+ schema: User.schema,
312
312
  metadata: {
313
- fields: [postTable.authorId],
314
- references: [userTable.id],
313
+ fields: [Post.schema.authorId],
314
+ references: [User.schema.id],
315
315
  },
316
316
  },
317
317
  {
318
- type: 'many',
319
- schema: commentTable,
320
318
  name: 'comments',
319
+ type: RelationTypes.MANY,
320
+ schema: Comment.schema,
321
321
  metadata: { relationName: 'comments' },
322
322
  },
323
- ],
324
- });
323
+ ];
324
+ }
325
325
  ```
326
326
 
327
- ### createRelations Return Value
328
-
329
- `createRelations` returns an object with two properties:
330
-
331
- ```typescript
332
- const result = createRelations({ source, relations });
333
-
334
- result.definitions; // Record<string, TRelationConfig> - keyed by relation name
335
- result.relations; // Drizzle relations() call result - pass to DataSource schema
336
- ```
327
+ ### How Configs Become Drizzle Relations
337
328
 
338
- - **`definitions`**: Used by `BaseEntity.relations` for include resolution at runtime.
339
- - **`relations`**: The actual Drizzle ORM relations definition, needed for DataSource schema registration.
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.
340
330
 
341
331
 
342
332
  ## Auto-Resolution
@@ -362,7 +352,7 @@ When building include queries, the `FilterBuilder.toInclude()` method automatica
362
352
 
363
353
  ```typescript
364
354
  // User model has hiddenProperties: ['password']
365
- const post = await postRepo.findOne({
355
+ const post = await postRepository.findOne({
366
356
  filter: {
367
357
  include: [{ relation: 'author' }]
368
358
  }
@@ -383,7 +373,7 @@ type UserWithPosts = User & {
383
373
  };
384
374
 
385
375
  // Use generic override
386
- const user = await userRepo.findOne<UserWithPosts>({
376
+ const user = await userRepository.findOne<UserWithPosts>({
387
377
  filter: {
388
378
  where: { id: '123' },
389
379
  include: [{ relation: 'posts' }]
@@ -405,7 +395,7 @@ type ProductWithChannels = Product & {
405
395
  })[];
406
396
  };
407
397
 
408
- const product = await productRepo.findOne<ProductWithChannels>({
398
+ const product = await productRepository.findOne<ProductWithChannels>({
409
399
  filter: {
410
400
  where: { id: 'prod1' },
411
401
  include: [{
@@ -441,7 +431,7 @@ type TInclusion = {
441
431
 
442
432
  ```typescript
443
433
  // Get users with post count
444
- const users = await userRepo.find({
434
+ const users = await userRepository.find({
445
435
  filter: {
446
436
  include: [{
447
437
  relation: 'posts',
@@ -465,7 +455,7 @@ async function getUser(id: string, includePosts: boolean) {
465
455
  ? [{ relation: 'posts' }]
466
456
  : [];
467
457
 
468
- return userRepo.findOne({
458
+ return userRepository.findOne({
469
459
  filter: {
470
460
  where: { id },
471
461
  include
@@ -483,7 +473,7 @@ If you try to include a relation that doesn't exist:
483
473
 
484
474
  ```typescript
485
475
  // Error: [FilterBuilder][toInclude] Relation NOT FOUND | relation: 'nonExistent'
486
- await userRepo.find({
476
+ await userRepository.find({
487
477
  filter: {
488
478
  include: [{ relation: 'nonExistent' }]
489
479
  }
@@ -520,14 +510,14 @@ in connector.query | Available keys: [Post, Comment]
520
510
 
521
511
  ```typescript
522
512
  // Instead of deep nesting, use separate queries
523
- const user = await userRepo.findById({ id: '123' });
524
- const posts = await postRepo.find({
513
+ const user = await userRepository.findById({ id: '123' });
514
+ const posts = await postRepository.find({
525
515
  filter: {
526
516
  where: { authorId: '123' },
527
517
  limit: 10
528
518
  }
529
519
  });
530
- const comments = await commentRepo.find({
520
+ const comments = await commentRepository.find({
531
521
  filter: {
532
522
  where: { postId: { inq: posts.map(p => p.id) } }
533
523
  }
@@ -563,7 +553,7 @@ const comments = await commentRepo.find({
563
553
 
564
554
  - **Related Topics:**
565
555
  - [Advanced Features](./advanced) - Hidden properties, transactions
566
- - [Repository Mixins](./mixins) - Default filter and fields visibility
556
+ - [Repository Mixins (Removed)](./mixins) - Where default-filter and fields-visibility behavior lives now
567
557
  - [Filter System](/references/base/filter-system/) - Query operators
568
558
 
569
559
  - **External Resources:**