@nodefony/mongoose 10.0.0-alpha.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 (51) hide show
  1. package/LICENSE +544 -0
  2. package/README.md +97 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
  4. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
  5. package/dist/index.js +90 -0
  6. package/dist/nodefony/config/config.js +57 -0
  7. package/dist/nodefony/config/defineModuleConfig.js +60 -0
  8. package/dist/nodefony/entity/sessionEntity.js +63 -0
  9. package/dist/nodefony/entity/tokenEntity.js +184 -0
  10. package/dist/nodefony/entity/userEntity.js +106 -0
  11. package/dist/nodefony/entity/webAuthnCredentialEntity.js +97 -0
  12. package/dist/nodefony/entity/webhookEndpointEntity.js +109 -0
  13. package/dist/nodefony/interfaces/IMongooseConfig.js +1 -0
  14. package/dist/nodefony/interfaces/index.js +1 -0
  15. package/dist/nodefony/registerStores.js +89 -0
  16. package/dist/nodefony/service/MongooseService.js +95 -0
  17. package/dist/nodefony/src/MongooseTokenStore.js +237 -0
  18. package/dist/nodefony/src/MongooseUserRepository.js +205 -0
  19. package/dist/nodefony/src/MongooseWebAuthnCredentialStore.js +144 -0
  20. package/dist/nodefony/src/MongooseWebhookStore.js +181 -0
  21. package/dist/nodefony/src/SessionStorage.js +241 -0
  22. package/dist/nodefony/src/mongoOrder.js +49 -0
  23. package/dist/nodefony/src/orm-core/MongooseOrm.js +440 -0
  24. package/dist/nodefony/src/orm-core/MongooseRepository.js +300 -0
  25. package/dist/nodefony/src/orm-core/MongooseTransaction.js +53 -0
  26. package/dist/nodefony/src/orm-core/index.js +4 -0
  27. package/dist/types/index.d.ts +74 -0
  28. package/dist/types/nodefony/config/config.d.ts +21 -0
  29. package/dist/types/nodefony/config/defineModuleConfig.d.ts +26 -0
  30. package/dist/types/nodefony/entity/sessionEntity.d.ts +42 -0
  31. package/dist/types/nodefony/entity/tokenEntity.d.ts +59 -0
  32. package/dist/types/nodefony/entity/userEntity.d.ts +54 -0
  33. package/dist/types/nodefony/entity/webAuthnCredentialEntity.d.ts +61 -0
  34. package/dist/types/nodefony/entity/webhookEndpointEntity.d.ts +62 -0
  35. package/dist/types/nodefony/interfaces/IMongooseConfig.d.ts +17 -0
  36. package/dist/types/nodefony/interfaces/index.d.ts +1 -0
  37. package/dist/types/nodefony/registerStores.d.ts +37 -0
  38. package/dist/types/nodefony/service/MongooseService.d.ts +48 -0
  39. package/dist/types/nodefony/src/MongooseTokenStore.d.ts +126 -0
  40. package/dist/types/nodefony/src/MongooseUserRepository.d.ts +82 -0
  41. package/dist/types/nodefony/src/MongooseWebAuthnCredentialStore.d.ts +52 -0
  42. package/dist/types/nodefony/src/MongooseWebhookStore.d.ts +72 -0
  43. package/dist/types/nodefony/src/SessionStorage.d.ts +63 -0
  44. package/dist/types/nodefony/src/mongoOrder.d.ts +40 -0
  45. package/dist/types/nodefony/src/orm-core/MongooseOrm.d.ts +132 -0
  46. package/dist/types/nodefony/src/orm-core/MongooseRepository.d.ts +51 -0
  47. package/dist/types/nodefony/src/orm-core/MongooseTransaction.d.ts +37 -0
  48. package/dist/types/nodefony/src/orm-core/index.d.ts +9 -0
  49. package/docs/configuration.md +776 -0
  50. package/docs/index.md +881 -0
  51. package/package.json +97 -0
