@nodefony/mongoose 10.0.0-alpha.7 → 10.0.0-alpha.9

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.
Files changed (38) hide show
  1. package/README.md +32 -0
  2. package/dist/_virtual/{_@oxc-project_runtime@0.150.0 → _@oxc-project_runtime@0.151.0}/helpers/esm/decorate.js +1 -1
  3. package/dist/_virtual/{_@oxc-project_runtime@0.150.0 → _@oxc-project_runtime@0.151.0}/helpers/esm/decorateMetadata.js +1 -1
  4. package/dist/index.js +10 -4
  5. package/dist/nodefony/entity/auditEventEntity.js +124 -0
  6. package/dist/nodefony/entity/idempotencyEntity.js +96 -0
  7. package/dist/nodefony/entity/sessionEntity.js +1 -1
  8. package/dist/nodefony/entity/totpSecretEntity.js +103 -0
  9. package/dist/nodefony/registerStores.js +41 -4
  10. package/dist/nodefony/service/MongooseService.js +6 -3
  11. package/dist/nodefony/src/MongooseAuditStore.js +213 -0
  12. package/dist/nodefony/src/MongooseIdempotencyStore.js +260 -0
  13. package/dist/nodefony/src/MongooseTokenStore.js +60 -17
  14. package/dist/nodefony/src/MongooseTotpSecretStore.js +151 -0
  15. package/dist/nodefony/src/MongooseWebAuthnCredentialStore.js +13 -1
  16. package/dist/nodefony/src/MongooseWebhookStore.js +14 -2
  17. package/dist/nodefony/src/SessionStorage.js +8 -0
  18. package/dist/nodefony/src/orm-core/MongooseOrm.js +62 -12
  19. package/dist/nodefony/src/orm-core/MongooseRepository.js +124 -14
  20. package/dist/nodefony/src/orm-core/MongooseTransaction.js +19 -6
  21. package/dist/types/index.d.ts +9 -0
  22. package/dist/types/nodefony/entity/auditEventEntity.d.ts +76 -0
  23. package/dist/types/nodefony/entity/idempotencyEntity.d.ts +75 -0
  24. package/dist/types/nodefony/entity/totpSecretEntity.d.ts +75 -0
  25. package/dist/types/nodefony/registerStores.d.ts +6 -3
  26. package/dist/types/nodefony/src/MongooseAuditStore.d.ts +70 -0
  27. package/dist/types/nodefony/src/MongooseIdempotencyStore.d.ts +121 -0
  28. package/dist/types/nodefony/src/MongooseTokenStore.d.ts +8 -1
  29. package/dist/types/nodefony/src/MongooseTotpSecretStore.d.ts +63 -0
  30. package/dist/types/nodefony/src/MongooseWebAuthnCredentialStore.d.ts +6 -0
  31. package/dist/types/nodefony/src/MongooseWebhookStore.d.ts +6 -0
  32. package/dist/types/nodefony/src/SessionStorage.d.ts +6 -0
  33. package/dist/types/nodefony/src/orm-core/MongooseOrm.d.ts +10 -3
  34. package/dist/types/nodefony/src/orm-core/MongooseRepository.d.ts +22 -1
  35. package/dist/types/nodefony/src/orm-core/MongooseTransaction.d.ts +12 -4
  36. package/docs/configuration.md +22 -22
  37. package/docs/index.md +107 -63
  38. package/package.json +22 -18
@@ -8,8 +8,8 @@ import type { ITransaction } from "@nodefony/orm-core";
8
8
  * MongoDB **exigent un replica set** (un standalone ne les supporte pas).
9
9
  *
10
10
  * MongoDB n'a **pas de savepoints** : {@link MongooseTransaction.savepoint} /
11
- * {@link MongooseTransaction.rollbackTo} sont des no-op documentés (limite du
12
- * driver, prévue par le contrat).
11
+ * {@link MongooseTransaction.rollbackTo} les REFUSENT (`SavepointNotSupportedError`).
12
+ * Un no-op laissait croire un rollback partiel qui n'annulait rien.
13
13
  */
