@venizia/ignis-docs 0.0.3 → 0.0.4-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +406 -17
- 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 +2 -2
- 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 +6 -0
- package/wiki/changelogs/planned-schema-migrator.md +0 -8
- 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/{get-started → guides}/core-concepts/components.md +24 -17
- 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 +31 -26
- package/wiki/references/base/components.md +24 -7
- package/wiki/references/base/controllers.md +50 -20
- package/wiki/references/base/datasources.md +30 -0
- 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 +604 -0
- package/wiki/references/base/models.md +215 -23
- package/wiki/references/base/providers.md +731 -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 -635
- 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 +14 -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 +16 -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/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 -97
- package/wiki/get-started/best-practices/security-guidelines.md +0 -99
- package/wiki/get-started/core-concepts/persistent.md +0 -539
- package/wiki/get-started/index.md +0 -65
- package/wiki/get-started/philosophy.md +0 -296
- package/wiki/get-started/prerequisites.md +0 -113
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Advanced Repository Features
|
|
3
|
+
description: Transactions, hidden properties, and performance optimization
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Advanced Repository Features
|
|
8
|
+
|
|
9
|
+
Transactions, hidden properties, performance optimization, type inference, and debugging.
|
|
10
|
+
|
|
11
|
+
## Prerequisites
|
|
12
|
+
|
|
13
|
+
Before reading this document, you should understand:
|
|
14
|
+
|
|
15
|
+
- [Basic Repository Operations](./index.md) - CRUD operations and basic filtering
|
|
16
|
+
- [Filter System](../filter-system/) - Advanced query building
|
|
17
|
+
- Database transactions - ACID properties and isolation levels
|
|
18
|
+
- TypeScript advanced types - Utility types and type inference
|
|
19
|
+
|
|
20
|
+
## Transactions
|
|
21
|
+
|
|
22
|
+
Orchestrate atomic operations across multiple repositories.
|
|
23
|
+
|
|
24
|
+
### Basic Transaction
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
const tx = await repo.beginTransaction();
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// All operations use the same transaction
|
|
31
|
+
const user = await userRepo.create({
|
|
32
|
+
data: { name: 'Alice', email: 'alice@example.com' },
|
|
33
|
+
options: { transaction: tx }
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const profile = await profileRepo.create({
|
|
37
|
+
data: { userId: user.data.id, bio: 'Hello!' },
|
|
38
|
+
options: { transaction: tx }
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Commit if all succeeded
|
|
42
|
+
await tx.commit();
|
|
43
|
+
|
|
44
|
+
return { user: user.data, profile: profile.data };
|
|
45
|
+
} catch (error) {
|
|
46
|
+
// Rollback on any error
|
|
47
|
+
await tx.rollback();
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Isolation Levels
|
|
53
|
+
|
|
54
|
+
Control how transactions interact with concurrent operations:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const tx = await repo.beginTransaction({
|
|
58
|
+
isolationLevel: 'SERIALIZABLE'
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| Level | Description | Use Case |
|
|
63
|
+
|-------|-------------|----------|
|
|
64
|
+
| `READ COMMITTED` | Default. See committed data only | Most applications |
|
|
65
|
+
| `REPEATABLE READ` | Consistent reads within transaction | Reports, analytics |
|
|
66
|
+
| `SERIALIZABLE` | Full isolation, prevents anomalies | Financial, inventory |
|
|
67
|
+
|
|
68
|
+
### Transaction with Multiple Repositories
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
async function transferFunds(fromId: string, toId: string, amount: number) {
|
|
72
|
+
const tx = await accountRepo.beginTransaction();
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
// Debit source account
|
|
76
|
+
await accountRepo.updateById({
|
|
77
|
+
id: fromId,
|
|
78
|
+
data: { balance: sql`balance - ${amount}` },
|
|
79
|
+
options: { transaction: tx }
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Credit destination account
|
|
83
|
+
await accountRepo.updateById({
|
|
84
|
+
id: toId,
|
|
85
|
+
data: { balance: sql`balance + ${amount}` },
|
|
86
|
+
options: { transaction: tx }
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Record the transfer
|
|
90
|
+
await transferRepo.create({
|
|
91
|
+
data: { fromId, toId, amount, status: 'completed' },
|
|
92
|
+
options: { transaction: tx }
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
await tx.commit();
|
|
96
|
+
} catch (error) {
|
|
97
|
+
await tx.rollback();
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
## Hidden Properties
|
|
105
|
+
|
|
106
|
+
Automatically exclude sensitive fields from query results.
|
|
107
|
+
|
|
108
|
+
### Configuration
|
|
109
|
+
|
|
110
|
+
Define hidden properties in your model:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
@model({
|
|
114
|
+
type: 'entity',
|
|
115
|
+
settings: {
|
|
116
|
+
hiddenProperties: ['password', 'secret', 'apiKey'],
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
export class User extends BaseEntity<typeof User.schema> {
|
|
120
|
+
static override schema = userTable;
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Automatic Exclusion
|
|
125
|
+
|
|
126
|
+
Hidden properties are excluded at the **SQL level** for maximum security:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// Read operations exclude hidden properties
|
|
130
|
+
const user = await userRepo.findById({ id: '123' });
|
|
131
|
+
// Result: { id: '123', email: 'john@example.com', name: 'John' }
|
|
132
|
+
// Note: password, secret, apiKey are NOT included
|
|
133
|
+
|
|
134
|
+
// Write operations exclude hidden from RETURNING clause
|
|
135
|
+
const created = await userRepo.create({
|
|
136
|
+
data: { email: 'new@example.com', password: 'hashed_secret' }
|
|
137
|
+
});
|
|
138
|
+
// Result: { count: 1, data: { id: '456', email: 'new@example.com' } }
|
|
139
|
+
// Note: password stored in DB but not returned
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Filtering by Hidden Properties
|
|
143
|
+
|
|
144
|
+
You **can** filter by hidden properties - you just can't see them in results:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// This works! Finds user but password not in result
|
|
148
|
+
const user = await userRepo.findOne({
|
|
149
|
+
filter: { where: { password: 'hashed_value' } }
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Relations with Hidden Properties
|
|
154
|
+
|
|
155
|
+
Hidden properties are also excluded from included relations:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
const post = await postRepo.findOne({
|
|
159
|
+
filter: {
|
|
160
|
+
include: [{ relation: 'author' }]
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
// post.author will NOT include password, secret, etc.
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Accessing Hidden Data
|
|
167
|
+
|
|
168
|
+
When you need hidden fields (e.g., for authentication), bypass the repository:
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
// Direct connector access - includes all fields
|
|
172
|
+
const connector = userRepo.getConnector();
|
|
173
|
+
const [fullUser] = await connector
|
|
174
|
+
.select()
|
|
175
|
+
.from(User.schema)
|
|
176
|
+
.where(eq(User.schema.email, 'john@example.com'));
|
|
177
|
+
// fullUser includes password, secret, apiKey
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
## Performance Optimization
|
|
182
|
+
|
|
183
|
+
### Core API for Flat Queries
|
|
184
|
+
|
|
185
|
+
The repository automatically uses Drizzle's Core API (faster) for simple queries:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
// Automatically optimized - uses Core API
|
|
189
|
+
const users = await repo.find({
|
|
190
|
+
filter: {
|
|
191
|
+
where: { status: 'active' },
|
|
192
|
+
limit: 10,
|
|
193
|
+
order: ['createdAt DESC']
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
// Uses: db.select().from(table).where(...).orderBy(...).limit(10)
|
|
197
|
+
|
|
198
|
+
// Uses Query API (has relations)
|
|
199
|
+
const usersWithPosts = await repo.find({
|
|
200
|
+
filter: {
|
|
201
|
+
where: { status: 'active' },
|
|
202
|
+
include: [{ relation: 'posts' }]
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
// Uses: db.query.tableName.findMany({ with: { posts: true }, ... })
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
| Filter Options | API Used | Performance |
|
|
209
|
+
|----------------|----------|-------------|
|
|
210
|
+
| `where`, `limit`, `order`, `offset` only | Core API | ~15-20% faster |
|
|
211
|
+
| Has `include` (relations) | Query API | Standard |
|
|
212
|
+
| Has `fields` selection | Query API | Standard |
|
|
213
|
+
|
|
214
|
+
### Always Use Limit
|
|
215
|
+
|
|
216
|
+
Prevent memory exhaustion on large tables:
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
// ✅ Good - bounded result set
|
|
220
|
+
await repo.find({
|
|
221
|
+
filter: {
|
|
222
|
+
where: { status: 'active' },
|
|
223
|
+
limit: 100
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// ⚠️ Dangerous - could return millions of rows
|
|
228
|
+
await repo.find({
|
|
229
|
+
filter: { where: { status: 'active' } }
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Pagination Pattern
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
async function getPaginatedUsers(page: number, pageSize: number = 20) {
|
|
237
|
+
const [users, total] = await Promise.all([
|
|
238
|
+
userRepo.find({
|
|
239
|
+
filter: {
|
|
240
|
+
where: { status: 'active' },
|
|
241
|
+
limit: pageSize,
|
|
242
|
+
skip: (page - 1) * pageSize,
|
|
243
|
+
order: ['createdAt DESC']
|
|
244
|
+
}
|
|
245
|
+
}),
|
|
246
|
+
userRepo.count({ where: { status: 'active' } })
|
|
247
|
+
]);
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
data: users,
|
|
251
|
+
pagination: {
|
|
252
|
+
page,
|
|
253
|
+
pageSize,
|
|
254
|
+
total,
|
|
255
|
+
totalPages: Math.ceil(total / pageSize)
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### WeakMap Cache
|
|
262
|
+
|
|
263
|
+
The filter builder caches table column metadata, avoiding repeated reflection:
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
// Internal optimization - automatic
|
|
267
|
+
// First query: getTableColumns(schema) → cached
|
|
268
|
+
// Subsequent queries: retrieved from WeakMap
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
## TypeScript Return Types
|
|
273
|
+
|
|
274
|
+
### shouldReturn Inference
|
|
275
|
+
|
|
276
|
+
Repository methods infer return types based on `shouldReturn`:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// shouldReturn: false - TypeScript knows data is null
|
|
280
|
+
const result1 = await repo.create({
|
|
281
|
+
data: { name: 'John' },
|
|
282
|
+
options: { shouldReturn: false }
|
|
283
|
+
});
|
|
284
|
+
// Type: Promise<{ count: number; data: null }>
|
|
285
|
+
console.log(result1.data); // null
|
|
286
|
+
|
|
287
|
+
// shouldReturn: true (default) - TypeScript knows data is the entity
|
|
288
|
+
const result2 = await repo.create({
|
|
289
|
+
data: { name: 'John' },
|
|
290
|
+
options: { shouldReturn: true }
|
|
291
|
+
});
|
|
292
|
+
// Type: Promise<{ count: number; data: User }>
|
|
293
|
+
console.log(result2.data.name); // 'John' - fully typed!
|
|
294
|
+
|
|
295
|
+
// Array operations
|
|
296
|
+
const results = await repo.createAll({
|
|
297
|
+
data: [{ name: 'John' }, { name: 'Jane' }],
|
|
298
|
+
options: { shouldReturn: true }
|
|
299
|
+
});
|
|
300
|
+
// Type: Promise<{ count: number; data: User[] }>
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Generic Return Types
|
|
304
|
+
|
|
305
|
+
Override return types for queries with relations:
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
// Define expected return type
|
|
309
|
+
type UserWithPosts = User & {
|
|
310
|
+
posts: Post[];
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// Use generic override
|
|
314
|
+
const user = await userRepo.findOne<UserWithPosts>({
|
|
315
|
+
filter: {
|
|
316
|
+
where: { id: '123' },
|
|
317
|
+
include: [{ relation: 'posts' }]
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// TypeScript knows the structure!
|
|
322
|
+
if (user) {
|
|
323
|
+
console.log(user.posts[0].title); // Fully typed
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**Supported Methods:**
|
|
328
|
+
- `find<R>()`, `findOne<R>()`, `findById<R>()`
|
|
329
|
+
- `create<R>()`, `createAll<R>()`
|
|
330
|
+
- `updateById<R>()`, `updateAll<R>()`
|
|
331
|
+
- `deleteById<R>()`, `deleteAll<R>()`
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
## Debugging
|
|
335
|
+
|
|
336
|
+
### Log Option
|
|
337
|
+
|
|
338
|
+
Enable logging for specific operations:
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
// Enable debug logging
|
|
342
|
+
await repo.create({
|
|
343
|
+
data: { name: 'John', email: 'john@example.com' },
|
|
344
|
+
options: {
|
|
345
|
+
log: { use: true, level: 'debug' }
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
// Output: [_create] Executing with opts: { data: [...], options: {...} }
|
|
349
|
+
|
|
350
|
+
// Available levels: 'debug', 'info', 'warn', 'error'
|
|
351
|
+
await repo.updateById({
|
|
352
|
+
id: '123',
|
|
353
|
+
data: { name: 'Jane' },
|
|
354
|
+
options: { log: { use: true, level: 'info' } }
|
|
355
|
+
});
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
**Available on:** `create`, `createAll`, `updateById`, `updateAll`, `deleteById`, `deleteAll`
|
|
359
|
+
|
|
360
|
+
### Query Interface Validation
|
|
361
|
+
|
|
362
|
+
The repository validates schema registration on startup:
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
// If schema key doesn't match, you get a helpful error:
|
|
366
|
+
// Error: [UserRepository] Schema key mismatch
|
|
367
|
+
// | Entity name 'User' not found in connector.query
|
|
368
|
+
// | Available keys: [Configuration, Post]
|
|
369
|
+
// | Ensure the model's TABLE_NAME matches the schema registration key
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
## Safety Features
|
|
374
|
+
|
|
375
|
+
### Empty Where Protection
|
|
376
|
+
|
|
377
|
+
Prevents accidental mass updates/deletes:
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
// ❌ Throws error - empty where without force
|
|
381
|
+
await repo.deleteAll({ where: {} });
|
|
382
|
+
|
|
383
|
+
// ✅ Explicit force flag - logs warning, proceeds
|
|
384
|
+
await repo.deleteAll({
|
|
385
|
+
where: {},
|
|
386
|
+
options: { force: true }
|
|
387
|
+
});
|
|
388
|
+
// Warning: [_delete] Entity: User | Performing delete with empty condition
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
| Scenario | `force: false` (default) | `force: true` |
|
|
392
|
+
|----------|-------------------------|---------------|
|
|
393
|
+
| Empty `where` | Throws error | Logs warning, proceeds |
|
|
394
|
+
| Valid `where` | Executes normally | Executes normally |
|
|
395
|
+
|
|
396
|
+
### Constructor Type Validation
|
|
397
|
+
|
|
398
|
+
The `@repository` decorator validates constructor parameters:
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
// ❌ Error: First parameter must extend AbstractDataSource
|
|
402
|
+
@repository({ model: User, dataSource: PostgresDataSource })
|
|
403
|
+
export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {
|
|
404
|
+
constructor(dataSource: any) { // 'any' not allowed!
|
|
405
|
+
super(dataSource);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ✅ Correct: Concrete DataSource type
|
|
410
|
+
@repository({ model: User, dataSource: PostgresDataSource })
|
|
411
|
+
export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {
|
|
412
|
+
constructor(
|
|
413
|
+
@inject({ key: 'datasources.PostgresDataSource' })
|
|
414
|
+
dataSource: PostgresDataSource,
|
|
415
|
+
) {
|
|
416
|
+
super(dataSource);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
## Direct Connector Access
|
|
423
|
+
|
|
424
|
+
For advanced queries not supported by the repository API:
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
// Get the Drizzle connector
|
|
428
|
+
const connector = repo.getConnector();
|
|
429
|
+
|
|
430
|
+
// Raw Drizzle query
|
|
431
|
+
const results = await connector
|
|
432
|
+
.select({
|
|
433
|
+
userId: userTable.id,
|
|
434
|
+
postCount: sql<number>`count(${postTable.id})`,
|
|
435
|
+
})
|
|
436
|
+
.from(userTable)
|
|
437
|
+
.leftJoin(postTable, eq(userTable.id, postTable.authorId))
|
|
438
|
+
.groupBy(userTable.id)
|
|
439
|
+
.having(sql`count(${postTable.id}) > 5`);
|
|
440
|
+
|
|
441
|
+
// Use with caution - bypasses repository features like hidden properties
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
## Repository Class Hierarchy
|
|
446
|
+
|
|
447
|
+
| Class | Description |
|
|
448
|
+
|-------|-------------|
|
|
449
|
+
| `AbstractRepository` | Base class, defines method signatures |
|
|
450
|
+
| `ReadableRepository` | Read-only operations (find, findOne, count) |
|
|
451
|
+
| `PersistableRepository` | Adds write operations (create, update, delete) |
|
|
452
|
+
| `DefaultCRUDRepository` | Full CRUD - **use this one** |
|
|
453
|
+
|
|
454
|
+
### Creating a Read-Only Repository
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
@repository({ model: AuditLog, dataSource: PostgresDataSource })
|
|
458
|
+
export class AuditLogRepository extends ReadableRepository<typeof AuditLog.schema> {
|
|
459
|
+
// Only has: find, findOne, findById, count, existsWith
|
|
460
|
+
// Write operations throw "NOT ALLOWED" error
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
## Default Filter Bypass
|
|
466
|
+
|
|
467
|
+
When models have a `defaultFilter` configured, you can bypass it for admin/maintenance operations:
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
// Normal query - default filter applies
|
|
471
|
+
await repo.find({
|
|
472
|
+
filter: { where: { status: 'active' } }
|
|
473
|
+
});
|
|
474
|
+
// WHERE isDeleted = false AND status = 'active' (if model has soft-delete default)
|
|
475
|
+
|
|
476
|
+
// Admin query - bypass default filter
|
|
477
|
+
await repo.find({
|
|
478
|
+
filter: { where: { status: 'active' } },
|
|
479
|
+
options: { shouldSkipDefaultFilter: true }
|
|
480
|
+
});
|
|
481
|
+
// WHERE status = 'active' (includes deleted records)
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
**Supported on all operations:**
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
// Read operations
|
|
488
|
+
await repo.find({ filter, options: { shouldSkipDefaultFilter: true } });
|
|
489
|
+
await repo.findOne({ filter, options: { shouldSkipDefaultFilter: true } });
|
|
490
|
+
await repo.count({ where, options: { shouldSkipDefaultFilter: true } });
|
|
491
|
+
|
|
492
|
+
// Write operations
|
|
493
|
+
await repo.updateAll({ where, data, options: { shouldSkipDefaultFilter: true } });
|
|
494
|
+
await repo.deleteAll({ where, options: { shouldSkipDefaultFilter: true, force: true } });
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
**Combined with transactions:**
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
const tx = await repo.beginTransaction();
|
|
501
|
+
await repo.updateAll({
|
|
502
|
+
where: { status: 'archived' },
|
|
503
|
+
data: { isDeleted: true },
|
|
504
|
+
options: {
|
|
505
|
+
transaction: tx,
|
|
506
|
+
shouldSkipDefaultFilter: true
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
await tx.commit();
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
> [!TIP]
|
|
513
|
+
> See [Default Filter](../filter-system/default-filter.md) for full documentation on configuring model default filters.
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
## Quick Reference
|
|
517
|
+
|
|
518
|
+
| Feature | Code |
|
|
519
|
+
|---------|------|
|
|
520
|
+
| Start transaction | `const tx = await repo.beginTransaction()` |
|
|
521
|
+
| Use transaction | `options: { transaction: tx }` |
|
|
522
|
+
| Commit | `await tx.commit()` |
|
|
523
|
+
| Rollback | `await tx.rollback()` |
|
|
524
|
+
| Bypass default filter | `options: { shouldSkipDefaultFilter: true }` |
|
|
525
|
+
| Enable logging | `options: { log: { use: true, level: 'debug' } }` |
|
|
526
|
+
| Force delete all | `options: { force: true }` |
|
|
527
|
+
| Skip returning data | `options: { shouldReturn: false }` |
|
|
528
|
+
| Access connector | `repo.getConnector()` |
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
## Next Steps
|
|
532
|
+
|
|
533
|
+
- [Overview](./index.md) - Repository basics
|
|
534
|
+
- [Filter System](../filter-system/) - Query operators
|
|
535
|
+
- [Default Filter](../filter-system/default-filter.md) - Automatic filter configuration
|
|
536
|
+
- [Repository Mixins](./mixins.md) - Composable features
|
|
537
|
+
- [Relations & Includes](./relations.md) - Eager loading
|
|
538
|
+
- [JSON Path Filtering](../filter-system/json-filtering) - JSONB queries
|
|
539
|
+
- [Array Operators](../filter-system/array-operators) - PostgreSQL arrays
|
|
540
|
+
|
|
541
|
+
## See Also
|
|
542
|
+
|
|
543
|
+
- **Related Concepts:**
|
|
544
|
+
- [Repositories Overview](./index) - Core repository operations
|
|
545
|
+
- [Transactions](/guides/core-concepts/persistent/transactions) - Transaction guide
|
|
546
|
+
- [DataSources](/guides/core-concepts/persistent/datasources) - Database connections
|
|
547
|
+
|
|
548
|
+
- **Related Topics:**
|
|
549
|
+
- [Repository Mixins](./mixins) - Soft delete and auditing
|
|
550
|
+
- [Relations & Includes](./relations) - Loading related data
|
|
551
|
+
- [Filter System](/references/base/filter-system/) - Query operators
|
|
552
|
+
|
|
553
|
+
- **Best Practices:**
|
|
554
|
+
- [Performance Optimization](/best-practices/performance-optimization) - Query optimization
|
|
555
|
+
- [Data Modeling](/best-practices/data-modeling) - Repository patterns
|