@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,300 @@
1
+ import { RequestContext, redactSecrets } from "nodefony";
2
+ import { UnknownCriteriaField, assertOrderOption, isFieldOperators, isUpdateOperators, likePatternToRegExp, queryFlowMonitor } from "@nodefony/orm-core";
3
+ //#region nodefony/src/orm-core/MongooseRepository.ts
4
+ /**
5
+ * Repository portable (contrat {@link IRepository}) au-dessus d'un modèle Mongoose.
6
+ *
7
+ * Démontre la portabilité du contrat sur un **store documentaire hétérogène** :
8
+ * - `options.relations` → `populate()` (virtuels/refs déclarés), pas `include` ;
9
+ * - **clé primaire `_id`** (MongoDB) ↔ `id` (contrat) : le critère `{ id }` est
10
+ * traduit en `{ _id }`, et la sortie (`toObject({ virtuals: true })`) porte le
11
+ * virtuel `id` (string hex de l'ObjectId) → contrat `id: string` respecté ;
12
+ * - liaison transactionnelle via {@link MongooseRepository.withTransaction}
13
+ * (`{ session }` sur toutes les ops ; requiert un replica set).
14
+ *
15
+ * @typeParam T - forme plate de l'entité gérée.
16
+ */
17
+ var MongooseRepository = class MongooseRepository {
18
+ #model;
19
+ #session;
20
+ /** Connecteur ORM (clé du registre) — tag des métriques de flux. */
21
+ #connector;
22
+ /**
23
+ * @param model - modèle Mongoose compilé.
24
+ * @param connector - nom de la connexion (clé du registre) — défaut `"nodefony"`.
25
+ * @param session - session transactionnelle à laquelle lier les ops (ou `null`).
26
+ */
27
+ constructor(model, connector = "nodefony", session = null) {
28
+ this.#model = model;
29
+ this.#connector = connector;
30
+ this.#session = session;
31
+ }
32
+ /**
33
+ * Tap dev-only : mesure la durée d'une opération et alimente **deux** sondes
34
+ * complémentaires (sans surcoût quand les deux sont inactives — flags lus avant
35
+ * toute allocation, prod = coût nul) :
36
+ * 1. **profiler par-requête** (buffer de scope ALS, debug bar) — descripteur
37
+ * de CHAQUE opération tracée ;
38
+ * 2. **flux ORM agrégé** ({@link queryFlowMonitor}, process-wide) — débit +
39
+ * latence ; le descripteur n'est sérialisé que sur le chemin **lent** (rare).
40
+ *
41
+ * @param descr - fabrique du descripteur (collection.op + filtre redacté) —
42
+ * thunk : jamais évalué hors observation.
43
+ * @param exec - exécution de l'opération.
44
+ * @param rowsOf - extraction du nombre de documents (optionnel).
45
+ */
46
+ async #prof(descr, exec, rowsOf) {
47
+ const buf = RequestContext.get()?.queries;
48
+ const flow = queryFlowMonitor.enabled;
49
+ if (!buf && !flow) return exec();
50
+ const start = performance.now();
51
+ const result = await exec();
52
+ const durationMs = performance.now() - start;
53
+ if (flow) {
54
+ const q = durationMs >= queryFlowMonitor.slowMs ? descr() : void 0;
55
+ queryFlowMonitor.record(this.#connector, durationMs, q);
56
+ }
57
+ if (buf) buf.push({
58
+ sql: descr(),
59
+ startMs: start,
60
+ durationMs,
61
+ rows: rowsOf?.(result),
62
+ connector: "mongoose"
63
+ });
64
+ return result;
65
+ }
66
+ /** Descripteur compact `Model.op {filtre}` (redacté + tronqué) pour les sondes. */
67
+ #descr(op, filter) {
68
+ const tail = filter !== void 0 ? ` ${JSON.stringify(filter)}` : "";
69
+ const s = `${this.#model.modelName}.${op}${tail}`;
70
+ return redactSecrets(s.length > 2e3 ? `${s.slice(0, 2e3)}…` : s);
71
+ }
72
+ /**
73
+ * Traduit les opérateurs riches portables en opérateurs MongoDB.
74
+ *
75
+ * Quasi-identité : `$gt`/`$in`/`$nin`/`$ne`/`$eq`/`$lt`... sont natifs Mongo ;
76
+ * `$like` (motif SQL) devient `$regex`, et `$null` une comparaison à `null`
77
+ * (`$eq`/`$ne`) — en Mongo `null` matche aussi le champ **absent**, ce qui est
78
+ * bien l'équivalent du `NULL` SQL (colonne sans valeur).
79
+ *
80
+ * @throws Error si `$null` est combiné à `$eq`/`$ne` sur le même champ : les
81
+ * deux viseraient la même clé Mongo et l'une écraserait l'autre **en
82
+ * silence** (le contrat les déclare exclusifs — cf `FieldOperators.$null`).
83
+ */
84
+ #mongoOps(ops) {
85
+ const out = {};
86
+ for (const [key, value] of Object.entries(ops)) if (key === "$like") out.$regex = likePatternToRegExp(value);
87
+ else if (key === "$null") {
88
+ const target = value ? "$eq" : "$ne";
89
+ if (target in out) throw new Error(`criteria: $null ne se combine pas avec ${target} sur le même champ (${this.#model.modelName})`);
90
+ out[target] = null;
91
+ } else if ((key === "$eq" || key === "$ne") && key in out) throw new Error(`criteria: $null ne se combine pas avec ${key} sur le même champ (${this.#model.modelName})`);
92
+ else out[key] = value;
93
+ return out;
94
+ }
95
+ /**
96
+ * Valide qu'un champ de critère existe sur le schéma (strict B2, parité
97
+ * Drizzle) et renvoie sa clé Mongo (`id` → `_id`, PK). Un champ inconnu lève
98
+ * {@link UnknownCriteriaField} au lieu d'être passé tel quel à Mongo (qui
99
+ * donnait 0 résultat silencieux — et divergeait de Drizzle qui, lui, renvoyait
100
+ * tout). Échoue tôt et pareil sur les deux drivers.
101
+ *
102
+ * @param field - clé brute du critère.
103
+ * @returns la clé Mongo résolue.
104
+ * @throws UnknownCriteriaField si le champ n'existe pas sur le schéma.
105
+ */
106
+ #resolveField(field) {
107
+ const key = field === "id" ? "_id" : field;
108
+ const paths = this.#model.schema.paths;
109
+ if (key === "_id" || paths[key] !== void 0 || paths[key.split(".")[0]] !== void 0) return key;
110
+ throw new UnknownCriteriaField(field, this.#model.modelName, Object.keys(paths));
111
+ }
112
+ /**
113
+ * Traduit le critère portable : `id` → `_id` (PK MongoDB) + opérateurs riches.
114
+ * Chaque champ est validé contre le schéma (strict, cf {@link MongooseRepository.#resolveField}).
115
+ */
116
+ #filter(criteria) {
117
+ if (!criteria) return {};
118
+ const out = {};
119
+ for (const [field, value] of Object.entries(criteria)) {
120
+ if (field === "$or") {
121
+ if (!Array.isArray(value)) throw new TypeError(`MongooseRepository(${this.#model.modelName}): $or attend un tableau de critères.`);
122
+ const branches = value.map((branch) => this.#filter(branch)).filter((f) => Object.keys(f).length > 0);
123
+ if (branches.length > 0) out.$or = branches;
124
+ continue;
125
+ }
126
+ const key = this.#resolveField(field);
127
+ out[key] = isFieldOperators(value) ? this.#mongoOps(value) : value;
128
+ }
129
+ return out;
130
+ }
131
+ /** Sérialise un document en objet plat (virtuels inclus → `id`, populates). */
132
+ #plain(doc) {
133
+ return doc.toObject({ virtuals: true });
134
+ }
135
+ async find(criteria, options) {
136
+ assertOrderOption(options?.order, this.#model.modelName);
137
+ const filter = this.#filter(criteria);
138
+ return this.#prof(() => this.#descr("find", filter), async () => {
139
+ let query = this.#model.find(filter);
140
+ if (this.#session) query = query.session(this.#session);
141
+ if (options?.relations?.length) query = query.populate(options.relations);
142
+ if (options?.offset !== void 0) query = query.skip(options.offset);
143
+ if (options?.limit !== void 0) query = query.limit(options.limit);
144
+ if (options?.order?.length) query = query.sort(Object.fromEntries(options.order.map(([field, dir]) => [field, dir === "DESC" ? -1 : 1])));
145
+ return (await query.exec()).map((doc) => this.#plain(doc));
146
+ }, (docs) => docs.length);
147
+ }
148
+ async findOne(criteria, options) {
149
+ assertOrderOption(options?.order, this.#model.modelName);
150
+ const filter = this.#filter(criteria);
151
+ return this.#prof(() => this.#descr("findOne", filter), async () => {
152
+ let query = this.#model.findOne(filter);
153
+ if (this.#session) query = query.session(this.#session);
154
+ if (options?.relations?.length) query = query.populate(options.relations);
155
+ if (options?.order?.length) query = query.sort(Object.fromEntries(options.order.map(([field, dir]) => [field, dir === "DESC" ? -1 : 1])));
156
+ const doc = await query.exec();
157
+ return doc ? this.#plain(doc) : null;
158
+ }, (doc) => doc ? 1 : 0);
159
+ }
160
+ async create(data) {
161
+ return this.#prof(() => this.#descr("create"), async () => {
162
+ const [doc] = await this.#model.create([data], { session: this.#session ?? void 0 });
163
+ return this.#plain(doc);
164
+ }, () => 1);
165
+ }
166
+ async createMany(data) {
167
+ if (data.length === 0) return [];
168
+ return this.#prof(() => this.#descr("insertMany"), async () => {
169
+ return (await this.#model.insertMany(data, { session: this.#session ?? void 0 })).map((doc) => this.#plain(doc));
170
+ }, (rows) => rows.length);
171
+ }
172
+ async updateOne(criteria, data) {
173
+ const filter = this.#filter(criteria);
174
+ return this.#prof(() => this.#descr("findOneAndUpdate", filter), async () => {
175
+ const doc = await this.#model.findOneAndUpdate(filter, data, {
176
+ returnDocument: "after",
177
+ session: this.#session ?? void 0
178
+ }).exec();
179
+ return doc ? this.#plain(doc) : null;
180
+ }, (doc) => doc ? 1 : 0);
181
+ }
182
+ async upsert(criteria, update, insertOnly) {
183
+ const filter = this.#filter(criteria);
184
+ return this.#prof(() => this.#descr("findOneAndUpdate", filter), async () => {
185
+ const doc = await this.#model.findOneAndUpdate(filter, {
186
+ ...this.#writeDoc(update),
187
+ $setOnInsert: insertOnly ?? {}
188
+ }, {
189
+ upsert: true,
190
+ returnDocument: "after",
191
+ session: this.#session ?? void 0
192
+ }).exec();
193
+ if (!doc) throw new Error("MongooseRepository.upsert: aucun document retourné");
194
+ return this.#plain(doc);
195
+ }, () => 1);
196
+ }
197
+ /**
198
+ * Traduit le `update` d'un upsert en document de mise à jour Mongo : les
199
+ * valeurs nues vont dans `$set`, les {@link UpdateOperators} dans l'opérateur
200
+ * natif de même nom (`$max`/`$min`).
201
+ *
202
+ * Mongo fait exactement ce qu'on attend : il n'écrit que si la valeur proposée
203
+ * est supérieure (resp. inférieure) à celle en base, et la pose telle quelle à
204
+ * l'insertion — donc pas de `$setOnInsert` à doubler. Pendant exact du
205
+ * `GREATEST(col, ?)` des adapters SQL.
206
+ */
207
+ #writeDoc(update) {
208
+ const $set = {};
209
+ const ops = {};
210
+ for (const [field, value] of Object.entries(update)) {
211
+ if (!isUpdateOperators(value)) {
212
+ $set[this.#resolveField(field)] = value;
213
+ continue;
214
+ }
215
+ const key = this.#resolveField(field);
216
+ if (value.$max !== void 0) (ops.$max ??= {})[key] = value.$max;
217
+ if (value.$min !== void 0) (ops.$min ??= {})[key] = value.$min;
218
+ }
219
+ return Object.keys($set).length > 0 ? {
220
+ $set,
221
+ ...ops
222
+ } : ops;
223
+ }
224
+ async updateMany(criteria, data) {
225
+ const filter = this.#filter(criteria);
226
+ return this.#prof(() => this.#descr("updateMany", filter), async () => {
227
+ return (await this.#model.updateMany(filter, data, { session: this.#session ?? void 0 })).modifiedCount ?? 0;
228
+ }, (n) => n);
229
+ }
230
+ async increment(criteria, changes) {
231
+ const filter = this.#filter(criteria);
232
+ return this.#prof(() => this.#descr("findOneAndUpdate", filter), async () => {
233
+ const doc = await this.#model.findOneAndUpdate(filter, { $inc: changes }, {
234
+ returnDocument: "after",
235
+ session: this.#session ?? void 0
236
+ }).exec();
237
+ return doc ? this.#plain(doc) : null;
238
+ }, (doc) => doc ? 1 : 0);
239
+ }
240
+ async delete(criteria) {
241
+ const filter = this.#filter(criteria);
242
+ return this.#prof(() => this.#descr("deleteMany", filter), async () => {
243
+ return (await this.#model.deleteMany(filter, { session: this.#session ?? void 0 })).deletedCount ?? 0;
244
+ }, (n) => n);
245
+ }
246
+ async deleteOne(criteria) {
247
+ const filter = this.#filter(criteria);
248
+ return this.#prof(() => this.#descr("deleteOne", filter), async () => {
249
+ return ((await this.#model.deleteOne(filter, { session: this.#session ?? void 0 })).deletedCount ?? 0) > 0;
250
+ }, (ok) => ok ? 1 : 0);
251
+ }
252
+ async findOneAndDelete(criteria) {
253
+ const filter = this.#filter(criteria);
254
+ return this.#prof(() => this.#descr("findOneAndDelete", filter), async () => {
255
+ const doc = await this.#model.findOneAndDelete(filter, { session: this.#session ?? void 0 }).exec();
256
+ return doc ? this.#plain(doc) : null;
257
+ }, (doc) => doc ? 1 : 0);
258
+ }
259
+ async count(criteria) {
260
+ const filter = this.#filter(criteria);
261
+ return this.#prof(() => this.#descr("countDocuments", filter), () => this.#model.countDocuments(filter, { session: this.#session ?? void 0 }));
262
+ }
263
+ /**
264
+ * `COUNT(DISTINCT …)` en agrégation — `$group` puis `$count`, donc la
265
+ * déduplication reste dans le serveur. `Model.distinct()` aurait rapatrié
266
+ * toutes les valeurs pour n'en mesurer que la longueur.
267
+ *
268
+ * `{$ne: null}` écarte à la fois la valeur nulle et le champ absent, ce qui
269
+ * aligne le résultat sur le `COUNT(DISTINCT col)` SQL — sans lui, les
270
+ * documents sans valeur formeraient un groupe `null` compté comme une valeur.
271
+ */
272
+ async countDistinct(field, criteria) {
273
+ const filter = this.#filter(criteria);
274
+ const path = this.#resolveField(field);
275
+ return this.#prof(() => this.#descr("countDistinct", filter), async () => {
276
+ const pipeline = [
277
+ { $match: filter },
278
+ { $match: { [path]: { $ne: null } } },
279
+ { $group: { _id: `$${path}` } },
280
+ { $count: "n" }
281
+ ];
282
+ const agg = this.#model.aggregate(pipeline);
283
+ if (this.#session) agg.session(this.#session);
284
+ return (await agg.exec())[0]?.n ?? 0;
285
+ });
286
+ }
287
+ async exists(criteria) {
288
+ const filter = this.#filter(criteria);
289
+ return this.#prof(() => this.#descr("exists", filter), async () => {
290
+ const q = this.#model.exists(filter);
291
+ if (this.#session) q.session(this.#session);
292
+ return await q.exec() !== null;
293
+ });
294
+ }
295
+ withTransaction(tx) {
296
+ return new MongooseRepository(this.#model, this.#connector, tx.getNative());
297
+ }
298
+ };
299
+ //#endregion
300
+ export { MongooseRepository };
@@ -0,0 +1,53 @@
1
+ //#region nodefony/src/orm-core/MongooseTransaction.ts
2
+ /**
3
+ * Adapte une `ClientSession` MongoDB au contrat portable {@link ITransaction}.
4
+ *
5
+ * Obtenue dans le callback de `MongooseOrm.transaction()`, qui gère le
6
+ * commit/abort automatique via `session.withTransaction()`. Les transactions
7
+ * MongoDB **exigent un replica set** (un standalone ne les supporte pas).
8
+ *
9
+ * MongoDB n'a **pas de savepoints** : {@link MongooseTransaction.savepoint} /
10
+ * {@link MongooseTransaction.rollbackTo} sont des no-op documentés (limite du
11
+ * driver, prévue par le contrat).
12
+ */
13
+ var MongooseTransaction = class {
14
+ #session;
15
+ #done = false;
16
+ /**
17
+ * @param session - session MongoDB transactionnelle sous-jacente.
18
+ */
19
+ constructor(session) {
20
+ this.#session = session;
21
+ }
22
+ /** Indique si la transaction est déjà terminée (commit ou rollback). */
23
+ isDone() {
24
+ return this.#done;
25
+ }
26
+ /**
27
+ * Valide la transaction (no-op si déjà terminée — idempotent, parité avec
28
+ * `DrizzleTransaction`). En mode managé (défaut via `IOrm.transaction`), le
29
+ * commit/abort est piloté par `session.withTransaction` : un appel manuel est
30
+ * inutile, et l'idempotence évite un double-commit accidentel.
31
+ */
32
+ async commit() {
33
+ if (this.#done) return;
34
+ this.#done = true;
35
+ await this.#session.commitTransaction();
36
+ }
37
+ /** Annule la transaction (no-op si déjà terminée — idempotent). */
38
+ async rollback() {
39
+ if (this.#done) return;
40
+ this.#done = true;
41
+ await this.#session.abortTransaction();
42
+ }
43
+ /** No-op : MongoDB ne gère pas les savepoints. */
44
+ async savepoint(_name) {}
45
+ /** No-op : MongoDB ne gère pas les savepoints. */
46
+ async rollbackTo(_name) {}
47
+ /** Expose la `ClientSession` native (trappe bas niveau). */
48
+ getNative() {
49
+ return this.#session;
50
+ }
51
+ };
52
+ //#endregion
53
+ export { MongooseTransaction };
@@ -0,0 +1,4 @@
1
+ import { MongooseRepository } from "./MongooseRepository.js";
2
+ import { MongooseTransaction } from "./MongooseTransaction.js";
3
+ import { MongooseOrm } from "./MongooseOrm.js";
4
+ export { MongooseOrm, MongooseRepository, MongooseTransaction };
@@ -0,0 +1,74 @@
1
+ /**
2
+ * `@nodefony/mongoose` — module Mongoose ORM (driver NoSQL) sur `@nodefony/orm-core`.
3
+ *
4
+ * **Module bootable** : enregistré dans le manifeste `modules`, son
5
+ * {@link MongooseService} connecte au boot un {@link MongooseOrm} par connecteur
6
+ * configuré. Refonte 2026-06-08 (Ph.2 virage ORM) : ne dérive plus de l'`Orm`
7
+ * legacy du core — le service `extends Service` et orchestre des adapters
8
+ * orm-core autonomes (modèle `DrizzleService`). Le core ne connaît plus l'ORM.
9
+ *
10
+ * Config = source de vérité Zod (`nodefony/config/config.ts`), validée au boot
11
+ * via {@link defineMongooseConfig} (style `@nodefony/redis`/`@nodefony/realtime`).
12
+ */
13
+ import mongoose from "mongoose";
14
+ import { Kernel, Module } from "nodefony";
15
+ import { defineMongooseConfig, mongooseConfigJsonSchema } from "./nodefony/config/defineModuleConfig.js";
16
+ import MongooseService from "./nodefony/service/MongooseService.js";
17
+ import type { IMongooseConfig, IMongooseConfigInput } from "./nodefony/interfaces/IMongooseConfig.js";
18
+ declare module "nodefony" {
19
+ interface NodefonyModuleConfig {
20
+ "@nodefony/mongoose": IMongooseConfigInput;
21
+ }
22
+ }
23
+ declare class Mongoose extends Module<IMongooseConfig> {
24
+ /**
25
+ * Module **optionnel** (driver NoSQL externe, opt-in) : un échec de son boot
26
+ * (Mongo injoignable) ne tue jamais le process — le store de session dégrade
27
+ * gracieusement (`#repo()` → null). Résilience cloud-native (l'orchestrateur
28
+ * relèvera Mongo). Convention-frère `@nodefony/redis`.
29
+ */
30
+ static critical: boolean;
31
+ constructor(kernel: Kernel);
32
+ /** JSON Schema de la config mongoose → data plane admin (config riche Studio). */
33
+ configSchema(): unknown;
34
+ /**
35
+ * Valide la config (défauts + `module.options` + surcharge env) au boot via
36
+ * `defineMongooseConfig`, et l'expose au container sous `mongooseConfig` pour
37
+ * que le `MongooseService` la consomme sans redupliquer la validation. Plante
38
+ * propre avec messages clairs si la config est invalide (convention Zod).
39
+ */
40
+ onKernelRegister(): Promise<this>;
41
+ /**
42
+ * Monte le data plane ORM (`/nodefony/orm/api/*` + providers santé/flux) via
43
+ * {@link wireOrmAdminPlane} — branchement GLOBAL et idempotent factorisé en
44
+ * orm-core (C5), identique à Drizzle. Avant la factorisation, ce wiring était
45
+ * déclenché par le seul module Drizzle → une app Mongoose-only avait un Studio
46
+ * ORM muet ; chaque driver l'invoque désormais. En plus, enregistre l'adapter
47
+ * d'erreurs Mongoose (spécifique au driver, hors plan d'administration).
48
+ */
49
+ onKernelBoot(): Promise<this>;
50
+ }
51
+ export default Mongoose;
52
+ export { mongoose, MongooseService };
53
+ export { defineMongooseConfig, mongooseConfigJsonSchema };
54
+ export { mongooseConfigSchema, type MongooseConfig, } from "./nodefony/config/config.js";
55
+ export type { IMongooseConfig, IMongooseConfigInput, IMongooseConnectorConfig, } from "./nodefony/interfaces/IMongooseConfig.js";
56
+ export { default as SessionStorage } from "./nodefony/src/SessionStorage.js";
57
+ export { default as SessionEntity, sessionSchema, SESSION_CONNECTOR, } from "./nodefony/entity/sessionEntity.js";
58
+ export type { SessionRow } from "./nodefony/entity/sessionEntity.js";
59
+ export { MongooseOrm, MongooseRepository, MongooseTransaction, } from "./nodefony/src/orm-core/index.js";
60
+ export type { IIndexAudit } from "./nodefony/src/orm-core/index.js";
61
+ export { userSchema, createUserEntity, registerUserEntity, } from "./nodefony/entity/userEntity.js";
62
+ export type { UserRow } from "./nodefony/entity/userEntity.js";
63
+ export { MongooseUserRepository } from "./nodefony/src/MongooseUserRepository.js";
64
+ export { accessTokenSchema, deniedJtiSchema, subjectRevocationSchema, createTokenEntities, registerTokenEntities, TOKEN_ENTITY_NAMES, } from "./nodefony/entity/tokenEntity.js";
65
+ export type { DeniedJtiRow, SubjectRevocationRow, } from "./nodefony/entity/tokenEntity.js";
66
+ export { MongooseTokenStore } from "./nodefony/src/MongooseTokenStore.js";
67
+ export { webAuthnCredentialSchema, createWebAuthnCredentialEntity, registerWebAuthnCredentialEntity, WEBAUTHN_CREDENTIAL_ENTITY, } from "./nodefony/entity/webAuthnCredentialEntity.js";
68
+ export type { WebAuthnCredentialRow } from "./nodefony/entity/webAuthnCredentialEntity.js";
69
+ export { MongooseWebAuthnCredentialStore } from "./nodefony/src/MongooseWebAuthnCredentialStore.js";
70
+ export { webhookEndpointSchema, createWebhookEndpointEntity, registerWebhookEndpointEntity, WEBHOOK_ENDPOINT_ENTITY, } from "./nodefony/entity/webhookEndpointEntity.js";
71
+ export type { WebhookEndpointRow } from "./nodefony/entity/webhookEndpointEntity.js";
72
+ export { MongooseWebhookStore } from "./nodefony/src/MongooseWebhookStore.js";
73
+ export { registerMongooseFrameworkStores, FRAMEWORK_CONNECTOR, } from "./nodefony/registerStores.js";
74
+ export type { IFrameworkStoresReport } from "./nodefony/registerStores.js";
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+ export declare const mongooseConfigSchema: z.ZodObject<{
3
+ debug: z.ZodDefault<z.ZodBoolean>;
4
+ connectors: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
5
+ uri: z.ZodOptional<z.ZodString>;
6
+ host: z.ZodDefault<z.ZodString>;
7
+ port: z.ZodDefault<z.ZodNumber>;
8
+ dbname: z.ZodDefault<z.ZodString>;
9
+ autoIndex: z.ZodOptional<z.ZodBoolean>;
10
+ options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
11
+ }, z.core.$strict>>>;
12
+ frameworkEntities: z.ZodDefault<z.ZodBoolean>;
13
+ }, z.core.$strict>;
14
+ /** Type de sortie (config normalisée + défauts appliqués). */
15
+ export type MongooseConfig = z.infer<typeof mongooseConfigSchema>;
16
+ /**
17
+ * Défauts du module, matérialisés depuis le schéma (source unique). Toujours
18
+ * valides par construction ; passés au `super(..., config)` du Module class.
19
+ */
20
+ declare const config: MongooseConfig;
21
+ export default config;
@@ -0,0 +1,26 @@
1
+ import type { IMongooseConfig, IMongooseConfigInput } from "../interfaces/IMongooseConfig.js";
2
+ /**
3
+ * Builder type-safe de la configuration de `@nodefony/mongoose`.
4
+ *
5
+ * ⭐ TL;DR : MACHINERIE DE BOOT — on n'édite (presque) jamais ce fichier. Même
6
+ * pattern que `nodefony.config.ts` ↔ `defineConfig()` du core : `config.ts` PORTE
7
+ * la config (schéma + défauts), `define<X>Config()` la VALIDE au boot (parse +
8
+ * env + freeze) et publie le JSON Schema Studio.
9
+ *
10
+ * Principes (alignés sur `defineRedisConfig` / `defineRealtimeConfig`) :
11
+ * - **Source unique** : `./config.ts` (Zod). Le builder VALIDE, applique l'ENV,
12
+ * puis GÈLE — il ne dévie jamais du schéma.
13
+ * - **Auto-documenté** : chaque champ Zod porte un `.describe()` →
14
+ * {@link mongooseConfigJsonSchema} produit un JSON Schema qu'un formulaire Studio
15
+ * (futur) consommera pour générer son UI d'édition.
16
+ *
17
+ * @param config - configuration brute (sections omises = défauts sûrs).
18
+ * @returns config validée, surchargée par l'env, et gelée.
19
+ * @throws ZodError si la config est invalide.
20
+ */
21
+ export declare function defineMongooseConfig(config?: IMongooseConfigInput): IMongooseConfig;
22
+ /**
23
+ * JSON Schema introspectable de la config Mongoose — destiné au formulaire
24
+ * d'édition Studio (futur) et à la documentation générée.
25
+ */
26
+ export declare function mongooseConfigJsonSchema(): unknown;
@@ -0,0 +1,42 @@
1
+ import type { SchemaDefinition } from "mongoose";
2
+ /** ORM cible du stockage de session (connecteur par défaut du module). */
3
+ export declare const SESSION_CONNECTOR = "nodefony";
4
+ /**
5
+ * Schéma Mongoose de stockage des sessions (compilé par `MongooseOrm` au boot).
6
+ *
7
+ * Équivalent portable de l'entité session legacy : mêmes champs logiques
8
+ * (`session_id` PK applicative, sacs `Attributes`/`flashBag`/`metaBag`,
9
+ * `user`). Les horodatages sont des **nombres** (ms epoch), comme l'adapter
10
+ * Drizzle, pour que `SessionStorage` reste strictement portable entre les ORM
11
+ * (cutoff GC = `updatedAt < now - ttl`, opérateur riche `$lt` natif Mongo).
12
+ */
13
+ declare const schema: SchemaDefinition;
14
+ /** Forme plate d'une ligne de session telle que renvoyée par le repository. */
15
+ export interface SessionRow {
16
+ session_id: string;
17
+ Attributes: unknown;
18
+ flashBag: unknown;
19
+ metaBag: unknown;
20
+ user: string | null;
21
+ createdAt: number;
22
+ updatedAt: number;
23
+ }
24
+ /**
25
+ * Entité session enregistrée dans le `entityRegistry` pour le connecteur
26
+ * `nodefony` — `MongooseOrm` compile le modèle à la connexion (au boot).
27
+ *
28
+ * ⚠️ **`frameworkEntities: false` ne la coupe PAS.** Ce commutateur gouverne les
29
+ * entités déclarées dans le flux de boot (tokens, webauthn, webhooks) ; la
30
+ * session, elle, s'enregistre à l'**import** de ce fichier — le décorateur
31
+ * `@entity` s'exécute au chargement du barrel, avant que la moindre config soit
32
+ * lue. Même chose pour son store (`SessionStorage.ts`, `registerStorage`).
33
+ *
34
+ * Conséquence pratique : couper `frameworkEntities` retire les autres entités,
35
+ * pas la collection `session`. Pour ne pas avoir de session Mongo du tout, il
36
+ * faut ne pas router le store session vers mongoose (`session.store`), pas
37
+ * baisser ce drapeau.
38
+ */
39
+ declare class SessionEntity {
40
+ }
41
+ export default SessionEntity;
42
+ export { schema as sessionSchema };
@@ -0,0 +1,59 @@
1
+ import type { SchemaDefinition } from "mongoose";
2
+ import type { IEntity } from "@nodefony/orm-core";
3
+ /**
4
+ * Schémas Mongoose du **store de jetons** `@nodefony/security` (pendant
5
+ * documentaire des tables Drizzle) — implémentation NoSQL d'`ITokenStore` (PAT,
6
+ * refresh, denylist `jti`, seuil de révocation en masse).
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 jeton est un **jti fourni par l'appelant** (pas généré par
11
+ * Mongo), on force `_id: String` → le jti EST la clé primaire (gratuit : unique +
12
+ * éligible à un TTL index natif). Le virtuel `id` (activé par `MongooseOrm`,
13
+ * `toObject:{virtuals:true}`) renvoie `String(_id)` = le jti.
14
+ *
15
+ * ⚠️ **Horodatages = `Number` (epoch ms), `timestamps:false`** : `IAccessTokenRecord`
16
+ * porte des `number` (`Date.now()`) et l'appelant fournit `createdAt` → pas de
17
+ * gestion auto Mongoose. Le `gc()` applicatif reste portable (`$lte` exclut les
18
+ * `null` par type bracketing Mongo, comme Drizzle exclut `NULL`).
19
+ */
20
+ export declare const accessTokenSchema: SchemaDefinition;
21
+ /** Denylist des access tokens (`jti` = `_id`) révoqués avant leur `exp`. */
22
+ export declare const deniedJtiSchema: SchemaDefinition;
23
+ /** Forme plate d'une ligne de denylist (`id` = virtuel = jti). */
24
+ export interface DeniedJtiRow {
25
+ id: string;
26
+ expiresAt: number;
27
+ }
28
+ /** Seuil de révocation en masse par porteur (`subjectId` = `_id`). */
29
+ export declare const subjectRevocationSchema: SchemaDefinition;
30
+ /** Forme plate d'une ligne de révocation par porteur (`id` = virtuel = subjectId). */
31
+ export interface SubjectRevocationRow {
32
+ id: string;
33
+ invalidBefore: number;
34
+ }
35
+ /** Noms logiques des entités du store (clés de lookup `getRepository`). */
36
+ export declare const TOKEN_ENTITY_NAMES: {
37
+ readonly records: "access_token";
38
+ readonly denied: "denied_jti";
39
+ readonly revocations: "subject_revocation";
40
+ };
41
+ /**
42
+ * Construit les descripteurs d'entités du store de jetons pour un ORM nommé.
43
+ *
44
+ * Le `connector` est **dynamique** (nom du connecteur de l'app, ex. `"nodefony"`) : les
45
+ * schémas sont statiques mais leur liaison à un ORM dépend de la config → pas
46
+ * d'`@entity` figé (parité `createUserEntity`). `timestamps:false` (l'appelant
47
+ * gère `createdAt`). À enregistrer **avant** `orm.connect()`.
48
+ *
49
+ * @param orm - clé de l'ORM cible dans le `ormRegistry`.
50
+ * @returns les trois descripteurs {@link IEntity} (records / denylist / seuils).
51
+ */
52
+ export declare function createTokenEntities(connector: string): IEntity[];
53
+ /**
54
+ * Enregistre les entités du store de jetons dans le `entityRegistry` pour un ORM
55
+ * donné. À appeler **avant** `orm.connect()` (le modèle est compilé au connect).
56
+ *
57
+ * @param connector - nom de la connexion cible (clé du registre).
58
+ */
59
+ export declare function registerTokenEntities(connector: string): void;
@@ -0,0 +1,54 @@
1
+ import type { SchemaDefinition } from "mongoose";
2
+ import type { IEntity } from "@nodefony/orm-core";
3
+ import type { IUserColumn } from "@nodefony/user";
4
+ /**
5
+ * Schéma Mongoose de l'utilisateur Nodefony — implémentation NoSQL du contrat
6
+ * `@nodefony/user`, **dérivée** de `USER_COLUMNS` (pendant documentaire de
7
+ * `userTable`).
8
+ *
9
+ * Rien n'est recopié : noms, types logiques, défauts et unicité viennent du
10
+ * contrat, et `tests/unit/userContractParity.test.ts` refuse le contraire.
11
+ *
12
+ * Deux origines du contrat ne sont pas déclarées ici, et c'est voulu : la clé
13
+ * primaire est `_id` (ObjectId), servie au contrat `id: string` par le
14
+ * **virtuel `id`** activé à la sérialisation par `MongooseOrm`
15
+ * (`toObject/toJSON: { virtuals: true }`) ; les horodatages sont gérés par
16
+ * l'option `timestamps: true` du descripteur. Le moteur les fournit — les
17
+ * redéclarer les mettrait en concurrence avec lui.
18
+ */
19
+ export declare const userSchema: SchemaDefinition;
20
+ /**
21
+ * Les colonnes du contrat qu'un schéma DOCUMENT porte EN PROPRE.
22
+ *
23
+ * Dérivée de {@link userSchema}, jamais recopiée : ce que le framework exige de
24
+ * l'entité d'une application est exactement ce qu'il produit pour la sienne. La
25
+ * clé (`_id` + virtuel `id`) et les horodatages (option `timestamps`) restent
26
+ * donc hors de cette liste — les exiger comme chemins refuserait une entité
27
+ * parfaitement correcte, et un refus faux apprend à passer outre les refus.
28
+ */
29
+ export declare const DOCUMENT_USER_COLUMNS: readonly IUserColumn[];
30
+ /**
31
+ * Forme plate d'une ligne `User` (virtuel `id` + horodatages inclus) renvoyée par
32
+ * le repository — le contrat `IUserRow` de `@nodefony/user`, ré-exporté sous son
33
+ * nom historique.
34
+ */
35
+ export type { IUserRow as UserRow } from "@nodefony/user";
36
+ /**
37
+ * Construit le descripteur d'entité `User` Mongoose pour un ORM nommé.
38
+ *
39
+ * Le `connector` est **dynamique** (nom du connecteur de l'app, ex. `"nodefony"`) : le
40
+ * schéma est statique mais sa liaison à un ORM dépend de la config → pas d'`@entity`
41
+ * figé (parité avec `createUserEntity` Drizzle). À enregistrer **avant**
42
+ * `orm.connect()` (le modèle est compilé à la connexion par `MongooseOrm`).
43
+ *
44
+ * @param orm - clé de l'ORM cible dans le `ormRegistry`.
45
+ * @returns descripteur {@link IEntity} (`name: "User"`, `timestamps: true`).
46
+ */
47
+ export declare function createUserEntity(connector: string): IEntity;
48
+ /**
49
+ * Enregistre l'entité `User` Mongoose dans le `entityRegistry` pour un ORM donné.
50
+ * À appeler **avant** `orm.connect()` (le modèle est compilé au connect).
51
+ *
52
+ * @param connector - nom de la connexion cible (clé du registre).
53
+ */
54
+ export declare function registerUserEntity(connector: string): void;