@@ -0,0 +1,61 @@
1
+ import type { SchemaDefinition } from "mongoose";
2
+ import type { IEntity } from "@nodefony/orm-core";
3
+ /**
4
+ * Schéma Mongoose du **store de credentials WebAuthn** `@nodefony/security`
5
+ * (pendant documentaire de la table Drizzle) — implémentation NoSQL d'
6
+ * `IWebAuthnCredentialStore` (passkeys).
7
+ *
8
+ * ⚠️ **`_id` = clé naturelle (String), PAS un ObjectId auto** : le contrat
9
+ * `IRepository` traduit le critère `{ id }` en `{ _id }` (cf `MongooseRepository`).
10
+ * Comme l'`id` d'un credential est un **identifiant base64url fourni par
11
+ * l'authenticator** (pas généré par Mongo), on force `_id: String` → le
12
+ * credentialId EST la clé primaire. Le virtuel `id` (activé par `MongooseOrm`,
13
+ * `toObject:{virtuals:true}`) renvoie `String(_id)` = le credentialId.
14
+ *
15
+ * ⚠️ **Horodatages = `Number` (epoch ms), `timestamps:false`** : `IWebAuthnCredential`
16
+ * porte des `number` (`Date.now()`) et l'appelant fournit `createdAt`.
17
+ *
18
+ * Le store ne lit/écrit JAMAIS `IWebAuthnCredential` directement : il traduit via
19
+ * {@link WebAuthnCredentialRow} (forme plate, `nickname: string | null`) — le
20
+ * contrat porte un `nickname?` optionnel et des champs `readonly`, que la
21
+ * frontière de persistance normalise.
22
+ */
23
+ export declare const webAuthnCredentialSchema: SchemaDefinition;
24
+ /**
25
+ * Forme **plate** d'une ligne de credentials renvoyée par le repository Mongoose —
26
+ * `id` = virtuel (= `_id` = credentialId), `nickname: string | null`. Le store
27
+ * mappe `Row ↔ IWebAuthnCredential` (`nickname?` omis si `null`).
28
+ */
29
+ export interface WebAuthnCredentialRow {
30
+ id: string;
31
+ userId: string;
32
+ publicKey: string;
33
+ signCount: number;
34
+ transports: string[];
35
+ backupEligible: boolean;
36
+ backupState: boolean;
37
+ uvInitialized: boolean;
38
+ nickname: string | null;
39
+ createdAt: number;
40
+ lastUsedAt: number | null;
41
+ }
42
+ /** Nom logique de l'entité (clé de lookup `getRepository`). */
43
+ export declare const WEBAUTHN_CREDENTIAL_ENTITY = "webauthn_credential";
44
+ /**
45
+ * Construit le descripteur d'entité du store de credentials pour un ORM nommé.
46
+ *
47
+ * Le `connector` est **dynamique** (nom du connecteur de l'app, ex. `"nodefony"`) : le
48
+ * schéma est statique mais sa liaison à un ORM dépend de la config → pas d'`@entity`
49
+ * figé (parité `createTokenEntities`). `timestamps:false`. À enregistrer **avant**
50
+ * `orm.connect()`.
51
+ *
52
+ * @param orm - clé de l'ORM cible dans le `ormRegistry`.
53
+ */
54
+ export declare function createWebAuthnCredentialEntity(connector: string): IEntity;
55
+ /**
56
+ * Enregistre l'entité du store de credentials dans le `entityRegistry` pour un
57
+ * ORM donné. À appeler **avant** `orm.connect()` (le modèle est compilé au connect).
58
+ *
59
+ * @param connector - nom de la connexion cible (clé du registre).
60
+ */
61
+ export declare function registerWebAuthnCredentialEntity(connector: string): void;
@@ -0,0 +1,62 @@
1
+ import type { SchemaDefinition } from "mongoose";
2
+ import type { IEntity } from "@nodefony/orm-core";
3
+ /**
4
+ * Schéma Mongoose du **store d'endpoints webhook** `@nodefony/security` (P6.13,
5
+ * pendant documentaire de la table Drizzle) — implémentation NoSQL d'
6
+ * `IWebhookStore` (registre durable des destinations notifiées).
7
+ *
8
+ * ⚠️ **`_id` = clé naturelle (String), PAS un ObjectId auto** : l'`id` d'un
9
+ * endpoint est un identifiant `wh_<random>` **fourni** (pas généré par Mongo) →
10
+ * on force `_id: String` → l'id EST la clé primaire. Le contrat `IRepository`
11
+ * traduit le critère `{ id }` en `{ _id }` ; le virtuel `id` (activé par
12
+ * `MongooseOrm`, `toObject:{virtuals:true}`) renvoie `String(_id)` = l'id.
13
+ *
14
+ * ⚠️ **Horodatages = `Number` (epoch ms), `timestamps:false`** : `IWebhookEndpoint`
15
+ * porte des `number` (`Date.now()`) et l'appelant fournit `createdAt`/`updatedAt`.
16
+ *
17
+ * `events` = tableau de strings ; `metadata` = objet libre (`Object`/Mixed, idem
18
+ * `tokenEntity.metadata`). Le store réécrit ces champs en bloc (pas de mutation
19
+ * partielle in-place) → pas de souci de change-tracking Mixed.
20
+ */
21
+ export declare const webhookEndpointSchema: SchemaDefinition;
22
+ /**
23
+ * Forme **plate** d'une ligne d'endpoint renvoyée par le repository Mongoose —
24
+ * `id` = virtuel (= `_id` = l'id `wh_…`), `events` mutable (le contrat porte
25
+ * `readonly string[]`). `MongooseWebhookStore` mappe `Row ↔ IWebhookEndpoint`.
26
+ */
27
+ export interface WebhookEndpointRow {
28
+ id: string;
29
+ url: string;
30
+ secretEnc: string;
31
+ events: string[];
32
+ enabled: boolean;
33
+ description: string | null;
34
+ tenantId: string | null;
35
+ createdBy: string | null;
36
+ createdAt: number;
37
+ updatedAt: number;
38
+ lastDeliveryAt: number | null;
39
+ lastDeliveryStatus: number | null;
40
+ lastDeliveryError: string | null;
41
+ failureCount: number;
42
+ metadata: Record<string, unknown>;
43
+ }
44
+ /** Nom logique de l'entité (clé de lookup `getRepository`). */
45
+ export declare const WEBHOOK_ENDPOINT_ENTITY = "webhook_endpoint";
46
+ /**
47
+ * Construit le descripteur d'entité du store webhook pour un ORM nommé.
48
+ *
49
+ * Le `connector` est **dynamique** (nom du connecteur de l'app, ex. `"nodefony"`) : le
50
+ * schéma est statique mais sa liaison à un ORM dépend de la config → pas
51
+ * d'`@entity` figé. `timestamps:false`. À enregistrer **avant** `orm.connect()`.
52
+ *
53
+ * @param orm - clé de l'ORM cible dans le `ormRegistry`.
54
+ */
55
+ export declare function createWebhookEndpointEntity(connector: string): IEntity;
56
+ /**
57
+ * Enregistre l'entité du store webhook dans le `entityRegistry` pour un ORM
58
+ * donné. À appeler **avant** `orm.connect()` (le modèle est compilé au connect).
59
+ *
60
+ * @param connector - nom de la connexion cible (clé du registre).
61
+ */
62
+ export declare function registerWebhookEndpointEntity(connector: string): void;
@@ -0,0 +1,17 @@
1
+ import type { z } from "zod";
2
+ import type { mongooseConfigSchema } from "../config/config.js";
3
+ /**
4
+ * Configuration normalisée et gelée de `@nodefony/mongoose` (sortie du builder
5
+ * {@link defineMongooseConfig}, consommée par le `MongooseService`).
6
+ *
7
+ * Type **dérivé du schéma Zod** — NE PAS redéclarer les champs à la main (ils
8
+ * divergeraient silencieusement de la source de vérité `config/config.ts`).
9
+ */
10
+ export type IMongooseConfig = z.infer<typeof mongooseConfigSchema>;
11
+ /**
12
+ * Entrée du builder `defineMongooseConfig` — tous les champs portant un défaut
13
+ * sont optionnels (l'app ne fournit que ce qu'elle surcharge dans `use()`).
14
+ */
15
+ export type IMongooseConfigInput = z.input<typeof mongooseConfigSchema>;
16
+ /** Définition d'une connexion Mongoose nommée (sous-objet de `connectors`). */
17
+ export type IMongooseConnectorConfig = IMongooseConfig["connectors"][string];
@@ -0,0 +1 @@
1
+ export type { IMongooseConfig, IMongooseConfigInput, IMongooseConnectorConfig, } from "./IMongooseConfig.js";
@@ -0,0 +1,37 @@
1
+ /**
2
+ * AUTO-ENREGISTREMENT des backends framework portés par Mongoose — « charger le
3
+ * module = ses backends deviennent sélectionnables par simple nom » (convention-
4
+ * frère : `registerDrizzleFrameworkStores` de `@nodefony/drizzle`).
5
+ *
6
+ * Appelé par `Mongoose.onKernelRegister` (AVANT le connect de `onBoot` — les
7
+ * modèles sont compilés à la connexion). Pas de dialecte (NoSQL) : les schémas
8
+ * sont portables par construction.
9
+ *
10
+ * Couverture PARTIELLE assumée : session (auto via `@entity`), tokens, webauthn,
11
+ * webhooks. PAS d'implémentation mongoose pour l'audit ni l'idempotence — les
12
+ * sélectionner sur mongoose échoue franc (« store inconnu »), jamais en silence.
13
+ *
14
+ * Mêmes garde-fous que Drizzle : entité `has`-guarded (l'app garde la main),
15
+ * fabrique `get`-guarded (premier-arrivé-premier-servi).
16
+ */
17
+ /**
18
+ * Connecteur conventionnel qui héberge le schéma framework (`"nodefony"` pour
19
+ * Mongoose, ≠ `"default"` de Drizzle — isole les entités homonymes dans le
20
+ * `entityRegistry` process-wide si les deux ORM cohabitent).
21
+ */
22
+ export declare const FRAMEWORK_CONNECTOR = "nodefony";
23
+ /** Bilan de l'auto-enregistrement (loggé par le module — jamais silencieux). */
24
+ export interface IFrameworkStoresReport {
25
+ /** Entités déclarées par l'auto-register (modèles compilés au connect). */
26
+ registered: string[];
27
+ /** Entités déjà enregistrées par l'app (customisation respectée). */
28
+ appOwned: string[];
29
+ }
30
+ /**
31
+ * Déclare les entités framework Mongoose (connecteur `nodefony`) et enregistre
32
+ * leurs fabriques de stores dans les registres de `@nodefony/security`.
33
+ * Idempotent (guards) — rejouable sans effet.
34
+ *
35
+ * @returns bilan à logger (registered / appOwned)
36
+ */
37
+ export declare function registerMongooseFrameworkStores(): IFrameworkStoresReport;
@@ -0,0 +1,48 @@
1
+ import type { ConnectOptions } from "mongoose";
2
+ import { Service } from "nodefony";
3
+ import type { Module } from "nodefony";
4
+ import { MongooseOrm } from "../src/orm-core/MongooseOrm.js";
5
+ import type { IMongooseConnectorConfig } from "../interfaces/IMongooseConfig.js";
6
+ /**
7
+ * Service bootable du module `@nodefony/mongoose` (driver NoSQL).
8
+ *
9
+ * Au boot du kernel (`onBoot`), instancie un {@link MongooseOrm} (adapter
10
+ * orm-core) **par connecteur** déclaré dans la config et le connecte ; chaque
11
+ * ORM s'auto-enregistre dans le `ormRegistry`. Ferme proprement les connexions
12
+ * à `onTerminate`.
13
+ *
14
+ * Refonte de l'ancien `Mongoose extends Orm` (core legacy) : ce service ne
15
+ * dérive plus de la base ORM du core — il **orchestre** des adapters orm-core
16
+ * autonomes, exactement comme `DrizzleService`. Le core ne connaît plus l'ORM.
17
+ */
18
+ declare class MongooseService extends Service {
19
+ #private;
20
+ module: Module;
21
+ constructor(module: Module);
22
+ /** Connecte tous les connecteurs déclarés en config (validée Zod). */
23
+ connectAll(): Promise<void>;
24
+ /** Assemble l'URI de connexion à partir de la config (`uri` ou composants). */
25
+ static buildUri(cfg: IMongooseConnectorConfig): string;
26
+ /**
27
+ * Options de connexion Mongoose d'un connecteur.
28
+ *
29
+ * `options` reste un fourre-tout transmis tel quel — Mongoose valide ses
30
+ * propres `ConnectOptions`, les re-modéliser en Zod serait une duplication
31
+ * qui dériverait. `autoIndex` fait exception, et une seule : il décide si les
32
+ * contraintes d'unicité sont construites au démarrage, ce qui mérite un champ
33
+ * typé, décrit, et visible dans la configuration d'un connecteur.
34
+ *
35
+ * Il **prime** donc sur une clé homonyme écrite dans `options` : entre deux
36
+ * canaux, celui qui est déclaré gagne — la même règle que les délais de
37
+ * connexion, où un choix explicite l'emporte sur un défaut.
38
+ *
39
+ * @param cfg - configuration validée du connecteur.
40
+ * @returns les options à passer à la connexion, ou `undefined` s'il n'y en a aucune.
41
+ */
42
+ static buildConnectOptions(cfg: IMongooseConnectorConfig): ConnectOptions | undefined;
43
+ /** Ferme toutes les connexions. */
44
+ disconnectAll(): Promise<void>;
45
+ /** Retourne l'ORM Mongoose d'un connecteur (défaut : `"nodefony"`). */
46
+ getOrm(name?: string): MongooseOrm | undefined;
47
+ }
48
+ export default MongooseService;
@@ -0,0 +1,126 @@
1
+ import type { IRepository } from "@nodefony/orm-core";
2
+ import type { IPage } from "nodefony";
3
+ import type { IAccessTokenRecord, ITokenListQuery, ITokenStore, ITokenUsage, TokenRevokeReason } from "@nodefony/security";
4
+ import type { MongooseOrm } from "./orm-core/index.js";
5
+ import { type DeniedJtiRow, type SubjectRevocationRow } from "../entity/tokenEntity.js";
6
+ /**
7
+ * Store de jetons **Mongoose** (NoSQL) — implémentation d'{@link ITokenStore} au
8
+ * dessus de trois repositories `@nodefony/orm-core` (`access_token`, `denied_jti`,
9
+ * `subject_revocation`). Pendant documentaire de `DrizzleTokenStore`.
10
+ *
11
+ * **Approche B** : `@nodefony/security` n'est connu qu'en `import type` (0 dép
12
+ * runtime). C'est l'application qui enregistre la fabrique (`registerTokenStore`)
13
+ * et les entités (`registerTokenEntities(orm)` avant `orm.connect()`).
14
+ *
15
+ * **Spécificité Mongo** : la clé naturelle (`jti` / `subjectId`) est portée par
16
+ * `_id` (cf {@link tokenEntity}). Le contrat traduit `{ id }` → `{ _id }`, donc
17
+ * les lookups passent par le champ `id` ; les **écritures** posent explicitement
18
+ * `_id` (Mongo ne génère pas notre jti). Les reads sont normalisés (`id` ← `_id`)
19
+ * pour ne pas dépendre du virtuel. Le reste est identique au store Drizzle : `gc`
20
+ * via `$lte` (le type bracketing Mongo exclut les `null`), idempotence de `revoke`
21
+ * par read-then-write.
22
+ */
23
+ export declare class MongooseTokenStore implements ITokenStore {
24
+ #private;
25
+ /**
26
+ * {@inheritDoc ITokenStore.sortableFields}
27
+ *
28
+ * Capacité pleine : le vocabulaire public entier est trié par Mongo, `id`
29
+ * compris — traduit en `_id` au moment de la requête.
30
+ */
31
+ readonly sortableFields: readonly ["createdAt", "name", "subjectId", "id"];
32
+ /**
33
+ * @param records - repository de `access_token` (PAT + refresh).
34
+ * @param denied - repository de la denylist `denied_jti`.
35
+ * @param revocations - repository des seuils `subject_revocation`.
36
+ * @param now - horloge (epoch ms) injectable pour les tests.
37
+ * @param retentionRevokedMs - rétention d'un PAT révoqué sans `exp` avant purge.
38
+ */
39
+ constructor(records: IRepository<IAccessTokenRecord>, denied: IRepository<DeniedJtiRow>, revocations: IRepository<SubjectRevocationRow>, now?: () => number, retentionRevokedMs?: number);
40
+ /**
41
+ * Construit le store depuis un {@link MongooseOrm} connecté. Les entités
42
+ * (`registerTokenEntities`) doivent avoir été enregistrées **avant** `connect()`.
43
+ *
44
+ * @param orm - ORM Mongoose connecté hébergeant les collections du store.
45
+ * @param now - horloge injectable (tests).
46
+ * @param retentionRevokedMs - rétention des PAT révoqués sans `exp`.
47
+ */
48
+ static from(orm: MongooseOrm, now?: () => number, retentionRevokedMs?: number): MongooseTokenStore;
49
+ /**
50
+ * Insère ou remplace un record (PAT / refresh) — 1 round-trip, `upsert`
51
+ * atomique sur la PK plutôt qu'un `findOne` d'existence + `create`/`updateOne`
52
+ * (dont l'`await` laisse deux put concurrents du même id lire « absent » et
53
+ * insérer tous les deux → E11000 pour le perdant). `put` pose le record
54
+ * COMPLET → tout hors `id` est ré-appliqué au conflit.
55
+ *
56
+ * `id` en critère suffit à poser `_id` : Mongo ajoute les égalités du filtre
57
+ * au document inséré (cf `MongooseRepository.upsert`), plus besoin du `_id`
58
+ * explicite. Parité stricte avec l'adapter Drizzle.
59
+ *
60
+ * @param record - le record complet à persister.
61
+ */
62
+ put(record: IAccessTokenRecord): Promise<void>;
63
+ findById(id: string): Promise<IAccessTokenRecord | null>;
64
+ findByHash(secretHash: string): Promise<IAccessTokenRecord | null>;
65
+ findBySubject(subjectId: string): Promise<IAccessTokenRecord[]>;
66
+ /** Tous les jetons (PAT + refresh) — vue d'administration cross-porteur. */
67
+ listAll(): Promise<IAccessTokenRecord[]>;
68
+ /**
69
+ * {@inheritDoc ITokenStore.listPage}
70
+ *
71
+ * `paginate()` d'orm-core (skip/limit + countDocuments) sur un filtre portable ;
72
+ * les `id` sont re-normalisés (`_id` → `id`) comme dans {@link listAll}.
73
+ *
74
+ * Le tri demandé est **traduit** avant de descendre : au repos, le jeton n'a
75
+ * pas de champ `id` (le `jti` EST le `_id`), et Mongo ne se plaint pas d'un tri
76
+ * sur un champ absent — il rend un ordre arbitraire. Sans traduction, un
77
+ * `?order=id` serait donc inerte ici et correct partout ailleurs.
78
+ */
79
+ listPage(query: ITokenListQuery): Promise<IPage<IAccessTokenRecord>>;
80
+ /** {@inheritDoc ITokenStore.countTokens} */
81
+ countTokens(query: ITokenListQuery): Promise<number>;
82
+ markUsed(id: string, usage: ITokenUsage): Promise<void>;
83
+ /**
84
+ * Révoque un jeton — **idempotent** : la 1ʳᵉ date/raison est conservée.
85
+ *
86
+ * Le « pas encore révoqué » est dans le filtre (`revokedAt: { $null: true }`),
87
+ * pas dans un `if` JS après lecture : une seule instruction, donc deux
88
+ * révocations concurrentes ne se recouvrent plus (la 2ᵉ ne matche rien au lieu
89
+ * d'écraser la date/raison de la 1ʳᵉ). Parité stricte avec l'adapter Drizzle.
90
+ *
91
+ * @param id - identifiant du jeton.
92
+ * @param reason - motif, posé seulement à la 1ʳᵉ révocation.
93
+ */
94
+ revoke(id: string, reason: TokenRevokeReason): Promise<void>;
95
+ /**
96
+ * Coupe toute une famille de refresh (détection de rejeu, RFC 9700) — les
97
+ * membres déjà révoqués gardent leur raison d'origine.
98
+ *
99
+ * Un seul `updateMany` filtré : atomique, et N+1 requêtes (1 find + 1 update
100
+ * par membre actif) tombent à 1.
101
+ *
102
+ * @param family - famille de refresh à couper.
103
+ * @param reason - motif appliqué aux membres encore actifs.
104
+ */
105
+ revokeFamily(family: string, reason: TokenRevokeReason): Promise<void>;
106
+ denyJti(jti: string, expiresAt: number): Promise<void>;
107
+ isJtiDenied(jti: string): Promise<boolean>;
108
+ /**
109
+ * Pose le seuil de révocation en masse d'un porteur (« déconnecte-moi de
110
+ * partout ») : tout jeton émis avant `invalidBefore` est mort.
111
+ *
112
+ * **Monotone — le seuil ne recule JAMAIS**, y compris sous deux logouts
113
+ * simultanés : la comparaison vit dans la valeur écrite (`$max`, natif Mongo),
114
+ * pas dans un `if` JS après lecture. Une lecture suivie d'une écriture
115
+ * laisserait les deux appels voir le même état et écrire tous les deux — c'est
116
+ * le dernier qui resterait, même porteur d'un seuil plus ANCIEN, et **des
117
+ * jetons révoqués redeviendraient valides**. Parité stricte avec l'adapter
118
+ * Drizzle (`GREATEST`/`MAX` SQL).
119
+ *
120
+ * @param subjectId - porteur visé.
121
+ * @param invalidBefore - seuil (epoch ms) ; ignoré s'il est antérieur au seuil courant.
122
+ */
123
+ revokeAllForSubject(subjectId: string, invalidBefore: number): Promise<void>;
124
+ getInvalidBefore(subjectId: string): Promise<number | null>;
125
+ gc(now?: number): Promise<number>;
126
+ }
@@ -0,0 +1,82 @@
1
+ import type { ClientSession, Model } from "mongoose";
2
+ import type { IPasswordAuthenticatedUser, IUserListQuery, IUserRepository } from "@nodefony/user";
3
+ import type { IPage } from "nodefony";
4
+ import type { Criteria, IRepository, ITransaction, RepositoryReadOptions } from "@nodefony/orm-core";
5
+ import type { MongooseOrm } from "./orm-core/MongooseOrm.js";
6
+ import type { UserRow } from "../entity/userEntity.js";
7
+ /** Modèle Mongoose à document libre (boundary — comme `MongooseRepository`). */
8
+ type LooseModel = Model<Record<string, unknown>>;
9
+ /**
10
+ * Adapter Mongoose du contrat {@link IUserRepository} — persistance NoSQL de
11
+ * l'utilisateur (P5.8), pendant documentaire de `DrizzleUserRepository` (P5.9).
12
+ *
13
+ * Décore le repository portable (`IRepository<UserRow>` de {@link MongooseOrm}) de
14
+ * deux responsabilités propres à l'utilisateur :
15
+ * - **mapping document ↔ `BaseUser`** : les consommateurs reçoivent le comportement
16
+ * (`hasRole`/`isActive`/`isLocked`), pas un document nu ;
17
+ * - **finders métier** : `findByIdentifier` (lookup unique) et
18
+ * `findBySocialProvider` (scan du tableau `socialProviders` via `$elemMatch` —
19
+ * équivalent Mongo du `json_each` SQL de Drizzle, pattern Shadow User OAuth).
20
+ *
21
+ * Le credential (`password`) transite par cette frontière — attendu : le repository
22
+ * **est** la frontière de persistance du hash (cf `IUserRepository`).
23
+ */
24
+ export declare class MongooseUserRepository implements IUserRepository {
25
+ #private;
26
+ /**
27
+ * Même vocabulaire public que les autres repositories — ici le tri part
28
+ * dans la requête, où il ne coûte qu'un index.
29
+ */
30
+ readonly sortableFields: readonly ["identifier", "enabled", "createdAt", "updatedAt", "id"];
31
+ /**
32
+ * @param base - repository portable sur l'entité `User` (CRUD + criteria).
33
+ * @param model - modèle Mongoose natif `User` (pour le scan `$elemMatch`).
34
+ * @param session - session transactionnelle liée aux ops natives, ou `null`.
35
+ */
36
+ constructor(base: IRepository<UserRow>, model: LooseModel, session?: ClientSession | null);
37
+ /**
38
+ * Construit le repository utilisateur depuis un {@link MongooseOrm} connecté.
39
+ * L'entité `User` doit avoir été enregistrée (cf `registerUserEntity`) **avant**
40
+ * `orm.connect()` (le modèle est compilé au connect).
41
+ *
42
+ * @param orm - ORM Mongoose connecté.
43
+ * @returns le repository utilisateur prêt à l'emploi.
44
+ */
45
+ static from(orm: MongooseOrm): MongooseUserRepository;
46
+ find(criteria?: Criteria<IPasswordAuthenticatedUser>, options?: RepositoryReadOptions): Promise<IPasswordAuthenticatedUser[]>;
47
+ findOne(criteria: Criteria<IPasswordAuthenticatedUser>, options?: RepositoryReadOptions): Promise<IPasswordAuthenticatedUser | null>;
48
+ create(data: Partial<IPasswordAuthenticatedUser>): Promise<IPasswordAuthenticatedUser>;
49
+ updateOne(criteria: Criteria<IPasswordAuthenticatedUser>, data: Partial<IPasswordAuthenticatedUser>): Promise<IPasswordAuthenticatedUser | null>;
50
+ upsert(criteria: Criteria<IPasswordAuthenticatedUser>, update: Partial<IPasswordAuthenticatedUser>, insertOnly?: Partial<IPasswordAuthenticatedUser>): Promise<IPasswordAuthenticatedUser>;
51
+ createMany(data: Partial<IPasswordAuthenticatedUser>[]): Promise<IPasswordAuthenticatedUser[]>;
52
+ exists(criteria: Criteria<IPasswordAuthenticatedUser>): Promise<boolean>;
53
+ deleteOne(criteria: Criteria<IPasswordAuthenticatedUser>): Promise<boolean>;
54
+ findOneAndDelete(criteria: Criteria<IPasswordAuthenticatedUser>): Promise<IPasswordAuthenticatedUser | null>;
55
+ increment(criteria: Criteria<IPasswordAuthenticatedUser>, changes: Partial<Record<keyof IPasswordAuthenticatedUser, number>>): Promise<IPasswordAuthenticatedUser | null>;
56
+ updateMany(criteria: Criteria<IPasswordAuthenticatedUser>, data: Partial<IPasswordAuthenticatedUser>): Promise<number>;
57
+ delete(criteria: Criteria<IPasswordAuthenticatedUser>): Promise<number>;
58
+ count(criteria?: Criteria<IPasswordAuthenticatedUser>): Promise<number>;
59
+ countDistinct(field: keyof IPasswordAuthenticatedUser & string, criteria?: Criteria<IPasswordAuthenticatedUser>): Promise<number>;
60
+ withTransaction(tx: ITransaction): IUserRepository;
61
+ findByIdentifier(identifier: string): Promise<IPasswordAuthenticatedUser | null>;
62
+ /**
63
+ * Recherche par compte externe lié — scanne le tableau `socialProviders` via
64
+ * `$elemMatch` (1 requête, équivalent Mongo du `json_each` Drizzle). `null` si
65
+ * aucun lien. Liée à la session transactionnelle courante le cas échéant.
66
+ */
67
+ findBySocialProvider(provider: string, providerId: string): Promise<IPasswordAuthenticatedUser | null>;
68
+ /**
69
+ * {@inheritDoc IUserRepository.listPage}
70
+ *
71
+ * Query native Mongo : `find(filter).sort().skip().limit(limit + 1)` — le store
72
+ * ne renvoie qu'une page (jamais de matérialisation complète). `roles: role` =
73
+ * containment de tableau natif, `$regex/i` = sous-chaîne insensible casse.
74
+ * `_id` en tiebreaker de tri (pagination offset déterministe).
75
+ */
76
+ listPage(query: IUserListQuery): Promise<IPage<IPasswordAuthenticatedUser>>;
77
+ /** {@inheritDoc IUserRepository.countActiveAdmins} */
78
+ /** {@inheritDoc IUserRepository.countUsers} */
79
+ countUsers(query: IUserListQuery): Promise<number>;
80
+ countActiveAdmins(adminRole: string): Promise<number>;
81
+ }
82
+ export {};
@@ -0,0 +1,52 @@
1
+ import { type IRepository } from "@nodefony/orm-core";
2
+ import type { IPage } from "nodefony";
3
+ import type { IWebAuthnCredential, IWebAuthnCredentialStore, IWebAuthnCredentialSummary, IWebAuthnListQuery, WebAuthnAuthUpdate } from "@nodefony/security";
4
+ import type { MongooseOrm } from "./orm-core/index.js";
5
+ import { type WebAuthnCredentialRow } from "../entity/webAuthnCredentialEntity.js";
6
+ /**
7
+ * Store de credentials WebAuthn **Mongoose** (NoSQL) — implémentation d'
8
+ * {@link IWebAuthnCredentialStore} au-dessus d'un unique repository
9
+ * `@nodefony/orm-core` (`webauthn_credential`). Pendant documentaire de
10
+ * `DrizzleWebAuthnCredentialStore`.
11
+ *
12
+ * **Approche B** : `@nodefony/security` n'est connu qu'en `import type` (0 dép
13
+ * runtime). C'est l'application qui enregistre la fabrique
14
+ * (`registerWebAuthnStore("mongoose", …)`) et l'entité
15
+ * (`registerWebAuthnCredentialEntity(orm)` avant `orm.connect()`).
16
+ *
17
+ * **Spécificité Mongo** : la clé naturelle (credentialId) est portée par `_id`
18
+ * (cf {@link webAuthnCredentialSchema}). Le contrat traduit `{ id }` → `{ _id }`,
19
+ * donc les lookups passent par le champ `id` ; les **écritures** posent
20
+ * explicitement `_id` (Mongo ne génère pas notre credentialId). Le mapping
21
+ * `Row ↔ IWebAuthnCredential` normalise `nickname` (`null` → omis).
22
+ */
23
+ export declare class MongooseWebAuthnCredentialStore implements IWebAuthnCredentialStore {
24
+ #private;
25
+ /** @param repo - repository de la collection `webauthn_credential`. */
26
+ constructor(repo: IRepository<WebAuthnCredentialRow>);
27
+ /**
28
+ * Construit le store depuis un {@link MongooseOrm} connecté. L'entité
29
+ * (`registerWebAuthnCredentialEntity`) doit avoir été enregistrée **avant**
30
+ * `connect()`.
31
+ *
32
+ * @param orm - ORM Mongoose connecté hébergeant la collection du store.
33
+ */
34
+ static from(orm: MongooseOrm): MongooseWebAuthnCredentialStore;
35
+ findById(credentialId: string): Promise<IWebAuthnCredential | null>;
36
+ findByUser(userId: string): Promise<IWebAuthnCredential[]>;
37
+ /** `countDocuments` natif — jamais un `find().length` (le plafond ne charge rien). */
38
+ countByUser(userId: string): Promise<number>;
39
+ save(credential: IWebAuthnCredential): Promise<void>;
40
+ update(credentialId: string, patch: WebAuthnAuthUpdate): Promise<void>;
41
+ delete(credentialId: string): Promise<void>;
42
+ /**
43
+ * {@inheritDoc IWebAuthnCredentialStore.listPage}
44
+ *
45
+ * Même helper `paginate()` que l'adapter SQL (`skip`/`limit` + `countDocuments`).
46
+ * La projection retire `publicKey` — elle ne franchit jamais la frontière du store.
47
+ * ⚠️ L'identité vient de `_id` (`#idOf`), pas du champ `id` de la row.
48
+ */
49
+ listPage(query: IWebAuthnListQuery): Promise<IPage<IWebAuthnCredentialSummary>>;
50
+ /** {@inheritDoc IWebAuthnCredentialStore.countCredentials} */
51
+ countCredentials(query: IWebAuthnListQuery): Promise<number>;
52
+ }
@@ -0,0 +1,72 @@
1
+ import type { IRepository } from "@nodefony/orm-core";
2
+ import type { IPage } from "nodefony";
3
+ import type { IWebhookEndpoint, IWebhookListQuery, IWebhookStore, WebhookEndpointUpdate } from "@nodefony/security";
4
+ import type { Model } from "mongoose";
5
+ import type { MongooseOrm } from "./orm-core/index.js";
6
+ /** Modèle Mongoose à document libre (boundary — comme `MongooseRepository`). */
7
+ type LooseModel = Model<Record<string, unknown>>;
8
+ import { type WebhookEndpointRow } from "../entity/webhookEndpointEntity.js";
9
+ /**
10
+ * Store d'endpoints webhook **Mongoose** (NoSQL) — implémentation d'
11
+ * {@link IWebhookStore} au-dessus d'un unique repository `@nodefony/orm-core`
12
+ * (`webhook_endpoint`). Pendant documentaire de `DrizzleWebhookStore` ; registre
13
+ * DURABLE des endpoints (survit au redémarrage, ≠ `MemoryWebhookStore`).
14
+ *
15
+ * **Approche B** : `@nodefony/security` n'est connu qu'en `import type` (0 dép
16
+ * runtime). C'est l'application qui enregistre la fabrique
17
+ * (`registerWebhookStore("mongoose", …)`) et l'entité
18
+ * (`registerWebhookEndpointEntity(orm)` avant `orm.connect()`).
19
+ *
20
+ * **Spécificité Mongo** : la clé naturelle (`wh_<random>`) est portée par `_id`
21
+ * (cf {@link webhookEndpointSchema}). Le contrat traduit `{ id }` → `{ _id }`,
22
+ * donc les lookups passent par `id` ; les **écritures** posent explicitement
23
+ * `_id` (Mongo ne génère pas notre id). Mapping `Row ↔ IWebhookEndpoint` :
24
+ * `IWebhookEndpoint` est déjà « plat tout `| null` », seuls les champs JSON
25
+ * `events`/`metadata` sont copiés défensivement.
26
+ */
27
+ export declare class MongooseWebhookStore implements IWebhookStore {
28
+ #private;
29
+ /**
30
+ * {@inheritDoc IWebhookStore.sortableFields}
31
+ *
32
+ * Capacité pleine : le vocabulaire public entier est trié par Mongo, `id`
33
+ * compris — traduit en `_id` au moment de la requête.
34
+ */
35
+ readonly sortableFields: readonly ["createdAt", "updatedAt", "url", "enabled", "failureCount", "id"];
36
+ /**
37
+ * @param repo - repository de la collection `webhook_endpoint`.
38
+ * @param model - modèle Mongoose natif, requis par le seul listing paginé
39
+ * (recherche `q` = `$or` sur deux champs, hors `Criteria` AND-only).
40
+ * `null` = `listPage` refuse plutôt que de tout charger en silence.
41
+ */
42
+ constructor(repo: IRepository<WebhookEndpointRow>, model?: LooseModel | null);
43
+ /**
44
+ * Construit le store depuis un {@link MongooseOrm} connecté. L'entité
45
+ * (`registerWebhookEndpointEntity`) doit avoir été enregistrée **avant**
46
+ * `connect()`.
47
+ *
48
+ * @param orm - ORM Mongoose connecté hébergeant la collection du store.
49
+ */
50
+ static from(orm: MongooseOrm): MongooseWebhookStore;
51
+ save(endpoint: IWebhookEndpoint): Promise<void>;
52
+ findById(id: string): Promise<IWebhookEndpoint | null>;
53
+ update(id: string, patch: WebhookEndpointUpdate): Promise<void>;
54
+ delete(id: string): Promise<void>;
55
+ listAll(): Promise<IWebhookEndpoint[]>;
56
+ /**
57
+ * {@inheritDoc IWebhookStore.listPage}
58
+ *
59
+ * Query native : `find(filter).sort(…).skip().limit(limit+1)` — une page,
60
+ * jamais la collection. Le `limit + 1` donne `hasNext` sans compter.
61
+ *
62
+ * Le tri demandé est **traduit** avant de descendre : au repos, l'endpoint n'a
63
+ * pas de champ `id` (l'identifiant EST le `_id`), et Mongo ne se plaint pas
64
+ * d'un tri sur un champ absent — il rend un ordre arbitraire. Sans traduction,
65
+ * un `?order=id` serait donc inerte ici et correct partout ailleurs. À défaut
66
+ * d'`order`, l'ordre par défaut `createdAt DESC, _id ASC` reste déterministe.
67
+ */
68
+ listPage(query: IWebhookListQuery): Promise<IPage<IWebhookEndpoint>>;
69
+ /** {@inheritDoc IWebhookStore.countEndpoints} */
70
+ countEndpoints(query: IWebhookListQuery): Promise<number>;
71
+ }
72
+ export {};
@@ -0,0 +1,63 @@
1
+ import { SessionsService } from "@nodefony/http";
2
+ import type { ISessionStorage, ISerializedSession, ISessionRecord, ISessionListFilter, ISessionListQuery } from "@nodefony/http";
3
+ import type { IPage } from "nodefony";
4
+ /**
5
+ * Stockage de session **Mongoose** (driver NoSQL), branché sur `@nodefony/orm-core`.
6
+ *
7
+ * Implémente le contrat {@link ISessionStorage} consommé par le `SessionsService`
8
+ * de `@nodefony/http` — store de session portable. Persiste via le repository
9
+ * orm-core de l'entité `session` (connecteur `nodefony`, modèle compilé au boot
10
+ * par `MongooseOrm`). Logique **identique** au store Drizzle (timestamps en ms,
11
+ * GC via l'opérateur riche portable `$lt`) — la portabilité du contrat orm-core.
12
+ */
13
+ declare class SessionStorage implements ISessionStorage {
14
+ #private;
15
+ manager: SessionsService;
16
+ idleTimeoutS: number;
17
+ absoluteTimeoutS: number;
18
+ /**
19
+ * Même vocabulaire public que les autres backends — le tri part dans le
20
+ * `sort()` Mongo, où il ne coûte qu'un index.
21
+ */
22
+ readonly sortableFields: readonly ["updatedAt", "createdAt", "user", "id"];
23
+ constructor(manager: SessionsService);
24
+ read(id: string): Promise<ISerializedSession>;
25
+ start(id: string): Promise<ISerializedSession>;
26
+ write(id: string, data: ISerializedSession): Promise<ISerializedSession>;
27
+ open(): Promise<number>;
28
+ close(): boolean;
29
+ destroy(id: string): Promise<boolean>;
30
+ gc(idleSeconds?: number, absoluteSeconds?: number): Promise<void>;
31
+ /**
32
+ * Prolonge l'idle d'une session (timeout glissant) : `updateOne updatedAt = now`
33
+ * sur `session_id` — SANS réécrire le blob (touch NIST/OWASP). N'affecte pas
34
+ * `createdAt` (= borne absolute). ORM déconnecté → no-op ; ligne absente →
35
+ * no-op silencieux. Parité avec le store Drizzle.
36
+ */
37
+ touch(id: string): Promise<void>;
38
+ /**
39
+ * Énumération admin (capacité optionnelle d'`ISessionStorage`) — `find` projeté,
40
+ * filtrable par `user`. **Redaction par construction** : seuls `user`/`metaBag`/
41
+ * timestamps sortent de la base ; `Attributes`/`flashBag` (potentiellement
42
+ * sensibles) restent en base. ORM déconnecté → `[]`. Strictement identique au
43
+ * store Drizzle (parité du contrat orm-core).
44
+ */
45
+ listAll(filter?: ISessionListFilter): Promise<ISessionRecord[]>;
46
+ /**
47
+ * Pagination **native** `skip/limit` + `countDocuments` (helper `paginate`
48
+ * orm-core) : une page = une requête bornée, quel que soit le volume en base.
49
+ * Ordre `updatedAt` DESC départagé par `session_id` — déterministe à horodatage
50
+ * égal. ORM déconnecté → page vide. Parité stricte avec le store Drizzle.
51
+ */
52
+ listPage(query: ISessionListQuery): Promise<IPage<ISessionRecord>>;
53
+ /** `countDocuments` natif filtré — aucun document matérialisé. Déconnecté → 0. */
54
+ countSessions(query?: Partial<ISessionListQuery>): Promise<number>;
55
+ /**
56
+ * Utilisateurs distincts, agrégés côté serveur. Les sessions anonymes portent
57
+ * un `user` nul ou absent : l'agrégation les écarte, elles ne comptent donc
58
+ * pas pour un utilisateur. Déconnecté → 0 (même dégradation que
59
+ * {@link countSessions}).
60
+ */
61
+ countDistinctUsers(query?: Partial<ISessionListQuery>): Promise<number>;
62
+ }
63
+ export default SessionStorage;