@nodefony/mongoose 10.0.0-alpha.8 → 10.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) 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 +13 -7
  5. package/dist/nodefony/config/defineModuleConfig.js +4 -3
  6. package/dist/nodefony/entity/auditEventEntity.js +124 -0
  7. package/dist/nodefony/entity/idempotencyEntity.js +96 -0
  8. package/dist/nodefony/entity/sessionEntity.js +1 -1
  9. package/dist/nodefony/entity/totpSecretEntity.js +103 -0
  10. package/dist/nodefony/registerStores.js +42 -5
  11. package/dist/nodefony/service/MongooseService.js +15 -5
  12. package/dist/nodefony/src/MongooseAuditStore.js +215 -0
  13. package/dist/nodefony/src/MongooseIdempotencyStore.js +264 -0
  14. package/dist/nodefony/src/MongooseTokenStore.js +60 -17
  15. package/dist/nodefony/src/MongooseTotpSecretStore.js +151 -0
  16. package/dist/nodefony/src/MongooseUserRepository.js +1 -1
  17. package/dist/nodefony/src/MongooseWebAuthnCredentialStore.js +13 -1
  18. package/dist/nodefony/src/MongooseWebhookStore.js +14 -2
  19. package/dist/nodefony/src/SessionStorage.js +8 -0
  20. package/dist/nodefony/src/orm-core/MongooseOrm.js +68 -22
  21. package/dist/nodefony/src/orm-core/MongooseRepository.js +147 -25
  22. package/dist/nodefony/src/orm-core/MongooseTransaction.js +19 -6
  23. package/dist/nodefony/src/writeCount.js +17 -0
  24. package/dist/types/index.d.ts +9 -0
  25. package/dist/types/nodefony/entity/auditEventEntity.d.ts +76 -0
  26. package/dist/types/nodefony/entity/idempotencyEntity.d.ts +75 -0
  27. package/dist/types/nodefony/entity/totpSecretEntity.d.ts +75 -0
  28. package/dist/types/nodefony/registerStores.d.ts +6 -3
  29. package/dist/types/nodefony/service/MongooseService.d.ts +2 -2
  30. package/dist/types/nodefony/src/MongooseAuditStore.d.ts +70 -0
  31. package/dist/types/nodefony/src/MongooseIdempotencyStore.d.ts +121 -0
  32. package/dist/types/nodefony/src/MongooseTokenStore.d.ts +8 -1
  33. package/dist/types/nodefony/src/MongooseTotpSecretStore.d.ts +63 -0
  34. package/dist/types/nodefony/src/MongooseUserRepository.d.ts +1 -1
  35. package/dist/types/nodefony/src/MongooseWebAuthnCredentialStore.d.ts +6 -0
  36. package/dist/types/nodefony/src/MongooseWebhookStore.d.ts +6 -0
  37. package/dist/types/nodefony/src/SessionStorage.d.ts +6 -0
  38. package/dist/types/nodefony/src/orm-core/MongooseOrm.d.ts +10 -3
  39. package/dist/types/nodefony/src/orm-core/MongooseRepository.d.ts +22 -1
  40. package/dist/types/nodefony/src/orm-core/MongooseTransaction.d.ts +12 -4
  41. package/dist/types/nodefony/src/writeCount.d.ts +12 -0
  42. package/docs/configuration.md +27 -27
  43. package/docs/index.md +133 -82
  44. package/package.json +23 -19
