@nodefony/mongoose 10.0.0-alpha.8 → 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.
- package/README.md +32 -0
- package/dist/_virtual/{_@oxc-project_runtime@0.150.0 → _@oxc-project_runtime@0.151.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.150.0 → _@oxc-project_runtime@0.151.0}/helpers/esm/decorateMetadata.js +1 -1
- package/dist/index.js +10 -4
- package/dist/nodefony/entity/auditEventEntity.js +124 -0
- package/dist/nodefony/entity/idempotencyEntity.js +96 -0
- package/dist/nodefony/entity/sessionEntity.js +1 -1
- package/dist/nodefony/entity/totpSecretEntity.js +103 -0
- package/dist/nodefony/registerStores.js +41 -4
- package/dist/nodefony/service/MongooseService.js +6 -3
- package/dist/nodefony/src/MongooseAuditStore.js +213 -0
- package/dist/nodefony/src/MongooseIdempotencyStore.js +260 -0
- package/dist/nodefony/src/MongooseTokenStore.js +60 -17
- package/dist/nodefony/src/MongooseTotpSecretStore.js +151 -0
- package/dist/nodefony/src/MongooseWebAuthnCredentialStore.js +13 -1
- package/dist/nodefony/src/MongooseWebhookStore.js +14 -2
- package/dist/nodefony/src/SessionStorage.js +8 -0
- package/dist/nodefony/src/orm-core/MongooseOrm.js +62 -12
- package/dist/nodefony/src/orm-core/MongooseRepository.js +124 -14
- package/dist/nodefony/src/orm-core/MongooseTransaction.js +19 -6
- package/dist/types/index.d.ts +9 -0
- package/dist/types/nodefony/entity/auditEventEntity.d.ts +76 -0
- package/dist/types/nodefony/entity/idempotencyEntity.d.ts +75 -0
- package/dist/types/nodefony/entity/totpSecretEntity.d.ts +75 -0
- package/dist/types/nodefony/registerStores.d.ts +6 -3
- package/dist/types/nodefony/src/MongooseAuditStore.d.ts +70 -0
- package/dist/types/nodefony/src/MongooseIdempotencyStore.d.ts +121 -0
- package/dist/types/nodefony/src/MongooseTokenStore.d.ts +8 -1
- package/dist/types/nodefony/src/MongooseTotpSecretStore.d.ts +63 -0
- package/dist/types/nodefony/src/MongooseWebAuthnCredentialStore.d.ts +6 -0
- package/dist/types/nodefony/src/MongooseWebhookStore.d.ts +6 -0
- package/dist/types/nodefony/src/SessionStorage.d.ts +6 -0
- package/dist/types/nodefony/src/orm-core/MongooseOrm.d.ts +10 -3
- package/dist/types/nodefony/src/orm-core/MongooseRepository.d.ts +22 -1
- package/dist/types/nodefony/src/orm-core/MongooseTransaction.d.ts +12 -4
- package/docs/configuration.md +22 -22
- package/docs/index.md +107 -63
- package/package.json +21 -17
|
@@ -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}
|
|
12
|
-
*
|
|
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
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Refusé : MongoDB n'a pas de savepoints.
|
|
33
|
+
*
|
|
34
|
+
* @throws SavepointNotSupportedError toujours.
|
|
35
|
+
*/
|
|
32
36
|
savepoint(_name: string): Promise<void>;
|
|
33
|
-
/**
|
|
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;
|
package/docs/configuration.md
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
298
|
-
(`MongooseService.#connectOne()` (`MongooseService.ts:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
554
|
-
(`getOrm()` (`MongooseService.ts:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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`,
|
|
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
|
-
> **
|
|
216
|
-
>
|
|
217
|
-
>
|
|
218
|
-
>
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
484
|
-
(`MongooseService.disconnectAll()` (`MongooseService.ts:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
534
|
-
`MongooseRepository.increment()` (`MongooseRepository.ts:
|
|
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:
|
|
553
|
+
natifs en Mongo. Deux méritent une explication (`MongooseRepository.#mongoOps()` (`MongooseRepository.ts:173`)) :
|
|
542
554
|
|
|
543
|
-
| Opérateur portable
|
|
544
|
-
|
|
|
545
|
-
| `$eq $
|
|
546
|
-
| `$
|
|
547
|
-
| `$
|
|
548
|
-
| `$
|
|
549
|
-
| `$
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
789
|
-
| `probe()` | les connexions du serveur et sa version (`MongooseOrm.ts:
|
|
790
|
-
| `describeEntity()` | les champs d'une entité, depuis le schéma compilé (`MongooseOrm.ts:
|
|
791
|
-
| `describeConnection()` | le pilote, la cible et la version de la bibliothèque (`MongooseOrm.ts:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|