@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.
- 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 +22 -18
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { SchemaDefinition } from "mongoose";
|
|
2
|
+
import type { IEntity } from "@nodefony/orm-core";
|
|
3
|
+
import type { AuditCategory, AuditOutcome, IAuditEventFlags } from "@nodefony/security";
|
|
4
|
+
/**
|
|
5
|
+
* Schéma Mongoose du **journal d'audit** `@nodefony/security` — pendant
|
|
6
|
+
* documentaire de la table Drizzle `audit_event`, implémentation NoSQL d'
|
|
7
|
+
* `IAuditStore` (append-only, tamper-evident).
|
|
8
|
+
*
|
|
9
|
+
* ⚠️ **`_id` = clé naturelle (String), PAS un ObjectId auto** : l'`id` d'un
|
|
10
|
+
* événement est posé par l'`AuditService` (corrélation + curseur de pagination)
|
|
11
|
+
* → on force `_id: String`, l'id EST la clé primaire. Même convention que
|
|
12
|
+
* `webhookEndpointEntity` et `tokenEntity`.
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ **Horodatage = `Number` (epoch ms), `timestamps:false`** : `IAuditEvent.ts`
|
|
15
|
+
* porte un `number` (`Date.now()`), pas une `Date` — et c'est ce champ qui porte
|
|
16
|
+
* l'ordre du journal.
|
|
17
|
+
*
|
|
18
|
+
* **Index** — mêmes axes que la variante SQL, et pour la même raison (filtrage
|
|
19
|
+
* de la console d'audit) ; perf de lecture seule, jamais de sémantique.
|
|
20
|
+
*
|
|
21
|
+
* L'ORDRE TOTAL du store (`ts DESC, _id DESC`) est porté par un index
|
|
22
|
+
* COMPOSITE déclaré sur l'entité (`indexes`, cf `createAuditEntities`) : un
|
|
23
|
+
* `SchemaDefinition` plat n'exprime que des index par champ, et sans lui la
|
|
24
|
+
* requête du journal parcourait toute la collection puis triait en mémoire
|
|
25
|
+
* (plan `COLLSCAN` + `SORT`, mesuré). C'est lui qui garantit, au coût d'un
|
|
26
|
+
* parcours d'index, qu'aucun événement ne se répète ni ne se perd d'une page à
|
|
27
|
+
* l'autre — `audit-store.test.ts` en lit le plan d'exécution.
|
|
28
|
+
*/
|
|
29
|
+
export declare const auditEventSchema: SchemaDefinition;
|
|
30
|
+
/**
|
|
31
|
+
* Forme **plate** d'une ligne du journal renvoyée par le repository Mongoose —
|
|
32
|
+
* `id` = virtuel (= `_id` = l'id d'événement). Miroir d'`IAuditEvent` avec les
|
|
33
|
+
* champs optionnels rendus explicitement `| null` (le document Mongo les porte
|
|
34
|
+
* à `null`, jamais absents). `MongooseAuditStore` mappe `Row ↔ IAuditEvent`.
|
|
35
|
+
*/
|
|
36
|
+
export interface AuditEventRow {
|
|
37
|
+
id: string;
|
|
38
|
+
ts: number;
|
|
39
|
+
category: AuditCategory;
|
|
40
|
+
action: string;
|
|
41
|
+
outcome: AuditOutcome;
|
|
42
|
+
actor: string | null;
|
|
43
|
+
resource: string | null;
|
|
44
|
+
reason: string | null;
|
|
45
|
+
ip: string | null;
|
|
46
|
+
userAgent: string | null;
|
|
47
|
+
requestId: string | null;
|
|
48
|
+
flags: IAuditEventFlags | null;
|
|
49
|
+
metadata: Record<string, unknown> | null;
|
|
50
|
+
}
|
|
51
|
+
/** Noms logiques des entités du store (clés de lookup `getRepository`). */
|
|
52
|
+
export declare const AUDIT_ENTITY_NAMES: {
|
|
53
|
+
readonly events: "audit_event";
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Construit les descripteurs d'entités du journal d'audit pour un ORM nommé.
|
|
57
|
+
*
|
|
58
|
+
* Le `connector` est **dynamique** (nom du connecteur de l'app, ex. `"nodefony"`) :
|
|
59
|
+
* le schéma est statique mais sa liaison à un ORM dépend de la config → pas
|
|
60
|
+
* d'`@entity` figé. Pas de dialecte (NoSQL) : le schéma est portable par
|
|
61
|
+
* construction. À enregistrer **avant** `orm.connect()` (modèle compilé au connect).
|
|
62
|
+
*
|
|
63
|
+
* `module: "security"` → la collection est regroupée sous @nodefony/security dans
|
|
64
|
+
* l'ERD Studio (l'audit est une feature security, hébergée par l'ORM).
|
|
65
|
+
*
|
|
66
|
+
* @param connector - clé de l'ORM cible dans le `ormRegistry`.
|
|
67
|
+
* @returns les descripteurs {@link IEntity} du journal d'audit.
|
|
68
|
+
*/
|
|
69
|
+
export declare function createAuditEntities(connector: string): IEntity[];
|
|
70
|
+
/**
|
|
71
|
+
* Enregistre les entités du journal d'audit dans le `entityRegistry` pour un ORM
|
|
72
|
+
* donné. À appeler **avant** `orm.connect()` (le modèle est compilé au connect).
|
|
73
|
+
*
|
|
74
|
+
* @param connector - nom de la connexion cible (clé du registre).
|
|
75
|
+
*/
|
|
76
|
+
export declare function registerAuditEntities(connector: string): void;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { SchemaDefinition } from "mongoose";
|
|
2
|
+
import type { IEntity } from "@nodefony/orm-core";
|
|
3
|
+
import type { IdempotentResponse } from "nodefony";
|
|
4
|
+
/**
|
|
5
|
+
* Schéma Mongoose du **store d'idempotence** — pendant documentaire de la table
|
|
6
|
+
* Drizzle `idempotency_key`, implémentation NoSQL d'`IIdempotencyStore` (contrat
|
|
7
|
+
* au CORE) pour dédoublonner les mutations rejouées PARTAGÉ cross-pod, là où le
|
|
8
|
+
* store mémoire par défaut reste affine à un pod.
|
|
9
|
+
*
|
|
10
|
+
* **Pourquoi MongoDB plutôt que Redis** : un cluster qui possède déjà une base
|
|
11
|
+
* Mongo mais pas de Redis obtient la dédup cross-pod sans nouvelle infra — le
|
|
12
|
+
* même argument qui fonde la variante SQL.
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ **`_id` = la clé d'idempotence (String)** : c'est la contrainte d'unicité de
|
|
15
|
+
* la clé primaire qui porte la **réservation atomique** de `begin`
|
|
16
|
+
* (`findOneAndUpdate(… , {upsert:true})` → `E11000` pour le perdant). Faire
|
|
17
|
+
* porter l'unicité par un index secondaire serait un faux ami : Mongoose
|
|
18
|
+
* construit ses index en tâche de fond, donc la contrainte n'existerait pas
|
|
19
|
+
* pendant la fenêtre de construction, et deux `begin` concurrents renverraient
|
|
20
|
+
* tous deux `fresh` — exactement le double-effet que ce store existe pour
|
|
21
|
+
* empêcher. Sur `_id`, la contrainte existe dès le premier document.
|
|
22
|
+
*
|
|
23
|
+
* Le champ `key` est **dupliqué** à côté de `_id` : c'est lui que le vocabulaire
|
|
24
|
+
* public filtre (`q` = préfixe de clé) et que `listPage` rend. Les deux sont
|
|
25
|
+
* écrits ensemble, jamais séparément.
|
|
26
|
+
*
|
|
27
|
+
* ⚠️ **PAS de TTL index natif** (≠ `expireAfterSeconds`) : l'échéance d'une
|
|
28
|
+
* entrée n'est pas une durée fixe depuis sa création — c'est d'abord un **bail**
|
|
29
|
+
* *in-flight* (`now + lease`), puis une **rétention** (`now + ttl`) posée à la
|
|
30
|
+
* complétion. Un TTL index effacerait selon une règle qui ignore cette bascule.
|
|
31
|
+
* `expiresAt` est donc un `Number` (epoch ms) indexé, purgé par le `gc()`
|
|
32
|
+
* applicatif — même contrepartie que la variante SQL.
|
|
33
|
+
*
|
|
34
|
+
* `state` : `if` = réservation *in-flight* ; `done` = réponse mémorisée
|
|
35
|
+
* (rejouée en `replayed`). `response` = la réponse mémorisée, `null` tant
|
|
36
|
+
* qu'*in-flight* ; elle ne sort JAMAIS par `listPage` (anti-IDOR sur le cache).
|
|
37
|
+
*/
|
|
38
|
+
export declare const idempotencyKeySchema: SchemaDefinition;
|
|
39
|
+
/**
|
|
40
|
+
* Forme plate d'une ligne du store d'idempotence telle que rendue par le
|
|
41
|
+
* repository Mongoose. Identique aux champs de {@link idempotencyKeySchema}
|
|
42
|
+
* → zéro mapping store ↔ entité.
|
|
43
|
+
*/
|
|
44
|
+
export interface IdempotencyKeyRow {
|
|
45
|
+
key: string;
|
|
46
|
+
fingerprint: string;
|
|
47
|
+
state: "if" | "done";
|
|
48
|
+
response: IdempotentResponse | null;
|
|
49
|
+
expiresAt: number;
|
|
50
|
+
}
|
|
51
|
+
/** Nom logique de l'entité (clé de lookup `getRepository` / `entityRegistry`). */
|
|
52
|
+
export declare const IDEMPOTENCY_ENTITY_NAME: "idempotency_key";
|
|
53
|
+
/**
|
|
54
|
+
* Construit le descripteur d'entité du store d'idempotence pour un ORM nommé.
|
|
55
|
+
*
|
|
56
|
+
* Le `connector` est **dynamique** (nom du connecteur de l'app, ex. `"nodefony"`) :
|
|
57
|
+
* le schéma est statique mais sa liaison à un ORM dépend de la config → pas
|
|
58
|
+
* d'`@entity` figé. À enregistrer **avant** `orm.connect()` (le modèle est
|
|
59
|
+
* compilé au connect).
|
|
60
|
+
*
|
|
61
|
+
* `module: "framework"` → la collection est regroupée sous @nodefony/framework
|
|
62
|
+
* dans l'ERD Studio (l'idempotence est une feature framework — data plane admin
|
|
63
|
+
* + `@Idempotent` — simplement hébergée par l'ORM).
|
|
64
|
+
*
|
|
65
|
+
* @param connector - clé de l'ORM cible dans le `ormRegistry`.
|
|
66
|
+
* @returns les descripteurs {@link IEntity} du store d'idempotence.
|
|
67
|
+
*/
|
|
68
|
+
export declare function createIdempotencyEntities(connector: string): IEntity[];
|
|
69
|
+
/**
|
|
70
|
+
* Enregistre l'entité du store d'idempotence dans le `entityRegistry` pour un
|
|
71
|
+
* ORM donné. À appeler **avant** `orm.connect()`.
|
|
72
|
+
*
|
|
73
|
+
* @param connector - nom de la connexion cible (clé du registre).
|
|
74
|
+
*/
|
|
75
|
+
export declare function registerIdempotencyEntities(connector: string): void;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { SchemaDefinition } from "mongoose";
|
|
2
|
+
import type { IEntity } from "@nodefony/orm-core";
|
|
3
|
+
/**
|
|
4
|
+
* Algorithme HMAC du code TOTP (RFC 6238) — union littérale (miroir du
|
|
5
|
+
* `TotpAlgorithm` de `@nodefony/security`, non ré-exporté à la racine). Écrite en
|
|
6
|
+
* clair plutôt que dérivée d'`ITotpSecret["algorithm"]` : sinon le `.d.ts` généré
|
|
7
|
+
* référencerait un chemin interne du package security (non portable, TS2883).
|
|
8
|
+
*/
|
|
9
|
+
type TotpAlgorithm = "SHA1" | "SHA256" | "SHA512";
|
|
10
|
+
/**
|
|
11
|
+
* Schéma Mongoose du **store de secrets TOTP** `@nodefony/security` (2FA) —
|
|
12
|
+
* pendant documentaire de la table Drizzle `totp_secret`, implémentation NoSQL
|
|
13
|
+
* d'`ITotpSecretStore`.
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ **`_id` = `userId` (String)** : le modèle est **1 secret / utilisateur**, donc
|
|
16
|
+
* la clé naturelle EST l'utilisateur — `save` devient un upsert par `_id`, et
|
|
17
|
+
* l'unicité est portée par la clé primaire elle-même (aucun index à construire
|
|
18
|
+
* en tâche de fond, donc aucune fenêtre où la contrainte n'existerait pas).
|
|
19
|
+
*
|
|
20
|
+
* Le champ `userId` est **dupliqué** à côté de `_id` : c'est lui que le
|
|
21
|
+
* vocabulaire public trie et filtre (`q` = préfixe d'`userId`), et un document
|
|
22
|
+
* Mongo n'expose pas `_id` sous un autre nom. Les deux sont écrits ensemble par
|
|
23
|
+
* le store, jamais séparément.
|
|
24
|
+
*
|
|
25
|
+
* ⚠️ **`secretEnc` = secret déjà CHIFFRÉ** (AES-256-GCM par le service détenteur
|
|
26
|
+
* de la clé) : le store ne voit que des octets opaques, jamais le secret `K` en
|
|
27
|
+
* clair. Il ne sort JAMAIS par `listPage` (cf `ITotpEnrollmentSummary`).
|
|
28
|
+
*
|
|
29
|
+
* ⚠️ **Horodatages = `Number` (epoch ms), `timestamps:false`** — sauf
|
|
30
|
+
* `lastUsedStep`, qui est une **tranche `T`** RFC 6238 (`floor(epochSeconds /
|
|
31
|
+
* period)`), pas un horodatage.
|
|
32
|
+
*
|
|
33
|
+
* `recoveryCodes` = tableau de condensats `sha256` des codes NON consommés ; le
|
|
34
|
+
* store les réécrit en bloc (jamais de mutation partielle in-place).
|
|
35
|
+
*/
|
|
36
|
+
export declare const totpSecretSchema: SchemaDefinition;
|
|
37
|
+
/**
|
|
38
|
+
* Forme **plate** d'une ligne de secret renvoyée par le repository Mongoose.
|
|
39
|
+
* Structure identique à `ITotpSecret` (tout `| null` déjà présent côté contrat)
|
|
40
|
+
* → mapping quasi-identité dans `MongooseTotpSecretStore`.
|
|
41
|
+
*/
|
|
42
|
+
export interface TotpSecretRow {
|
|
43
|
+
userId: string;
|
|
44
|
+
secretEnc: string;
|
|
45
|
+
algorithm: TotpAlgorithm;
|
|
46
|
+
digits: number;
|
|
47
|
+
period: number;
|
|
48
|
+
recoveryCodes: string[];
|
|
49
|
+
confirmedAt: number | null;
|
|
50
|
+
lastUsedStep: number | null;
|
|
51
|
+
createdAt: number;
|
|
52
|
+
lastUsedAt: number | null;
|
|
53
|
+
}
|
|
54
|
+
/** Nom logique de l'entité (clé de lookup `getRepository`). */
|
|
55
|
+
export declare const TOTP_SECRET_ENTITY = "totp_secret";
|
|
56
|
+
/**
|
|
57
|
+
* Construit le descripteur d'entité du store de secrets TOTP pour un ORM nommé.
|
|
58
|
+
*
|
|
59
|
+
* Le `connector` est **dynamique** (nom du connecteur de l'app, ex. `"nodefony"`) :
|
|
60
|
+
* le schéma est statique mais sa liaison à un ORM dépend de la config → pas
|
|
61
|
+
* d'`@entity` figé. À enregistrer **avant** `orm.connect()` (le modèle est
|
|
62
|
+
* compilé au connect). `module: "security"` → regroupé sous @nodefony/security
|
|
63
|
+
* dans l'ERD Studio.
|
|
64
|
+
*
|
|
65
|
+
* @param connector - clé de l'ORM cible dans le `ormRegistry`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function createTotpSecretEntity(connector: string): IEntity;
|
|
68
|
+
/**
|
|
69
|
+
* Enregistre l'entité du store de secrets TOTP dans le `entityRegistry` pour un
|
|
70
|
+
* ORM donné. À appeler **avant** `orm.connect()`.
|
|
71
|
+
*
|
|
72
|
+
* @param connector - nom de la connexion cible (clé du registre).
|
|
73
|
+
*/
|
|
74
|
+
export declare function registerTotpSecretEntity(connector: string): void;
|
|
75
|
+
export {};
|
|
@@ -7,9 +7,12 @@
|
|
|
7
7
|
* modèles sont compilés à la connexion). Pas de dialecte (NoSQL) : les schémas
|
|
8
8
|
* sont portables par construction.
|
|
9
9
|
*
|
|
10
|
-
* Couverture
|
|
11
|
-
* webhooks
|
|
12
|
-
*
|
|
10
|
+
* Couverture : session (auto via `@entity`), utilisateurs, tokens, webauthn,
|
|
11
|
+
* webhooks, audit, TOTP et idempotence — soit **la totalité des briques
|
|
12
|
+
* durables**. C'est la propriété que Mongo doit tenir : une application doit
|
|
13
|
+
* pouvoir tourner SANS aucun backend SQL, et un backend durable est un chemin
|
|
14
|
+
* complet ou n'en est pas un. Une brique qui manquerait se sélectionnerait en
|
|
15
|
+
* échec franc (« store inconnu »), jamais en silence.
|
|
13
16
|
*
|
|
14
17
|
* Mêmes garde-fous que Drizzle : entité `has`-guarded (l'app garde la main),
|
|
15
18
|
* fabrique `get`-guarded (premier-arrivé-premier-servi).
|
|
@@ -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
|
|
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
|
+
}
|
|
@@ -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.
|
|
113
|
-
* la clé primaire
|
|
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>;
|