@@ -0,0 +1,70 @@
1
+ import type { IPage } from "nodefony";
2
+ import type { IAuditEvent, IAuditListQuery, IAuditStore } from "@nodefony/security";
3
+ import type { Model } from "mongoose";
4
+ import type { MongooseOrm } from "./orm-core/index.js";
5
+ /** Modèle Mongoose à document libre (boundary — comme `MongooseRepository`). */
6
+ type LooseModel = Model<Record<string, unknown>>;
7
+ /**
8
+ * Journal d'audit **Mongoose** (NoSQL) — implémentation documentaire d'
9
+ * {@link IAuditStore} (append-only, tamper-evident) pour la **rétention longue**
10
+ * et le **partage cross-pod**, là où le store mémoire par défaut reste affine à
11
+ * un pod et volatile. Pendant documentaire de `DrizzleAuditStore`.
12
+ *
13
+ * **Append-only** : `append` est la seule écriture ; aucune mutation ni
14
+ * suppression ciblée d'un événement — seul {@link MongooseAuditStore.gc}
15
+ * (rétention) retire des documents. L'immuabilité EST la garantie d'audit.
16
+ *
17
+ * **Pagination curseur EXACTE (`listPage`)** — l'ordre total est `(ts DESC, _id
18
+ * DESC)` : `ts` porte l'ordre chronologique, `_id` (= l'id d'événement) casse
19
+ * les collisions à la milliseconde (rafales de login). Le curseur transporte
20
+ * **les deux** (`<ts>:<id>`) et se compare en **composite** — en Mongo, un `$or`
21
+ * de deux clauses, qui sort du `Criteria` AND-only d'`IRepository`, d'où la
22
+ * query native (même trappe que le frère SQL). Une ligne de garde (`limit + 1`)
23
+ * détermine `hasNext` sans page vide parasite.
24
+ *
25
+ * ⚠️ **Le tri se fait sur `_id`, jamais sur un champ `id`** : au repos un
26
+ * document Mongo n'a PAS de champ `id` (c'est un virtuel de lecture), et Mongo
27
+ * ne se plaint pas d'un tri sur un champ absent — il rend un ordre arbitraire.
28
+ * Un ordre total qui n'ordonne rien casserait le curseur en silence.
29
+ *
30
+ * **Résolution LAZY + dégradation gracieuse** (calqué sur le frère SQL) : le
31
+ * modèle est résolu à CHAQUE appel, pas capturé à la construction — l'ORM se
32
+ * déconnecte au shutdown avant le drain des serveurs, et un `connect()` rejoué
33
+ * recrée la connexion. Si l'ORM n'est pas connecté : `append` est un no-op
34
+ * **best-effort** (l'audit ne bloque ni ne fait échouer le flux métier — un
35
+ * événement au shutdown est perdu plutôt que de crasher un login), `listPage`
36
+ * rend une page vide et `gc` rend 0.
37
+ *
38
+ * Horloge injectable (`now`) pour des tests déterministes.
39
+ */
40
+ export declare class MongooseAuditStore implements IAuditStore {
41
+ #private;
42
+ /**
43
+ * @param resolveModel - résolveur **lazy** du modèle Mongoose (`null` = ORM non
44
+ * connecté → dégradation gracieuse).
45
+ * @param now - horloge (epoch ms) injectable pour des tests déterministes.
46
+ * @param retentionMs - fenêtre de rétention (ms) avant purge par `gc`.
47
+ */
48
+ constructor(resolveModel: () => LooseModel | null, now?: () => number, retentionMs?: number);
49
+ /**
50
+ * Connecteur ORM qui porte ce store, lu par `readStoreConnector` pour le
51
+ * registre des stores : la console rattache la brique à SON connecteur au
52
+ * lieu de le déduire. `undefined` pour un store construit sans ORM (bancs).
53
+ */
54
+ get connector(): string | undefined;
55
+ /**
56
+ * Construit le store depuis un {@link MongooseOrm}. Le modèle est résolu
57
+ * **lazy** (gardé par `isConnected()` → `null` tant que l'ORM n'est pas/plus
58
+ * connecté). L'entité (`registerAuditEntities`) doit avoir été enregistrée
59
+ * **avant** `orm.connect()` (le modèle est compilé au connect).
60
+ *
61
+ * @param orm - ORM Mongoose hébergeant la collection `audit_event`.
62
+ * @param now - horloge injectable (tests).
63
+ * @param retentionMs - fenêtre de rétention (ms).
64
+ */
65
+ static from(orm: MongooseOrm, now?: () => number, retentionMs?: number): MongooseAuditStore;
66
+ append(event: IAuditEvent): Promise<void>;
67
+ listPage(query: IAuditListQuery): Promise<IPage<IAuditEvent>>;
68
+ gc(now?: number): Promise<number>;
69
+ }
70
+ export {};
@@ -0,0 +1,121 @@
1
+ import type { IIdempotencyKeyEntry, IIdempotencyListQuery, IIdempotencyStore, IdempotencyOutcome, IdempotentResponse, IPage } from "nodefony";
2
+ import type { Model } from "mongoose";
3
+ import type { MongooseOrm } from "./orm-core/index.js";
4
+ /** Modèle Mongoose à document libre (boundary — comme `MongooseRepository`). */
5
+ type LooseModel = Model<Record<string, unknown>>;
6
+ /**
7
+ * Store d'idempotence **Mongoose** (NoSQL) — implémentation documentaire d'
8
+ * {@link IIdempotencyStore} (contrat au CORE) pour dédoublonner les mutations
9
+ * rejouées PARTAGÉ cross-pod, là où le store mémoire par défaut reste affine à
10
+ * un pod. Pendant documentaire de `DrizzleIdempotencyStore`.
11
+ *
12
+ * **Pourquoi MongoDB plutôt que Redis** : un cluster qui possède déjà une base
13
+ * Mongo mais pas de Redis obtient la dédup cross-pod sans nouvelle infra — le
14
+ * même argument qui fonde la variante SQL.
15
+ *
16
+ * **Réservation atomique (`begin`)** — clé de voûte. Un `findOneAndUpdate`
17
+ * filtré sur l'entrée MORTE, en `upsert`, est l'équivalent Mongo du `SET … NX
18
+ * PX` Redis et du `INSERT … ON CONFLICT … WHERE expiré` SQL, en **une seule
19
+ * instruction atomique** côté serveur :
20
+ * - clé absente → le filtre ne matche rien → l'`upsert` insère → `fresh` ;
21
+ * - clé présente mais **morte** (bail/rétention expirés) → le filtre matche →
22
+ * l'update la **vole** atomiquement → `fresh` ;
23
+ * - clé présente et **vivante** → le filtre ne matche pas → l'`upsert` tente
24
+ * d'insérer → violation d'unicité sur `_id` (`E11000`) → on lit l'état
25
+ * (`in-flight` / `replayed` / `mismatch`).
26
+ *
27
+ * C'est la **clé primaire** qui porte cette unicité, jamais un index secondaire :
28
+ * Mongoose construit ses index en tâche de fond, donc la contrainte n'existerait
29
+ * pas pendant la fenêtre de construction, et deux `begin` concurrents
30
+ * renverraient tous deux `fresh` — exactement le double-effet que ce store
31
+ * existe pour empêcher.
32
+ *
33
+ * Le store ne renvoie JAMAIS `fresh` sur le chemin de contention → anti
34
+ * double-effet garanti (l'invariant capital d'un store d'idempotence).
35
+ *
36
+ * **Pas de TTL natif** (≠ Redis `PX`, et un index TTL Mongo ignorerait la
37
+ * bascule bail → rétention) → un {@link MongooseIdempotencyStore.gc} applicatif
38
+ * purge les entrées expirées, comme la variante SQL.
39
+ *
40
+ * **Empreinte préservée à la complétion** : `complete()` ne touche pas le champ
41
+ * `fingerprint` → un rejeu de la clé avec un AUTRE payload après complétion est
42
+ * toujours détecté (`mismatch` 422, draft §2.7).
43
+ *
44
+ * **Résolution LAZY + dégradation gracieuse** (calqué sur le frère SQL) : le
45
+ * modèle est résolu à CHAQUE appel, jamais capturé à la construction — le
46
+ * framework fabrique ce store à `onKernelBoot`, AVANT le connect de l'ORM
47
+ * (`onBoot`), et l'ORM se déconnecte au shutdown avant le drain des serveurs. Si
48
+ * l'ORM n'est pas connecté, `begin` renvoie `fresh` (la mutation s'exécute SANS
49
+ * dédup) et `complete`/`abort`/`gc` sont des no-op — l'idempotence est
50
+ * temporairement inactive plutôt que de crasher une mutation en vol.
51
+ */
52
+ export declare class MongooseIdempotencyStore implements IIdempotencyStore {
53
+ #private;
54
+ /**
55
+ * @param resolveModel - résolveur **lazy** du modèle Mongoose (`null` = ORM non
56
+ * connecté → dégradation gracieuse). Lazy car l'ordre de boot/shutdown n'est
57
+ * pas garanti à la construction.
58
+ * @param now - horloge (epoch ms) injectable pour des tests déterministes.
59
+ * @param leaseMs - bail d'une entrée *in-flight* (ms).
60
+ * @param ttlMs - rétention d'une réponse mémorisée (ms).
61
+ * @param connector - connecteur ORM qui porte la collection, publié au registre des stores.
62
+ */
63
+ constructor(resolveModel: () => LooseModel | null, now?: () => number, leaseMs?: number, ttlMs?: number, connector?: string);
64
+ /**
65
+ * Connecteur ORM qui porte ce store, lu par `readStoreConnector` pour le
66
+ * registre des stores : la console rattache la brique à SON connecteur au
67
+ * lieu de le déduire. `undefined` pour un store construit sans ORM (bancs).
68
+ */
69
+ get connector(): string | undefined;
70
+ /**
71
+ * Construit le store depuis un {@link MongooseOrm}. Le modèle est résolu
72
+ * **lazy** (gardé par `isConnected()` → `null` tant que l'ORM n'est pas/plus
73
+ * connecté). L'entité (`registerIdempotencyEntities`) doit avoir été
74
+ * enregistrée **avant** `orm.connect()` (le modèle est compilé au connect).
75
+ *
76
+ * @param orm - ORM Mongoose hébergeant la collection `idempotency_key`.
77
+ * @param now - horloge injectable (tests).
78
+ * @param leaseMs - bail *in-flight* (ms).
79
+ * @param ttlMs - rétention d'une réponse mémorisée (ms).
80
+ */
81
+ static from(orm: MongooseOrm, now?: () => number, leaseMs?: number, ttlMs?: number): MongooseIdempotencyStore;
82
+ /**
83
+ * Approximation **per-pod, best-effort** : compteur local des réservations
84
+ * faites par CE pod (incrémenté au `fresh`, décrémenté au `complete`/`abort`),
85
+ * non décrémenté si le bail expire sans complétion, et désaligné cross-pod. La
86
+ * vérité cluster passe par un `countDocuments`, jamais ce getter (sync). Borné à ≥ 0.
87
+ */
88
+ get size(): number;
89
+ begin(key: string, fingerprint: string): Promise<IdempotencyOutcome>;
90
+ complete(key: string, response: IdempotentResponse): Promise<void>;
91
+ abort(key: string): Promise<void>;
92
+ /**
93
+ * Purge les entrées mortes (`expiresAt <= now`) — supplée l'absence de TTL
94
+ * exploitable ici (cf le schéma : l'échéance bascule du bail à la rétention).
95
+ * À déclencher périodiquement (le framework arme un `GcScheduler` au boot
96
+ * quand cette méthode existe).
97
+ *
98
+ * @param now - horloge de purge (défaut : horloge injectée).
99
+ * @returns le nombre d'entrées supprimées.
100
+ */
101
+ gc(now?: number): Promise<number>;
102
+ /**
103
+ * {@inheritDoc IIdempotencyStore.listPage}
104
+ *
105
+ * Query native projetée : on ne lit QUE les champs exposés — la réponse
106
+ * mémorisée (`response`) ne quitte jamais la base par ce chemin, quelle que
107
+ * soit la taille de la page (anti-IDOR sur le cache).
108
+ *
109
+ * Les entrées expirées sont exclues (`expiresAt > now`) : le GC applicatif
110
+ * passe plus tard, mais une clé échue n'est déjà plus opposable.
111
+ *
112
+ * ⚠️ **Le préfixe `q` est ÉCHAPPÉ**, donc littéral. Une chaîne saisie dans une
113
+ * console n'est pas une expression régulière : laisser passer les
114
+ * métacaractères ouvrirait un balayage de collection arbitraire — et pire, une
115
+ * catastrophe de retour sur trace. L'écart avec le frère SQL (où `%` et `_`
116
+ * restent des jokers) est assumé dans ce sens : ici le contrat — « `q` =
117
+ * préfixe » — est tenu au pied de la lettre.
118
+ */
119
+ listPage(query: IIdempotencyListQuery): Promise<IPage<IIdempotencyKeyEntry>>;
120
+ }
121
+ export {};
@@ -37,6 +37,12 @@ export declare class MongooseTokenStore implements ITokenStore {
37
37
  * @param retentionRevokedMs - rétention d'un PAT révoqué sans `exp` avant purge.
38
38
  */
39
39
  constructor(records: IRepository<IAccessTokenRecord>, denied: IRepository<DeniedJtiRow>, revocations: IRepository<SubjectRevocationRow>, now?: () => number, retentionRevokedMs?: number);
40
+ /**
41
+ * Connecteur ORM qui porte ce store, lu par `readStoreConnector` pour le
42
+ * registre des stores : la console rattache la brique à SON connecteur au
43
+ * lieu de le déduire. `undefined` pour un store construit sans ORM (bancs).
44
+ */
45
+ get connector(): string | undefined;
40
46
  /**
41
47
  * Construit le store depuis un {@link MongooseOrm} connecté. Les entités
42
48
  * (`registerTokenEntities`) doivent avoir été enregistrées **avant** `connect()`.
@@ -69,7 +75,8 @@ export declare class MongooseTokenStore implements ITokenStore {
69
75
  * {@inheritDoc ITokenStore.listPage}
70
76
  *
71
77
  * `paginate()` d'orm-core (skip/limit + countDocuments) sur un filtre portable ;
72
- * les `id` sont re-normalisés (`_id` → `id`) comme dans {@link listAll}.
78
+ * les documents sont convertis en records (`_id` → `id`, sans `__v`) comme dans
79
+ * {@link listAll}.
73
80
  *
74
81
  * Le tri demandé est **traduit** avant de descendre : au repos, le jeton n'a
75
82
  * pas de champ `id` (le `jti` EST le `_id`), et Mongo ne se plaint pas d'un tri
@@ -0,0 +1,63 @@
1
+ import { type IRepository } from "@nodefony/orm-core";
2
+ import type { IPage } from "nodefony";
3
+ import type { ITotpEnrollmentSummary, ITotpListQuery, ITotpSecret, ITotpSecretStore, TotpSecretUpdate } from "@nodefony/security";
4
+ import type { MongooseOrm } from "./orm-core/index.js";
5
+ import { type TotpSecretRow } from "../entity/totpSecretEntity.js";
6
+ /**
7
+ * Store de secrets TOTP **Mongoose** (NoSQL) — implémentation documentaire d'
8
+ * {@link ITotpSecretStore} au-dessus d'un unique repository `@nodefony/orm-core`
9
+ * (`totp_secret`). Pendant documentaire de `DrizzleTotpSecretStore` : là où
10
+ * `MemoryTotpSecretStore` est volatile, ce store survit au redémarrage et se
11
+ * partage entre pods.
12
+ *
13
+ * **Modèle 1 secret / utilisateur** (clé = `userId`, portée par `_id`) → `save`
14
+ * est un upsert par clé primaire.
15
+ *
16
+ * **Approche B** : `@nodefony/security` n'est connu qu'en `import type` (0 dép
17
+ * runtime). L'entité (`registerTotpSecretEntity`) doit être enregistrée
18
+ * **avant** `orm.connect()` (le modèle est compilé au connect).
19
+ *
20
+ * **100 % portable** (aucune query native) — toutes les opérations passent par
21
+ * le contrat `IRepository`, comme son frère SQL : l'`userId` est cherché sous
22
+ * `id` (le contrat traduit `{ id }` → `{ _id }`), et le listing par `paginate()`.
23
+ *
24
+ * **`secretEnc` opaque** : le store persiste le secret DÉJÀ chiffré (AES-256-GCM
25
+ * côté service) — il ne déchiffre jamais, ne voit que des octets. Il ne sort
26
+ * JAMAIS par `listPage` (cf {@link ITotpEnrollmentSummary}).
27
+ */
28
+ export declare class MongooseTotpSecretStore implements ITotpSecretStore {
29
+ #private;
30
+ /**
31
+ * @param repo - repository de la collection `totp_secret`.
32
+ */
33
+ constructor(repo: IRepository<TotpSecretRow>);
34
+ /**
35
+ * Connecteur ORM qui porte ce store, lu par `readStoreConnector` pour le
36
+ * registre des stores : la console rattache la brique à SON connecteur au
37
+ * lieu de le déduire. `undefined` pour un store construit sans ORM (bancs).
38
+ */
39
+ get connector(): string | undefined;
40
+ /**
41
+ * Construit le store depuis un {@link MongooseOrm} connecté. L'entité
42
+ * (`registerTotpSecretEntity`) doit avoir été enregistrée **avant**
43
+ * `orm.connect()`.
44
+ *
45
+ * @param orm - ORM Mongoose connecté hébergeant la collection du store.
46
+ */
47
+ static from(orm: MongooseOrm): MongooseTotpSecretStore;
48
+ findByUser(userId: string): Promise<ITotpSecret | null>;
49
+ save(secret: ITotpSecret): Promise<void>;
50
+ update(userId: string, patch: TotpSecretUpdate): Promise<void>;
51
+ delete(userId: string): Promise<void>;
52
+ /**
53
+ * {@inheritDoc ITotpSecretStore.listPage}
54
+ *
55
+ * 100 % portable : le helper `paginate()` d'orm-core (limite/décalage + total
56
+ * optionnel) sur un critère simple. La projection en vue d'enrôlement retire
57
+ * `secretEnc` et les condensats — ils ne franchissent jamais la frontière du
58
+ * store, quel que soit l'appelant.
59
+ */
60
+ listPage(query: ITotpListQuery): Promise<IPage<ITotpEnrollmentSummary>>;
61
+ /** {@inheritDoc ITotpSecretStore.countEnrollments} */
62
+ countEnrollments(query: ITotpListQuery): Promise<number>;
63
+ }
@@ -56,7 +56,7 @@ export declare class MongooseUserRepository implements IUserRepository {
56
56
  updateMany(criteria: Criteria<IPasswordAuthenticatedUser>, data: Partial<IPasswordAuthenticatedUser>): Promise<number>;
57
57
  delete(criteria: Criteria<IPasswordAuthenticatedUser>): Promise<number>;
58
58
  count(criteria?: Criteria<IPasswordAuthenticatedUser>): Promise<number>;
59
- countDistinct(field: keyof IPasswordAuthenticatedUser & string, criteria?: Criteria<IPasswordAuthenticatedUser>): Promise<number>;
59
+ countDistinct(field: keyof IPasswordAuthenticatedUser, criteria?: Criteria<IPasswordAuthenticatedUser>): Promise<number>;
60
60
  withTransaction(tx: ITransaction): IUserRepository;
61
61
  findByIdentifier(identifier: string): Promise<IPasswordAuthenticatedUser | null>;
62
62
  /**
@@ -24,6 +24,12 @@ export declare class MongooseWebAuthnCredentialStore implements IWebAuthnCredent
24
24
  #private;
25
25
  /** @param repo - repository de la collection `webauthn_credential`. */
26
26
  constructor(repo: IRepository<WebAuthnCredentialRow>);
27
+ /**
28
+ * Connecteur ORM qui porte ce store, lu par `readStoreConnector` pour le
29
+ * registre des stores : la console rattache la brique à SON connecteur au
30
+ * lieu de le déduire. `undefined` pour un store construit sans ORM (bancs).
31
+ */
32
+ get connector(): string | undefined;
27
33
  /**
28
34
  * Construit le store depuis un {@link MongooseOrm} connecté. L'entité
29
35
  * (`registerWebAuthnCredentialEntity`) doit avoir été enregistrée **avant**
@@ -40,6 +40,12 @@ export declare class MongooseWebhookStore implements IWebhookStore {
40
40
  * `null` = `listPage` refuse plutôt que de tout charger en silence.
41
41
  */
42
42
  constructor(repo: IRepository<WebhookEndpointRow>, model?: LooseModel | null);
43
+ /**
44
+ * Connecteur ORM qui porte ce store, lu par `readStoreConnector` pour le
45
+ * registre des stores : la console rattache la brique à SON connecteur au
46
+ * lieu de le déduire. `undefined` pour un store construit sans ORM (bancs).
47
+ */
48
+ get connector(): string | undefined;
43
49
  /**
44
50
  * Construit le store depuis un {@link MongooseOrm} connecté. L'entité
45
51
  * (`registerWebhookEndpointEntity`) doit avoir été enregistrée **avant**
@@ -21,6 +21,12 @@ declare class SessionStorage implements ISessionStorage {
21
21
  */
22
22
  readonly sortableFields: readonly ["updatedAt", "createdAt", "user", "id"];
23
23
  constructor(manager: SessionsService);
24
+ /**
25
+ * Connecteur ORM qui porte les sessions — lu par `readStoreConnector` pour le
26
+ * registre des stores. `undefined` tant que l'ORM de ce connecteur n'est pas
27
+ * enregistré : on ne publie pas un rattachement que rien ne porte.
28
+ */
29
+ get connector(): string | undefined;
24
30
  read(id: string): Promise<ISerializedSession>;
25
31
  start(id: string): Promise<ISerializedSession>;
26
32
  write(id: string, data: ISerializedSession): Promise<ISerializedSession>;
@@ -1,3 +1,4 @@
1
+ import type { Container } from "nodefony";
1
2
  import type { ConnectOptions } from "mongoose";
2
3
  import { Orm } from "@nodefony/orm-core";
3
4
  import type { IColumnInfo, IConnectionInfo, IOrmProbe, IRepository, ITransaction } from "@nodefony/orm-core";
@@ -51,8 +52,11 @@ export declare class MongooseOrm extends Orm {
51
52
  * @param name - clé unique de l'ORM dans le `ormRegistry`.
52
53
  * @param uri - URI de connexion MongoDB (replica set requis pour les tx).
53
54
  * @param options - options de connexion Mongoose (auth, pool, timeouts).
55
+ * @param container - container du service qui construit l'ORM : il porte le
56
+ * `syslog` du kernel. Sans lui, la perte et la reprise de connexion
57
+ * s'écrivent dans un journal que rien ne relie à la sortie du serveur.
54
58
  */
55
- constructor(name: string, uri: string, options?: ConnectOptions);
59
+ constructor(name: string, uri: string, options?: ConnectOptions, container?: Container);
56
60
  protected onConnect(): Promise<void>;
57
61
  /** Constat d'index de la connexion courante, ou `null` hors connexion. */
58
62
  get pendingIndexAudit(): Promise<IIndexAudit[]> | null;
@@ -109,8 +113,11 @@ export declare class MongooseOrm extends Orm {
109
113
  probe(): Promise<IOrmProbe>;
110
114
  /**
111
115
  * Colonnes normalisées d'une entité depuis les `paths` du schéma Mongoose —
112
- * alimente le graphe canonique / ERD / contexte IA. Pas de PK SQL : `_id` est
113
- * la clé primaire implicite de tout document.
116
+ * alimente le graphe canonique / ERD / contexte IA. Rendues dans le
117
+ * vocabulaire du CONTRAT, pas du moteur : la clé primaire `_id` s'y nomme
118
+ * `id` (comme dans les critères et les lignes lues), et la clé de version
119
+ * interne `__v` n'y figure pas — un graphe qui changerait de noms selon
120
+ * l'adaptateur ne décrirait plus la même application.
114
121
  *
115
122
  * @param name - nom logique de l'entité.
116
123
  * @returns colonnes (`[]` si l'entité n'est pas connue de cet ORM).
@@ -2,6 +2,25 @@ import type { ClientSession, Model } from "mongoose";
2
2
  import type { Criteria, UpdateData, IRepository, ITransaction, RepositoryReadOptions } from "@nodefony/orm-core";
3
3
  /** Modèle Mongoose à document libre (boundary — typé finement côté repo). */
4
4
  type LooseModel = Model<Record<string, unknown>>;
5
+ /**
6
+ * Champ d'une AUTRE entité qui référence celle d'un repository — une entrée de
7
+ * l'index inverse que {@link MongooseOrm} bâtit au `connect()`.
8
+ *
9
+ * MongoDB ne tient aucune clé étrangère : c'est cette table qui porte, à la
10
+ * suppression d'un parent, la politique que le SQL généré confie à la base.
11
+ */
12
+ export interface IMongooseReferrer {
13
+ /** Modèle de l'entité qui porte la référence. */
14
+ readonly model: LooseModel;
15
+ /** Chemin du champ `ObjectId` qui désigne le parent. */
16
+ readonly path: string;
17
+ /**
18
+ * Référence obligatoire → la suppression du parent est REFUSÉE (`restrict`) ;
19
+ * facultative → le champ est remis à `null` (`set null`). Même règle que le
20
+ * SQL généré, qui déduit l'effacement de la nullabilité de la colonne.
21
+ */
22
+ readonly required: boolean;
23
+ }
5
24
  /**
6
25
  * Repository portable (contrat {@link IRepository}) au-dessus d'un modèle Mongoose.
7
26
  *
@@ -21,8 +40,10 @@ export declare class MongooseRepository<T = unknown> implements IRepository<T> {
21
40
  * @param model - modèle Mongoose compilé.
22
41
  * @param connector - nom de la connexion (clé du registre) — défaut `"nodefony"`.
23
42
  * @param session - session transactionnelle à laquelle lier les ops (ou `null`).
43
+ * @param referrers - champs d'autres entités qui référencent celle-ci (index
44
+ * inverse de {@link MongooseOrm}) ; `null` quand aucune ne la vise.
24
45
  */
25
- constructor(model: LooseModel, connector?: string, session?: ClientSession | null);
46
+ constructor(model: LooseModel, connector?: string, session?: ClientSession | null, referrers?: readonly IMongooseReferrer[] | null);
26
47
  find(criteria?: Criteria<T>, options?: RepositoryReadOptions): Promise<T[]>;
27
48
  findOne(criteria: Criteria<T>, options?: RepositoryReadOptions): Promise<T | null>;
28
49
  create(data: Partial<T>): Promise<T>;
@@ -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;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Compte rendu par une écriture MongoDB (`deletedCount`, `modifiedCount`), 0 s'il manque.
3
+ *
4
+ * Le pilote le type `number`, mais le recopie de la réponse du serveur
5
+ * (`res.n`) : une écriture NON acquittée (`w: 0`) n'en reçoit aucune, et le
6
+ * compte arrive `undefined`. Sans ce repli, un `gc()` rendrait `undefined` et
7
+ * un `> 0` conclurait « rien écrit » pour une raison qui n'est pas la bonne.
8
+ *
9
+ * @param count - le compte tel que le pilote le rend.
10
+ * @returns le compte, ou 0 quand le serveur n'en a rendu aucun.
11
+ */
12
+ export declare function writeCount(count: number | undefined): number;