@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,440 @@
1
+ import { MongooseRepository } from "./MongooseRepository.js";
2
+ import { MongooseTransaction } from "./MongooseTransaction.js";
3
+ import { createRequire } from "node:module";
4
+ import mongoose from "mongoose";
5
+ import { Orm, entityRegistry } from "@nodefony/orm-core";
6
+ import path from "node:path";
7
+ import fs from "node:fs";
8
+ //#region nodefony/src/orm-core/MongooseOrm.ts
9
+ /**
10
+ * Nom canonique d'un index à partir de sa définition (`{ identifier: 1 }` →
11
+ * `identifier_1`) — la convention de nommage de MongoDB lui-même, pour que le
12
+ * journal désigne l'index sous le nom qu'un exploitant lira dans la base.
13
+ */
14
+ function indexName(definition) {
15
+ if (!definition || typeof definition !== "object") return String(definition);
16
+ return Object.entries(definition).map(([field, direction]) => `${field}_${String(direction)}`).join("_");
17
+ }
18
+ /**
19
+ * Adapter Mongoose **branché sur `@nodefony/orm-core`** (P5.4).
20
+ *
21
+ * 2ᵉ adapter, **hétérogène** au SQL : valide que le contrat enrichi
22
+ * (`relations`/`withTransaction`) est réellement portable sur un store
23
+ * documentaire. Distinct du service legacy `nodefony/service/orm.ts`.
24
+ *
25
+ * Spécificités MongoDB exposées par l'implémentation :
26
+ * - **connexion isolée** via `mongoose.createConnection` (pas le singleton global)
27
+ * → indispensable au multi-ORM (plusieurs connexions logiques) ;
28
+ * - relations sans clé étrangère SQL : `one-to-many` = **virtual populate**
29
+ * (réf ObjectId injectée sur l'enfant + virtuel sur le parent), `many-to-one`/
30
+ * `one-to-one` = champ réf sur la source. `many-to-many` → natif ;
31
+ * - transactions = **sessions** (requièrent un replica set).
32
+ */
33
+ var MongooseOrm = class MongooseOrm extends Orm {
34
+ #connection = null;
35
+ /**
36
+ * Listeners de cycle de vie attachés à la connexion Mongoose — gardés pour
37
+ * pouvoir les DÉTACHER : `disconnect()` puis `connect()` sur le même ORM
38
+ * empilerait sinon un jeu de listeners par cycle (règle « pas de listener
39
+ * sans cleanup »). `null` tant qu'aucune connexion n'est ouverte.
40
+ */
41
+ #lifecycle = null;
42
+ /**
43
+ * Mongoose traduit les signaux de topologie du driver MongoDB (SDAM) : il
44
+ * SAIT qu'un serveur est tombé, même sans le moindre trafic — d'où
45
+ * `"events"`. C'est la seule des trois familles d'adapters du dépôt qui
46
+ * dispose d'une surveillance de serveur indépendante des requêtes.
47
+ */
48
+ get liveness() {
49
+ return "events";
50
+ }
51
+ #models = null;
52
+ #repositories = null;
53
+ #uri;
54
+ #options;
55
+ /**
56
+ * @param name - clé unique de l'ORM dans le `ormRegistry`.
57
+ * @param uri - URI de connexion MongoDB (replica set requis pour les tx).
58
+ * @param options - options de connexion Mongoose (auth, pool, timeouts).
59
+ */
60
+ constructor(name, uri, options) {
61
+ super(name);
62
+ this.#uri = uri;
63
+ this.#options = options;
64
+ }
65
+ /** Entités enregistrées ciblant cet ORM. */
66
+ #ownEntities() {
67
+ return entityRegistry.list().filter((entity) => entity.connector === this.name);
68
+ }
69
+ /** FK déterministe camelCase `<entité>Id` (réf ObjectId côté enfant). */
70
+ #foreignKey(entityName) {
71
+ return `${entityName.charAt(0).toLowerCase()}${entityName.slice(1)}Id`;
72
+ }
73
+ async onConnect() {
74
+ if (this.#connection) {
75
+ this.#unwireLifecycle();
76
+ await this.#connection.close().catch(() => void 0);
77
+ this.#connection = null;
78
+ }
79
+ const connection = mongoose.createConnection(this.#uri, this.#healthyTimeouts());
80
+ await connection.asPromise();
81
+ this.#connection = connection;
82
+ this.#wireLifecycle(connection);
83
+ this.#models = Object.create(null);
84
+ const entities = this.#ownEntities();
85
+ const schemas = /* @__PURE__ */ new Map();
86
+ for (const entity of entities) schemas.set(entity.name, new mongoose.Schema(entity.schema, {
87
+ toObject: { virtuals: true },
88
+ toJSON: { virtuals: true },
89
+ timestamps: entity.timestamps ?? false
90
+ }));
91
+ for (const entity of entities) {
92
+ if (!entity.relations) continue;
93
+ const sourceSchema = schemas.get(entity.name);
94
+ for (const relation of entity.relations) {
95
+ const targetSchema = schemas.get(relation.target);
96
+ if (!sourceSchema || !targetSchema) throw new Error(`MongooseOrm "${this.name}": relation target "${relation.target}" (from "${entity.name}.${relation.field}") not registered for this ORM.`);
97
+ switch (relation.type) {
98
+ case "one-to-many": {
99
+ const fk = relation.foreignKey ?? this.#foreignKey(entity.name);
100
+ if (!targetSchema.path(fk)) targetSchema.add({ [fk]: {
101
+ type: mongoose.Schema.Types.ObjectId,
102
+ ref: entity.name
103
+ } });
104
+ sourceSchema.virtual(relation.field, {
105
+ ref: relation.target,
106
+ localField: "_id",
107
+ foreignField: fk
108
+ });
109
+ break;
110
+ }
111
+ case "many-to-one":
112
+ case "one-to-one": {
113
+ const fk = relation.foreignKey ?? relation.field;
114
+ if (!sourceSchema.path(fk)) sourceSchema.add({ [fk]: {
115
+ type: mongoose.Schema.Types.ObjectId,
116
+ ref: relation.target
117
+ } });
118
+ break;
119
+ }
120
+ case "many-to-many": throw new Error(`MongooseOrm "${this.name}": many-to-many ("${entity.name}.${relation.field}") non portable — déclarer via getNativeConnection().`);
121
+ }
122
+ }
123
+ }
124
+ for (const entity of entities) {
125
+ const model = connection.model(entity.name, schemas.get(entity.name));
126
+ this.#models[entity.name] = model;
127
+ entity.model = model;
128
+ }
129
+ const audit = this.verifyIndexes();
130
+ this.#indexAudit = audit;
131
+ audit.catch(() => void 0);
132
+ }
133
+ /**
134
+ * Constat d'index en cours — la promesse de la passe lancée au `connect()`.
135
+ *
136
+ * Exposée pour que ce qui doit SAVOIR puisse attendre (un banc, une sonde
137
+ * d'administration, un futur point de disponibilité) sans que le démarrage,
138
+ * lui, ait à le faire.
139
+ */
140
+ #indexAudit = null;
141
+ /** Constat d'index de la connexion courante, ou `null` hors connexion. */
142
+ get pendingIndexAudit() {
143
+ return this.#indexAudit;
144
+ }
145
+ /**
146
+ * Constate l'écart entre les index DÉCLARÉS par les schémas et ceux que la
147
+ * base porte réellement, et journalise tout manque en `CRITIC`.
148
+ *
149
+ * **Pourquoi c'est nécessaire.** Mongoose construit les index en tâche de
150
+ * fond à la compilation des modèles, et l'issue de cette construction n'était
151
+ * écoutée par personne. Or plusieurs de ces index portent des contraintes
152
+ * d'unicité dont dépend l'authentification. Reproduit sur un serveur réel :
153
+ * une collection portant déjà des doublons fait échouer la construction de
154
+ * l'index unique — et le process continue, code de sortie 0, **sans un seul
155
+ * message**, avec pour seul index `_id_`. La contrainte n'existe pas, et rien
156
+ * ne le dit : c'est la dégradation silencieuse que la doctrine interdit.
157
+ *
158
+ * **Pourquoi APRÈS `init()`.** Un `diffIndexes()` lancé aussitôt après la
159
+ * compilation annonce comme manquants des index dont la construction est
160
+ * simplement en cours — mesuré. `init()` est le point où la construction est
161
+ * terminée, en succès comme en échec ; c'est donc là, et pas avant, que
162
+ * l'écart veut dire quelque chose.
163
+ *
164
+ * **Pourquoi ce n'est pas attendu au démarrage.** Construire un index sur une
165
+ * grosse collection prend des minutes ; faire patienter le pod changerait son
166
+ * comportement bien au-delà de ce défaut. Le constat court donc en tâche de
167
+ * fond et parle dès qu'il sait — {@link MongooseOrm.pendingIndexAudit} permet
168
+ * de l'attendre quand il le faut.
169
+ *
170
+ * **Ce que cette méthode ne fait PAS** : réparer. `syncIndexes()` de mongoose
171
+ * SUPPRIME les index non déclarés — irréversible, et catastrophique sur une
172
+ * base qu'un exploitant a indexée à la main. Réparer reste un geste explicite.
173
+ *
174
+ * @returns un verdict par entité (vide si la connexion est déjà close).
175
+ */
176
+ async verifyIndexes() {
177
+ const models = this.#models;
178
+ if (!models) return [];
179
+ const audits = [];
180
+ for (const [name, model] of Object.entries(models)) {
181
+ const audit = {
182
+ entity: name,
183
+ collection: model.collection?.name ?? name,
184
+ missing: [],
185
+ extra: []
186
+ };
187
+ try {
188
+ await model.init();
189
+ } catch (error) {
190
+ audit.error = error instanceof Error ? error.message : String(error);
191
+ }
192
+ try {
193
+ const diff = await model.diffIndexes();
194
+ audit.missing = diff.toCreate.map(indexName);
195
+ audit.extra = diff.toDrop.map(indexName);
196
+ } catch (error) {
197
+ if (this.isConnected()) audit.error ??= error instanceof Error ? error.message : String(error);
198
+ else return audits;
199
+ }
200
+ this.#reportIndexAudit(audit);
201
+ audits.push(audit);
202
+ }
203
+ return audits;
204
+ }
205
+ /**
206
+ * Porte un verdict d'index au journal, à la hauteur de ce qu'il signifie.
207
+ *
208
+ * Un index manquant est un `CRITIC` : la contrainte que le code croit tenir
209
+ * n'est pas tenue. Un index en trop n'est qu'une information — il vient
210
+ * souvent d'un exploitant qui savait ce qu'il faisait, et rien ici ne le
211
+ * supprimera.
212
+ */
213
+ #reportIndexAudit(audit) {
214
+ if (audit.error) this.log(`index de "${audit.entity}" (collection "${audit.collection}") : la base a refusé la construction — ${audit.error}`, "CRITIC");
215
+ if (audit.missing.length > 0) this.log(`index DÉCLARÉS mais ABSENTS de la collection "${audit.collection}" (entité "${audit.entity}") : ${audit.missing.join(", ")} — toute contrainte d'unicité qu'ils portent n'est PAS appliquée`, "CRITIC");
216
+ if (audit.extra.length > 0) this.log(`index présents en base et non déclarés sur "${audit.collection}" : ${audit.extra.join(", ")} — laissés en place`, "INFO");
217
+ }
218
+ /**
219
+ * Délais d'attente **par défaut**, plus courts que ceux du driver.
220
+ *
221
+ * Le driver MongoDB attend **30 s** pour trouver un serveur utilisable
222
+ * (`serverSelectionTimeoutMS`) et autant pour établir une connexion
223
+ * (`connectTimeoutMS`) — vérifié au source, `connection_string.js`. Mesuré
224
+ * sur une base arrêtée : une requête PEND 30 s avant d'échouer. Pour une
225
+ * requête HTTP c'est absurde : le client a abandonné depuis longtemps, et
226
+ * le worker reste bloqué à attendre une base dont on sait déjà qu'elle ne
227
+ * répond pas.
228
+ *
229
+ * **5 s** est tenable parce que `retryReads`/`retryWrites` valent `true`
230
+ * par défaut : une opération qui échoue en sélection est RETENTÉE, ce qui
231
+ * porte la fenêtre effective à une dizaine de secondes — de quoi absorber
232
+ * l'élection d'un nouveau primaire sans faire attendre le client deux fois
233
+ * plus longtemps que nécessaire.
234
+ *
235
+ * `socketTimeoutMS` n'est délibérément PAS touché (le driver le laisse
236
+ * infini) : le borner ici tuerait les agrégations longues légitimes. Ce
237
+ * rôle revient à `timeoutMS` (CSOT), que seule l'application peut fixer en
238
+ * connaissance de ses opérations.
239
+ *
240
+ * 🔴 **Un choix EXPLICITE gagne toujours** — qu'il vienne des options ou de
241
+ * la chaîne de connexion. Poser un défaut n'autorise pas à écraser une
242
+ * intention : une URI qui porte `?serverSelectionTimeoutMS=20000` dit ce
243
+ * qu'elle veut, et l'objet d'options primerait silencieusement sur elle.
244
+ */
245
+ #healthyTimeouts() {
246
+ const provided = this.#options ?? {};
247
+ const inUri = (key) => new RegExp(`[?&]${key}=`, "iu").test(this.#uri);
248
+ const defaults = {};
249
+ if (provided.serverSelectionTimeoutMS === void 0 && !inUri("serverSelectionTimeoutMS")) defaults.serverSelectionTimeoutMS = 5e3;
250
+ if (provided.connectTimeoutMS === void 0 && !inUri("connectTimeoutMS")) defaults.connectTimeoutMS = 5e3;
251
+ return {
252
+ ...defaults,
253
+ ...provided
254
+ };
255
+ }
256
+ /**
257
+ * Traduit les événements du driver en signaux du contrat `orm-core`.
258
+ *
259
+ * Mongoose SAIT quand le serveur tombe — le setter de `readyState` émet
260
+ * l'état, et le driver câble `serverDescriptionChanged` (topologie simple)
261
+ * ou `topologyDescriptionChanged` (replica set : perte du primaire). Rien
262
+ * n'écoutait, d'où une santé ORM qui affirmait « connecté » pendant toute
263
+ * une coupure. `error` est écouté AUSSI parce qu'une `Connection` est un
264
+ * `EventEmitter` : sans auditeur, une erreur émise ferait tomber le process.
265
+ */
266
+ #wireLifecycle(connection) {
267
+ const lost = (why) => () => this.connectionLost(why);
268
+ const listeners = [
269
+ ["disconnected", lost("mongoose: disconnected")],
270
+ ["close", lost("mongoose: close")],
271
+ ["error", ((e) => this.connectionLost(`mongoose: ${e?.message ?? String(e)}`))],
272
+ ["reconnected", () => this.connectionRestored()],
273
+ ["connected", () => this.connectionRestored()]
274
+ ];
275
+ for (const [event, handler] of listeners) connection.on(event, handler);
276
+ this.#lifecycle = listeners;
277
+ }
278
+ /** Détache les listeners de cycle de vie (anti-fuite, anti-empilement). */
279
+ #unwireLifecycle() {
280
+ const connection = this.#connection;
281
+ if (connection && this.#lifecycle) for (const [event, handler] of this.#lifecycle) connection.removeListener(event, handler);
282
+ this.#lifecycle = null;
283
+ }
284
+ async disconnect() {
285
+ this.alive = false;
286
+ this.stopHeartbeat();
287
+ this.#unwireLifecycle();
288
+ const connection = this.#connection;
289
+ if (connection) {
290
+ const sink = () => void 0;
291
+ connection.on("error", sink);
292
+ await connection.close();
293
+ }
294
+ this.#connection = null;
295
+ this.#models = null;
296
+ this.#repositories = null;
297
+ this.#indexAudit = null;
298
+ }
299
+ getRepository(name) {
300
+ const model = this.#models?.[name];
301
+ if (!model) throw new Error(`MongooseOrm "${this.name}": no entity model registered under "${name}".`);
302
+ if (this.#repositories === null) this.#repositories = Object.create(null);
303
+ let repository = this.#repositories[name];
304
+ if (repository === void 0) {
305
+ repository = new MongooseRepository(model, this.name);
306
+ this.#repositories[name] = repository;
307
+ }
308
+ return repository;
309
+ }
310
+ async transaction(work) {
311
+ const connection = this.#connection;
312
+ if (!connection) throw new Error(`MongooseOrm "${this.name}": not connected.`);
313
+ const session = await connection.startSession();
314
+ let result;
315
+ try {
316
+ await session.withTransaction(async () => {
317
+ result = await work(new MongooseTransaction(session));
318
+ });
319
+ } finally {
320
+ await session.endSession();
321
+ }
322
+ return result;
323
+ }
324
+ getNativeConnection() {
325
+ if (!this.#connection) throw new Error(`MongooseOrm "${this.name}": not connected.`);
326
+ return this.#connection;
327
+ }
328
+ /**
329
+ * Ping bas-coût : commande `{ ping: 1 }` sur la base native (`admin().command`)
330
+ * — round-trip réel vers MongoDB pour le diagnostic du data plane.
331
+ *
332
+ * @throws si la connexion (ou sa base native) n'est pas prête, ou si la base
333
+ * ne répond pas.
334
+ */
335
+ async ping() {
336
+ const db = this.#connection?.db;
337
+ if (!db) throw new Error(`MongooseOrm "${this.name}": not connected.`);
338
+ await db.admin().command({ ping: 1 });
339
+ }
340
+ /**
341
+ * Sonde Mongo (best-effort) : connexions du serveur (`serverStatus`) → pool.
342
+ * Round-trip réseau → uniquement pendant un abonnement actif. `{}` si indispo.
343
+ *
344
+ * @returns sonde `pool` + `extra`, ou `{}`.
345
+ */
346
+ async probe() {
347
+ const db = this.#connection?.db;
348
+ if (!db) return {};
349
+ try {
350
+ const status = await db.admin().serverStatus();
351
+ const conn = status.connections;
352
+ return {
353
+ pool: {
354
+ borrowed: conn?.current,
355
+ available: conn?.available
356
+ },
357
+ extra: status.version ? { serverVersion: status.version } : {}
358
+ };
359
+ } catch {
360
+ return {};
361
+ }
362
+ }
363
+ /**
364
+ * Colonnes normalisées d'une entité depuis les `paths` du schéma Mongoose —
365
+ * alimente le graphe canonique / ERD / contexte IA. Pas de PK SQL : `_id` est
366
+ * la clé primaire implicite de tout document.
367
+ *
368
+ * @param name - nom logique de l'entité.
369
+ * @returns colonnes (`[]` si l'entité n'est pas connue de cet ORM).
370
+ */
371
+ describeEntity(name) {
372
+ const model = this.#models?.[name];
373
+ if (!model) return [];
374
+ const paths = model.schema.paths;
375
+ return Object.entries(paths).map(([field, schemaType]) => ({
376
+ name: field,
377
+ type: schemaType.instance || "Mixed",
378
+ primaryKey: field === "_id",
379
+ nullable: field === "_id" ? false : schemaType.isRequired !== true,
380
+ unique: schemaType.options.unique === true
381
+ }));
382
+ }
383
+ /**
384
+ * Décrit la connexion : driver `mongodb` + cible (hôte:port/base, **sans
385
+ * credentials**) + version de la lib `mongoose`. Aucun secret n'est exposé
386
+ * dans le data plane (l'URI est nettoyée de tout `user:pass@`).
387
+ *
388
+ * @returns driver + cible nettoyée + version de l'ORM.
389
+ */
390
+ describeConnection() {
391
+ return {
392
+ driver: "mongodb",
393
+ target: this.safeTarget(),
394
+ ormVersion: MongooseOrm.#ormVersion()
395
+ };
396
+ }
397
+ /**
398
+ * Cible affichable de l'URI, **sans credentials** : `hôte:port/base`. Jamais
399
+ * de `user:pass@` (anti info-leak dans le data plane / les logs).
400
+ */
401
+ safeTarget() {
402
+ try {
403
+ const url = new URL(this.#uri);
404
+ url.username = "";
405
+ url.password = "";
406
+ return `${url.host}${url.pathname}`;
407
+ } catch {
408
+ return this.#uri.replace(/^mongodb(\+srv)?:\/\//, "").replace(/^[^@/]*@/, "").replace(/\?.*$/, "");
409
+ }
410
+ }
411
+ /** Version de la lib `mongoose` (résolue + cachée une seule fois). */
412
+ static #cachedOrmVersion;
413
+ static #ormVersion() {
414
+ if (MongooseOrm.#cachedOrmVersion === void 0) MongooseOrm.#cachedOrmVersion = MongooseOrm.#resolvePkgVersion("mongoose") ?? null;
415
+ return MongooseOrm.#cachedOrmVersion ?? void 0;
416
+ }
417
+ /**
418
+ * Version d'un package npm via son `package.json` (`createRequire` + remontée
419
+ * FS). `require("<pkg>/package.json")` direct échoue souvent : `exports` ne
420
+ * publie pas toujours `./package.json`.
421
+ */
422
+ static #resolvePkgVersion(name) {
423
+ try {
424
+ const req = createRequire(import.meta.url);
425
+ let dir = path.dirname(req.resolve(name));
426
+ for (let i = 0; i < 8; i++) {
427
+ const pkgPath = path.join(dir, "package.json");
428
+ if (fs.existsSync(pkgPath)) {
429
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
430
+ if (pkg.name === name) return pkg.version;
431
+ }
432
+ const parent = path.dirname(dir);
433
+ if (parent === dir) break;
434
+ dir = parent;
435
+ }
436
+ } catch {}
437
+ }
438
+ };
439
+ //#endregion
440
+ export { MongooseOrm };