14
14
  export declare class MongooseTransaction implements ITransaction {
15
15
  #private;
@@ -28,9 +28,17 @@ export declare class MongooseTransaction implements ITransaction {
28
28
  commit(): Promise<void>;
29
29
  /** Annule la transaction (no-op si déjà terminée — idempotent). */
30
30
  rollback(): Promise<void>;
31
- /** No-op : MongoDB ne gère pas les savepoints. */
31
+ /**
32
+ * Refusé : MongoDB n'a pas de savepoints.
33
+ *
34
+ * @throws SavepointNotSupportedError toujours.
35
+ */
32
36
  savepoint(_name: string): Promise<void>;
33
- /** No-op : MongoDB ne gère pas les savepoints. */
37
+ /**
38
+ * Refusé : MongoDB n'a pas de savepoints.
39
+ *
40
+ * @throws SavepointNotSupportedError toujours.
41
+ */
34
42
  rollbackTo(_name: string): Promise<void>;
35
43
  /** Expose la `ClientSession` native (trappe bas niveau). */
36
44
  getNative<C = unknown>(): C;
@@ -105,7 +105,7 @@ mêmes noms partout — pour qu'on ne se pose jamais la question de savoir où r
105
105
  | `nodefony/config/config.ts` | **Le QUOI** | Le schéma Zod commenté, source unique des défauts, et le type TS qui en dérive |
106
106
  | `nodefony/config/defineModuleConfig.ts` | **Le COMMENT** | Le builder : valide → applique l'environnement → gèle. Plus le JSON Schema. |
107
107
 
108
- Le schéma (`mongooseConfigSchema` (`config.ts:83`)) porte les valeurs d'usine sous forme de
108
+ Le schéma (`mongooseConfigSchema` (`config.ts:98`)) porte les valeurs d'usine sous forme de
109
109
  `.default(…)`, et chaque champ son `.describe(…)`. Les défauts effectifs du module ne sont pas
110
110
  retapés à la main : ils sont **matérialisés depuis le schéma lui-même**
111
111
  (`mongooseConfigSchema.parse({})` (`config.ts:122`)). Changer un défaut = changer le `.default()`,
@@ -134,11 +134,11 @@ qui collisionnerait à l'import). Trois écarts réels, tous **assumés et véri
134
134
  Ce n'est pas de la fantaisie : les entités sont indexées par `(connecteur, nom)` dans un registre
135
135
  **global au processus**, donc deux drivers avec le même nom de connecteur feraient collision sur
136
136
  leurs entités `session` homonymes. Un nom distinct règle le problème par construction
137
- (`FRAMEWORK_CONNECTOR` (`registerStores.ts:49`)).
137
+ (`FRAMEWORK_CONNECTOR` (`registerStores.ts:77`)).
138
138
  2. **Mongoose expose deux variables dédiées** (`MONGODB_URI`, `NF_MONGODB_DEBUG`) là où Drizzle ne lit
139
139
  que l'infra déclarée. C'est un héritage de convention du driver Mongo, conservé parce qu'il est
140
140
  universellement connu — mais l'infra déclarée reste le chemin recommandé.
141
- 3. **`options` n'est pas re-modélisé** (`options` (`config.ts:71`)) : c'est un dictionnaire libre
141
+ 3. **`options` n'est pas re-modélisé** (`options` (`config.ts:86`)) : c'est un dictionnaire libre
142
142
  passé tel quel à Mongoose. Re-décrire les dizaines de `ConnectOptions` en Zod produirait une
143
143
  deuxième vérité, condamnée à diverger de la bibliothèque à chaque version. Le prix à payer est
144
144
  assumé : une faute de frappe dans `options` n'est pas attrapée par Zod, elle l'est par Mongoose
@@ -232,7 +232,7 @@ export default defineConfig(() => ({
232
232
  ```
233
233
 
234
234
  Si `NF_DATABASE_URL` pointe déjà sur du `mongodb://`, tu peux laisser **tous** ces champs sur leur
235
- défaut `auto` : la résolution suit l'infra déclarée (`resolveAutoStore()` (`infra.ts:241`)) et
235
+ défaut `auto` : la résolution suit l'infra déclarée (`resolveAutoStore()` (`infra.ts:297`)) et
236
236
  journalise sa raison. Détail dans le hub, section
237
237
  [Ce qui se passe quand tu ne choisis rien](./index.md#ce-qui-se-passe-quand-tu-ne-choisis-rien).
238
238
 
@@ -260,14 +260,14 @@ Et six champs par connecteur :
260
260
 
261
261
  ### `connectors` — une ou plusieurs bases
262
262
 
263
- Chaque entrée de `connectors` (`config.ts:93`) devient une **connexion isolée** ouverte au démarrage,
263
+ Chaque entrée de `connectors` (`config.ts:108`) devient une **connexion isolée** ouverte au démarrage,
264
264
  puis un ORM inscrit sous ce nom. Deux entrées = deux connexions, deux ORM, deux jeux d'entités : c'est
265
265
  ainsi qu'une application lit une base métier et écrit dans une base d'archives sans les mélanger.
266
266
 
267
267
  L'adresse se donne de deux façons, jamais les deux à la fois utilement :
268
268
 
269
269
  - **En pièces détachées** — `host`, `port`, `dbname` : lisible, adapté au développement. Le service
270
- les assemble en `mongodb://hôte:port/base` (`MongooseService.buildUri()` (`MongooseService.ts:94`)).
270
+ les assemble en `mongodb://hôte:port/base` (`MongooseService.buildUri()` (`MongooseService.ts:102`)).
271
271
  - **En une URI** — `uri` (`config.ts:41`) : la seule forme capable d'exprimer un replica set, un
272
272
  `mongodb+srv`, des options de requête. **Dès qu'`uri` est présent, les trois autres champs ne sont
273
273
  plus lus du tout.**
@@ -287,15 +287,15 @@ publique** de ton application : le changer déplace des entités.
287
287
 
288
288
  > [!WARNING]
289
289
  > Le connecteur nommé `nodefony` a un statut particulier : c'est **lui** qui porte le schéma du
290
- > framework (`FRAMEWORK_CONNECTOR` (`registerStores.ts:49`)) et le stockage de session
290
+ > framework (`FRAMEWORK_CONNECTOR` (`registerStores.ts:77`)) et le stockage de session
291
291
  > (`SESSION_CONNECTOR` (`sessionEntity.ts:5`)). Le renommer ou le supprimer sans le remplacer casse
292
292
  > les stores framework — ils cherchent un ORM `nodefony` connecté et échouent franchement s'il manque
293
- > (`resolveConnectedOrm()` (`registerStores.ts:64`)).
293
+ > (`resolveConnectedOrm()` (`registerStores.ts:92`)).
294
294
 
295
295
  ### `options` — ce que Mongoose sait faire et Nodefony ne re-décrit pas
296
296
 
297
- `options` (`config.ts:71`) est transmis tel quel au driver
298
- (`MongooseService.#connectOne()` (`MongooseService.ts:87`)). On y met tout ce qui touche au
297
+ `options` (`config.ts:86`) est transmis tel quel au driver
298
+ (`MongooseService.#connectOne()` (`MongooseService.ts:141`)). On y met tout ce qui touche au
299
299
  **transport** plutôt qu'à l'adresse : taille du pool, délais, TLS, identifiants séparés.
300
300
 
301
301
  ```typescript
@@ -320,7 +320,7 @@ description des options du driver.
320
320
 
321
321
  ### `debug` — voir passer les requêtes
322
322
 
323
- `debug` (`config.ts:85`) active la trace intégrée de Mongoose (`connectAll()` (`MongooseService.ts:81`)).
323
+ `debug` (`config.ts:100`) active la trace intégrée de Mongoose (`connectAll()` (`MongooseService.ts:81`)).
324
324
  Chaque opération part sur la sortie standard, avec sa collection, son filtre et ses champs.
325
325
 
326
326
  C'est un **réglage de processus, pas de connecteur** : il vaut pour toutes les connexions à la fois.
@@ -330,7 +330,7 @@ détaillée dans la section [Observabilité](#-observabilité--studio).
330
330
 
331
331
  ### `frameworkEntities` — le module pose-t-il le schéma du framework ?
332
332
 
333
- C'est le champ le plus discret et le plus structurant (`frameworkEntities` (`config.ts:102`)). Sur son
333
+ C'est le champ le plus discret et le plus structurant (`frameworkEntities` (`config.ts:117`)). Sur son
334
334
  défaut `true`, le module fait deux choses de plus qu'ouvrir des connexions, dès son enregistrement et
335
335
  **avant** que la connexion ne s'ouvre (`Mongoose.onKernelRegister()` (`mongoose/index.ts:65`)) :
336
336
 
@@ -338,7 +338,7 @@ défaut `true`, le module fait deux choses de plus qu'ouvrir des connexions, dè
338
338
  pour que leurs modèles soient compilés au moment de la connexion ;
339
339
  2. il **enregistre les fabriques** correspondantes dans les registres de `@nodefony/security`, ce qui
340
340
  rend le nom `"mongoose"` sélectionnable dans `tokenStore`, `passkeys`, `webhooks`
341
- (`registerMongooseFrameworkStores()` (`registerStores.ts:94`)).
341
+ (`registerMongooseFrameworkStores()` (`registerStores.ts:126`)).
342
342
 
343
343
  Le passer à `false` transforme le module en **pur driver de données** : tes entités à toi, rien
344
344
  d'autre. Les stores framework Mongo deviennent alors introuvables — les sélectionner échoue
@@ -405,7 +405,7 @@ NF__MONGOOSE__CONNECTORS__NODEFONY__DBNAME=recette # champ imbriqué
405
405
  NF__MONGOOSE__CONNECTORS__NODEFONY__PORT=27018 # coercé en nombre
406
406
  ```
407
407
 
408
- Ces overrides sont posés **avant** la validation Zod (`Kernel.applyEnvConfigOverrides()` (`Kernel.ts:1708`)) :
408
+ Ces overrides sont posés **avant** la validation Zod (`Kernel.applyEnvConfigOverrides()` (`Kernel.ts:1807`)) :
409
409
  une valeur aberrante est donc rejetée comme si tu l'avais écrite dans ton fichier. C'est voulu — un
410
410
  réglage d'environnement invalide doit casser aussi fort qu'un réglage de code.
411
411
 
@@ -436,7 +436,7 @@ De la plus faible à la plus forte priorité :
436
436
 
437
437
  1. **Les défauts du schéma** — `localhost:27017/nodefony`, `debug: false`, `frameworkEntities: true`.
438
438
  2. **Ta config d'app** — `use("@nodefony/mongoose", { … })`, fusionnée en profondeur sous les défauts
439
- (`Kernel.loadModulesFromManifest()` (`Kernel.ts:1600`)).
439
+ (`Kernel.loadModulesFromManifest()` (`Kernel.ts:1656`)).
440
440
  3. **Un override venu d'un autre module** — la clé `module-mongoose` dans la config d'un module tiers.
441
441
  4. **`NF__MONGOOSE__…`** — l'override générique d'environnement.
442
442
  5. **La validation Zod** — types, bornes, défauts des champs restés absents.
@@ -550,8 +550,8 @@ use("@nodefony/mongoose", {
550
550
 
551
551
  Les deux connexions s'ouvrent en série au démarrage, dans l'ordre de déclaration
552
552
  (`connectAll()` (`MongooseService.ts:81`)), et se ferment toutes à l'arrêt
553
- (`disconnectAll()` (`MongooseService.ts:181`)). Un service peut demander l'une ou l'autre par son nom
554
- (`getOrm()` (`MongooseService.ts:189`)), mais l'usage courant reste le registre d'ORM.
553
+ (`disconnectAll()` (`MongooseService.ts:195`)). Un service peut demander l'une ou l'autre par son nom
554
+ (`getOrm()` (`MongooseService.ts:203`)), mais l'usage courant reste le registre d'ORM.
555
555
 
556
556
  ## 🔐 Le secret de connexion
557
557
 
@@ -561,10 +561,10 @@ données d'un coup. La règle est donc sans nuance.
561
561
  - **Jamais dans le dépôt.** Ni dans `nodefony.config.ts`, ni dans un fichier d'exemple, ni « juste
562
562
  pour le développement ». Le secret arrive par `MONGODB_URI` ou `NF_DATABASE_URL`.
563
563
  - **Ni dans les journaux, ni dans Studio.** L'URI est systématiquement nettoyée de tout
564
- `utilisateur:motdepasse@` avant d'être affichée (`MongooseOrm.safeTarget()` (`MongooseOrm.ts:595`)),
564
+ `utilisateur:motdepasse@` avant d'être affichée (`MongooseOrm.safeTarget()` (`MongooseOrm.ts:688`)),
565
565
  y compris pour les URI multi-hôtes que l'analyseur d'URL standard ne sait pas découper. C'est cette
566
566
  cible nettoyée que voit le plan d'administration
567
- (`MongooseOrm.describeConnection()` (`MongooseOrm.ts:583`)) et le message de connexion au démarrage.
567
+ (`MongooseOrm.describeConnection()` (`MongooseOrm.ts:676`)) et le message de connexion au démarrage.
568
568
  - **Les identifiants passés par `options`** (`user`, `pass`) suivent la même règle : ils viennent de
569
569
  l'environnement, pas du fichier. Le framework rédige d'ailleurs la valeur de tout override
570
570
  d'environnement dont le chemin ressemble à un secret, avant de le journaliser
@@ -665,7 +665,7 @@ coûteux à diagnostiquer qu'un serveur qui refuse de démarrer.
665
665
 
666
666
  Le cas courant : la config est parfaite, mais Mongo n'est pas joignable — conteneur pas encore prêt,
667
667
  réseau coupé, identifiants périmés. Le comportement **dépend de l'environnement**, arbitré par la
668
- politique de boot du cœur (`Kernel.isBootErrorFatal()` (`Kernel.ts:2843`)) :
668
+ politique de boot du cœur (`Kernel.isBootErrorFatal()` (`Kernel.ts:3130`)) :
669
669
 
670
670
  | Environnement | Ce qui se passe |
671
671
  | ------------------- | -------------------------------------------------------------------------------------- |
@@ -688,7 +688,7 @@ tomber pour le relancer, et c'est le modèle cloud-native que le framework appli
688
688
 
689
689
  À l'arrêt, les connexions se ferment alors que des requêtes peuvent encore être en vol. Le stockage de
690
690
  session **dégrade gracieusement** plutôt que de lever une exception
691
- (`SessionStorage.#repo()` (`SessionStorage.ts:45`)) : une session non persistée le temps de l'arrêt
691
+ (`SessionStorage.#repo()` (`SessionStorage.ts:66`)) : une session non persistée le temps de l'arrêt
692
692
  vaut mieux qu'une erreur 500 et un rejet non capturé. À l'inverse, une entité absente sur un ORM
693
693
  **connecté** est une vraie erreur de configuration : celle-là est levée sans ménagement.
694
694
 
@@ -718,7 +718,7 @@ frontière du plan d'administration.
718
718
  | `NF_DATABASE_URL` est bien posée, Mongo n'est pas utilisé | L'URL est de famille SQL : elle appartient à `@nodefony/drizzle` | Une URL `mongodb://`/`mongodb+srv://`, ou passer par `MONGODB_URI` |
719
719
  | Le démarrage échoue sur le schéma de l'URL | Schéma inconnu (`mongo://`, faute de frappe) — refus délibéré, jamais de repli | Corriger le schéma : `mongodb://` ou `mongodb+srv://` |
720
720
  | `Transaction numbers are only allowed on a replica set…` | Serveur isolé : pas de transactions | Un replica set, même à un nœud (`?replicaSet=rs0&directConnection=true`) |
721
- | `ORM "nodefony" introuvable` au montage d'un store | `@nodefony/security` chargé **avant** `@nodefony/mongoose` | Placer le driver **avant** dans `modules` (`resolveConnectedOrm()` (`registerStores.ts:64`)) |
721
+ | `ORM "nodefony" introuvable` au montage d'un store | `@nodefony/security` chargé **avant** `@nodefony/mongoose` | Placer le driver **avant** dans `modules` (`resolveConnectedOrm()` (`registerStores.ts:92`)) |
722
722
  | Le store `mongoose` est introuvable pour les jetons | `frameworkEntities: false` — le module est en mode données seules | Repasser à `true`, ou choisir un backend qui porte la brique |
723
723
  | `session: { store: "mongoose" }` marche malgré `frameworkEntities: false` | Attendu : la session s'enregistre à l'import, pas via ce champ | Rien à corriger — voir l'avertissement de la section `frameworkEntities` |
724
724
  | Une option de `options` n'a aucun effet | Elle n'est pas validée par Zod ; Mongoose l'a ignorée ou refusée | Vérifier son nom exact dans les `ConnectOptions` de la version de Mongoose installée |
package/docs/index.md CHANGED
@@ -192,11 +192,11 @@ des connexions, la compilation des schémas, et présente le tout derrière le c
192
192
 
193
193
  Nodefony ne demande pas à chaque backend de tout savoir faire. **Chaque adapter déclare ce qu'il
194
194
  implémente**, dans son `package.json` (clé `nodefony.stores`), et le framework lit cette déclaration
195
- à chaud (`readAdapterManifest()` (`KernelAdminApi.ts:84`)). Rien n'est curaté dans le cœur : la
195
+ à chaud (`readAdapterManifest()` (`KernelAdminApi.ts:93`)). Rien n'est curaté dans le cœur : la
196
196
  source de vérité, c'est l'adapter lui-même — ce qui vaut aussi pour un adapter tiers.
197
197
 
198
- Ce que `@nodefony/mongoose` déclare : `session`, `user`, `tokens`, `passkeys`, `webhooks`, avec la
199
- nature `durable`. Comparé aux deux autres adapters officiels :
198
+ Ce que `@nodefony/mongoose` déclare : les huit briques — `session`, `user`, `tokens`, `passkeys`,
199
+ `totp`, `audit`, `webhooks`, `idempotency` — avec la nature `durable`. Comparé aux deux autres adapters officiels :
200
200
 
201
201
  <!-- prettier-ignore -->
202
202
  | Brique | `@nodefony/drizzle` (SQL) | `@nodefony/mongoose` (Mongo) | `@nodefony/redis` (cache) |
@@ -206,24 +206,16 @@ nature `durable`. Comparé aux deux autres adapters officiels :
206
206
  | `tokens` | ✅ | ✅ | ✅ |
207
207
  | `passkeys` | ✅ | ✅ | ✅ |
208
208
  | `webhooks` | ✅ | ✅ | — |
209
- | `totp` | ✅ | — | — |
210
- | `audit` | ✅ | — | — |
211
- | `idempotency` | ✅ | — | ✅ |
209
+ | `totp` | ✅ | ✅ | — |
210
+ | `audit` | ✅ | ✅ | — |
211
+ | `idempotency` | ✅ | ✅ | ✅ |
212
212
  | Nature | durable | durable | cache |
213
213
 
214
214
  > [!IMPORTANT]
215
- > **Ces trois cases vides sont un manque, et il sera comblé.** L'objectif est qu'une application
216
- > puisse tourner **entièrement sur MongoDB, sans charger `@nodefony/drizzle`** — donc mongoose à 8/8
217
- > (`MIGRATION_STATUS.md`, P7.11). Le raisonnement « ces briques-là appellent d'autres propriétés »
218
- > décrit une préférence technique, pas ce que vit l'utilisateur : **tu choisis une base de données, tu
219
- > ne choisis pas de perdre le 2FA, la traçabilité ou la déduplication.**
220
- >
221
- > En attendant, ces trois briques se résolvent ailleurs **et te le disent** (repli annoncé au boot,
222
- > avertissement en production) — mais mesure ce que le repli coûte : secrets TOTP perdus au
223
- > redémarrage (utilisateurs verrouillés hors de leur second facteur), journal d'audit volatil,
224
- > idempotence sans effet entre pods. La parade immédiate tient en une ligne : charger
225
- > `@nodefony/drizzle` à côté de Mongo, **même en SQLite local** — les deux modules cohabitent, chaque
226
- > brique choisit son store.
215
+ > **Une application tourne entièrement sur MongoDB, sans charger `@nodefony/drizzle`.** Les huit
216
+ > briques y ont leur store, et ce n'est pas qu'une déclaration : `npm run test:all -- --mongo`
217
+ > démarre l'application sur MongoDB et rejoue toute la passe d'intégration, et chaque store joue le
218
+ > même banc de contrat que son jumeau SQL — cf [Parité des adaptateurs](../../../../../docs/guides/parite-des-adaptateurs.md).
227
219
  >
228
220
  > La colonne `redis` obéit à une autre logique : elle gagnera `totp` (au régime opt-in de ses jetons
229
221
  > et passkeys, jamais choisi par `auto`), mais pas `user`, `audit` ni `webhooks` — non parce que
@@ -233,7 +225,7 @@ nature `durable`. Comparé aux deux autres adapters officiels :
233
225
  ### Ce qui se passe quand tu ne choisis rien
234
226
 
235
227
  Chaque brique a une clé `store` dont le défaut est `"auto"`. La résolution
236
- (`resolveAutoStore()` (`infra.ts:241`)) suit l'infrastructure **déclarée**, bornée aux backends
228
+ (`resolveAutoStore()` (`infra.ts:297`)) suit l'infrastructure **déclarée**, bornée aux backends
237
229
  réellement chargés :
238
230
 
239
231
  | Ta situation | Ce que `auto` choisit |
@@ -323,6 +315,9 @@ const ArticleEntity = defineEntity({
323
315
  module: "blog",
324
316
  schema: articleSchema,
325
317
  timestamps: true, // Mongoose gère createdAt / updatedAt
318
+ // Index COMPOSITE : un schéma plat n'en exprime que par champ. Sans lui, un
319
+ // tri sur deux champs parcourt la collection puis trie en mémoire.
320
+ indexes: [{ fields: { views: -1, createdAt: -1 } }], // « les plus lus, récents d'abord »
326
321
  });
327
322
 
328
323
  // ── 2. Le controller : le repository se demande au registre ─────────────────
@@ -362,6 +357,23 @@ class Blog extends Module {
362
357
  export default Blog;
363
358
  ```
364
359
 
360
+ ### Le raccourci — `nodefony create entity`
361
+
362
+ Dans une application MongoDB (`create app --database mongodb`), tout ce qui précède se génère,
363
+ avec la même grammaire de champs qu'en SQL :
364
+
365
+ ```bash
366
+ npx nodefony create entity Post title:string(120) body:text? views:int=0 author:ref:User
367
+ ```
368
+
369
+ La commande écrit le schéma Mongoose (`nodefony/entity/Post.ts`), les schémas de validation, un
370
+ service CRUD, un controller REST + WebSocket et les tests, puis câble `@entities([...])` et
371
+ `@controllers([...])`. L'entité se pose sur le connecteur `nodefony` ; une référence est un
372
+ `ObjectId` indexé, que le schéma d'entrée exige bien formé (24 caractères hexadécimaux, sinon 422).
373
+ Les options propres au SQL (`--table`, `--column-case`, `--id-name`, `--dialect`, `--id`,
374
+ `--index`, `--unique`) sont refusées en le disant ; l'entité `User` ne se régénère pas, elle
375
+ s'étend dans `nodefony/entity/User.ts`.
376
+
365
377
  ### 3. Ce qu'on observe
366
378
 
367
379
  ```bash
@@ -387,7 +399,7 @@ INFO mongoose Mongoose ORM "nodefony" connected (127.0.0.1:27017/blog)
387
399
 
388
400
  Une fois le driver chargé, les stores Mongo deviennent **sélectionnables par leur nom**, sans aucun
389
401
  câblage : le module les enregistre lui-même à son démarrage
390
- (`registerMongooseFrameworkStores()` (`registerStores.ts:94`)).
402
+ (`registerMongooseFrameworkStores()` (`registerStores.ts:126`)).
391
403
 
392
404
  ```typescript
393
405
  // nodefony.config.ts — sessions, jetons, passkeys et webhooks dans Mongo
@@ -472,7 +484,7 @@ sequenceDiagram
472
484
 
473
485
  L'ordre n'est pas cosmétique. Les entités sont déclarées à `onKernelRegister`, **strictement avant**
474
486
  le `connect` de `onBoot` : les modèles sont compilés à la connexion
475
- (`MongooseOrm.onConnect()` (`MongooseOrm.ts:74`)), donc une entité déclarée trop tard n'existe tout
487
+ (`MongooseOrm.onConnect()` (`MongooseOrm.ts:137`)), donc une entité déclarée trop tard n'existe tout
476
488
  simplement pas. C'est aussi pour ça que `@entities` s'exécute à la phase `onRegister` et non `onBoot`.
477
489
 
478
490
  Chaque connecteur ouvre une **connexion isolée** (`mongoose.createConnection`), pas le singleton
@@ -480,8 +492,8 @@ global de Mongoose : c'est ce qui permet à plusieurs bases — voire plusieurs
480
492
  le même processus.
481
493
 
482
494
  Le service orchestre ce cycle de bout en bout : il ouvre une connexion par connecteur déclaré au
483
- démarrage (`MongooseService.connectAll()` (`MongooseService.ts:63`)) et referme tout à l'arrêt
484
- (`MongooseService.disconnectAll()` (`MongooseService.ts:178`)). Le module se déclare **non critique**
495
+ démarrage (`MongooseService.connectAll()` (`MongooseService.ts:89`)) et referme tout à l'arrêt
496
+ (`MongooseService.disconnectAll()` (`MongooseService.ts:195`)). Le module se déclare **non critique**
485
497
  (`Mongoose.critical` (`mongoose/index.ts:48`)) : une base injoignable ne tue pas le processus —
486
498
  l'application monte quand même, l'échec est journalisé, et c'est l'orchestrateur qui relèvera Mongo.
487
499
 
@@ -490,15 +502,15 @@ l'application monte quand même, l'échec est journalisé, et c'est l'orchestrat
490
502
  > indexées par `(connecteur, nom)` dans un registre **global au processus**. Si Drizzle (dont le
491
503
  > connecteur par défaut est `default`) et Mongoose tournaient ensemble avec le même nom, leurs deux
492
504
  > entités `session` entreraient en collision. Un nom distinct par driver règle le problème par
493
- > construction (`FRAMEWORK_CONNECTOR` (`registerStores.ts:49`)).
505
+ > construction (`FRAMEWORK_CONNECTOR` (`registerStores.ts:77`)).
494
506
 
495
507
  ### Le trajet d'une requête
496
508
 
497
509
  `repo.find({ views: { $gte: 10 } }, { limit: 20 })` traverse quatre étapes :
498
510
 
499
- 1. **Traduction du critère** (`MongooseRepository.#filter()` (`MongooseRepository.ts:184`)) : chaque
511
+ 1. **Traduction du critère** (`MongooseRepository.#filter()` (`MongooseRepository.ts:244`)) : chaque
500
512
  champ est résolu (`id` devient `_id`), chaque opérateur portable est converti.
501
- 2. **Validation du champ** (`MongooseRepository.#resolveField()` (`MongooseRepository.ts:162`)) : un
513
+ 2. **Validation du champ** (`MongooseRepository.#resolveField()` (`MongooseRepository.ts:222`)) : un
502
514
  champ absent du schéma lève `UnknownCriteriaField` — plutôt que de renvoyer zéro résultat sans
503
515
  rien dire, ce qui est la pire façon d'échouer.
504
516
  3. **Exécution** : la requête Mongoose est construite (session transactionnelle, `populate`, `skip`,
@@ -506,7 +518,7 @@ l'application monte quand même, l'échec est journalisé, et c'est l'orchestrat
506
518
  4. **Sérialisation** : chaque document sort en objet plat, **virtuels compris** — c'est là que `id`
507
519
  apparaît.
508
520
 
509
- Une sonde facultative encadre l'opération (`MongooseRepository.#prof()` (`MongooseRepository.ts:79`)) :
521
+ Une sonde facultative encadre l'opération (`MongooseRepository.#prof()` (`MongooseRepository.ts:118`)) :
510
522
  voir [Performance et mémoire](#-performance-et-mémoire).
511
523
 
512
524
  ## 🧰 Le repository portable — l'API que tu utilises vraiment
@@ -530,23 +542,24 @@ drivers. Les signatures exactes vivent dans le graphe généré
530
542
  | `withTransaction` | rejouer les mêmes opérations dans une transaction | ajoute la `session` à chaque opération |
531
543
 
532
544
  Les écritures qui « lisent puis écrivent » sont **atomiques par construction**
533
- (`MongooseRepository.upsert()` (`MongooseRepository.ts:343`),
534
- `MongooseRepository.increment()` (`MongooseRepository.ts:429`)) : un seul aller-retour, la
545
+ (`MongooseRepository.upsert()` (`MongooseRepository.ts:405`),
546
+ `MongooseRepository.increment()` (`MongooseRepository.ts:491`)) : un seul aller-retour, la
535
547
  comparaison est faite par le serveur. Ce n'est pas une optimisation cosmétique — c'est ce qui évite
536
548
  que deux requêtes simultanées lisent le même état et s'écrasent mutuellement.
537
549
 
538
550
  ### Les critères et leurs opérateurs
539
551
 
540
552
  Les opérateurs portables sont ceux d'[`orm-core`](../../orm-core/docs/index.md), et la plupart sont
541
- natifs en Mongo. Deux méritent une explication (`MongooseRepository.#mongoOps()` (`MongooseRepository.ts:127`)) :
553
+ natifs en Mongo. Deux méritent une explication (`MongooseRepository.#mongoOps()` (`MongooseRepository.ts:173`)) :
542
554
 
543
- | Opérateur portable | Côté Mongo | Remarque |
544
- | --------------------------- | ------------------------- | ------------------------------------------------------------------- |
545
- | `$eq $ne $gt $gte $lt $lte` | identiques | natifs |
546
- | `$in` / `$nin` | identiques | natifs |
547
- | `$like: "ab%"` | `$regex` **ancrée** | motif SQL traduit (`sqlLikeToRegex()` (`MongooseRepository.ts:25`)) |
548
- | `$null: true` / `false` | `$eq: null` / `$ne: null` | en Mongo, `null` couvre aussi le champ **absent** |
549
- | `$max` / `$min` (écriture) | `$max` / `$min` natifs | l'équivalent du `GREATEST(col, ?)` SQL |
555
+ | Opérateur portable | Côté Mongo | Remarque |
556
+ | -------------------------- | ------------------------- | ------------------------------------------------------------------- |
557
+ | `$eq $gt $gte $lt $lte` | identiques | natifs |
558
+ | `$ne` / `$nin` | `$nin` + `null` | comme en SQL, jamais un champ absent (un `$nin` vide reste neutre) |
559
+ | `$in` | identique | natif |
560
+ | `$like: "ab%"` | `$regex` **ancrée** | motif SQL traduit (`sqlLikeToRegex()` (`MongooseRepository.ts:25`)) |
561
+ | `$null: true` / `false` | `$eq: null` / `$ne: null` | en Mongo, `null` couvre aussi le champ **absent** |
562
+ | `$max` / `$min` (écriture) | `$max` / `$min` natifs | l'équivalent du `GREATEST(col, ?)` SQL |
550
563
 
551
564
  ```typescript
552
565
  await articles.find({ tags: "nodefony" }); // tableau : appartenance native
@@ -575,6 +588,36 @@ Le chargement se demande à la lecture : `find(criteria, { relations: ["comments
575
588
  `populate`. Le refus du `many-to-many` est volontaire : il n'a pas de traduction unique en Mongo
576
589
  (tableau de références ? collection de liaison ?), et un choix imposé serait un mauvais choix.
577
590
 
591
+ **L'adapter tient la politique d'effacement du SQL généré.** Au `connect()`, il relève tout
592
+ champ `ObjectId` qui porte un `ref` vers une entité de la même connexion, et chaque suppression
593
+ d'un parent (`delete`, `deleteOne`, `findOneAndDelete`) la consulte :
594
+
595
+ | La référence est… | Supprimer le parent… |
596
+ | ------------------------------ | -------------------------------------------------------------------------- |
597
+ | obligatoire (`required: true`) | est **refusé** : `ReferencedEntityError`, que le rendu HTTP traduit en 409 |
598
+ | facultative | passe, et la référence de l'enfant est **remise à `null`** |
599
+
600
+ Une entité que personne ne référence ne paie rien. Une **auto-référence** (`Category.parent`) ne
601
+ retient pas un parent dont l'enfant part dans le même appel : l'arbre entier se supprime d'un coup.
602
+ Une référence **sans index** est signalée au `connect()` (`WARNING`, champ nommé) : sans lui,
603
+ chaque suppression du parent parcourt toute la collection de l'enfant. Quatre limites, à connaître :
604
+
605
+ - **Hors transaction, contrôle et suppression sont deux opérations** : un enfant créé entre les
606
+ deux n'est pas vu. Le SQL, lui, est atomique ; dans `withTransaction`, les deux partagent la
607
+ session.
608
+ - **Un tableau de références** (`[{ type: ObjectId, ref }]`) n'est pas gardé : retirer l'élément
609
+ n'a pas d'équivalent dans `restrict` / `set null`.
610
+ - **La suppression douce** (`deletedAt`) est une mise à jour : elle ne déclenche rien — en SQL
611
+ non plus.
612
+ - **La connexion native** (`getNativeConnection()`) contourne la garde, comme toute requête brute.
613
+ Et une suppression en masse charge tous les `_id` visés : c'est ce qui permet à un seul parent
614
+ retenu de bloquer le lot entier.
615
+
616
+ > [!WARNING]
617
+ > L'**insertion** n'est pas gardée : un enfant qui désigne un parent inexistant est accepté, et
618
+ > `populate` rend alors `null` à sa place. Le contrat d'entrée vérifie la forme de l'identifiant,
619
+ > pas l'existence du parent.
620
+
578
621
  ### Transactions
579
622
 
580
623
  ```typescript
@@ -586,22 +629,23 @@ await orm.transaction(async (tx) => {
586
629
  }); // commit si la fonction résout, annulation si elle échoue
587
630
  ```
588
631
 
589
- `MongooseOrm.transaction()` (`MongooseOrm.ts:483`) s'appuie sur les sessions Mongo « managées »
632
+ `MongooseOrm.transaction()` (`MongooseOrm.ts:571`) s'appuie sur les sessions Mongo « managées »
590
633
  (commit, annulation et **reprises** gérées par le driver).
591
634
 
592
635
  > [!IMPORTANT]
593
636
  > **Les transactions exigent un replica set.** Un serveur MongoDB isolé (`mongod` seul, l'installation
594
637
  > par défaut) ne les supporte pas. En développement, démarre un replica set à un nœud ; en production,
595
638
  > Atlas et la plupart des services managés en fournissent un d'office. Les points de sauvegarde
596
- > intermédiaires (`savepoint`) n'existent pas en Mongo : ce sont des opérations neutres.
639
+ > intermédiaires n'existent pas en Mongo : `savepoint()` et `rollbackTo()` lèvent
640
+ > `SavepointNotSupportedError` — pour annuler, c'est la transaction entière qu'on abandonne.
597
641
 
598
642
  ### La trappe native — quand le contrat ne suffit plus
599
643
 
600
- `MongooseOrm.getNativeConnection()` (`MongooseOrm.ts:501`) rend la connexion Mongoose telle quelle :
644
+ `MongooseOrm.getNativeConnection()` (`MongooseOrm.ts:589`) rend la connexion Mongoose telle quelle :
601
645
  agrégations, `$or`, index, `$text`, `bulkWrite`, changements de flux. Le module lui-même s'en sert
602
646
  là où le contrat portable ne suffit pas — par exemple pour la recherche texte du listing des
603
647
  webhooks, qui a besoin d'un `$or` sur deux champs
604
- (`MongooseWebhookStore.#listFilter()` (`MongooseWebhookStore.ts:167`)).
648
+ (`MongooseWebhookStore.#listFilter()` (`MongooseWebhookStore.ts:195`)).
605
649
 
606
650
  C'est un **anti-blocage assumé** : le contrat portable couvre le quotidien, la trappe couvre le reste.
607
651
  Le code qui l'emprunte cesse d'être portable, et ça se voit — ce qui est exactement le but.
@@ -632,18 +676,18 @@ devient alors un pur driver de données, sans schéma framework.
632
676
 
633
677
  Trois comportements valent d'être connus :
634
678
 
635
- - **Purge à deux bornes** — `idleTimeoutS` et `absoluteTimeoutS` (`SessionStorage.gc()` (`SessionStorage.ts:156`)) :
679
+ - **Purge à deux bornes** — `idleTimeoutS` et `absoluteTimeoutS` (`SessionStorage.gc()` (`SessionStorage.ts:32`)) :
636
680
  l'inactivité (depuis la dernière activité) et l'âge absolu (depuis la création, **jamais prolongé** —
637
681
  la ré-authentification finit par être imposée, conformément aux recommandations NIST/OWASP).
638
- - **Prolongation sans réécriture** (`SessionStorage.touch()` (`SessionStorage.ts:174`)) : rafraîchir
682
+ - **Prolongation sans réécriture** (`SessionStorage.touch()` (`SessionStorage.ts:195`)) : rafraîchir
639
683
  l'activité ne réécrit pas le contenu de la session, juste son horodatage.
640
- - **Écran d'administration redacté par construction** (`SessionStorage.listPage()` (`SessionStorage.ts:277`)) :
684
+ - **Écran d'administration redacté par construction** (`SessionStorage.listPage()` (`SessionStorage.ts:286`)) :
641
685
  le contenu applicatif et les messages flash **ne sortent pas de la base**. Studio affiche qui est
642
686
  connecté, jamais ce qu'il y a dans sa session.
643
687
 
644
688
  Quand l'ORM n'est plus connecté — typiquement pendant l'arrêt du serveur, alors que des requêtes sont
645
689
  encore en vol — le store dégrade **gracieusement** au lieu de lever une exception
646
- (`SessionStorage.#repo()` (`SessionStorage.ts:45`)). Une session non persistée le temps de l'arrêt
690
+ (`SessionStorage.#repo()` (`SessionStorage.ts:66`)). Une session non persistée le temps de l'arrêt
647
691
  vaut mieux qu'une erreur 500 et un rejet non capturé.
648
692
 
649
693
  ### Utilisateurs
@@ -651,7 +695,7 @@ vaut mieux qu'une erreur 500 et un rejet non capturé.
651
695
  `MongooseUserRepository` rend des objets `BaseUser` (avec leur comportement : rôles, actif, verrouillé),
652
696
  pas des documents nus. Deux recherches lui sont propres :
653
697
 
654
- - **par compte social lié** (`MongooseUserRepository.findBySocialProvider()` (`MongooseUserRepository.ts:256`)) :
698
+ - **par compte social lié** (`MongooseUserRepository.findBySocialProvider()` (`MongooseUserRepository.ts:264`)) :
655
699
  un `$elemMatch` sur un tableau libre de fournisseurs — le pendant Mongo du parcours JSON en SQL.
656
700
  C'est ce qui porte le motif « Shadow User » d'OAuth (un compte créé à la volée au premier login social),
657
701
  **sans colonne par fournisseur** : ajouter GitHub demain n'est pas une migration.
@@ -667,25 +711,25 @@ compte côté serveur — c'est le garde-fou qui empêche de supprimer le dernie
667
711
  Trois collections : les jetons eux-mêmes, la denylist de `jti`, les seuils de révocation par porteur.
668
712
  Deux invariants de sécurité sont tenus **par la requête**, pas par du code JavaScript entre deux appels :
669
713
 
670
- - **Révocation idempotente** (`MongooseTokenStore.revoke()` (`MongooseTokenStore.ts:215`)) : la
714
+ - **Révocation idempotente** (`MongooseTokenStore.revoke()` (`MongooseTokenStore.ts:275`)) : la
671
715
  condition « pas encore révoqué » est dans le filtre. Deux révocations simultanées ne se recouvrent
672
716
  pas ; la première date et la première raison sont conservées.
673
- - **Seuil monotone** (`MongooseTokenStore.revokeAllForSubject()` (`MongooseTokenStore.ts:297`)) : le
717
+ - **Seuil monotone** (`MongooseTokenStore.revokeAllForSubject()` (`MongooseTokenStore.ts:334`)) : le
674
718
  « déconnecte-moi de partout » utilise `$max`. Avec une lecture suivie d'une écriture, deux
675
719
  déconnexions simultanées pourraient reposer un seuil **plus ancien** — et des jetons révoqués
676
720
  redeviendraient valides. Ici, c'est structurellement impossible.
677
721
 
678
- La purge, bornée par `retentionRevokedMs` (`MongooseTokenStore.gc()` (`MongooseTokenStore.ts:313`)), s'appuie sur une particularité utile
722
+ La purge, bornée par `retentionRevokedMs` (`MongooseTokenStore.gc()` (`MongooseTokenStore.ts:350`)), s'appuie sur une particularité utile
679
723
  de Mongo : une comparaison numérique **ignore** les documents dont le champ est `null`. Les jetons sans
680
724
  expiration ne sont donc jamais balayés par erreur ; ils partent par une règle de rétention distincte.
681
725
 
682
726
  ### Passkeys
683
727
 
684
728
  Une collection, l'identifiant du credential en clé primaire. L'enregistrement passe par un `upsert`
685
- atomique (`MongooseWebAuthnCredentialStore.save()` (`MongooseWebAuthnCredentialStore.ts:94`)) : deux
729
+ atomique (`MongooseWebAuthnCredentialStore.save()` (`MongooseWebAuthnCredentialStore.ts:109`)) : deux
686
730
  enregistrements concurrents de la même passkey ne peuvent pas produire de collision de clé.
687
731
 
688
- Le listing d'administration (`MongooseWebAuthnCredentialStore.listPage()` (`MongooseWebAuthnCredentialStore.ts:158`))
732
+ Le listing d'administration (`MongooseWebAuthnCredentialStore.listPage()` (`MongooseWebAuthnCredentialStore.ts:173`))
689
733
  projette **sans la clé publique** — elle ne franchit jamais la frontière du store. La recherche libre
690
734
  est un **préfixe ancré**, pas une expression régulière fournie par l'appelant : une recherche
691
735
  utilisateur n'est jamais interprétée comme du code.
@@ -693,12 +737,12 @@ utilisateur n'est jamais interprétée comme du code.
693
737
  ### Webhooks
694
738
 
695
739
  Registre **durable** des destinations à notifier, par opposition au store mémoire qui disparaît au
696
- redémarrage. Le listing paginé (`MongooseWebhookStore.listPage()` (`MongooseWebhookStore.ts:188`))
740
+ redémarrage. Le listing paginé (`MongooseWebhookStore.listPage()` (`MongooseWebhookStore.ts:227`))
697
741
  lit `limit + 1` documents pour savoir s'il existe une page suivante — sans second comptage — et
698
742
  échappe les métacaractères de la recherche texte.
699
743
 
700
744
  Détail révélateur de la doctrine du framework : si le store est construit sans modèle natif, le
701
- listing paginé **refuse** de répondre (`MongooseWebhookStore.#nativeModel()` (`MongooseWebhookStore.ts:151`))
745
+ listing paginé **refuse** de répondre (`MongooseWebhookStore.#nativeModel()` (`MongooseWebhookStore.ts:179`))
702
746
  au lieu de retomber sur un chargement complet de la collection. Une garantie silencieusement trahie
703
747
  serait pire qu'une erreur.
704
748
 
@@ -740,7 +784,7 @@ natif.
740
784
  **Les horodatages sont des nombres, pas des dates.** Les contrats du framework portent des `number`
741
785
  (millisecondes depuis l'époque) : les stocker tels quels garde la logique de purge **strictement
742
786
  identique** à celle de l'adapter SQL. Seule l'entité `User` utilise la gestion automatique de Mongoose
743
- (`createUserEntity()` (`userEntity.ts:90`)), parce que son contrat porte des dates.
787
+ (`createUserEntity()` (`userEntity.ts:104`)), parce que son contrat porte des dates.
744
788
 
745
789
  Le contrat expose partout `id: string`, jamais un `ObjectId` : le champ virtuel `id` est activé à la
746
790
  sérialisation, sur toutes les entités compilées par l'adapter.
@@ -757,7 +801,7 @@ heures plus tard.
757
801
  | `debug` | trace toutes les opérations Mongoose (développement) | `false` |
758
802
  | `frameworkEntities` | déclare le schéma framework et rend ses stores sélectionnables | `true` |
759
803
 
760
- Les valeurs font foi dans le schéma (`mongooseConfigSchema` (`config.ts:83`)) ; la surcharge par
804
+ Les valeurs font foi dans le schéma (`mongooseConfigSchema` (`config.ts:98`)) ; la surcharge par
761
805
  l'environnement est appliquée **après** la validation
762
806
  (`applyEnvOverrides()` (`defineModuleConfig.ts:22`)), ce qui garde le schéma pur et publiable en
763
807
  JSON Schema pour Studio.
@@ -785,15 +829,15 @@ Schema). Le module fournit les sondes correspondantes :
785
829
 
786
830
  | Sonde | Ce qu'elle renvoie |
787
831
  | ---------------------- | --------------------------------------------------------------------------- |
788
- | `ping()` | un aller-retour réel vers la base (`MongooseOrm.ts:515`) |
789
- | `probe()` | les connexions du serveur et sa version (`MongooseOrm.ts:529`) |
790
- | `describeEntity()` | les champs d'une entité, depuis le schéma compilé (`MongooseOrm.ts:558`) |
791
- | `describeConnection()` | le pilote, la cible et la version de la bibliothèque (`MongooseOrm.ts:583`) |
832
+ | `ping()` | un aller-retour réel vers la base (`MongooseOrm.ts:603`) |
833
+ | `probe()` | les connexions du serveur et sa version (`MongooseOrm.ts:617`) |
834
+ | `describeEntity()` | les champs d'une entité, depuis le schéma compilé (`MongooseOrm.ts:649`) |
835
+ | `describeConnection()` | le pilote, la cible et la version de la bibliothèque (`MongooseOrm.ts:676`) |
792
836
 
793
837
  > [!IMPORTANT]
794
838
  > **Aucun identifiant ne sort jamais.** La cible affichée est nettoyée de tout `utilisateur:mot de
795
839
  passe@` avant d'atteindre le plan d'administration ou les journaux
796
- > (`MongooseOrm.safeTarget()` (`MongooseOrm.ts:432`)), y compris pour les URI multi-hôtes que
840
+ > (`MongooseOrm.safeTarget()` (`MongooseOrm.ts:688`)), y compris pour les URI multi-hôtes que
797
841
  > l'analyseur d'URL standard ne sait pas découper.
798
842
 
799
843
  ## ⚡ Performance et mémoire
@@ -803,10 +847,10 @@ Le module suit la règle de fond du framework : **ce qui n'est pas observé ne c
803
847
  - **Instrumentation à coût nul hors observation.** Chaque opération peut alimenter deux sondes (le
804
848
  profil par requête de la barre de debug, et le flux agrégé). Les deux drapeaux sont lus **avant**
805
849
  toute allocation, et la description de la requête n'est construite que si l'on regarde
806
- (`MongooseRepository.#prof()` (`MongooseRepository.ts:79`)). En production, le chemin est celui d'un
850
+ (`MongooseRepository.#prof()` (`MongooseRepository.ts:118`)). En production, le chemin est celui d'un
807
851
  appel direct.
808
852
  - **Repositories alloués à la demande.** Le cache est créé au premier accès, pas à la connexion
809
- (`MongooseOrm.getRepository()` (`MongooseOrm.ts:465`)).
853
+ (`MongooseOrm.getRepository()` (`MongooseOrm.ts:548`)).
810
854
  - **Un aller-retour par écriture.** Les opérations « lire puis écrire » sont exprimées en une seule
811
855
  requête atomique — moins de latence _et_ pas de course.
812
856
  - **Le comptage reste côté serveur.** Les listings paginés lisent `limit + 1` documents pour savoir
@@ -827,7 +871,7 @@ Le module suit la règle de fond du framework : **ce qui n'est pas observé ne c
827
871
  | `many-to-many non portable` | Refus explicite : pas de traduction unique en Mongo | Déclarer la relation via la connexion native |
828
872
  | Les sessions disparaissent au redémarrage | `session.store` resté sur `memory` | `session: { store: "mongoose" }`, ou déclarer `NF_DATABASE_URL` et laisser `auto` |
829
873
  | `audit store "mongoose" inconnu` — boot avorté en production | Brique **non portée** par Mongo, sélectionnée explicitement | Laisser `auto` (repli annoncé) ou choisir un backend qui la porte |
830
- | Les comptes ne survivent pas au redémarrage | `provisionUsers` toujours branché sur l'annuaire mémoire | Câbler `MongooseUserRepository.from(orm)` (`MongooseUserRepository.ts:74`) |
874
+ | Les comptes ne survivent pas au redémarrage | `provisionUsers` toujours branché sur l'annuaire mémoire | Câbler `MongooseUserRepository.from(orm)` (`MongooseUserRepository.ts:89`) |
831
875
  | Un champ écrit se relit `undefined`, sans aucune erreur | Il n'est pas dans le **schéma** : Mongoose est strict et l'écarte en silence | L'ajouter au schéma de l'entité — le type TypeScript seul ne suffit pas |
832
876
  | Le premier `npm test` du module met une éternité | Le serveur Mongo de test télécharge son binaire (une seule fois) | Définir `NF_MONGO_TEST_URI` sur un conteneur Mongo |
833
877