@mostajs/orm 1.0.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/LICENSE +21 -0
- package/README.md +548 -0
- package/dist/core/base-repository.d.ts +26 -0
- package/dist/core/base-repository.js +82 -0
- package/dist/core/config.d.ts +62 -0
- package/dist/core/config.js +116 -0
- package/dist/core/errors.d.ts +30 -0
- package/dist/core/errors.js +49 -0
- package/dist/core/factory.d.ts +41 -0
- package/dist/core/factory.js +142 -0
- package/dist/core/normalizer.d.ts +9 -0
- package/dist/core/normalizer.js +19 -0
- package/dist/core/registry.d.ts +43 -0
- package/dist/core/registry.js +78 -0
- package/dist/core/types.d.ts +228 -0
- package/dist/core/types.js +5 -0
- package/dist/dialects/abstract-sql.dialect.d.ts +113 -0
- package/dist/dialects/abstract-sql.dialect.js +1071 -0
- package/dist/dialects/cockroachdb.dialect.d.ts +2 -0
- package/dist/dialects/cockroachdb.dialect.js +23 -0
- package/dist/dialects/db2.dialect.d.ts +2 -0
- package/dist/dialects/db2.dialect.js +190 -0
- package/dist/dialects/hana.dialect.d.ts +2 -0
- package/dist/dialects/hana.dialect.js +199 -0
- package/dist/dialects/hsqldb.dialect.d.ts +2 -0
- package/dist/dialects/hsqldb.dialect.js +114 -0
- package/dist/dialects/mariadb.dialect.d.ts +2 -0
- package/dist/dialects/mariadb.dialect.js +87 -0
- package/dist/dialects/mongo.dialect.d.ts +2 -0
- package/dist/dialects/mongo.dialect.js +480 -0
- package/dist/dialects/mssql.dialect.d.ts +27 -0
- package/dist/dialects/mssql.dialect.js +127 -0
- package/dist/dialects/mysql.dialect.d.ts +24 -0
- package/dist/dialects/mysql.dialect.js +101 -0
- package/dist/dialects/oracle.dialect.d.ts +2 -0
- package/dist/dialects/oracle.dialect.js +206 -0
- package/dist/dialects/postgres.dialect.d.ts +26 -0
- package/dist/dialects/postgres.dialect.js +105 -0
- package/dist/dialects/spanner.dialect.d.ts +2 -0
- package/dist/dialects/spanner.dialect.js +259 -0
- package/dist/dialects/sqlite.dialect.d.ts +2 -0
- package/dist/dialects/sqlite.dialect.js +1027 -0
- package/dist/dialects/sybase.dialect.d.ts +2 -0
- package/dist/dialects/sybase.dialect.js +119 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +26 -0
- package/docs/api-reference.md +1009 -0
- package/docs/dialects.md +673 -0
- package/docs/tutorial.md +846 -0
- package/package.json +91 -0
|
@@ -0,0 +1,1009 @@
|
|
|
1
|
+
# MostaORM — Référence de l'API
|
|
2
|
+
|
|
3
|
+
> Référence complète de toutes les fonctions, classes, types et interfaces exportés par `@mosta/orm`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table des matières
|
|
8
|
+
|
|
9
|
+
- [createConnection()](#createconnection)
|
|
10
|
+
- [getDialect()](#getdialect)
|
|
11
|
+
- [getConfigFromEnv()](#getconfigfromenv)
|
|
12
|
+
- [disconnectDialect()](#disconnectdialect)
|
|
13
|
+
- [testConnection()](#testconnection)
|
|
14
|
+
- [registerSchema()](#registerschema--registerschemas)
|
|
15
|
+
- [registerSchemas()](#registerschema--registerschemas)
|
|
16
|
+
- [getSchema()](#getschema)
|
|
17
|
+
- [getAllSchemas()](#getallschemas)
|
|
18
|
+
- [BaseRepository\<T\>](#baserepositoryt)
|
|
19
|
+
- [findAll()](#findall)
|
|
20
|
+
- [findOne()](#findone)
|
|
21
|
+
- [findById()](#findbyid)
|
|
22
|
+
- [findByIdWithRelations()](#findbyidwithrelations)
|
|
23
|
+
- [findWithRelations()](#findwithrelations)
|
|
24
|
+
- [create()](#create)
|
|
25
|
+
- [update()](#update)
|
|
26
|
+
- [updateMany()](#updatemany)
|
|
27
|
+
- [delete()](#delete)
|
|
28
|
+
- [deleteMany()](#deletemany)
|
|
29
|
+
- [count()](#count)
|
|
30
|
+
- [search()](#search)
|
|
31
|
+
- [distinct()](#distinct)
|
|
32
|
+
- [aggregate()](#aggregate)
|
|
33
|
+
- [upsert()](#upsert)
|
|
34
|
+
- [increment()](#increment)
|
|
35
|
+
- [addToSet()](#addtoset)
|
|
36
|
+
- [pull()](#pull)
|
|
37
|
+
- [Types](#types)
|
|
38
|
+
- [EntitySchema](#entityschema)
|
|
39
|
+
- [FieldDef](#fielddef)
|
|
40
|
+
- [RelationDef](#relationdef)
|
|
41
|
+
- [IndexDef](#indexdef)
|
|
42
|
+
- [ConnectionConfig](#connectionconfig)
|
|
43
|
+
- [FilterQuery](#filterquery)
|
|
44
|
+
- [QueryOptions](#queryoptions)
|
|
45
|
+
- [AggregateStage](#aggregatestage)
|
|
46
|
+
- [PaginatedResult](#paginatedresult)
|
|
47
|
+
- [Erreurs](#erreurs)
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## createConnection()
|
|
52
|
+
|
|
53
|
+
Initialise et connecte le dialecte. Enregistre optionnellement des schémas.
|
|
54
|
+
Retourne un **singleton** — les appels suivants retournent la même instance.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
function createConnection(
|
|
58
|
+
config: ConnectionConfig,
|
|
59
|
+
schemas?: EntitySchema[]
|
|
60
|
+
): Promise<IDialect>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Paramètres**
|
|
64
|
+
|
|
65
|
+
| Paramètre | Type | Requis | Description |
|
|
66
|
+
|-----------|------|--------|-------------|
|
|
67
|
+
| `config` | `ConnectionConfig` | ✅ | Configuration de la connexion |
|
|
68
|
+
| `schemas` | `EntitySchema[]` | ❌ | Schémas à enregistrer avant connexion |
|
|
69
|
+
|
|
70
|
+
**Exemple**
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { createConnection } from '@mosta/orm'
|
|
74
|
+
import { UserSchema, PostSchema } from './schemas/index.js'
|
|
75
|
+
|
|
76
|
+
const dialect = await createConnection(
|
|
77
|
+
{
|
|
78
|
+
dialect: 'postgres',
|
|
79
|
+
uri: process.env.DATABASE_URL!,
|
|
80
|
+
schemaStrategy: 'update',
|
|
81
|
+
showSql: true,
|
|
82
|
+
poolSize: 10,
|
|
83
|
+
cacheEnabled: true,
|
|
84
|
+
cacheTtlSeconds: 60,
|
|
85
|
+
},
|
|
86
|
+
[UserSchema, PostSchema]
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## getDialect()
|
|
93
|
+
|
|
94
|
+
Retourne le dialect courant (singleton). Si aucune connexion n'existe, en crée une à partir de la configuration fournie ou des variables d'environnement.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
function getDialect(config?: ConnectionConfig): Promise<IDialect>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Exemple**
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// Depuis les variables d'environnement (DB_DIALECT + SGBD_URI)
|
|
104
|
+
const dialect = await getDialect()
|
|
105
|
+
|
|
106
|
+
// Ou avec une config explicite
|
|
107
|
+
const dialect = await getDialect({ dialect: 'sqlite', uri: ':memory:' })
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## getConfigFromEnv()
|
|
113
|
+
|
|
114
|
+
Lit la configuration depuis les variables d'environnement. Lance une erreur si `DB_DIALECT` ou `SGBD_URI` sont absentes.
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
function getConfigFromEnv(): ConnectionConfig
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Variables lues**
|
|
121
|
+
|
|
122
|
+
| Variable | Requis | Description |
|
|
123
|
+
|----------|--------|-------------|
|
|
124
|
+
| `DB_DIALECT` | ✅ | Identifiant du dialecte |
|
|
125
|
+
| `SGBD_URI` | ✅ | URI de connexion |
|
|
126
|
+
| `DB_SCHEMA_STRATEGY` | ❌ | Stratégie de schéma (défaut: `'none'`) |
|
|
127
|
+
| `DB_SHOW_SQL` | ❌ | Afficher les requêtes (`'true'/'false'`) |
|
|
128
|
+
| `DB_FORMAT_SQL` | ❌ | Indenter les requêtes affichées |
|
|
129
|
+
| `DB_POOL_SIZE` | ❌ | Taille du pool de connexions |
|
|
130
|
+
| `DB_CACHE_ENABLED` | ❌ | Activer le cache |
|
|
131
|
+
| `DB_CACHE_TTL` | ❌ | Durée de vie du cache (secondes) |
|
|
132
|
+
| `DB_BATCH_SIZE` | ❌ | Taille des lots pour les opérations bulk |
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## disconnectDialect()
|
|
137
|
+
|
|
138
|
+
Ferme la connexion et réinitialise le singleton.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
function disconnectDialect(): Promise<void>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Exemple**
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// Nettoyage en fin d'application
|
|
148
|
+
process.on('SIGTERM', async () => {
|
|
149
|
+
await disconnectDialect()
|
|
150
|
+
process.exit(0)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// Indispensable entre les tests
|
|
154
|
+
afterAll(async () => {
|
|
155
|
+
await disconnectDialect()
|
|
156
|
+
})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## testConnection()
|
|
162
|
+
|
|
163
|
+
Teste une connexion sans modifier le dialecte actif. Utile dans les assistants de configuration.
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
function testConnection(config: ConnectionConfig): Promise<boolean>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Exemple**
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
const ok = await testConnection({
|
|
173
|
+
dialect: 'postgres',
|
|
174
|
+
uri: 'postgresql://user:pass@localhost:5432/mydb',
|
|
175
|
+
})
|
|
176
|
+
console.log(ok ? '✅ Connexion réussie' : '❌ Connexion échouée')
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## registerSchema() / registerSchemas()
|
|
182
|
+
|
|
183
|
+
Enregistre un ou plusieurs schémas dans le registre global.
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
function registerSchema(schema: EntitySchema): void
|
|
187
|
+
function registerSchemas(schemas: EntitySchema[]): void
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
> Doit être appelé **avant** `getDialect()` ou `createConnection()` si vous n'utilisez pas le paramètre `schemas` de `createConnection()`.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## getSchema()
|
|
195
|
+
|
|
196
|
+
Récupère un schéma enregistré par son nom d'entité.
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
function getSchema(name: string): EntitySchema
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Lance `DialectNotFoundError` si le schéma n'est pas enregistré.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## getAllSchemas()
|
|
207
|
+
|
|
208
|
+
Retourne tous les schémas enregistrés.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
function getAllSchemas(): EntitySchema[]
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## BaseRepository\<T\>
|
|
217
|
+
|
|
218
|
+
Classe générique à étendre pour créer vos repositories.
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
class BaseRepository<T extends { id: string }> implements IRepository<T> {
|
|
222
|
+
constructor(schema: EntitySchema, dialect: IDialect)
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Paramètres du constructeur**
|
|
227
|
+
|
|
228
|
+
| Paramètre | Type | Description |
|
|
229
|
+
|-----------|------|-------------|
|
|
230
|
+
| `schema` | `EntitySchema` | Schéma de l'entité |
|
|
231
|
+
| `dialect` | `IDialect` | Instance du dialecte (depuis `createConnection`) |
|
|
232
|
+
|
|
233
|
+
**Pattern recommandé**
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
class UserRepository extends BaseRepository<User> {
|
|
237
|
+
constructor(dialect: IDialect) {
|
|
238
|
+
super(UserSchema, dialect)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async findByEmail(email: string): Promise<User | null> {
|
|
242
|
+
return this.findOne({ email })
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
### findAll()
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
findAll(filter?: FilterQuery, options?: QueryOptions): Promise<T[]>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Retourne tous les documents correspondant au filtre.
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
// Tous les utilisateurs actifs, triés par nom
|
|
259
|
+
const users = await userRepo.findAll(
|
|
260
|
+
{ active: true },
|
|
261
|
+
{ sort: { name: 1 }, limit: 50 }
|
|
262
|
+
)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
### findOne()
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
findOne(filter: FilterQuery, options?: QueryOptions): Promise<T | null>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Retourne le premier document correspondant, ou `null`.
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
const user = await userRepo.findOne({ email: 'alice@example.com' })
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
### findById()
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
findById(id: string, options?: QueryOptions): Promise<T | null>
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Recherche par identifiant unique (UUID ou ObjectId MongoDB).
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
const user = await userRepo.findById('550e8400-e29b-41d4-a716-446655440000')
|
|
291
|
+
if (!user) throw new Error('User not found')
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
### findByIdWithRelations()
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
findByIdWithRelations(
|
|
300
|
+
id: string,
|
|
301
|
+
relations?: string[],
|
|
302
|
+
options?: QueryOptions
|
|
303
|
+
): Promise<T | null>
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Charge un document avec ses relations (JOIN SQL / populate MongoDB).
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
const post = await postRepo.findByIdWithRelations('abc123', ['author', 'tags'])
|
|
310
|
+
// post.author = { id: '...', name: 'Alice', email: '...' }
|
|
311
|
+
// post.tags = [{ id: '...', name: 'typescript' }, ...]
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
### findWithRelations()
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
findWithRelations(
|
|
320
|
+
filter: FilterQuery,
|
|
321
|
+
relations: string[],
|
|
322
|
+
options?: QueryOptions
|
|
323
|
+
): Promise<T[]>
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Charge plusieurs documents avec leurs relations.
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
const posts = await postRepo.findWithRelations(
|
|
330
|
+
{ published: true },
|
|
331
|
+
['author', 'category'],
|
|
332
|
+
{ sort: { createdAt: -1 }, limit: 10 }
|
|
333
|
+
)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
### create()
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
create(data: Partial<T>): Promise<T>
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Crée un nouveau document. Un `id` (UUID) est généré automatiquement si non fourni.
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
const user = await userRepo.create({
|
|
348
|
+
name: 'Alice Dupont',
|
|
349
|
+
email: 'alice@example.com',
|
|
350
|
+
role: 'user',
|
|
351
|
+
})
|
|
352
|
+
console.log(user.id) // UUID généré
|
|
353
|
+
console.log(user.createdAt) // Date courante (si timestamps: true)
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
### update()
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
update(id: string, data: Partial<T>): Promise<T | null>
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Mise à jour partielle — seuls les champs fournis sont modifiés.
|
|
365
|
+
Retourne le document mis à jour, ou `null` si introuvable.
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
const updated = await userRepo.update('abc123', {
|
|
369
|
+
name: 'Alice Martin',
|
|
370
|
+
updatedAt: new Date(), // mis à jour automatiquement si timestamps: true
|
|
371
|
+
})
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
### updateMany()
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
updateMany(filter: FilterQuery, data: Partial<T>): Promise<number>
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Met à jour tous les documents correspondant au filtre.
|
|
383
|
+
Retourne le nombre de documents modifiés.
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
// Désactiver tous les comptes expirés
|
|
387
|
+
const count = await userRepo.updateMany(
|
|
388
|
+
{ expiresAt: { $lt: new Date() } },
|
|
389
|
+
{ active: false }
|
|
390
|
+
)
|
|
391
|
+
console.log(`${count} comptes désactivés`)
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
### delete()
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
delete(id: string): Promise<boolean>
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Supprime un document par son ID.
|
|
403
|
+
Retourne `true` si supprimé, `false` si introuvable.
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
const ok = await userRepo.delete('abc123')
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
### deleteMany()
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
deleteMany(filter: FilterQuery): Promise<number>
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Supprime tous les documents correspondant au filtre.
|
|
418
|
+
Retourne le nombre de documents supprimés.
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
const count = await userRepo.deleteMany({ active: false, createdAt: { $lt: cutoffDate } })
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
### count()
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
count(filter?: FilterQuery): Promise<number>
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Compte les documents correspondant au filtre (tous si omis).
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
const total = await userRepo.count()
|
|
436
|
+
const admins = await userRepo.count({ role: 'admin' })
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
### search()
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
search(query: string, options?: QueryOptions): Promise<T[]>
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Recherche textuelle dans tous les champs de type `string` du schéma.
|
|
448
|
+
|
|
449
|
+
- MongoDB : utilise les index `text`
|
|
450
|
+
- SQL : génère `WHERE field1 LIKE '%query%' OR field2 LIKE '%query%' ...`
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
const results = await productRepo.search('laptop pro', { limit: 10 })
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
### distinct()
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
distinct(field: string, filter?: FilterQuery): Promise<unknown[]>
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Retourne les valeurs uniques d'un champ.
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
const categories = await productRepo.distinct('category')
|
|
468
|
+
// ['electronics', 'clothing', 'food']
|
|
469
|
+
|
|
470
|
+
const activeRoles = await userRepo.distinct('role', { active: true })
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
### aggregate()
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
aggregate<R = Record<string, unknown>>(stages: AggregateStage[]): Promise<R[]>
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Exécute un pipeline d'agrégation.
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
interface CategoryStats {
|
|
485
|
+
_by: string
|
|
486
|
+
count: number
|
|
487
|
+
avgPrice: number
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const stats = await productRepo.aggregate<CategoryStats>([
|
|
491
|
+
{ $match: { active: true } },
|
|
492
|
+
{
|
|
493
|
+
$group: {
|
|
494
|
+
_by: 'category',
|
|
495
|
+
count: { $count: true },
|
|
496
|
+
avgPrice: { $avg: 'price' },
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
{ $sort: { count: -1 } },
|
|
500
|
+
{ $limit: 10 },
|
|
501
|
+
])
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
### upsert()
|
|
507
|
+
|
|
508
|
+
```typescript
|
|
509
|
+
upsert(filter: FilterQuery, data: Partial<T>): Promise<T>
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Équivalent de `INSERT OR UPDATE` — crée si inexistant, met à jour sinon.
|
|
513
|
+
Correspond à `saveOrUpdate()` en Hibernate.
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
const config = await configRepo.upsert(
|
|
517
|
+
{ key: 'theme' },
|
|
518
|
+
{ key: 'theme', value: 'dark', updatedAt: new Date() }
|
|
519
|
+
)
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
### increment()
|
|
525
|
+
|
|
526
|
+
```typescript
|
|
527
|
+
increment(id: string, field: string, amount: number): Promise<T | null>
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
Incrémente (ou décrémente si négatif) un champ numérique de façon atomique.
|
|
531
|
+
|
|
532
|
+
```typescript
|
|
533
|
+
// Incrémenter le compteur de vues
|
|
534
|
+
await articleRepo.increment('abc123', 'views', 1)
|
|
535
|
+
|
|
536
|
+
// Décrémenter le stock
|
|
537
|
+
await productRepo.increment('xyz789', 'stock', -3)
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
### addToSet()
|
|
543
|
+
|
|
544
|
+
```typescript
|
|
545
|
+
addToSet(id: string, field: string, value: unknown): Promise<T | null>
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
Ajoute une valeur dans un champ tableau, **sans créer de doublon**.
|
|
549
|
+
Équivalent de `$addToSet` MongoDB, ou d'une vérification `CONTAINS` en SQL.
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
// Ajouter un tag (sans doublon)
|
|
553
|
+
await productRepo.addToSet('abc123', 'tags', 'bestseller')
|
|
554
|
+
|
|
555
|
+
// Ajouter un rôle à un utilisateur
|
|
556
|
+
await userRepo.addToSet('user123', 'roles', 'editor')
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
### pull()
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
pull(id: string, field: string, value: unknown): Promise<T | null>
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
Retire une valeur d'un champ tableau.
|
|
568
|
+
|
|
569
|
+
```typescript
|
|
570
|
+
// Retirer un tag
|
|
571
|
+
await productRepo.pull('abc123', 'tags', 'discontinued')
|
|
572
|
+
|
|
573
|
+
// Révoquer un rôle
|
|
574
|
+
await userRepo.pull('user123', 'roles', 'editor')
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
## Types
|
|
580
|
+
|
|
581
|
+
### EntitySchema
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
interface EntitySchema {
|
|
585
|
+
/** Nom PascalCase de l'entité (ex: 'User', 'Product') */
|
|
586
|
+
name: string
|
|
587
|
+
|
|
588
|
+
/** Nom de la table SQL ou collection MongoDB (ex: 'users', 'products') */
|
|
589
|
+
collection: string
|
|
590
|
+
|
|
591
|
+
/** Définition des champs */
|
|
592
|
+
fields: Record<string, FieldDef>
|
|
593
|
+
|
|
594
|
+
/** Relations vers d'autres entités */
|
|
595
|
+
relations: Record<string, RelationDef>
|
|
596
|
+
|
|
597
|
+
/** Index de la table/collection */
|
|
598
|
+
indexes: IndexDef[]
|
|
599
|
+
|
|
600
|
+
/** Ajoute automatiquement createdAt et updatedAt */
|
|
601
|
+
timestamps: boolean
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
### FieldDef
|
|
608
|
+
|
|
609
|
+
```typescript
|
|
610
|
+
interface FieldDef {
|
|
611
|
+
/** Type du champ */
|
|
612
|
+
type: 'string' | 'number' | 'boolean' | 'date' | 'json' | 'array'
|
|
613
|
+
|
|
614
|
+
/** Le champ est obligatoire (NOT NULL en SQL) */
|
|
615
|
+
required?: boolean
|
|
616
|
+
|
|
617
|
+
/** Le champ doit être unique (UNIQUE en SQL, unique index en MongoDB) */
|
|
618
|
+
unique?: boolean
|
|
619
|
+
|
|
620
|
+
/** L'index unique accepte les valeurs NULL multiples (MongoDB sparse) */
|
|
621
|
+
sparse?: boolean
|
|
622
|
+
|
|
623
|
+
/** Valeur par défaut */
|
|
624
|
+
default?: unknown
|
|
625
|
+
|
|
626
|
+
/** Valeurs acceptées (validation) */
|
|
627
|
+
enum?: string[]
|
|
628
|
+
|
|
629
|
+
/** Convertir en minuscules avant stockage */
|
|
630
|
+
lowercase?: boolean
|
|
631
|
+
|
|
632
|
+
/** Supprimer les espaces en début/fin avant stockage */
|
|
633
|
+
trim?: boolean
|
|
634
|
+
|
|
635
|
+
/** Type des éléments (pour type: 'array') */
|
|
636
|
+
arrayOf?: FieldType | EmbeddedSchemaDef
|
|
637
|
+
}
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
---
|
|
641
|
+
|
|
642
|
+
### RelationDef
|
|
643
|
+
|
|
644
|
+
```typescript
|
|
645
|
+
interface RelationDef {
|
|
646
|
+
/** Nom de l'entité cible (doit être enregistrée dans le registry) */
|
|
647
|
+
target: string
|
|
648
|
+
|
|
649
|
+
/** Type de relation */
|
|
650
|
+
type: 'one-to-one' | 'many-to-one' | 'one-to-many' | 'many-to-many'
|
|
651
|
+
|
|
652
|
+
/** Cette relation est obligatoire */
|
|
653
|
+
required?: boolean
|
|
654
|
+
|
|
655
|
+
/** Champs à sélectionner lors du chargement (projection) */
|
|
656
|
+
select?: string[]
|
|
657
|
+
|
|
658
|
+
/** La relation peut être null */
|
|
659
|
+
nullable?: boolean
|
|
660
|
+
|
|
661
|
+
/** Nom de la table de jonction (many-to-many SQL uniquement) */
|
|
662
|
+
through?: string
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
668
|
+
### IndexDef
|
|
669
|
+
|
|
670
|
+
```typescript
|
|
671
|
+
interface IndexDef {
|
|
672
|
+
/** Champs et direction de tri */
|
|
673
|
+
fields: Record<string, 'asc' | 'desc' | 'text'>
|
|
674
|
+
|
|
675
|
+
/** Index unique */
|
|
676
|
+
unique?: boolean
|
|
677
|
+
|
|
678
|
+
/** Exclure les valeurs NULL de l'index */
|
|
679
|
+
sparse?: boolean
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
---
|
|
684
|
+
|
|
685
|
+
### ConnectionConfig
|
|
686
|
+
|
|
687
|
+
```typescript
|
|
688
|
+
interface ConnectionConfig {
|
|
689
|
+
/** Identifiant du dialecte */
|
|
690
|
+
dialect: DialectType
|
|
691
|
+
|
|
692
|
+
/** URI de connexion */
|
|
693
|
+
uri: string
|
|
694
|
+
|
|
695
|
+
/** Afficher les requêtes générées (défaut: false) */
|
|
696
|
+
showSql?: boolean
|
|
697
|
+
|
|
698
|
+
/** Indenter les requêtes affichées (défaut: false) */
|
|
699
|
+
formatSql?: boolean
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Stratégie de gestion du schéma (défaut: 'none')
|
|
703
|
+
* - 'none' : ne rien faire
|
|
704
|
+
* - 'validate' : vérifier, refuser si incohérent
|
|
705
|
+
* - 'update' : créer/modifier les tables/collections
|
|
706
|
+
* - 'create' : supprimer et recréer au démarrage
|
|
707
|
+
* - 'create-drop' : supprimer au démarrage ET à l'arrêt
|
|
708
|
+
*/
|
|
709
|
+
schemaStrategy?: 'none' | 'validate' | 'update' | 'create' | 'create-drop'
|
|
710
|
+
|
|
711
|
+
/** Taille max du pool de connexions */
|
|
712
|
+
poolSize?: number
|
|
713
|
+
|
|
714
|
+
/** Activer le cache des résultats de requête */
|
|
715
|
+
cacheEnabled?: boolean
|
|
716
|
+
|
|
717
|
+
/** Durée de vie du cache en secondes (défaut: 60) */
|
|
718
|
+
cacheTtlSeconds?: number
|
|
719
|
+
|
|
720
|
+
/** Taille des lots pour les opérations bulk (défaut: 25) */
|
|
721
|
+
batchSize?: number
|
|
722
|
+
|
|
723
|
+
/** Options supplémentaires spécifiques au dialecte */
|
|
724
|
+
options?: Record<string, unknown>
|
|
725
|
+
}
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
**Dialectes disponibles** : `'mongodb' | 'sqlite' | 'postgres' | 'mysql' | 'mariadb' | 'oracle' | 'mssql' | 'cockroachdb' | 'db2' | 'hana' | 'hsqldb' | 'spanner' | 'sybase'`
|
|
729
|
+
|
|
730
|
+
---
|
|
731
|
+
|
|
732
|
+
### FilterQuery
|
|
733
|
+
|
|
734
|
+
```typescript
|
|
735
|
+
interface FilterQuery {
|
|
736
|
+
[field: string]: FilterValue // égalité implicite ou opérateur
|
|
737
|
+
|
|
738
|
+
/** OU logique */
|
|
739
|
+
$or?: FilterQuery[]
|
|
740
|
+
|
|
741
|
+
/** ET logique */
|
|
742
|
+
$and?: FilterQuery[]
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
interface FilterOperator {
|
|
746
|
+
$eq?: unknown // égal à
|
|
747
|
+
$ne?: unknown // différent de
|
|
748
|
+
$gt?: unknown // supérieur à (strictement)
|
|
749
|
+
$gte?: unknown // supérieur ou égal
|
|
750
|
+
$lt?: unknown // inférieur à (strictement)
|
|
751
|
+
$lte?: unknown // inférieur ou égal
|
|
752
|
+
$in?: unknown[] // dans la liste
|
|
753
|
+
$nin?: unknown[] // hors de la liste
|
|
754
|
+
$regex?: string // expression régulière
|
|
755
|
+
$regexFlags?: string // flags regex (ex: 'i' = insensible à la casse)
|
|
756
|
+
$exists?: boolean // le champ existe
|
|
757
|
+
}
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
**Exemples**
|
|
761
|
+
|
|
762
|
+
```typescript
|
|
763
|
+
// Égalité implicite
|
|
764
|
+
{ status: 'active' }
|
|
765
|
+
|
|
766
|
+
// Opérateurs de comparaison
|
|
767
|
+
{ age: { $gte: 18, $lt: 65 } }
|
|
768
|
+
|
|
769
|
+
// Liste
|
|
770
|
+
{ role: { $in: ['admin', 'moderator'] } }
|
|
771
|
+
|
|
772
|
+
// Regex
|
|
773
|
+
{ name: { $regex: '^alice', $regexFlags: 'i' } }
|
|
774
|
+
|
|
775
|
+
// OU
|
|
776
|
+
{ $or: [{ role: 'admin' }, { permissions: { $in: ['manage_users'] } }] }
|
|
777
|
+
|
|
778
|
+
// ET
|
|
779
|
+
{ $and: [{ active: true }, { verified: true }] }
|
|
780
|
+
|
|
781
|
+
// Existence
|
|
782
|
+
{ deletedAt: { $exists: false } }
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
---
|
|
786
|
+
|
|
787
|
+
### QueryOptions
|
|
788
|
+
|
|
789
|
+
```typescript
|
|
790
|
+
interface QueryOptions {
|
|
791
|
+
/** Tri des résultats : 1 = ASC, -1 = DESC */
|
|
792
|
+
sort?: Record<string, 1 | -1>
|
|
793
|
+
|
|
794
|
+
/** Nombre de documents à sauter (pagination) */
|
|
795
|
+
skip?: number
|
|
796
|
+
|
|
797
|
+
/** Nombre maximum de documents à retourner */
|
|
798
|
+
limit?: number
|
|
799
|
+
|
|
800
|
+
/** Champs à inclure dans les résultats (projection inclusive) */
|
|
801
|
+
select?: string[]
|
|
802
|
+
|
|
803
|
+
/** Champs à exclure des résultats (projection exclusive) */
|
|
804
|
+
exclude?: string[]
|
|
805
|
+
}
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
**Exemple — pagination**
|
|
809
|
+
|
|
810
|
+
```typescript
|
|
811
|
+
const PAGE = 2
|
|
812
|
+
const LIMIT = 20
|
|
813
|
+
|
|
814
|
+
const users = await userRepo.findAll(
|
|
815
|
+
{ active: true },
|
|
816
|
+
{
|
|
817
|
+
sort: { createdAt: -1 },
|
|
818
|
+
skip: (PAGE - 1) * LIMIT,
|
|
819
|
+
limit: LIMIT,
|
|
820
|
+
select: ['id', 'name', 'email', 'role'],
|
|
821
|
+
}
|
|
822
|
+
)
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
---
|
|
826
|
+
|
|
827
|
+
### AggregateStage
|
|
828
|
+
|
|
829
|
+
```typescript
|
|
830
|
+
// Filtrage ($match → WHERE / $match)
|
|
831
|
+
type AggregateMatchStage = { $match: FilterQuery }
|
|
832
|
+
|
|
833
|
+
// Groupement ($group → GROUP BY)
|
|
834
|
+
type AggregateGroupStage = {
|
|
835
|
+
$group: {
|
|
836
|
+
_by: string | null // null = grouper tous les documents ensemble
|
|
837
|
+
[field: string]: AggregateAccumulator | string | null
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// Accumulateurs disponibles dans $group
|
|
842
|
+
interface AggregateAccumulator {
|
|
843
|
+
$count?: true // COUNT(*)
|
|
844
|
+
$sum?: number | string // SUM(field) ou COUNT(*) si 1
|
|
845
|
+
$avg?: string // AVG(field)
|
|
846
|
+
$min?: string // MIN(field)
|
|
847
|
+
$max?: string // MAX(field)
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Tri ($sort → ORDER BY)
|
|
851
|
+
type AggregateSortStage = { $sort: Record<string, 1 | -1> }
|
|
852
|
+
|
|
853
|
+
// Limitation ($limit → LIMIT)
|
|
854
|
+
type AggregateLimitStage = { $limit: number }
|
|
855
|
+
|
|
856
|
+
// Union
|
|
857
|
+
type AggregateStage =
|
|
858
|
+
| AggregateMatchStage
|
|
859
|
+
| AggregateGroupStage
|
|
860
|
+
| AggregateSortStage
|
|
861
|
+
| AggregateLimitStage
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
**Exemple complet**
|
|
865
|
+
|
|
866
|
+
```typescript
|
|
867
|
+
// Chiffre d'affaires par mois, pour les 6 derniers mois
|
|
868
|
+
const revenue = await orderRepo.aggregate([
|
|
869
|
+
{
|
|
870
|
+
$match: {
|
|
871
|
+
createdAt: { $gte: sixMonthsAgo },
|
|
872
|
+
status: 'completed',
|
|
873
|
+
},
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
$group: {
|
|
877
|
+
_by: 'month',
|
|
878
|
+
totalRevenue: { $sum: 'total' },
|
|
879
|
+
orderCount: { $count: true },
|
|
880
|
+
avgOrderValue: { $avg: 'total' },
|
|
881
|
+
},
|
|
882
|
+
},
|
|
883
|
+
{ $sort: { _by: 1 } },
|
|
884
|
+
])
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
---
|
|
888
|
+
|
|
889
|
+
### PaginatedResult
|
|
890
|
+
|
|
891
|
+
Type utilitaire pour les réponses paginées. Non retourné directement par `findAll` — à construire dans vos repositories.
|
|
892
|
+
|
|
893
|
+
```typescript
|
|
894
|
+
interface PaginatedResult<T> {
|
|
895
|
+
data: T[]
|
|
896
|
+
total: number
|
|
897
|
+
page: number
|
|
898
|
+
limit: number
|
|
899
|
+
totalPages: number
|
|
900
|
+
}
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
**Exemple d'implémentation dans un repository**
|
|
904
|
+
|
|
905
|
+
```typescript
|
|
906
|
+
async findPaginated(
|
|
907
|
+
filter: FilterQuery = {},
|
|
908
|
+
page: number = 1,
|
|
909
|
+
limit: number = 20,
|
|
910
|
+
options?: Omit<QueryOptions, 'skip' | 'limit'>
|
|
911
|
+
): Promise<PaginatedResult<T>> {
|
|
912
|
+
const [data, total] = await Promise.all([
|
|
913
|
+
this.findAll(filter, { ...options, skip: (page - 1) * limit, limit }),
|
|
914
|
+
this.count(filter),
|
|
915
|
+
])
|
|
916
|
+
return {
|
|
917
|
+
data,
|
|
918
|
+
total,
|
|
919
|
+
page,
|
|
920
|
+
limit,
|
|
921
|
+
totalPages: Math.ceil(total / limit),
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
---
|
|
927
|
+
|
|
928
|
+
## Erreurs
|
|
929
|
+
|
|
930
|
+
```typescript
|
|
931
|
+
import {
|
|
932
|
+
MostaORMError,
|
|
933
|
+
EntityNotFoundError,
|
|
934
|
+
ConnectionError,
|
|
935
|
+
ValidationError,
|
|
936
|
+
DialectNotFoundError,
|
|
937
|
+
} from '@mosta/orm'
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
| Classe | Hérite de | Quand |
|
|
941
|
+
|--------|-----------|-------|
|
|
942
|
+
| `MostaORMError` | `Error` | Erreur générique MostaORM |
|
|
943
|
+
| `EntityNotFoundError` | `MostaORMError` | Entité introuvable par ID |
|
|
944
|
+
| `ConnectionError` | `MostaORMError` | Échec de connexion au serveur DB |
|
|
945
|
+
| `ValidationError` | `MostaORMError` | Violation de contrainte de schéma |
|
|
946
|
+
| `DialectNotFoundError` | `MostaORMError` | Dialecte inconnu ou driver manquant |
|
|
947
|
+
|
|
948
|
+
**Gestion des erreurs**
|
|
949
|
+
|
|
950
|
+
```typescript
|
|
951
|
+
import { EntityNotFoundError, ConnectionError } from '@mosta/orm'
|
|
952
|
+
|
|
953
|
+
try {
|
|
954
|
+
const user = await userRepo.findById(id)
|
|
955
|
+
if (!user) throw new EntityNotFoundError('User', id)
|
|
956
|
+
return user
|
|
957
|
+
} catch (err) {
|
|
958
|
+
if (err instanceof EntityNotFoundError) {
|
|
959
|
+
return res.status(404).json({ error: err.message })
|
|
960
|
+
}
|
|
961
|
+
if (err instanceof ConnectionError) {
|
|
962
|
+
return res.status(503).json({ error: 'Database unavailable' })
|
|
963
|
+
}
|
|
964
|
+
throw err
|
|
965
|
+
}
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
---
|
|
969
|
+
|
|
970
|
+
## Interface IPlugin
|
|
971
|
+
|
|
972
|
+
Permet d'étendre le comportement du repository via des hooks.
|
|
973
|
+
|
|
974
|
+
```typescript
|
|
975
|
+
interface IPlugin {
|
|
976
|
+
name: string
|
|
977
|
+
|
|
978
|
+
/** Modifier le schéma au démarrage (ajout de champs, index...) */
|
|
979
|
+
onSchemaInit?(schema: EntitySchema): EntitySchema
|
|
980
|
+
|
|
981
|
+
/** Avant insertion */
|
|
982
|
+
preSave?(doc: Record<string, unknown>, ctx: HookContext): Promise<Record<string, unknown>> | Record<string, unknown>
|
|
983
|
+
|
|
984
|
+
/** Après insertion */
|
|
985
|
+
postSave?(doc: Record<string, unknown>, ctx: HookContext): Promise<void> | void
|
|
986
|
+
|
|
987
|
+
/** Avant mise à jour */
|
|
988
|
+
preUpdate?(id: string, data: Record<string, unknown>, ctx: HookContext): Promise<Record<string, unknown>> | Record<string, unknown>
|
|
989
|
+
|
|
990
|
+
/** Après mise à jour */
|
|
991
|
+
postUpdate?(doc: Record<string, unknown>, ctx: HookContext): Promise<void> | void
|
|
992
|
+
|
|
993
|
+
/** Avant suppression */
|
|
994
|
+
preDelete?(id: string, ctx: HookContext): Promise<void> | void
|
|
995
|
+
|
|
996
|
+
/** Modifier le filtre des requêtes (ex: soft-delete) */
|
|
997
|
+
onQuery?(filter: FilterQuery, ctx: HookContext): FilterQuery
|
|
998
|
+
|
|
999
|
+
/** Transformer les résultats (ex: normalisation) */
|
|
1000
|
+
onResult?(doc: Record<string, unknown>, ctx: HookContext): Record<string, unknown>
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
interface HookContext {
|
|
1004
|
+
entity: EntitySchema
|
|
1005
|
+
dialect: DialectType
|
|
1006
|
+
operation: 'create' | 'update' | 'delete' | 'find'
|
|
1007
|
+
userId?: string
|
|
1008
|
+
}
|
|
1009
|
+
```
|