@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.
- package/README.md +7 -7
- package/content/best-practices/api-usage-examples.md +15 -12
- package/content/best-practices/architectural-patterns.md +70 -78
- package/content/best-practices/architecture-decisions.md +91 -60
- package/content/best-practices/code-style-standards/advanced-patterns.md +56 -44
- package/content/best-practices/code-style-standards/constants-configuration.md +11 -11
- package/content/best-practices/code-style-standards/control-flow.md +5 -2
- package/content/best-practices/code-style-standards/documentation.md +13 -13
- package/content/best-practices/code-style-standards/function-patterns.md +9 -10
- package/content/best-practices/code-style-standards/index.md +1 -1
- package/content/best-practices/code-style-standards/naming-conventions.md +10 -8
- package/content/best-practices/code-style-standards/route-definitions.md +30 -12
- package/content/best-practices/code-style-standards/tooling.md +8 -5
- package/content/best-practices/code-style-standards/type-safety.md +13 -12
- package/content/best-practices/common-pitfalls.md +56 -37
- package/content/best-practices/contribution-workflow.md +13 -14
- package/content/best-practices/data-modeling.md +46 -22
- package/content/best-practices/deployment-strategies.md +28 -27
- package/content/best-practices/error-handling.md +48 -24
- package/content/best-practices/index.md +5 -5
- package/content/best-practices/performance-optimization.md +40 -31
- package/content/best-practices/security-guidelines.md +52 -23
- package/content/best-practices/testing-strategies.md +65 -51
- package/content/best-practices/troubleshooting-tips.md +24 -24
- package/content/extensions/components/{swagger.md → api-reference.md} +40 -31
- package/content/extensions/components/authentication/api.md +19 -19
- package/content/extensions/components/authentication/errors.md +7 -7
- package/content/extensions/components/authentication/index.md +10 -8
- package/content/extensions/components/authentication/usage.md +101 -6
- package/content/extensions/components/authorization/api.md +45 -25
- package/content/extensions/components/authorization/errors.md +6 -6
- package/content/extensions/components/authorization/index.md +11 -10
- package/content/extensions/components/authorization/usage.md +21 -21
- package/content/extensions/components/health-check.md +1 -1
- package/content/extensions/components/index.md +5 -5
- package/content/extensions/components/mail/errors.md +15 -15
- package/content/extensions/components/mail/index.md +1 -2
- package/content/extensions/components/mail/usage.md +1 -1
- package/content/extensions/components/request-tracker.md +1 -1
- package/content/extensions/components/socket-io/api.md +9 -9
- package/content/extensions/components/socket-io/errors.md +5 -5
- package/content/extensions/components/socket-io/index.md +8 -8
- package/content/extensions/components/socket-io/usage.md +1 -1
- package/content/extensions/components/static-asset/api.md +17 -4
- package/content/extensions/components/static-asset/errors.md +4 -4
- package/content/extensions/components/static-asset/index.md +26 -28
- package/content/extensions/components/static-asset/usage.md +13 -12
- package/content/extensions/components/template/index.md +2 -2
- package/content/extensions/components/template/setup-page.md +1 -1
- package/content/extensions/components/websocket/api.md +3 -3
- package/content/extensions/components/websocket/errors.md +5 -5
- package/content/extensions/components/websocket/index.md +5 -5
- package/content/extensions/components/websocket/usage.md +3 -3
- package/content/extensions/helpers/cron/index.md +2 -2
- package/content/extensions/helpers/crypto/index.md +1 -1
- package/content/extensions/helpers/env/index.md +27 -12
- package/content/extensions/helpers/error/index.md +81 -25
- package/content/extensions/helpers/index.md +2 -3
- package/content/extensions/helpers/inversion/index.md +15 -7
- package/content/extensions/helpers/kafka/compile-binary.md +92 -0
- package/content/extensions/helpers/kafka/examples.md +1 -1
- package/content/extensions/helpers/kafka/index.md +3 -0
- package/content/extensions/helpers/logger/index.md +32 -2
- package/content/extensions/helpers/network/index.md +6 -0
- package/content/extensions/helpers/queue/index.md +14 -17
- package/content/extensions/helpers/redis/index.md +548 -323
- package/content/extensions/helpers/socket-io/index.md +14 -10
- package/content/extensions/helpers/storage/api.md +44 -8
- package/content/extensions/helpers/storage/index.md +43 -7
- package/content/extensions/helpers/template/index.md +6 -3
- package/content/extensions/helpers/types/index.md +11 -8
- package/content/extensions/helpers/websocket/api.md +9 -9
- package/content/extensions/helpers/websocket/index.md +7 -7
- package/content/extensions/helpers/worker-thread/index.md +2 -2
- package/content/extensions/index.md +3 -4
- package/content/extensions/src-details/mcp-server.md +18 -24
- package/content/guides/core-concepts/application/bootstrapping.md +11 -14
- package/content/guides/core-concepts/application/index.md +3 -3
- package/content/guides/core-concepts/components.md +19 -10
- package/content/guides/core-concepts/dependency-injection.md +6 -3
- package/content/guides/core-concepts/grpc-controllers.md +6 -5
- package/content/guides/core-concepts/persistent/datasources.md +42 -43
- package/content/guides/core-concepts/persistent/index.md +16 -7
- package/content/guides/core-concepts/persistent/models.md +24 -20
- package/content/guides/core-concepts/persistent/postgres-drivers.md +201 -0
- package/content/guides/core-concepts/persistent/repositories.md +40 -23
- package/content/guides/core-concepts/persistent/search-meilisearch.md +185 -0
- package/content/guides/core-concepts/persistent/search-typesense.md +431 -0
- package/content/guides/core-concepts/persistent/transactions.md +61 -25
- package/content/guides/core-concepts/rest-controllers.md +12 -9
- package/content/guides/core-concepts/services.md +330 -60
- package/content/guides/get-started/5-minute-quickstart.md +15 -15
- package/content/guides/get-started/philosophy.md +36 -36
- package/content/guides/get-started/setup.md +3 -3
- package/content/guides/index.md +3 -3
- package/content/guides/migrations/redis-helpers-migration.md +177 -0
- package/content/guides/migrations/scoped-rbac-migration.md +17 -17
- package/content/guides/migrations/unified-connectors-migration.md +113 -0
- package/content/guides/reference/glossary.md +19 -12
- package/content/guides/reference/mcp-docs-server.md +22 -18
- package/content/guides/tutorials/building-a-crud-api.md +37 -44
- package/content/guides/tutorials/complete-installation.md +17 -17
- package/content/guides/tutorials/ecommerce-api.md +163 -124
- package/content/guides/tutorials/realtime-chat.md +181 -135
- package/content/guides/tutorials/testing.md +65 -523
- package/content/index.md +2 -180
- package/content/public/apple-touch-icon.png +0 -0
- package/content/public/og-image.png +0 -0
- package/content/public/site.webmanifest +11 -0
- package/content/references/base/application.md +4 -5
- package/content/references/base/bootstrapping.md +18 -5
- package/content/references/base/components.md +149 -120
- package/content/references/base/connectors.md +178 -0
- package/content/references/base/controllers.md +41 -30
- package/content/references/base/datasources.md +163 -92
- package/content/references/base/dependency-injection.md +34 -22
- package/content/references/base/filter-system/application-usage.md +17 -14
- package/content/references/base/filter-system/array-operators.md +7 -2
- package/content/references/base/filter-system/comparison-operators.md +3 -0
- package/content/references/base/filter-system/default-filter.md +89 -71
- package/content/references/base/filter-system/fields-order-pagination.md +22 -22
- package/content/references/base/filter-system/index.md +6 -3
- package/content/references/base/filter-system/json-filtering.md +20 -1
- package/content/references/base/filter-system/list-operators.md +1 -1
- package/content/references/base/filter-system/logical-operators.md +33 -1
- package/content/references/base/filter-system/null-operators.md +30 -1
- package/content/references/base/filter-system/quick-reference.md +23 -4
- package/content/references/base/filter-system/tips.md +5 -5
- package/content/references/base/filter-system/use-cases.md +12 -12
- package/content/references/base/grpc-controllers.md +13 -13
- package/content/references/base/index.md +24 -12
- package/content/references/base/middlewares.md +265 -327
- package/content/references/base/models.md +63 -49
- package/content/references/base/providers.md +136 -130
- package/content/references/base/repositories/advanced.md +59 -58
- package/content/references/base/repositories/index.md +115 -91
- package/content/references/base/repositories/mixins.md +55 -291
- package/content/references/base/repositories/relations.md +54 -64
- package/content/references/base/repositories/soft-deletable.md +31 -30
- package/content/references/base/services.md +296 -93
- package/content/references/configuration/environment-variables.md +49 -31
- package/content/references/configuration/index.md +6 -6
- package/content/references/index.md +17 -12
- package/content/references/quick-reference.md +65 -106
- package/content/references/utilities/crypto.md +65 -23
- package/content/references/utilities/index.md +3 -3
- package/content/references/utilities/jsx.md +6 -4
- package/content/references/utilities/module.md +68 -20
- package/content/references/utilities/parse.md +4 -14
- package/content/references/utilities/promise.md +9 -7
- package/content/references/utilities/schema.md +5 -3
- package/dist/mcp-server/common/guards.d.ts +8 -0
- package/dist/mcp-server/common/guards.d.ts.map +1 -0
- package/dist/mcp-server/common/guards.js +14 -0
- package/dist/mcp-server/common/guards.js.map +1 -0
- package/dist/mcp-server/common/index.d.ts +1 -0
- package/dist/mcp-server/common/index.d.ts.map +1 -1
- package/dist/mcp-server/common/index.js +1 -0
- package/dist/mcp-server/common/index.js.map +1 -1
- package/dist/mcp-server/helpers/docs.helper.d.ts.map +1 -1
- package/dist/mcp-server/helpers/docs.helper.js +4 -2
- package/dist/mcp-server/helpers/docs.helper.js.map +1 -1
- package/dist/mcp-server/helpers/github.helper.js +1 -1
- package/dist/mcp-server/index.js +7 -2
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/mcp-server/tools/base.tool.d.ts +6 -2
- package/dist/mcp-server/tools/base.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/base.tool.js.map +1 -1
- package/dist/mcp-server/tools/docs/search-documents.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.js +4 -1
- package/dist/mcp-server/tools/github/search-code.tool.js.map +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +3 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js.map +1 -1
- package/package.json +9 -9
- package/content/extensions/helpers/testing/index.md +0 -510
- package/content/references/base/middleware.md +0 -347
|
@@ -1,335 +1,99 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Repository Mixins
|
|
3
|
-
description:
|
|
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-
|
|
5
|
+
lastUpdated: 2026-07-06
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
# Repository Mixins <Badge type="
|
|
8
|
+
# Repository Mixins <Badge type="danger" text="removed" />
|
|
9
9
|
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
> Repository mixins were extracted and refactored in v0.0.5 to provide better composition and reusability.
|
|
13
|
+
## What They Were
|
|
14
14
|
|
|
15
|
-
|
|
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
|
|
33
|
-
| `DefaultFilterMixin` | Automatic filter
|
|
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
|
-
|
|
24
|
+
The functionality was not dropped - it was folded directly into the repository hierarchy.
|
|
37
25
|
|
|
38
|
-
|
|
26
|
+
### Engine-neutral: `AbstractRepository`
|
|
39
27
|
|
|
40
|
-
**File:** `packages/core/src/base/repositories/
|
|
28
|
+
**File:** `packages/core/src/base/repositories/core/abstract.ts`
|
|
41
29
|
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
43
|
+
Builds on those getters to implement SQL-level column exclusion and full-filter merging for Drizzle:
|
|
70
44
|
|
|
71
45
|
```typescript
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
+
## Migration
|
|
91
62
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
260
|
-
|
|
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
|
-
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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: [
|
|
314
|
-
references: [
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
524
|
-
const posts = await
|
|
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
|
|
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) -
|
|
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:**
|