@venizia/ignis-docs 0.0.7 → 0.0.8-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mcp-server/common/paths.d.ts +4 -2
- package/dist/mcp-server/common/paths.d.ts.map +1 -1
- package/dist/mcp-server/common/paths.js +8 -6
- package/dist/mcp-server/common/paths.js.map +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.js +7 -7
- package/dist/mcp-server/tools/docs/get-document-metadata.tool.js +3 -3
- package/dist/mcp-server/tools/docs/get-package-overview.tool.d.ts +1 -1
- package/dist/mcp-server/tools/docs/get-package-overview.tool.js +1 -1
- package/package.json +1 -1
- package/wiki/best-practices/api-usage-examples.md +9 -9
- package/wiki/best-practices/architectural-patterns.md +19 -3
- package/wiki/best-practices/architecture-decisions.md +6 -6
- package/wiki/best-practices/code-style-standards/advanced-patterns.md +1 -1
- package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
- package/wiki/best-practices/code-style-standards/function-patterns.md +2 -2
- package/wiki/best-practices/code-style-standards/index.md +2 -2
- package/wiki/best-practices/code-style-standards/naming-conventions.md +1 -1
- package/wiki/best-practices/code-style-standards/route-definitions.md +4 -4
- package/wiki/best-practices/data-modeling.md +1 -1
- package/wiki/best-practices/deployment-strategies.md +1 -1
- package/wiki/best-practices/error-handling.md +2 -2
- package/wiki/best-practices/performance-optimization.md +3 -3
- package/wiki/best-practices/security-guidelines.md +2 -2
- package/wiki/best-practices/troubleshooting-tips.md +1 -1
- package/wiki/{references → extensions}/components/authentication/api.md +12 -20
- package/wiki/{references → extensions}/components/authentication/errors.md +1 -1
- package/wiki/{references → extensions}/components/authentication/index.md +5 -8
- package/wiki/{references → extensions}/components/authentication/usage.md +20 -36
- package/wiki/{references → extensions}/components/authorization/api.md +62 -13
- package/wiki/{references → extensions}/components/authorization/errors.md +12 -7
- package/wiki/{references → extensions}/components/authorization/index.md +93 -6
- package/wiki/{references → extensions}/components/authorization/usage.md +42 -4
- package/wiki/{references → extensions}/components/health-check.md +5 -4
- package/wiki/{references → extensions}/components/index.md +2 -0
- package/wiki/{references → extensions}/components/mail/index.md +1 -1
- package/wiki/{references → extensions}/components/request-tracker.md +1 -1
- package/wiki/{references → extensions}/components/socket-io/api.md +2 -2
- package/wiki/{references → extensions}/components/socket-io/errors.md +2 -0
- package/wiki/{references → extensions}/components/socket-io/index.md +24 -20
- package/wiki/{references → extensions}/components/socket-io/usage.md +2 -2
- package/wiki/{references → extensions}/components/static-asset/api.md +14 -15
- package/wiki/{references → extensions}/components/static-asset/errors.md +3 -1
- package/wiki/{references → extensions}/components/static-asset/index.md +158 -89
- package/wiki/{references → extensions}/components/static-asset/usage.md +8 -5
- package/wiki/{references → extensions}/components/swagger.md +3 -3
- package/wiki/{references → extensions}/components/template/index.md +4 -4
- package/wiki/{references → extensions}/components/template/setup-page.md +1 -1
- package/wiki/{references → extensions}/components/template/single-page.md +1 -1
- package/wiki/{references → extensions}/components/websocket/api.md +7 -6
- package/wiki/{references → extensions}/components/websocket/errors.md +17 -3
- package/wiki/{references → extensions}/components/websocket/index.md +17 -11
- package/wiki/{references → extensions}/components/websocket/usage.md +2 -2
- package/wiki/{references → extensions}/helpers/crypto/index.md +1 -1
- package/wiki/{references → extensions}/helpers/env/index.md +9 -5
- package/wiki/{references → extensions}/helpers/error/index.md +2 -7
- package/wiki/{references → extensions}/helpers/index.md +18 -6
- package/wiki/{references → extensions}/helpers/kafka/admin.md +13 -1
- package/wiki/{references → extensions}/helpers/kafka/consumer.md +28 -28
- package/wiki/{references → extensions}/helpers/kafka/examples.md +19 -19
- package/wiki/{references → extensions}/helpers/kafka/index.md +51 -48
- package/wiki/{references → extensions}/helpers/kafka/producer.md +18 -18
- package/wiki/{references → extensions}/helpers/kafka/schema-registry.md +25 -25
- package/wiki/{references → extensions}/helpers/logger/index.md +2 -2
- package/wiki/{references → extensions}/helpers/queue/index.md +400 -4
- package/wiki/{references → extensions}/helpers/storage/api.md +170 -10
- package/wiki/{references → extensions}/helpers/storage/index.md +44 -8
- package/wiki/{references → extensions}/helpers/template/index.md +1 -1
- package/wiki/{references → extensions}/helpers/testing/index.md +4 -4
- package/wiki/{references → extensions}/helpers/types/index.md +63 -16
- package/wiki/{references → extensions}/helpers/websocket/index.md +1 -1
- package/wiki/extensions/index.md +48 -0
- package/wiki/guides/core-concepts/application/bootstrapping.md +55 -37
- package/wiki/guides/core-concepts/application/index.md +95 -35
- package/wiki/guides/core-concepts/components-guide.md +23 -19
- package/wiki/guides/core-concepts/components.md +34 -10
- package/wiki/guides/core-concepts/dependency-injection.md +99 -34
- package/wiki/guides/core-concepts/grpc-controllers.md +295 -0
- package/wiki/guides/core-concepts/persistent/datasources.md +27 -8
- package/wiki/guides/core-concepts/persistent/models.md +43 -1
- package/wiki/guides/core-concepts/persistent/repositories.md +75 -8
- package/wiki/guides/core-concepts/persistent/transactions.md +38 -8
- package/wiki/guides/core-concepts/{controllers.md → rest-controllers.md} +30 -33
- package/wiki/guides/core-concepts/services.md +19 -5
- package/wiki/guides/get-started/5-minute-quickstart.md +6 -7
- package/wiki/guides/get-started/philosophy.md +1 -1
- package/wiki/guides/index.md +2 -2
- package/wiki/guides/reference/glossary.md +7 -7
- package/wiki/guides/reference/mcp-docs-server.md +1 -1
- package/wiki/guides/tutorials/building-a-crud-api.md +2 -2
- package/wiki/guides/tutorials/complete-installation.md +17 -14
- package/wiki/guides/tutorials/ecommerce-api.md +18 -18
- package/wiki/guides/tutorials/realtime-chat.md +8 -8
- package/wiki/guides/tutorials/testing.md +2 -2
- package/wiki/index.md +4 -3
- package/wiki/references/base/application.md +341 -21
- package/wiki/references/base/bootstrapping.md +43 -13
- package/wiki/references/base/components.md +259 -8
- package/wiki/references/base/controllers.md +556 -253
- package/wiki/references/base/datasources.md +159 -79
- package/wiki/references/base/dependency-injection.md +299 -48
- package/wiki/references/base/filter-system/application-usage.md +18 -2
- package/wiki/references/base/filter-system/array-operators.md +14 -6
- package/wiki/references/base/filter-system/comparison-operators.md +9 -3
- package/wiki/references/base/filter-system/default-filter.md +28 -3
- package/wiki/references/base/filter-system/fields-order-pagination.md +17 -13
- package/wiki/references/base/filter-system/index.md +169 -11
- package/wiki/references/base/filter-system/json-filtering.md +51 -18
- package/wiki/references/base/filter-system/list-operators.md +4 -3
- package/wiki/references/base/filter-system/logical-operators.md +7 -2
- package/wiki/references/base/filter-system/null-operators.md +50 -0
- package/wiki/references/base/filter-system/quick-reference.md +82 -243
- package/wiki/references/base/filter-system/range-operators.md +7 -1
- package/wiki/references/base/filter-system/tips.md +34 -7
- package/wiki/references/base/filter-system/use-cases.md +6 -5
- package/wiki/references/base/grpc-controllers.md +984 -0
- package/wiki/references/base/index.md +32 -24
- package/wiki/references/base/middleware.md +347 -0
- package/wiki/references/base/models.md +390 -46
- package/wiki/references/base/providers.md +14 -14
- package/wiki/references/base/repositories/advanced.md +84 -69
- package/wiki/references/base/repositories/index.md +447 -12
- package/wiki/references/base/repositories/mixins.md +103 -98
- package/wiki/references/base/repositories/relations.md +129 -45
- package/wiki/references/base/repositories/soft-deletable.md +104 -23
- package/wiki/references/base/services.md +94 -14
- package/wiki/references/index.md +12 -10
- package/wiki/references/quick-reference.md +98 -65
- package/wiki/references/utilities/crypto.md +21 -4
- package/wiki/references/utilities/date.md +25 -7
- package/wiki/references/utilities/index.md +26 -24
- package/wiki/references/utilities/jsx.md +54 -54
- package/wiki/references/utilities/module.md +8 -6
- package/wiki/references/utilities/parse.md +16 -9
- package/wiki/references/utilities/performance.md +22 -7
- package/wiki/references/utilities/promise.md +19 -16
- package/wiki/references/utilities/request.md +48 -26
- package/wiki/references/utilities/schema.md +69 -6
- package/wiki/references/utilities/statuses.md +131 -140
- /package/wiki/{references → extensions}/components/mail/api.md +0 -0
- /package/wiki/{references → extensions}/components/mail/errors.md +0 -0
- /package/wiki/{references → extensions}/components/mail/usage.md +0 -0
- /package/wiki/{references → extensions}/components/template/api-page.md +0 -0
- /package/wiki/{references → extensions}/components/template/errors-page.md +0 -0
- /package/wiki/{references → extensions}/components/template/usage-page.md +0 -0
- /package/wiki/{references → extensions}/helpers/cron/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/inversion/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/network/api.md +0 -0
- /package/wiki/{references → extensions}/helpers/network/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/redis/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/socket-io/api.md +0 -0
- /package/wiki/{references → extensions}/helpers/socket-io/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/template/single-page.md +0 -0
- /package/wiki/{references → extensions}/helpers/uid/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/websocket/api.md +0 -0
- /package/wiki/{references → extensions}/helpers/worker-thread/index.md +0 -0
- /package/wiki/{references → extensions}/src-details/mcp-server.md +0 -0
|
@@ -29,150 +29,153 @@ Ignis uses the mixin pattern to compose repository features. This enables:
|
|
|
29
29
|
|
|
30
30
|
| Mixin | Responsibility |
|
|
31
31
|
|-------|----------------|
|
|
32
|
+
| `FieldsVisibilityMixin` | Hidden properties exclusion at SQL level |
|
|
32
33
|
| `DefaultFilterMixin` | Automatic filter application from model settings |
|
|
33
|
-
| `FieldsVisibilityMixin` | Hidden properties exclusion |
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
##
|
|
36
|
+
## FieldsVisibilityMixin
|
|
37
37
|
|
|
38
|
-
Provides
|
|
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.
|
|
39
39
|
|
|
40
|
-
**File:** `packages/core/src/base/repositories/mixins/
|
|
40
|
+
**File:** `packages/core/src/base/repositories/mixins/fields-visibility.ts`
|
|
41
|
+
|
|
42
|
+
### Abstract Requirements
|
|
43
|
+
|
|
44
|
+
Classes using this mixin must implement:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
abstract getEntity(): BaseEntity<TTableSchemaWithId>;
|
|
48
|
+
```
|
|
41
49
|
|
|
42
50
|
### Properties
|
|
43
51
|
|
|
44
52
|
| Property | Type | Description |
|
|
45
53
|
|----------|------|-------------|
|
|
46
|
-
| `
|
|
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) |
|
|
47
56
|
|
|
48
57
|
### Methods
|
|
49
58
|
|
|
50
59
|
| Method | Returns | Description |
|
|
51
60
|
|--------|---------|-------------|
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
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. |
|
|
55
68
|
|
|
56
69
|
### Usage
|
|
57
70
|
|
|
58
71
|
```typescript
|
|
59
|
-
import {
|
|
72
|
+
import { FieldsVisibilityMixin } from '@venizia/ignis';
|
|
60
73
|
import { BaseHelper } from '@venizia/ignis-helpers';
|
|
61
74
|
|
|
62
|
-
class MyRepository extends
|
|
63
|
-
// Required abstract
|
|
75
|
+
class MyRepository extends FieldsVisibilityMixin(BaseHelper) {
|
|
76
|
+
// Required abstract implementation
|
|
64
77
|
abstract getEntity(): BaseEntity;
|
|
65
|
-
abstract get filterBuilder(): FilterBuilder;
|
|
66
78
|
|
|
67
79
|
// Now has access to:
|
|
68
|
-
// -
|
|
69
|
-
// -
|
|
70
|
-
// -
|
|
80
|
+
// - hiddenProperties (getter/setter)
|
|
81
|
+
// - visibleProperties (getter/setter)
|
|
82
|
+
// - getHiddenProperties()
|
|
83
|
+
// - hasHiddenProperties()
|
|
84
|
+
// - getVisibleProperties()
|
|
71
85
|
}
|
|
72
86
|
```
|
|
73
87
|
|
|
74
|
-
###
|
|
88
|
+
### Visible Properties for Drizzle
|
|
89
|
+
|
|
90
|
+
The `getVisibleProperties()` method returns a columns object for Drizzle's `select()` or `returning()`:
|
|
75
91
|
|
|
76
92
|
```typescript
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
93
|
+
// Model with hiddenProperties: ['password', 'apiKey']
|
|
94
|
+
// Schema columns: { id, email, password, apiKey, createdAt }
|
|
95
|
+
|
|
96
|
+
const visibleProps = this.getVisibleProperties();
|
|
97
|
+
// Result: { id: column, email: column, createdAt: column }
|
|
98
|
+
// (password and apiKey excluded)
|
|
99
|
+
|
|
100
|
+
// Used in Drizzle queries
|
|
101
|
+
await connector.select(visibleProps).from(schema);
|
|
102
|
+
// SELECT id, email, created_at FROM users
|
|
81
103
|
```
|
|
82
104
|
|
|
83
|
-
###
|
|
105
|
+
### How It Resolves Hidden Properties
|
|
84
106
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}): TFilter<DataObject> {
|
|
90
|
-
const { userFilter, shouldSkipDefaultFilter } = opts;
|
|
91
|
-
|
|
92
|
-
// Skip default filter if explicitly requested
|
|
93
|
-
if (shouldSkipDefaultFilter) {
|
|
94
|
-
return userFilter ?? {};
|
|
95
|
-
}
|
|
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.
|
|
96
111
|
|
|
97
|
-
// Get default filter from model metadata
|
|
98
|
-
const defaultFilter = this.getDefaultFilter();
|
|
99
112
|
|
|
100
|
-
|
|
101
|
-
if (!defaultFilter) {
|
|
102
|
-
return userFilter ?? {};
|
|
103
|
-
}
|
|
113
|
+
## DefaultFilterMixin
|
|
104
114
|
|
|
105
|
-
|
|
106
|
-
return this.filterBuilder.mergeFilter({ defaultFilter, userFilter });
|
|
107
|
-
}
|
|
108
|
-
```
|
|
115
|
+
Provides automatic default filter application for all repository queries. Reads `defaultFilter` from `@model` metadata settings and merges it with user-provided filters.
|
|
109
116
|
|
|
117
|
+
**File:** `packages/core/src/base/repositories/mixins/default-filter.ts`
|
|
110
118
|
|
|
111
|
-
|
|
119
|
+
### Abstract Requirements
|
|
112
120
|
|
|
113
|
-
|
|
121
|
+
Classes using this mixin must implement:
|
|
114
122
|
|
|
115
|
-
|
|
123
|
+
```typescript
|
|
124
|
+
abstract getEntity(): BaseEntity<TTableSchemaWithId>;
|
|
125
|
+
abstract get filterBuilder(): FilterBuilder;
|
|
126
|
+
```
|
|
116
127
|
|
|
117
128
|
### Properties
|
|
118
129
|
|
|
119
130
|
| Property | Type | Description |
|
|
120
131
|
|----------|------|-------------|
|
|
121
|
-
| `
|
|
122
|
-
| `_visibleProperties` | `Record<string, any> \| null \| undefined` | Cached visible columns |
|
|
132
|
+
| `_defaultFilter` | `TFilter \| null \| undefined` | Cached default filter (`null` = not yet computed, `undefined` = computed with no default filter) |
|
|
123
133
|
|
|
124
134
|
### Methods
|
|
125
135
|
|
|
126
136
|
| Method | Returns | Description |
|
|
127
137
|
|--------|---------|-------------|
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `hasHiddenProperties()` | `boolean` | Check if model has hidden properties |
|
|
132
|
-
| `get visibleProperties` | `Record<string, any> \| undefined` | Get visible columns for Drizzle |
|
|
133
|
-
| `set visibleProperties` | `void` | Override visible properties |
|
|
134
|
-
| `getVisibleProperties()` | `Record<string, any> \| undefined` | Build visible columns object |
|
|
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 |
|
|
135
141
|
|
|
136
142
|
### Usage
|
|
137
143
|
|
|
138
144
|
```typescript
|
|
139
|
-
import {
|
|
145
|
+
import { DefaultFilterMixin } from '@venizia/ignis';
|
|
140
146
|
import { BaseHelper } from '@venizia/ignis-helpers';
|
|
141
147
|
|
|
142
|
-
class MyRepository extends
|
|
143
|
-
// Required abstract
|
|
148
|
+
class MyRepository extends DefaultFilterMixin(BaseHelper) {
|
|
149
|
+
// Required abstract implementations
|
|
144
150
|
abstract getEntity(): BaseEntity;
|
|
151
|
+
abstract get filterBuilder(): FilterBuilder;
|
|
145
152
|
|
|
146
153
|
// Now has access to:
|
|
147
|
-
// -
|
|
148
|
-
// -
|
|
149
|
-
// -
|
|
150
|
-
// - hasHiddenProperties()
|
|
151
|
-
// - getVisibleProperties()
|
|
154
|
+
// - getDefaultFilter()
|
|
155
|
+
// - hasDefaultFilter()
|
|
156
|
+
// - applyDefaultFilter()
|
|
152
157
|
}
|
|
153
158
|
```
|
|
154
159
|
|
|
155
|
-
###
|
|
156
|
-
|
|
157
|
-
The `getVisibleProperties()` method returns a columns object for Drizzle's `select()` or `returning()`:
|
|
160
|
+
### applyDefaultFilter Options
|
|
158
161
|
|
|
159
162
|
```typescript
|
|
160
|
-
|
|
161
|
-
//
|
|
163
|
+
applyDefaultFilter<DataObject = any>(opts: {
|
|
164
|
+
userFilter?: TFilter<DataObject>; // User-provided filter
|
|
165
|
+
shouldSkipDefaultFilter?: boolean; // If true, bypass default filter
|
|
166
|
+
}): TFilter<DataObject>
|
|
167
|
+
```
|
|
162
168
|
|
|
163
|
-
|
|
164
|
-
// Result: { id: column, email: column, createdAt: column }
|
|
165
|
-
// (password and apiKey excluded)
|
|
169
|
+
**Behavior:**
|
|
166
170
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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`).
|
|
171
174
|
|
|
172
175
|
|
|
173
176
|
## Mixin Composition
|
|
174
177
|
|
|
175
|
-
The `AbstractRepository` composes
|
|
178
|
+
The `AbstractRepository` composes both mixins:
|
|
176
179
|
|
|
177
180
|
```typescript
|
|
178
181
|
export abstract class AbstractRepository<...>
|
|
@@ -180,17 +183,17 @@ export abstract class AbstractRepository<...>
|
|
|
180
183
|
implements IPersistableRepository<...>
|
|
181
184
|
{
|
|
182
185
|
// Inherits from both mixins:
|
|
183
|
-
// From DefaultFilterMixin:
|
|
184
|
-
// - getDefaultFilter()
|
|
185
|
-
// - hasDefaultFilter()
|
|
186
|
-
// - applyDefaultFilter()
|
|
187
|
-
//
|
|
188
186
|
// From FieldsVisibilityMixin:
|
|
189
|
-
// - hiddenProperties
|
|
190
|
-
// - visibleProperties
|
|
187
|
+
// - hiddenProperties (getter/setter)
|
|
188
|
+
// - visibleProperties (getter/setter)
|
|
191
189
|
// - getHiddenProperties()
|
|
192
190
|
// - hasHiddenProperties()
|
|
193
191
|
// - getVisibleProperties()
|
|
192
|
+
//
|
|
193
|
+
// From DefaultFilterMixin:
|
|
194
|
+
// - getDefaultFilter()
|
|
195
|
+
// - hasDefaultFilter()
|
|
196
|
+
// - applyDefaultFilter()
|
|
194
197
|
}
|
|
195
198
|
```
|
|
196
199
|
|
|
@@ -207,7 +210,7 @@ DefaultFilterMixin(FieldsVisibilityMixin(BaseHelper))
|
|
|
207
210
|
|
|
208
211
|
## Creating Custom Mixins
|
|
209
212
|
|
|
210
|
-
Follow the TypeScript mixin pattern
|
|
213
|
+
Follow the TypeScript mixin pattern using `TMixinTarget`:
|
|
211
214
|
|
|
212
215
|
```typescript
|
|
213
216
|
import { TMixinTarget } from '@venizia/ignis-helpers';
|
|
@@ -269,44 +272,46 @@ class ProductRepository extends AuditableRepository {
|
|
|
269
272
|
|
|
270
273
|
## Caching Behavior
|
|
271
274
|
|
|
272
|
-
Both mixins use caching for performance:
|
|
275
|
+
Both mixins use a three-state caching pattern for performance:
|
|
273
276
|
|
|
274
277
|
```typescript
|
|
275
278
|
// DefaultFilterMixin caching
|
|
276
279
|
// null = not computed yet
|
|
277
|
-
// undefined = computed, no default filter
|
|
280
|
+
// undefined = computed, no default filter exists
|
|
278
281
|
// TFilter = computed, has default filter
|
|
279
|
-
|
|
282
|
+
_defaultFilter: TFilter | null | undefined = null;
|
|
280
283
|
|
|
281
284
|
getDefaultFilter() {
|
|
282
285
|
if (this._defaultFilter !== null) {
|
|
283
|
-
return this._defaultFilter; // Return cached value
|
|
286
|
+
return this._defaultFilter; // Return cached value (either TFilter or undefined)
|
|
284
287
|
}
|
|
285
|
-
// Compute and cache...
|
|
288
|
+
// Compute from MetadataRegistry and cache...
|
|
286
289
|
}
|
|
287
290
|
|
|
288
291
|
// FieldsVisibilityMixin caching
|
|
289
292
|
// null = not computed yet
|
|
290
|
-
// Set<string> = computed
|
|
291
|
-
|
|
293
|
+
// Set<string> = computed (may be empty)
|
|
294
|
+
_hiddenProperties: Set<string> | null = null;
|
|
292
295
|
|
|
293
296
|
// null = not computed yet
|
|
294
|
-
// undefined = computed, no hidden properties
|
|
295
|
-
// Record<string, any> = computed, has
|
|
296
|
-
|
|
297
|
+
// undefined = computed, no hidden properties exist
|
|
298
|
+
// Record<string, any> = computed, has visible column map
|
|
299
|
+
_visibleProperties: Record<string, any> | null | undefined = null;
|
|
297
300
|
```
|
|
298
301
|
|
|
302
|
+
This ensures metadata lookups happen only once per repository instance, with subsequent calls returning the cached value.
|
|
303
|
+
|
|
299
304
|
|
|
300
305
|
## Quick Reference
|
|
301
306
|
|
|
302
307
|
| Mixin | Method | Purpose |
|
|
303
308
|
|-------|--------|---------|
|
|
304
|
-
| `DefaultFilterMixin` | `hasDefaultFilter()` | Check if default filter exists |
|
|
305
|
-
| `DefaultFilterMixin` | `getDefaultFilter()` | Get raw default filter |
|
|
306
|
-
| `DefaultFilterMixin` | `applyDefaultFilter()` | Merge filters |
|
|
307
309
|
| `FieldsVisibilityMixin` | `hasHiddenProperties()` | Check if hidden props exist |
|
|
308
|
-
| `FieldsVisibilityMixin` | `getHiddenProperties()` | Get hidden property names |
|
|
309
|
-
| `FieldsVisibilityMixin` | `getVisibleProperties()` | Get Drizzle columns object |
|
|
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 |
|
|
310
315
|
|
|
311
316
|
|
|
312
317
|
## Next Steps
|
|
@@ -62,6 +62,9 @@ const post = await postRepo.findOne({
|
|
|
62
62
|
});
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
> [!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
|
+
|
|
65
68
|
|
|
66
69
|
## Scoped Includes
|
|
67
70
|
|
|
@@ -138,6 +141,23 @@ const user = await userRepo.findOne({
|
|
|
138
141
|
});
|
|
139
142
|
```
|
|
140
143
|
|
|
144
|
+
### Skip Default Filter on Includes
|
|
145
|
+
|
|
146
|
+
Each inclusion can independently bypass the related model's default filter:
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
// Include soft-deleted posts that would normally be filtered out
|
|
150
|
+
const user = await userRepo.findOne({
|
|
151
|
+
filter: {
|
|
152
|
+
where: { id: '123' },
|
|
153
|
+
include: [{
|
|
154
|
+
relation: 'posts',
|
|
155
|
+
shouldSkipDefaultFilter: true
|
|
156
|
+
}]
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
141
161
|
|
|
142
162
|
## Nested Includes
|
|
143
163
|
|
|
@@ -146,7 +166,7 @@ Include relations of relations (up to 2 levels recommended):
|
|
|
146
166
|
### Two-Level Nesting
|
|
147
167
|
|
|
148
168
|
```typescript
|
|
149
|
-
// User
|
|
169
|
+
// User -> Posts -> Comments
|
|
150
170
|
const user = await userRepo.findOne({
|
|
151
171
|
filter: {
|
|
152
172
|
where: { id: '123' },
|
|
@@ -179,7 +199,7 @@ const user = await userRepo.findOne({
|
|
|
179
199
|
### Many-to-Many Through Junction
|
|
180
200
|
|
|
181
201
|
```typescript
|
|
182
|
-
// Product
|
|
202
|
+
// Product -> SaleChannelProduct (junction) -> SaleChannel
|
|
183
203
|
const product = await productRepo.findOne({
|
|
184
204
|
filter: {
|
|
185
205
|
where: { id: 'prod1' },
|
|
@@ -216,7 +236,26 @@ const product = await productRepo.findOne({
|
|
|
216
236
|
|
|
217
237
|
## Defining Relations
|
|
218
238
|
|
|
219
|
-
Relations
|
|
239
|
+
Relations are defined using the `createRelations` helper and the `TRelationConfig` type. These use Drizzle ORM's relation system under the hood.
|
|
240
|
+
|
|
241
|
+
### Relation Config Type
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
type TRelationConfig = {
|
|
245
|
+
name: string; // Relation name used in includes
|
|
246
|
+
} & (
|
|
247
|
+
| {
|
|
248
|
+
type: 'one'; // one-to-one or many-to-one
|
|
249
|
+
schema: TTableSchemaWithId;
|
|
250
|
+
metadata: { fields, references, relationName? };
|
|
251
|
+
}
|
|
252
|
+
| {
|
|
253
|
+
type: 'many'; // one-to-many
|
|
254
|
+
schema: TTableSchemaWithId;
|
|
255
|
+
metadata: { relationName? };
|
|
256
|
+
}
|
|
257
|
+
);
|
|
258
|
+
```
|
|
220
259
|
|
|
221
260
|
### In Your Model
|
|
222
261
|
|
|
@@ -230,14 +269,14 @@ export const userTable = pgTable('User', {
|
|
|
230
269
|
email: text('email').notNull(),
|
|
231
270
|
});
|
|
232
271
|
|
|
233
|
-
|
|
272
|
+
const userRelationsConfig = createRelations({
|
|
234
273
|
source: userTable,
|
|
235
274
|
relations: [
|
|
236
275
|
{
|
|
237
|
-
type: '
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
276
|
+
type: 'many',
|
|
277
|
+
schema: postTable,
|
|
278
|
+
name: 'posts',
|
|
279
|
+
metadata: { relationName: 'posts' },
|
|
241
280
|
},
|
|
242
281
|
],
|
|
243
282
|
});
|
|
@@ -245,45 +284,64 @@ export const userRelations = createRelations({
|
|
|
245
284
|
@model({ type: 'entity' })
|
|
246
285
|
export class User extends BaseEntity<typeof User.schema> {
|
|
247
286
|
static override schema = userTable;
|
|
248
|
-
static override relations = () =>
|
|
287
|
+
static override relations = () => userRelationsConfig.definitions;
|
|
249
288
|
static override TABLE_NAME = 'User';
|
|
250
289
|
}
|
|
251
290
|
```
|
|
252
291
|
|
|
253
292
|
### Relation Types
|
|
254
293
|
|
|
255
|
-
| Type | Description | Example |
|
|
256
|
-
|
|
257
|
-
| `
|
|
258
|
-
| `
|
|
259
|
-
| `belongsTo` | Inverse of hasMany/hasOne | Post belongs to User |
|
|
294
|
+
| Type | Drizzle Function | Description | Example |
|
|
295
|
+
|------|------------------|-------------|---------|
|
|
296
|
+
| `'one'` | `one()` | One-to-one or many-to-one | Post has one Author, User has one Profile |
|
|
297
|
+
| `'many'` | `many()` | One-to-many | User has many Posts |
|
|
260
298
|
|
|
261
|
-
|
|
299
|
+
> [!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.
|
|
301
|
+
|
|
302
|
+
### Example: Post Model with Both Types
|
|
262
303
|
|
|
263
304
|
```typescript
|
|
264
|
-
|
|
305
|
+
const postRelationsConfig = createRelations({
|
|
265
306
|
source: postTable,
|
|
266
307
|
relations: [
|
|
267
308
|
{
|
|
268
|
-
type: '
|
|
269
|
-
|
|
270
|
-
foreignKey: 'authorId',
|
|
309
|
+
type: 'one',
|
|
310
|
+
schema: userTable,
|
|
271
311
|
name: 'author',
|
|
312
|
+
metadata: {
|
|
313
|
+
fields: [postTable.authorId],
|
|
314
|
+
references: [userTable.id],
|
|
315
|
+
},
|
|
272
316
|
},
|
|
273
317
|
{
|
|
274
|
-
type: '
|
|
275
|
-
|
|
276
|
-
foreignKey: 'postId',
|
|
318
|
+
type: 'many',
|
|
319
|
+
schema: commentTable,
|
|
277
320
|
name: 'comments',
|
|
321
|
+
metadata: { relationName: 'comments' },
|
|
278
322
|
},
|
|
279
323
|
],
|
|
280
324
|
});
|
|
281
325
|
```
|
|
282
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
|
+
```
|
|
337
|
+
|
|
338
|
+
- **`definitions`**: Used by `BaseEntity.relations` for include resolution at runtime.
|
|
339
|
+
- **`relations`**: The actual Drizzle ORM relations definition, needed for DataSource schema registration.
|
|
340
|
+
|
|
283
341
|
|
|
284
342
|
## Auto-Resolution
|
|
285
343
|
|
|
286
|
-
Relations are automatically resolved from the entity's static `relations` property. No need to pass them in the repository constructor:
|
|
344
|
+
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:
|
|
287
345
|
|
|
288
346
|
```typescript
|
|
289
347
|
@repository({ model: User, dataSource: PostgresDataSource })
|
|
@@ -293,6 +351,27 @@ export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {
|
|
|
293
351
|
```
|
|
294
352
|
|
|
295
353
|
|
|
354
|
+
## Hidden Properties in Relations
|
|
355
|
+
|
|
356
|
+
When building include queries, the `FilterBuilder.toInclude()` method automatically:
|
|
357
|
+
|
|
358
|
+
1. Resolves hidden properties for each related model via `resolveHiddenProperties()`.
|
|
359
|
+
2. Resolves the default filter for each related model via `resolveDefaultFilter()`.
|
|
360
|
+
3. Merges the default filter with any user-provided `scope`.
|
|
361
|
+
4. Excludes hidden columns from the nested query's `columns` selection.
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
// User model has hiddenProperties: ['password']
|
|
365
|
+
const post = await postRepo.findOne({
|
|
366
|
+
filter: {
|
|
367
|
+
include: [{ relation: 'author' }]
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// post.author will NOT include password - excluded at SQL level
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
|
|
296
375
|
## Type Safety with Generics
|
|
297
376
|
|
|
298
377
|
For queries with `include`, use generic type overrides for full type safety:
|
|
@@ -343,6 +422,19 @@ product?.saleChannelProducts[0].saleChannel.name;
|
|
|
343
422
|
```
|
|
344
423
|
|
|
345
424
|
|
|
425
|
+
## TInclusion Type Reference
|
|
426
|
+
|
|
427
|
+
Each element in the `include` array has this shape:
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
type TInclusion = {
|
|
431
|
+
relation: string; // Name of the relation to include
|
|
432
|
+
scope?: TFilter; // Optional nested filter (where, order, limit, fields, include)
|
|
433
|
+
shouldSkipDefaultFilter?: boolean; // Skip the related model's default filter
|
|
434
|
+
};
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
|
|
346
438
|
## Common Patterns
|
|
347
439
|
|
|
348
440
|
### Find All with Count of Relations
|
|
@@ -382,21 +474,6 @@ async function getUser(id: string, includePosts: boolean) {
|
|
|
382
474
|
}
|
|
383
475
|
```
|
|
384
476
|
|
|
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
477
|
|
|
401
478
|
## Error Handling
|
|
402
479
|
|
|
@@ -405,7 +482,7 @@ const post = await postRepo.findOne({
|
|
|
405
482
|
If you try to include a relation that doesn't exist:
|
|
406
483
|
|
|
407
484
|
```typescript
|
|
408
|
-
// Error: Relation
|
|
485
|
+
// Error: [FilterBuilder][toInclude] Relation NOT FOUND | relation: 'nonExistent'
|
|
409
486
|
await userRepo.find({
|
|
410
487
|
filter: {
|
|
411
488
|
include: [{ relation: 'nonExistent' }]
|
|
@@ -413,7 +490,15 @@ await userRepo.find({
|
|
|
413
490
|
});
|
|
414
491
|
```
|
|
415
492
|
|
|
416
|
-
**Fix:** Check your model's `relations` definition.
|
|
493
|
+
**Fix:** Check your model's `relations` definition and ensure the relation name matches.
|
|
494
|
+
|
|
495
|
+
### Invalid Include Format
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
// Error: [FilterBuilder][toInclude] Invalid include format | include: ...
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
**Fix:** Ensure each include element has a `relation` string property.
|
|
417
502
|
|
|
418
503
|
### Schema Key Mismatch
|
|
419
504
|
|
|
@@ -431,6 +516,7 @@ in connector.query | Available keys: [Post, Comment]
|
|
|
431
516
|
2. **Use `fields` in scope** - Only fetch needed columns
|
|
432
517
|
3. **Use `limit` in scope** - Don't fetch unbounded related data
|
|
433
518
|
4. **Consider separate queries** - For complex data needs, multiple simple queries often outperform one complex nested query
|
|
519
|
+
5. **Use `shouldSkipDefaultFilter` sparingly** - Only when you explicitly need filtered-out records
|
|
434
520
|
|
|
435
521
|
```typescript
|
|
436
522
|
// Instead of deep nesting, use separate queries
|
|
@@ -443,7 +529,7 @@ const posts = await postRepo.find({
|
|
|
443
529
|
});
|
|
444
530
|
const comments = await commentRepo.find({
|
|
445
531
|
filter: {
|
|
446
|
-
where: { postId: {
|
|
532
|
+
where: { postId: { inq: posts.map(p => p.id) } }
|
|
447
533
|
}
|
|
448
534
|
});
|
|
449
535
|
```
|
|
@@ -460,6 +546,7 @@ const comments = await commentRepo.find({
|
|
|
460
546
|
| Limit included | `include: [{ relation: 'posts', scope: { limit: 5 } }]` |
|
|
461
547
|
| Nested include | `include: [{ relation: 'posts', scope: { include: [{ relation: 'comments' }] } }]` |
|
|
462
548
|
| Select fields | `include: [{ relation: 'posts', scope: { fields: ['id', 'title'] } }]` |
|
|
549
|
+
| Skip default filter | `include: [{ relation: 'posts', shouldSkipDefaultFilter: true }]` |
|
|
463
550
|
|
|
464
551
|
|
|
465
552
|
## Next Steps
|
|
@@ -476,11 +563,8 @@ const comments = await commentRepo.find({
|
|
|
476
563
|
|
|
477
564
|
- **Related Topics:**
|
|
478
565
|
- [Advanced Features](./advanced) - Hidden properties, transactions
|
|
479
|
-
- [Repository Mixins](./mixins) -
|
|
566
|
+
- [Repository Mixins](./mixins) - Default filter and fields visibility
|
|
480
567
|
- [Filter System](/references/base/filter-system/) - Query operators
|
|
481
568
|
|
|
482
569
|
- **External Resources:**
|
|
483
570
|
- [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
|