@venizia/ignis-docs 0.0.2 → 0.0.4-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 +1 -1
- package/package.json +4 -2
- package/wiki/best-practices/api-usage-examples.md +591 -0
- package/wiki/best-practices/architectural-patterns.md +415 -0
- package/wiki/best-practices/architecture-decisions.md +488 -0
- package/wiki/{get-started/best-practices → best-practices}/code-style-standards.md +647 -182
- package/wiki/{get-started/best-practices → best-practices}/common-pitfalls.md +109 -4
- package/wiki/{get-started/best-practices → best-practices}/contribution-workflow.md +34 -7
- package/wiki/best-practices/data-modeling.md +376 -0
- package/wiki/best-practices/deployment-strategies.md +698 -0
- package/wiki/best-practices/index.md +27 -0
- package/wiki/best-practices/performance-optimization.md +196 -0
- package/wiki/best-practices/security-guidelines.md +218 -0
- package/wiki/{get-started/best-practices → best-practices}/troubleshooting-tips.md +97 -1
- package/wiki/changelogs/2025-12-16-initial-architecture.md +1 -1
- package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +1 -1
- package/wiki/changelogs/2025-12-17-refactor.md +1 -1
- package/wiki/changelogs/2025-12-18-performance-optimizations.md +5 -5
- package/wiki/changelogs/2025-12-18-repository-validation-security.md +13 -7
- package/wiki/changelogs/2025-12-26-nested-relations-and-generics.md +86 -0
- package/wiki/changelogs/2025-12-26-transaction-support.md +57 -0
- package/wiki/changelogs/2025-12-29-dynamic-binding-registration.md +104 -0
- package/wiki/changelogs/2025-12-29-snowflake-uid-helper.md +100 -0
- package/wiki/changelogs/2025-12-30-repository-enhancements.md +214 -0
- package/wiki/changelogs/2025-12-31-json-path-filtering-array-operators.md +214 -0
- package/wiki/changelogs/2025-12-31-string-id-custom-generator.md +137 -0
- package/wiki/changelogs/2026-01-02-default-filter-and-repository-mixins.md +418 -0
- package/wiki/changelogs/index.md +8 -1
- package/wiki/changelogs/planned-schema-migrator.md +2 -10
- package/wiki/{get-started/core-concepts → guides/core-concepts/application}/bootstrapping.md +18 -5
- package/wiki/{get-started/core-concepts/application.md → guides/core-concepts/application/index.md} +47 -104
- package/wiki/guides/core-concepts/components-guide.md +509 -0
- package/wiki/guides/core-concepts/components.md +122 -0
- package/wiki/{get-started → guides}/core-concepts/controllers.md +30 -13
- package/wiki/{get-started → guides}/core-concepts/dependency-injection.md +97 -0
- package/wiki/guides/core-concepts/persistent/datasources.md +179 -0
- package/wiki/guides/core-concepts/persistent/index.md +119 -0
- package/wiki/guides/core-concepts/persistent/models.md +241 -0
- package/wiki/guides/core-concepts/persistent/repositories.md +219 -0
- package/wiki/guides/core-concepts/persistent/transactions.md +170 -0
- package/wiki/{get-started → guides}/core-concepts/services.md +26 -3
- package/wiki/{get-started → guides/get-started}/5-minute-quickstart.md +59 -14
- package/wiki/guides/get-started/philosophy.md +682 -0
- package/wiki/guides/get-started/setup.md +157 -0
- package/wiki/guides/index.md +89 -0
- package/wiki/guides/reference/glossary.md +243 -0
- package/wiki/{get-started → guides/reference}/mcp-docs-server.md +0 -10
- package/wiki/{get-started → guides/tutorials}/building-a-crud-api.md +134 -132
- package/wiki/{get-started/quickstart.md → guides/tutorials/complete-installation.md} +107 -71
- package/wiki/guides/tutorials/ecommerce-api.md +1399 -0
- package/wiki/guides/tutorials/realtime-chat.md +1261 -0
- package/wiki/guides/tutorials/testing.md +723 -0
- package/wiki/index.md +176 -37
- package/wiki/references/base/application.md +27 -0
- package/wiki/references/base/bootstrapping.md +30 -26
- package/wiki/references/base/components.md +532 -31
- package/wiki/references/base/controllers.md +136 -38
- package/wiki/references/base/datasources.md +108 -5
- package/wiki/references/base/dependency-injection.md +39 -3
- package/wiki/references/base/filter-system/application-usage.md +224 -0
- package/wiki/references/base/filter-system/array-operators.md +132 -0
- package/wiki/references/base/filter-system/comparison-operators.md +109 -0
- package/wiki/references/base/filter-system/default-filter.md +428 -0
- package/wiki/references/base/filter-system/fields-order-pagination.md +155 -0
- package/wiki/references/base/filter-system/index.md +127 -0
- package/wiki/references/base/filter-system/json-filtering.md +197 -0
- package/wiki/references/base/filter-system/list-operators.md +71 -0
- package/wiki/references/base/filter-system/logical-operators.md +156 -0
- package/wiki/references/base/filter-system/null-operators.md +58 -0
- package/wiki/references/base/filter-system/pattern-matching.md +108 -0
- package/wiki/references/base/filter-system/quick-reference.md +431 -0
- package/wiki/references/base/filter-system/range-operators.md +63 -0
- package/wiki/references/base/filter-system/tips.md +190 -0
- package/wiki/references/base/filter-system/use-cases.md +452 -0
- package/wiki/references/base/index.md +90 -0
- package/wiki/references/base/middlewares.md +602 -0
- package/wiki/references/base/models.md +215 -23
- package/wiki/references/base/providers.md +732 -0
- package/wiki/references/base/repositories/advanced.md +555 -0
- package/wiki/references/base/repositories/index.md +228 -0
- package/wiki/references/base/repositories/mixins.md +331 -0
- package/wiki/references/base/repositories/relations.md +486 -0
- package/wiki/references/base/repositories.md +40 -549
- package/wiki/references/base/services.md +28 -4
- package/wiki/references/components/authentication.md +22 -2
- package/wiki/references/components/health-check.md +12 -0
- package/wiki/references/components/index.md +23 -0
- package/wiki/references/components/mail.md +687 -0
- package/wiki/references/components/request-tracker.md +16 -0
- package/wiki/references/components/socket-io.md +18 -0
- package/wiki/references/components/static-asset.md +14 -26
- package/wiki/references/components/swagger.md +17 -0
- package/wiki/references/configuration/environment-variables.md +427 -0
- package/wiki/references/configuration/index.md +73 -0
- package/wiki/references/helpers/cron.md +14 -0
- package/wiki/references/helpers/crypto.md +15 -0
- package/wiki/references/helpers/env.md +16 -0
- package/wiki/references/helpers/error.md +17 -0
- package/wiki/references/helpers/index.md +15 -0
- package/wiki/references/helpers/inversion.md +24 -4
- package/wiki/references/helpers/logger.md +19 -0
- package/wiki/references/helpers/network.md +11 -0
- package/wiki/references/helpers/queue.md +19 -0
- package/wiki/references/helpers/redis.md +21 -0
- package/wiki/references/helpers/socket-io.md +24 -5
- package/wiki/references/helpers/storage.md +18 -10
- package/wiki/references/helpers/testing.md +18 -0
- package/wiki/references/helpers/types.md +167 -0
- package/wiki/references/helpers/uid.md +167 -0
- package/wiki/references/helpers/worker-thread.md +16 -0
- package/wiki/references/index.md +177 -0
- package/wiki/references/quick-reference.md +634 -0
- package/wiki/references/src-details/boot.md +3 -3
- package/wiki/references/src-details/dev-configs.md +0 -4
- package/wiki/references/src-details/docs.md +2 -2
- package/wiki/references/src-details/index.md +86 -0
- package/wiki/references/src-details/inversion.md +1 -6
- package/wiki/references/src-details/mcp-server.md +3 -15
- package/wiki/references/utilities/index.md +86 -10
- package/wiki/references/utilities/jsx.md +577 -0
- package/wiki/references/utilities/request.md +0 -2
- package/wiki/references/utilities/statuses.md +740 -0
- package/wiki/changelogs/planned-transaction-support.md +0 -216
- package/wiki/get-started/best-practices/api-usage-examples.md +0 -266
- package/wiki/get-started/best-practices/architectural-patterns.md +0 -170
- package/wiki/get-started/best-practices/data-modeling.md +0 -177
- package/wiki/get-started/best-practices/deployment-strategies.md +0 -121
- package/wiki/get-started/best-practices/performance-optimization.md +0 -88
- package/wiki/get-started/best-practices/security-guidelines.md +0 -99
- package/wiki/get-started/core-concepts/components.md +0 -98
- package/wiki/get-started/core-concepts/persistent.md +0 -543
- package/wiki/get-started/index.md +0 -65
- package/wiki/get-started/philosophy.md +0 -296
- package/wiki/get-started/prerequisites.md +0 -113
|
@@ -1,571 +1,62 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
title: Repositories Reference
|
|
3
|
+
description: Technical reference for repository classes and CRUD operations
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
---
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
# Repositories
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
::: tip Documentation Reorganized
|
|
10
|
+
This documentation has been reorganized into focused guides for easier navigation.
|
|
11
|
+
:::
|
|
6
12
|
|
|
7
|
-
## Quick
|
|
13
|
+
## Quick Links
|
|
8
14
|
|
|
9
|
-
|
|
|
10
|
-
|
|
11
|
-
| **
|
|
12
|
-
| **
|
|
13
|
-
| **
|
|
14
|
-
| **
|
|
15
|
+
| Guide | Description |
|
|
16
|
+
|-------|-------------|
|
|
17
|
+
| [**Overview**](./repositories/) | Repository basics, methods, setup |
|
|
18
|
+
| [**Filter System**](./filter-system/) | Where clauses, comparison, pattern matching |
|
|
19
|
+
| [**Default Filter**](./filter-system/default-filter) | Automatic filter application |
|
|
20
|
+
| [**Relations & Includes**](./repositories/relations) | Eager loading, nested queries |
|
|
21
|
+
| [**JSON Path Filtering**](./filter-system/json-filtering) | Query JSONB columns |
|
|
22
|
+
| [**Array Operators**](./filter-system/array-operators) | PostgreSQL array queries |
|
|
23
|
+
| [**Advanced Features**](./repositories/advanced) | Transactions, hidden props, performance |
|
|
24
|
+
| [**Repository Mixins**](./repositories/mixins) | Composable repository features |
|
|
15
25
|
|
|
16
|
-
|
|
26
|
+
---
|
|
17
27
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
**File:** `packages/core/src/base/repositories/core/base.ts`
|
|
21
|
-
|
|
22
|
-
### Key Properties
|
|
23
|
-
|
|
24
|
-
- `entity` (`BaseEntity`): An instance of the model class associated with this repository. It provides access to the Drizzle schema. Auto-resolved from `@repository` metadata or passed in constructor.
|
|
25
|
-
- `dataSource` (`IDataSource`): The datasource instance injected into the repository, which holds the database connection. Auto-injected from `@repository` decorator or passed in constructor.
|
|
26
|
-
- `connector`: A getter that provides direct access to the Drizzle ORM instance from the datasource.
|
|
27
|
-
- `filterBuilder` (`DrizzleFilterBuilder`): An instance of the filter builder responsible for converting `Ignis`'s filter objects into Drizzle-compatible query options.
|
|
28
|
-
- `operationScope` (`TRepositoryOperationScope`): Defines whether the repository is read-only or read-write.
|
|
29
|
-
- `defaultLimit` (`number`): Default limit for queries (default: 10).
|
|
30
|
-
|
|
31
|
-
### Key Methods
|
|
32
|
-
|
|
33
|
-
- `getEntityRelations()`: Returns a map of relation configurations from the entity's static `relations` property.
|
|
34
|
-
|
|
35
|
-
### Abstract Methods
|
|
36
|
-
|
|
37
|
-
`AbstractRepository` defines the method signatures for standard CRUD operations that concrete repository classes must implement:
|
|
38
|
-
- `count()`
|
|
39
|
-
- `existsWith()`
|
|
40
|
-
- `find()`
|
|
41
|
-
- `findOne()`
|
|
42
|
-
- `findById()`
|
|
43
|
-
- `create()`
|
|
44
|
-
- `updateById()`
|
|
45
|
-
- `deleteById()`
|
|
46
|
-
- (and `...All` variants)
|
|
47
|
-
|
|
48
|
-
## `ReadableRepository`
|
|
49
|
-
|
|
50
|
-
The `ReadableRepository` provides a **read-only** implementation of the repository pattern. It is ideal for data sources that should not be modified, such as views or tables from an external system.
|
|
51
|
-
|
|
52
|
-
- **File:** `packages/core/src/base/repositories/core/readable.ts`
|
|
53
|
-
|
|
54
|
-
### Implemented Methods
|
|
55
|
-
|
|
56
|
-
`ReadableRepository` provides concrete implementations for all read operations:
|
|
57
|
-
|
|
58
|
-
- **`find(opts)`**: Returns an array of entities matching the filter.
|
|
59
|
-
- **`findOne(opts)`**: Returns the first entity matching the filter.
|
|
60
|
-
- **`findById(opts)`**: A convenience method that calls `findOne` with an ID-based `where` clause.
|
|
61
|
-
- **`count(opts)`**: Returns the number of entities matching the `where` clause.
|
|
62
|
-
- **`existsWith(opts)`**: Returns `true` if at least one entity matches the `where` clause.
|
|
63
|
-
|
|
64
|
-
### Write Operations
|
|
65
|
-
|
|
66
|
-
`ReadableRepository` throws a "NOT ALLOWED" error for all write operations (`create`, `update`, `delete`).
|
|
67
|
-
|
|
68
|
-
## `PersistableRepository`
|
|
69
|
-
|
|
70
|
-
The `PersistableRepository` extends `ReadableRepository` and adds **write operations**. It provides the core logic for creating, updating, and deleting records with built-in safety mechanisms.
|
|
71
|
-
|
|
72
|
-
- **File:** `packages/core/src/base/repositories/core/persistable.ts`
|
|
73
|
-
|
|
74
|
-
### Implemented Methods
|
|
75
|
-
|
|
76
|
-
- `create(opts)`
|
|
77
|
-
- `createAll(opts)`
|
|
78
|
-
- `updateById(opts)`
|
|
79
|
-
- `updateAll(opts)`
|
|
80
|
-
- `updateBy(opts)` - Alias for `updateAll`
|
|
81
|
-
- `deleteById(opts)`
|
|
82
|
-
- `deleteAll(opts)`
|
|
83
|
-
- `deleteBy(opts)` - Alias for `deleteAll`
|
|
84
|
-
|
|
85
|
-
### Safety Features
|
|
86
|
-
|
|
87
|
-
#### Empty Where Clause Protection
|
|
88
|
-
|
|
89
|
-
The `PersistableRepository` includes safety mechanisms to prevent accidental mass updates or deletions:
|
|
90
|
-
|
|
91
|
-
**Update Operations (`updateAll`):**
|
|
92
|
-
```typescript
|
|
93
|
-
// ❌ Throws error: Empty where condition without force flag
|
|
94
|
-
await repository.updateAll({
|
|
95
|
-
data: { status: 'inactive' },
|
|
96
|
-
where: {}, // Empty condition
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// ✅ Warning logged: Explicitly allow mass update with force flag
|
|
100
|
-
await repository.updateAll({
|
|
101
|
-
data: { status: 'inactive' },
|
|
102
|
-
where: {},
|
|
103
|
-
options: { force: true }, // Force flag allows empty where
|
|
104
|
-
});
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
**Delete Operations (`deleteAll`):**
|
|
108
|
-
```typescript
|
|
109
|
-
// ❌ Throws error: Empty where condition without force flag
|
|
110
|
-
await repository.deleteAll({
|
|
111
|
-
where: {}, // Empty condition
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// ✅ Warning logged: Explicitly allow mass delete with force flag
|
|
115
|
-
await repository.deleteAll({
|
|
116
|
-
where: {},
|
|
117
|
-
options: { force: true }, // Force flag allows empty where
|
|
118
|
-
});
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
#### Behavior Summary
|
|
122
|
-
|
|
123
|
-
| Scenario | `force: false` (default) | `force: true` |
|
|
124
|
-
|----------|-------------------------|---------------|
|
|
125
|
-
| Empty `where` clause | ❌ Throws error | ✅ Logs warning and proceeds |
|
|
126
|
-
| Valid `where` clause | ✅ Executes normally | ✅ Executes normally |
|
|
127
|
-
|
|
128
|
-
**Warning Messages:**
|
|
129
|
-
|
|
130
|
-
When performing operations with empty `where` conditions and `force: true`, the repository logs a warning:
|
|
131
|
-
|
|
132
|
-
```
|
|
133
|
-
[_update] Entity: MyEntity | Performing update with empty condition | data: {...} | condition: {}
|
|
134
|
-
[_delete] Entity: MyEntity | Performing delete with empty condition | condition: {}
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
This helps track potentially dangerous operations in your logs.
|
|
138
|
-
|
|
139
|
-
You will typically not use this class directly, but rather the `DefaultCRUDRepository`.
|
|
140
|
-
|
|
141
|
-
## `DefaultCRUDRepository`
|
|
142
|
-
|
|
143
|
-
This is the primary class you should extend for repositories that require full **Create, Read, Update, and Delete (CRUD)** capabilities. It extends `PersistableRepository` and serves as the standard, full-featured repository implementation.
|
|
144
|
-
|
|
145
|
-
- **File:** `packages/core/src/base/repositories/core/default-crud.ts`
|
|
146
|
-
|
|
147
|
-
### @repository Decorator Requirements
|
|
148
|
-
|
|
149
|
-
**IMPORTANT:** Both `model` AND `dataSource` are required in the `@repository` decorator for schema auto-discovery. Without both, the model won't be registered and relational queries will fail.
|
|
150
|
-
|
|
151
|
-
```typescript
|
|
152
|
-
// ❌ WRONG - Will throw error
|
|
153
|
-
@repository({ model: User }) // Missing dataSource!
|
|
154
|
-
export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {}
|
|
155
|
-
|
|
156
|
-
// ❌ WRONG - Will throw error
|
|
157
|
-
@repository({ dataSource: PostgresDataSource }) // Missing model!
|
|
158
|
-
export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {}
|
|
159
|
-
|
|
160
|
-
// ✅ CORRECT - Both model and dataSource provided
|
|
161
|
-
@repository({ model: User, dataSource: PostgresDataSource })
|
|
162
|
-
export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {}
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
### Injection Patterns
|
|
166
|
-
|
|
167
|
-
The `@repository` decorator supports two injection patterns:
|
|
168
|
-
|
|
169
|
-
#### Pattern 1: Zero Boilerplate (Recommended)
|
|
170
|
-
|
|
171
|
-
DataSource is auto-injected from metadata - no constructor needed:
|
|
172
|
-
|
|
173
|
-
```typescript
|
|
174
|
-
@repository({ model: User, dataSource: PostgresDataSource })
|
|
175
|
-
export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {
|
|
176
|
-
// No constructor needed - datasource auto-injected at param index 0
|
|
177
|
-
|
|
178
|
-
async findByEmail(email: string) {
|
|
179
|
-
return this.findOne({ filter: { where: { email } } });
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
#### Pattern 2: Explicit @inject
|
|
185
|
-
|
|
186
|
-
When you need constructor control, use explicit `@inject`. **Important:** The first parameter must extend `AbstractDataSource` - this is enforced via reflection:
|
|
187
|
-
|
|
188
|
-
```typescript
|
|
189
|
-
@repository({ model: User, dataSource: PostgresDataSource })
|
|
190
|
-
export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {
|
|
191
|
-
constructor(
|
|
192
|
-
@inject({ key: 'datasources.PostgresDataSource' })
|
|
193
|
-
dataSource: PostgresDataSource, // ✅ Must be concrete DataSource type, NOT 'any'
|
|
194
|
-
) {
|
|
195
|
-
super(dataSource);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
**Note:** When `@inject` is at param index 0, auto-injection is skipped (your `@inject` takes precedence).
|
|
201
|
-
|
|
202
|
-
### Constructor Type Validation
|
|
203
|
-
|
|
204
|
-
The framework validates constructor parameters at decorator time:
|
|
205
|
-
|
|
206
|
-
1. **First parameter must extend `AbstractDataSource`** - Using `any`, `object`, or non-DataSource types will throw an error
|
|
207
|
-
2. **Type compatibility check** - The constructor parameter type must be compatible with the `dataSource` specified in `@repository`
|
|
208
|
-
|
|
209
|
-
```typescript
|
|
210
|
-
// ❌ Error: First parameter must extend AbstractDataSource | Received: 'Object'
|
|
211
|
-
@repository({ model: User, dataSource: PostgresDataSource })
|
|
212
|
-
export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {
|
|
213
|
-
constructor(
|
|
214
|
-
@inject({ key: 'datasources.PostgresDataSource' })
|
|
215
|
-
dataSource: any, // Will cause runtime error!
|
|
216
|
-
) {
|
|
217
|
-
super(dataSource);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// ❌ Error: Type mismatch | Constructor expects 'MongoDataSource' but @repository specifies 'PostgresDataSource'
|
|
222
|
-
@repository({ model: User, dataSource: PostgresDataSource })
|
|
223
|
-
export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {
|
|
224
|
-
constructor(
|
|
225
|
-
@inject({ key: 'datasources.MongoDataSource' })
|
|
226
|
-
dataSource: MongoDataSource, // Wrong type!
|
|
227
|
-
) {
|
|
228
|
-
super(dataSource);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
### Example Implementation
|
|
234
|
-
|
|
235
|
-
```typescript
|
|
236
|
-
// src/repositories/configuration.repository.ts
|
|
237
|
-
import { Configuration, TConfigurationSchema } from '@/models/entities';
|
|
238
|
-
import { PostgresDataSource } from '@/datasources';
|
|
239
|
-
import { inject, repository, DefaultCRUDRepository } from '@venizia/ignis';
|
|
240
|
-
|
|
241
|
-
// Pattern 1: Zero boilerplate (recommended)
|
|
242
|
-
@repository({ model: Configuration, dataSource: PostgresDataSource })
|
|
243
|
-
export class ConfigurationRepository extends DefaultCRUDRepository<TConfigurationSchema> {
|
|
244
|
-
// No constructor needed - datasource and entity auto-resolved!
|
|
245
|
-
|
|
246
|
-
// Custom data access methods
|
|
247
|
-
async findByCode(code: string): Promise<Configuration | undefined> {
|
|
248
|
-
const result = await this.connector.query.Configuration.findFirst({
|
|
249
|
-
where: (table, { eq }) => eq(table.code, code)
|
|
250
|
-
});
|
|
251
|
-
return result;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Pattern 2: With explicit constructor (when you need custom initialization)
|
|
256
|
-
@repository({ model: Configuration, dataSource: PostgresDataSource })
|
|
257
|
-
export class ConfigurationRepository extends DefaultCRUDRepository<TConfigurationSchema> {
|
|
258
|
-
constructor(
|
|
259
|
-
@inject({ key: 'datasources.PostgresDataSource' })
|
|
260
|
-
dataSource: PostgresDataSource,
|
|
261
|
-
) {
|
|
262
|
-
super(dataSource); // Just pass dataSource - entity and relations auto-resolved!
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
This architecture provides a clean and powerful abstraction for data access, separating the "how" of data fetching (Drizzle logic) from the "what" of business logic (services).
|
|
268
|
-
|
|
269
|
-
## Advanced Features
|
|
270
|
-
|
|
271
|
-
### Log Option for Debugging
|
|
272
|
-
|
|
273
|
-
All CRUD operations support a `log` option for debugging:
|
|
274
|
-
|
|
275
|
-
```typescript
|
|
276
|
-
// Enable logging for a specific operation
|
|
277
|
-
await repo.create({
|
|
278
|
-
data: { name: 'John', email: 'john@example.com' },
|
|
279
|
-
options: {
|
|
280
|
-
log: { use: true, level: 'debug' }
|
|
281
|
-
}
|
|
282
|
-
});
|
|
283
|
-
// Output: [_create] Executing with opts: { data: [...], options: {...} }
|
|
284
|
-
|
|
285
|
-
// Available log levels: 'debug', 'info', 'warn', 'error'
|
|
286
|
-
await repo.updateById({
|
|
287
|
-
id: '123',
|
|
288
|
-
data: { name: 'Jane' },
|
|
289
|
-
options: { log: { use: true, level: 'info' } }
|
|
290
|
-
});
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
**Available on:** `create`, `createAll`, `updateById`, `updateAll`, `deleteById`, `deleteAll`
|
|
294
|
-
|
|
295
|
-
### TypeScript Return Type Inference
|
|
296
|
-
|
|
297
|
-
Repository methods now have improved type inference based on `shouldReturn`:
|
|
298
|
-
|
|
299
|
-
```typescript
|
|
300
|
-
// When shouldReturn: false - TypeScript knows data is null
|
|
301
|
-
const result1 = await repo.create({
|
|
302
|
-
data: { name: 'John' },
|
|
303
|
-
options: { shouldReturn: false }
|
|
304
|
-
});
|
|
305
|
-
// Type: Promise<TCount & { data: null }>
|
|
306
|
-
console.log(result1.data); // null
|
|
307
|
-
|
|
308
|
-
// When shouldReturn: true (default) - TypeScript knows data is the entity
|
|
309
|
-
const result2 = await repo.create({
|
|
310
|
-
data: { name: 'John' },
|
|
311
|
-
options: { shouldReturn: true }
|
|
312
|
-
});
|
|
313
|
-
// Type: Promise<TCount & { data: User }>
|
|
314
|
-
console.log(result2.data.name); // 'John' - fully typed!
|
|
315
|
-
|
|
316
|
-
// Same for array operations
|
|
317
|
-
const results = await repo.createAll({
|
|
318
|
-
data: [{ name: 'John' }, { name: 'Jane' }],
|
|
319
|
-
options: { shouldReturn: true }
|
|
320
|
-
});
|
|
321
|
-
// Type: Promise<TCount & { data: User[] }>
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
### Relations Auto-Resolution
|
|
325
|
-
|
|
326
|
-
Relations are now automatically resolved from the entity's static `relations` property:
|
|
28
|
+
## Quick Start
|
|
327
29
|
|
|
328
30
|
```typescript
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
static override schema = userTable;
|
|
333
|
-
static override relations = () => userRelations.definitions;
|
|
334
|
-
}
|
|
31
|
+
import { DefaultCRUDRepository, repository } from '@venizia/ignis';
|
|
32
|
+
import { Todo } from '@/models/todo.model';
|
|
33
|
+
import { PostgresDataSource } from '@/datasources/postgres.datasource';
|
|
335
34
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
// No need to pass relations in constructor - auto-resolved!
|
|
35
|
+
@repository({ model: Todo, dataSource: PostgresDataSource })
|
|
36
|
+
export class TodoRepository extends DefaultCRUDRepository<typeof Todo.schema> {
|
|
37
|
+
// Inherits: find, findOne, create, updateById, deleteById, etc.
|
|
340
38
|
}
|
|
341
|
-
|
|
342
|
-
// Relations are available for include queries
|
|
343
|
-
const users = await repo.find({
|
|
344
|
-
filter: {
|
|
345
|
-
where: { status: 'active' },
|
|
346
|
-
include: [{ relation: 'posts' }], // Works automatically
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
### Query Interface Validation
|
|
352
|
-
|
|
353
|
-
The `getQueryInterface()` method validates that the entity's schema is properly registered:
|
|
354
|
-
|
|
355
|
-
```typescript
|
|
356
|
-
// If schema key doesn't match, you get a helpful error:
|
|
357
|
-
// Error: [UserRepository] Schema key mismatch | Entity name 'User' not found in connector.query | Available keys: [Configuration, Post] | Ensure the model's TABLE_NAME matches the schema registration key
|
|
358
39
|
```
|
|
359
40
|
|
|
360
|
-
##
|
|
361
|
-
|
|
362
|
-
### Core API for Flat Queries
|
|
363
|
-
|
|
364
|
-
The `ReadableRepository` automatically optimizes flat queries (no relations, no field selection) using Drizzle's Core API instead of Query API. This provides ~15-20% performance improvement for simple queries.
|
|
365
|
-
|
|
366
|
-
**Automatic Optimization:**
|
|
41
|
+
## Common Operations
|
|
367
42
|
|
|
368
43
|
```typescript
|
|
369
|
-
//
|
|
370
|
-
const users = await repo.find({
|
|
371
|
-
filter: {
|
|
372
|
-
where: { status: 'active' },
|
|
373
|
-
limit: 10,
|
|
374
|
-
order: ['createdAt DESC'],
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
// Uses: db.select().from(table).where(...).orderBy(...).limit(10)
|
|
378
|
-
|
|
379
|
-
// This query uses Query API (relations need relational mapper)
|
|
380
|
-
const usersWithPosts = await repo.find({
|
|
381
|
-
filter: {
|
|
382
|
-
where: { status: 'active' },
|
|
383
|
-
include: [{ relation: 'posts' }], // Has relations
|
|
384
|
-
}
|
|
385
|
-
});
|
|
386
|
-
// Uses: db.query.tableName.findMany({ with: { posts: true }, ... })
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
**When Core API is used:**
|
|
390
|
-
|
|
391
|
-
| Filter Options | API Used | Reason |
|
|
392
|
-
|----------------|----------|--------|
|
|
393
|
-
| `where`, `limit`, `order`, `offset` only | Core API | Flat query, no overhead |
|
|
394
|
-
| Has `include` (relations) | Query API | Needs relational mapper |
|
|
395
|
-
| Has `fields` selection | Query API | Core API field syntax differs |
|
|
396
|
-
|
|
397
|
-
**Protected Helper Method:**
|
|
398
|
-
|
|
399
|
-
For advanced use cases, you can directly use the `findWithCoreAPI` method:
|
|
400
|
-
|
|
401
|
-
```typescript
|
|
402
|
-
// Available in subclasses
|
|
403
|
-
protected async findWithCoreAPI(opts: {
|
|
404
|
-
filter: TFilter<DataObject>;
|
|
405
|
-
findOne?: boolean;
|
|
406
|
-
}): Promise<Array<DataObject>>;
|
|
407
|
-
|
|
408
|
-
// Check if Core API can be used
|
|
409
|
-
protected canUseCoreAPI(filter: TFilter<DataObject>): boolean;
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
### WeakMap Cache for Filter Builder
|
|
413
|
-
|
|
414
|
-
The `DrizzleFilterBuilder` uses a static WeakMap cache for `getTableColumns()` results, avoiding repeated reflection calls:
|
|
415
|
-
|
|
416
|
-
```typescript
|
|
417
|
-
// Internal optimization - no action needed
|
|
418
|
-
// First call: getTableColumns(schema) → cached
|
|
419
|
-
// Subsequent calls: retrieved from WeakMap
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
This is especially beneficial for:
|
|
423
|
-
- High-concurrency environments
|
|
424
|
-
- Queries with nested AND/OR conditions (each recursion reuses cache)
|
|
425
|
-
- Multiple queries to the same table
|
|
426
|
-
|
|
427
|
-
## Query Operators
|
|
428
|
-
|
|
429
|
-
The filter builder supports a comprehensive set of query operators for building complex queries.
|
|
430
|
-
|
|
431
|
-
**File:** `packages/core/src/base/repositories/operators/query.ts`
|
|
432
|
-
|
|
433
|
-
### Available Operators
|
|
434
|
-
|
|
435
|
-
| Operator | Alias | SQL Equivalent | Description |
|
|
436
|
-
|----------|-------|----------------|-------------|
|
|
437
|
-
| `eq` | - | `=` | Equal to |
|
|
438
|
-
| `ne` | `neq` | `!=` | Not equal to |
|
|
439
|
-
| `gt` | - | `>` | Greater than |
|
|
440
|
-
| `gte` | - | `>=` | Greater than or equal |
|
|
441
|
-
| `lt` | - | `<` | Less than |
|
|
442
|
-
| `lte` | - | `<=` | Less than or equal |
|
|
443
|
-
| `like` | - | `LIKE` | Pattern matching (case-sensitive) |
|
|
444
|
-
| `nlike` | - | `NOT LIKE` | Negative pattern matching |
|
|
445
|
-
| `ilike` | - | `ILIKE` | Pattern matching (case-insensitive, PostgreSQL) |
|
|
446
|
-
| `nilike` | - | `NOT ILIKE` | Negative case-insensitive pattern |
|
|
447
|
-
| `in` | `inq` | `IN` | Value in array |
|
|
448
|
-
| `nin` | - | `NOT IN` | Value not in array |
|
|
449
|
-
| `between` | - | `BETWEEN` | Value between two values |
|
|
450
|
-
| `is` | - | `IS NULL` | Null check |
|
|
451
|
-
| `isn` | - | `IS NOT NULL` | Not null check |
|
|
452
|
-
| `regexp` | - | `~` | PostgreSQL POSIX regex (case-sensitive) |
|
|
453
|
-
| `iregexp` | - | `~*` | PostgreSQL POSIX regex (case-insensitive) |
|
|
454
|
-
|
|
455
|
-
### Logical Operators
|
|
456
|
-
|
|
457
|
-
| Operator | Description |
|
|
458
|
-
|----------|-------------|
|
|
459
|
-
| `and` | Combine conditions with AND |
|
|
460
|
-
| `or` | Combine conditions with OR |
|
|
461
|
-
|
|
462
|
-
### Usage Examples
|
|
463
|
-
|
|
464
|
-
**Simple equality:**
|
|
465
|
-
```typescript
|
|
44
|
+
// Find all active
|
|
466
45
|
await repo.find({ filter: { where: { status: 'active' } } });
|
|
467
|
-
// SQL: WHERE status = 'active'
|
|
468
|
-
```
|
|
469
46
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
await repo.find({
|
|
473
|
-
filter: {
|
|
474
|
-
where: {
|
|
475
|
-
age: { gte: 18, lt: 65 },
|
|
476
|
-
score: { gt: 100 }
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
});
|
|
480
|
-
// SQL: WHERE age >= 18 AND age < 65 AND score > 100
|
|
481
|
-
```
|
|
482
|
-
|
|
483
|
-
**Array operators:**
|
|
484
|
-
```typescript
|
|
485
|
-
// IN operator
|
|
486
|
-
await repo.find({ filter: { where: { id: [1, 2, 3] } } });
|
|
487
|
-
// SQL: WHERE id IN (1, 2, 3)
|
|
488
|
-
|
|
489
|
-
// Using explicit IN
|
|
490
|
-
await repo.find({ filter: { where: { status: { in: ['active', 'pending'] } } } });
|
|
47
|
+
// Find by ID
|
|
48
|
+
await repo.findById({ id: '123' });
|
|
491
49
|
|
|
492
|
-
//
|
|
493
|
-
await repo.
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
**Pattern matching:**
|
|
497
|
-
```typescript
|
|
498
|
-
// LIKE (case-sensitive)
|
|
499
|
-
await repo.find({ filter: { where: { name: { like: '%john%' } } } });
|
|
500
|
-
|
|
501
|
-
// ILIKE (case-insensitive, PostgreSQL)
|
|
502
|
-
await repo.find({ filter: { where: { email: { ilike: '%@gmail.com' } } } });
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
**Regex (PostgreSQL):**
|
|
506
|
-
```typescript
|
|
507
|
-
// Case-sensitive regex
|
|
508
|
-
await repo.find({ filter: { where: { name: { regexp: '^John' } } } });
|
|
509
|
-
|
|
510
|
-
// Case-insensitive regex
|
|
511
|
-
await repo.find({ filter: { where: { name: { iregexp: '^john' } } } });
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
**Between:**
|
|
515
|
-
```typescript
|
|
516
|
-
await repo.find({
|
|
517
|
-
filter: {
|
|
518
|
-
where: {
|
|
519
|
-
createdAt: { between: [new Date('2024-01-01'), new Date('2024-12-31')] }
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
});
|
|
523
|
-
// SQL: WHERE created_at BETWEEN '2024-01-01' AND '2024-12-31'
|
|
524
|
-
```
|
|
525
|
-
|
|
526
|
-
**Logical operators:**
|
|
527
|
-
```typescript
|
|
528
|
-
// OR conditions
|
|
529
|
-
await repo.find({
|
|
530
|
-
filter: {
|
|
531
|
-
where: {
|
|
532
|
-
or: [
|
|
533
|
-
{ status: 'active' },
|
|
534
|
-
{ isPublished: true }
|
|
535
|
-
]
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
});
|
|
50
|
+
// Create
|
|
51
|
+
await repo.create({ data: { name: 'New Item' } });
|
|
539
52
|
|
|
540
|
-
//
|
|
541
|
-
await repo.
|
|
542
|
-
filter: {
|
|
543
|
-
where: {
|
|
544
|
-
and: [
|
|
545
|
-
{ role: 'admin' },
|
|
546
|
-
{ createdAt: { gte: new Date('2024-01-01') } }
|
|
547
|
-
]
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
});
|
|
53
|
+
// Update
|
|
54
|
+
await repo.updateById({ id: '123', data: { name: 'Updated' } });
|
|
551
55
|
|
|
552
|
-
//
|
|
553
|
-
await repo.
|
|
554
|
-
filter: {
|
|
555
|
-
where: {
|
|
556
|
-
status: 'active',
|
|
557
|
-
or: [
|
|
558
|
-
{ role: 'admin' },
|
|
559
|
-
{ and: [{ role: 'user' }, { verified: true }] }
|
|
560
|
-
]
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
});
|
|
56
|
+
// Delete
|
|
57
|
+
await repo.deleteById({ id: '123' });
|
|
564
58
|
```
|
|
565
59
|
|
|
566
|
-
|
|
60
|
+
---
|
|
567
61
|
|
|
568
|
-
|
|
569
|
-
- **Empty NOT IN array:** Returns `true` (all rows match)
|
|
570
|
-
- **BETWEEN validation:** Requires exactly 2 elements in array, throws error otherwise
|
|
571
|
-
- **Invalid columns:** Throws error if column doesn't exist in schema
|
|
62
|
+
**[Continue to Overview →](./repositories/)**
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Services Reference
|
|
3
|
+
description: Technical reference for BaseService and business logic layer
|
|
4
|
+
difficulty: beginner
|
|
5
|
+
---
|
|
6
|
+
|
|
1
7
|
# Deep Dive: Services
|
|
2
8
|
|
|
3
9
|
Technical reference for `BaseService` - the foundation for business logic layers in Ignis.
|
|
@@ -68,17 +74,17 @@ export class UserService extends BaseService {
|
|
|
68
74
|
// 2. Dependencies (like UserRepository) are injected
|
|
69
75
|
constructor(
|
|
70
76
|
@inject({ key: 'repositories.UserRepository' })
|
|
71
|
-
private
|
|
77
|
+
private _userRepository: UserRepository,
|
|
72
78
|
) {
|
|
73
79
|
super({ scope: UserService.name });
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
// 3. Method is called by a controller
|
|
77
|
-
async getUserProfile(userId: string): Promise<Partial<TUser>> {
|
|
78
|
-
this.logger.info(`Fetching profile for user ${userId}`);
|
|
83
|
+
async getUserProfile(opts: { userId: string }): Promise<Partial<TUser>> {
|
|
84
|
+
this.logger.info(`Fetching profile for user ${opts.userId}`);
|
|
79
85
|
|
|
80
86
|
// 4. Orchestrates logic: calls the repository
|
|
81
|
-
const user = await this.
|
|
87
|
+
const user = await this._userRepository.findById({ id: opts.userId });
|
|
82
88
|
|
|
83
89
|
if (!user) {
|
|
84
90
|
throw getError({ message: 'User not found' });
|
|
@@ -95,3 +101,21 @@ export class UserService extends BaseService {
|
|
|
95
101
|
```
|
|
96
102
|
|
|
97
103
|
By adhering to this pattern, you keep your code organized, testable, and maintainable. You can easily test `UserService` by providing a mock `UserRepository` without needing a real database connection.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## See Also
|
|
108
|
+
|
|
109
|
+
- **Related References:**
|
|
110
|
+
- [Controllers](./controllers.md) - HTTP request handlers that call services
|
|
111
|
+
- [Repositories](./repositories/) - Data access layer used by services
|
|
112
|
+
- [Providers](./providers.md) - Factory pattern for runtime instantiation
|
|
113
|
+
- [Dependency Injection](./dependency-injection.md) - DI container and injection patterns
|
|
114
|
+
|
|
115
|
+
- **Guides:**
|
|
116
|
+
- [Building Services](/guides/core-concepts/services.md)
|
|
117
|
+
- [Dependency Injection Guide](/guides/core-concepts/dependency-injection.md)
|
|
118
|
+
|
|
119
|
+
- **Best Practices:**
|
|
120
|
+
- [Service Layer Patterns](/best-practices/architecture/service-patterns.md)
|
|
121
|
+
- [Testing Services](/best-practices/testing/unit-testing.md)
|