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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -63,10 +63,10 @@ let Mongoose = class Mongoose extends Module {
63
63
  * propre avec messages clairs si la config est invalide (convention Zod).
64
64
  */
65
65
  async onKernelRegister() {
66
- const validated = defineMongooseConfig(this.options ?? {});
66
+ const validated = defineMongooseConfig(this.options);
67
67
  this.options = validated;
68
68
  registerUserStore("mongoose");
69
- if (validated.frameworkEntities !== false) {
69
+ if (validated.frameworkEntities) {
70
70
  const report = registerMongooseFrameworkStores();
71
71
  if (report.appOwned.length) this.log(`schéma framework : entités déjà enregistrées par l'app [${report.appOwned.join(", ")}] — auto-register respecte l'app`, "DEBUG");
72
72
  if (report.registered.length) this.log(`schéma framework déclaré sur "${FRAMEWORK_CONNECTOR}" : [${report.registered.join(", ")}]`, "DEBUG");
@@ -85,7 +85,7 @@ let Mongoose = class Mongoose extends Module {
85
85
  wireOrmAdminPlane(this.kernel);
86
86
  registerErrorAdapter("mongoose", {
87
87
  isError: (e) => e instanceof mongoose.Error,
88
- errorToString: (e) => String(e?.message ?? e)
88
+ errorToString: (e) => e instanceof Error ? e.message : String(e)
89
89
  });
90
90
  return this;
91
91
  }
@@ -18,10 +18,11 @@ import { z } from "zod";
18
18
  function applyEnvOverrides(config) {
19
19
  const env = process.env;
20
20
  const database = resolveInfra(env).database;
21
- const uri = env.MONGODB_URI ?? (database && database.family === "mongo" ? database.url : void 0);
21
+ const uri = env.MONGODB_URI ?? (database?.family === "mongo" ? database.url : void 0);
22
22
  if (uri) {
23
- const target = config.connectors.nodefony ? "nodefony" : Object.keys(config.connectors)[0];
24
- if (target && config.connectors[target]) config.connectors[target].uri = uri;
23
+ const target = Object.hasOwn(config.connectors, "nodefony") ? "nodefony" : Object.keys(config.connectors).at(0);
24
+ const connector = target === void 0 ? void 0 : config.connectors[target];
25
+ if (connector !== void 0) connector.uri = uri;
25
26
  }
26
27
  if (env.NF_MONGODB_DEBUG === "1" || env.NF_MONGODB_DEBUG === "true") config.debug = true;
27
28
  return config;
@@ -83,7 +83,7 @@ function registerMongooseFrameworkStores() {
83
83
  if (getTokenStoreFactory("mongoose")) return;
84
84
  registerTokenStore("mongoose", (ctx) => {
85
85
  const orm = resolveConnectedOrm(`tokenStore "mongoose"`);
86
- const days = ctx?.config?.tokenStore?.retentionRevokedDays;
86
+ const days = ctx.config.tokenStore.retentionRevokedDays;
87
87
  return MongooseTokenStore.from(orm, void 0, typeof days === "number" ? days * 864e5 : void 0);
88
88
  });
89
89
  });
@@ -99,7 +99,7 @@ function registerMongooseFrameworkStores() {
99
99
  if (getAuditStoreFactory("mongoose")) return;
100
100
  registerAuditStore("mongoose", (ctx) => {
101
101
  const orm = resolveConnectedOrm(`audit.store "mongoose"`);
102
- const days = ctx?.config?.audit?.retentionDays;
102
+ const days = ctx.config.audit.retentionDays;
103
103
  return MongooseAuditStore.from(orm, void 0, typeof days === "number" ? days * 864e5 : void 0);
104
104
  });
105
105
  });
@@ -21,7 +21,7 @@ var MongooseService = class MongooseService extends Service {
21
21
  /** ORM connectés, indexés par nom de connecteur. */
22
22
  #orms = /* @__PURE__ */ new Map();
23
23
  constructor(module) {
24
- super(serviceName, module.container, module.notificationsCenter, module.options ?? {});
24
+ super(serviceName, module.container, module.notificationsCenter, module.options);
25
25
  this.module = module;
26
26
  this.module.hookKernel("onBoot", async () => {
27
27
  queryFlowMonitor.setEnabled(resolveOrmFlowEnabled(this.kernel));
@@ -34,7 +34,14 @@ var MongooseService = class MongooseService extends Service {
34
34
  await this.disconnectAll().catch(() => {});
35
35
  });
36
36
  }
37
- /** Config validée (Zod) exposée par le Module (`this.module.config`). */
37
+ /**
38
+ * Config du module telle que le service la TROUVE.
39
+ *
40
+ * Validée par Zod au boot du kernel ; mais le service se construit aussi hors
41
+ * kernel (bancs, usage direct) avec une config partielle, voire absente — et
42
+ * « config absente → rien à connecter » est un contrat couvert par
43
+ * `MongooseService.test.ts`. Le type le dit, donc chaque lecture garde.
44
+ */
38
45
  #config() {
39
46
  return this.module.config;
40
47
  }
@@ -1,3 +1,4 @@
1
+ import { writeCount } from "./writeCount.js";
1
2
  import { AUDIT_ENTITY_NAMES } from "../entity/auditEventEntity.js";
2
3
  import { assertPageQuery } from "nodefony";
3
4
  //#region nodefony/src/MongooseAuditStore.ts
@@ -133,7 +134,7 @@ var MongooseAuditStore = class MongooseAuditStore {
133
134
  const hasNext = docs.length > limit;
134
135
  const rows = (hasNext ? docs.slice(0, limit) : docs).map((doc) => doc.toObject({ virtuals: true }));
135
136
  const items = rows.map((row) => this.#toEvent(row));
136
- const last = rows[rows.length - 1];
137
+ const last = rows.at(-1);
137
138
  return {
138
139
  items,
139
140
  limit,
@@ -161,7 +162,8 @@ var MongooseAuditStore = class MongooseAuditStore {
161
162
  const model = this.#resolveModel();
162
163
  if (!model) return 0;
163
164
  const threshold = now - this.#retentionMs;
164
- return (await model.deleteMany({ ts: { $lt: threshold } }).exec()).deletedCount ?? 0;
165
+ const result = await model.deleteMany({ ts: { $lt: threshold } }).exec();
166
+ return writeCount(result.deletedCount);
165
167
  }
166
168
  /** Compose le filtre Mongo des critères AND ; `{}` si aucun (= tout). */
167
169
  #buildFilter(filter) {
@@ -1,3 +1,4 @@
1
+ import { writeCount } from "./writeCount.js";
1
2
  import { IDEMPOTENCY_ENTITY_NAME } from "../entity/idempotencyEntity.js";
2
3
  import { assertPageQuery, escapeRegExp } from "nodefony";
3
4
  //#region nodefony/src/MongooseIdempotencyStore.ts
@@ -164,22 +165,24 @@ var MongooseIdempotencyStore = class MongooseIdempotencyStore {
164
165
  async complete(key, response) {
165
166
  const model = this.#resolveModel();
166
167
  if (!model) return;
167
- if (((await model.updateOne({
168
+ const result = await model.updateOne({
168
169
  _id: key,
169
170
  state: "if"
170
171
  }, { $set: {
171
172
  state: "done",
172
173
  response,
173
174
  expiresAt: this.#now() + this.#ttlMs
174
- } }).exec()).modifiedCount ?? 0) > 0) this.#dec();
175
+ } }).exec();
176
+ if (writeCount(result.modifiedCount) > 0) this.#dec();
175
177
  }
176
178
  async abort(key) {
177
179
  const model = this.#resolveModel();
178
180
  if (!model) return;
179
- if (((await model.deleteOne({
181
+ const result = await model.deleteOne({
180
182
  _id: key,
181
183
  state: "if"
182
- }).exec()).deletedCount ?? 0) > 0) this.#dec();
184
+ }).exec();
185
+ if (writeCount(result.deletedCount) > 0) this.#dec();
183
186
  }
184
187
  /**
185
188
  * Purge les entrées mortes (`expiresAt <= now`) — supplée l'absence de TTL
@@ -193,7 +196,8 @@ var MongooseIdempotencyStore = class MongooseIdempotencyStore {
193
196
  async gc(now = this.#now()) {
194
197
  const model = this.#resolveModel();
195
198
  if (!model) return 0;
196
- return (await model.deleteMany({ expiresAt: { $lte: now } }).exec()).deletedCount ?? 0;
199
+ const result = await model.deleteMany({ expiresAt: { $lte: now } }).exec();
200
+ return writeCount(result.deletedCount);
197
201
  }
198
202
  /**
199
203
  * {@inheritDoc IIdempotencyStore.listPage}
@@ -242,7 +246,7 @@ var MongooseIdempotencyStore = class MongooseIdempotencyStore {
242
246
  items: page.map((doc) => ({
243
247
  key: doc._id,
244
248
  state: doc.state === "if" ? "in-flight" : "done",
245
- expiresAtMs: Number(doc.expiresAt),
249
+ expiresAtMs: doc.expiresAt,
246
250
  hasResponse: doc.state !== "if"
247
251
  })),
248
252
  total,
@@ -167,7 +167,7 @@ var MongooseUserRepository = class MongooseUserRepository {
167
167
  const offset = Math.max(0, Math.floor(query.offset ?? 0));
168
168
  const filter = this.#listFilter(query);
169
169
  const sort = toMongoSort(mongoOrder(query.order, this.sortableFields, USER_DEFAULT_ORDER));
170
- if (sort._id === void 0) sort._id = 1;
170
+ if (!Object.hasOwn(sort, "_id")) sort._id = 1;
171
171
  let cursor = this.#model.find(filter).sort(sort).skip(offset).limit(limit + 1);
172
172
  if (this.#session) cursor = cursor.session(this.#session);
173
173
  const docs = await cursor.exec();
@@ -107,7 +107,7 @@ var MongooseOrm = class MongooseOrm extends Orm {
107
107
  switch (relation.type) {
108
108
  case "one-to-many": {
109
109
  const fk = relation.foreignKey ?? this.#foreignKey(entity.name);
110
- if (!targetSchema.path(fk)) targetSchema.add({ [fk]: {
110
+ if (targetSchema.path(fk) === void 0) targetSchema.add({ [fk]: {
111
111
  type: mongoose.Schema.Types.ObjectId,
112
112
  ref: entity.name
113
113
  } });
@@ -121,7 +121,7 @@ var MongooseOrm = class MongooseOrm extends Orm {
121
121
  case "many-to-one":
122
122
  case "one-to-one": {
123
123
  const fk = relation.foreignKey ?? relation.field;
124
- if (!sourceSchema.path(fk)) sourceSchema.add({ [fk]: {
124
+ if (sourceSchema.path(fk) === void 0) sourceSchema.add({ [fk]: {
125
125
  type: mongoose.Schema.Types.ObjectId,
126
126
  ref: relation.target
127
127
  } });
@@ -191,7 +191,7 @@ var MongooseOrm = class MongooseOrm extends Orm {
191
191
  for (const [name, model] of Object.entries(models)) {
192
192
  const audit = {
193
193
  entity: name,
194
- collection: model.collection?.name ?? name,
194
+ collection: model.collection.name,
195
195
  missing: [],
196
196
  extra: []
197
197
  };
@@ -279,7 +279,7 @@ var MongooseOrm = class MongooseOrm extends Orm {
279
279
  const listeners = [
280
280
  ["disconnected", lost("mongoose: disconnected")],
281
281
  ["close", lost("mongoose: close")],
282
- ["error", ((e) => this.connectionLost(`mongoose: ${e?.message ?? String(e)}`))],
282
+ ["error", (e) => this.connectionLost(`mongoose: ${e instanceof Error ? e.message : String(e)}`)],
283
283
  ["reconnected", () => this.connectionRestored()],
284
284
  ["connected", () => this.connectionRestored()]
285
285
  ];
@@ -332,7 +332,7 @@ var MongooseOrm = class MongooseOrm extends Orm {
332
332
  model.schema.eachPath((fieldPath, type) => {
333
333
  const ref = type.options?.ref;
334
334
  if (fieldPath === "_id" || type.instance !== "ObjectId" || typeof ref !== "string" || models[ref] === void 0) return;
335
- if (index === null) index = Object.create(null);
335
+ index ??= Object.create(null);
336
336
  (index[ref] ??= []).push({
337
337
  model,
338
338
  path: fieldPath,
@@ -346,7 +346,7 @@ var MongooseOrm = class MongooseOrm extends Orm {
346
346
  getRepository(name) {
347
347
  const model = this.#models?.[name];
348
348
  if (!model) throw new Error(`MongooseOrm "${this.name}": no entity model registered under "${name}".`);
349
- if (this.#repositories === null) this.#repositories = Object.create(null);
349
+ this.#repositories ??= Object.create(null);
350
350
  let repository = this.#repositories[name];
351
351
  if (repository === void 0) {
352
352
  repository = new MongooseRepository(model, this.name, null, this.#referrers?.[name] ?? null);
@@ -358,15 +358,11 @@ var MongooseOrm = class MongooseOrm extends Orm {
358
358
  const connection = this.#connection;
359
359
  if (!connection) throw new Error(`MongooseOrm "${this.name}": not connected.`);
360
360
  const session = await connection.startSession();
361
- let result;
362
361
  try {
363
- await session.withTransaction(async () => {
364
- result = await work(new MongooseTransaction(session));
365
- });
362
+ return await session.withTransaction(() => work(new MongooseTransaction(session)));
366
363
  } finally {
367
364
  await session.endSession();
368
365
  }
369
- return result;
370
366
  }
371
367
  getNativeConnection() {
372
368
  if (!this.#connection) throw new Error(`MongooseOrm "${this.name}": not connected.`);
@@ -1,4 +1,5 @@
1
1
  import { MONGO_ORDER_ALIASES } from "../mongoOrder.js";
2
+ import { writeCount } from "../writeCount.js";
2
3
  import { RequestContext, redactSecrets } from "nodefony";
3
4
  import { ReferencedEntityError, UnknownCriteriaField, assertOrderOption, isFieldOperators, isUpdateOperators, likePatternToRegExp, queryFlowMonitor } from "@nodefony/orm-core";
4
5
  //#region nodefony/src/orm-core/MongooseRepository.ts
@@ -48,6 +49,15 @@ var MongooseRepository = class MongooseRepository {
48
49
  this.#referrers = referrers;
49
50
  }
50
51
  /**
52
+ * Options de session pour les méthodes dont le type refuse `session: null`
53
+ * (`updateMany`, `deleteMany`, `countDocuments`…) : la clé est ABSENTE hors
54
+ * transaction. Un objet neuf par appel, comme le littéral qu'il remplace —
55
+ * Mongoose peut enrichir l'objet d'options qu'il reçoit.
56
+ */
57
+ #sessionOptions() {
58
+ return this.#session === null ? {} : { session: this.#session };
59
+ }
60
+ /**
51
61
  * Tap dev-only : mesure la durée d'une opération et alimente **deux** sondes
52
62
  * complémentaires (sans surcoût quand les deux sont inactives — flags lus avant
53
63
  * toute allocation, prod = coût nul) :
@@ -142,7 +152,7 @@ var MongooseRepository = class MongooseRepository {
142
152
  #resolveField(field) {
143
153
  const key = field === "id" ? "_id" : field;
144
154
  const paths = this.#model.schema.paths;
145
- if (key === "_id" || paths[key] !== void 0 || paths[key.split(".")[0]] !== void 0) return key;
155
+ if (key === "_id" || paths[key] !== void 0 || paths[key.split(".")[0] ?? key] !== void 0) return key;
146
156
  throw new UnknownCriteriaField(field, this.#model.modelName, Object.keys(paths));
147
157
  }
148
158
  /**
@@ -195,14 +205,15 @@ var MongooseRepository = class MongooseRepository {
195
205
  }
196
206
  async create(data) {
197
207
  return this.#prof(() => this.#descr("create"), async () => {
198
- const [doc] = await this.#model.create([data], { session: this.#session ?? void 0 });
208
+ const [doc] = await this.#model.create([data], { session: this.#session });
209
+ if (doc === void 0) throw new Error(`create: le pilote n'a rendu aucun document (${this.#model.modelName})`);
199
210
  return this.#plain(doc);
200
211
  }, () => 1);
201
212
  }
202
213
  async createMany(data) {
203
214
  if (data.length === 0) return [];
204
215
  return this.#prof(() => this.#descr("insertMany"), async () => {
205
- return (await this.#model.insertMany(data, { session: this.#session ?? void 0 })).map((doc) => this.#plain(doc));
216
+ return (await this.#model.insertMany(data, { session: this.#session })).map((doc) => this.#plain(doc));
206
217
  }, (rows) => rows.length);
207
218
  }
208
219
  async updateOne(criteria, data) {
@@ -210,7 +221,7 @@ var MongooseRepository = class MongooseRepository {
210
221
  return this.#prof(() => this.#descr("findOneAndUpdate", filter), async () => {
211
222
  const doc = await this.#model.findOneAndUpdate(filter, data, {
212
223
  returnDocument: "after",
213
- session: this.#session ?? void 0
224
+ session: this.#session
214
225
  }).exec();
215
226
  return doc ? this.#plain(doc) : null;
216
227
  }, (doc) => doc ? 1 : 0);
@@ -224,9 +235,8 @@ var MongooseRepository = class MongooseRepository {
224
235
  }, {
225
236
  upsert: true,
226
237
  returnDocument: "after",
227
- session: this.#session ?? void 0
238
+ session: this.#session
228
239
  }).exec();
229
- if (!doc) throw new Error("MongooseRepository.upsert: aucun document retourné");
230
240
  return this.#plain(doc);
231
241
  }, () => 1);
232
242
  }
@@ -260,7 +270,8 @@ var MongooseRepository = class MongooseRepository {
260
270
  async updateMany(criteria, data) {
261
271
  const filter = this.#filter(criteria);
262
272
  return this.#prof(() => this.#descr("updateMany", filter), async () => {
263
- return (await this.#model.updateMany(filter, data, { session: this.#session ?? void 0 })).modifiedCount ?? 0;
273
+ const res = await this.#model.updateMany(filter, data, this.#sessionOptions());
274
+ return writeCount(res.modifiedCount);
264
275
  }, (n) => n);
265
276
  }
266
277
  async increment(criteria, changes) {
@@ -268,7 +279,7 @@ var MongooseRepository = class MongooseRepository {
268
279
  return this.#prof(() => this.#descr("findOneAndUpdate", filter), async () => {
269
280
  const doc = await this.#model.findOneAndUpdate(filter, { $inc: changes }, {
270
281
  returnDocument: "after",
271
- session: this.#session ?? void 0
282
+ session: this.#session
272
283
  }).exec();
273
284
  return doc ? this.#plain(doc) : null;
274
285
  }, (doc) => doc ? 1 : 0);
@@ -281,7 +292,8 @@ var MongooseRepository = class MongooseRepository {
281
292
  filter = { _id: { $in: ids } };
282
293
  }
283
294
  const removed = await this.#prof(() => this.#descr("deleteMany", filter), async () => {
284
- return (await this.#model.deleteMany(filter, { session: this.#session ?? void 0 })).deletedCount ?? 0;
295
+ const res = await this.#model.deleteMany(filter, this.#sessionOptions());
296
+ return writeCount(res.deletedCount);
285
297
  }, (n) => n);
286
298
  if (ids !== null && removed > 0) await this.#releaseOptional(ids);
287
299
  return removed;
@@ -294,7 +306,8 @@ var MongooseRepository = class MongooseRepository {
294
306
  filter = { _id: ids[0] };
295
307
  }
296
308
  const removed = await this.#prof(() => this.#descr("deleteOne", filter), async () => {
297
- return ((await this.#model.deleteOne(filter, { session: this.#session ?? void 0 })).deletedCount ?? 0) > 0;
309
+ const res = await this.#model.deleteOne(filter, this.#sessionOptions());
310
+ return writeCount(res.deletedCount) > 0;
298
311
  }, (ok) => ok ? 1 : 0);
299
312
  if (ids !== null && removed) await this.#releaseOptional(ids);
300
313
  return removed;
@@ -307,7 +320,7 @@ var MongooseRepository = class MongooseRepository {
307
320
  filter = { _id: ids[0] };
308
321
  }
309
322
  const doc = await this.#prof(() => this.#descr("findOneAndDelete", filter), async () => {
310
- const found = await this.#model.findOneAndDelete(filter, { session: this.#session ?? void 0 }).exec();
323
+ const found = await this.#model.findOneAndDelete(filter, { session: this.#session }).exec();
311
324
  return found ? this.#plain(found) : null;
312
325
  }, (d) => d ? 1 : 0);
313
326
  if (ids !== null && doc !== null) await this.#releaseOptional(ids);
@@ -337,7 +350,7 @@ var MongooseRepository = class MongooseRepository {
337
350
  async #referencedTargets(filter, single) {
338
351
  const referrers = this.#referrers;
339
352
  if (referrers === null) return null;
340
- const session = this.#session ?? void 0;
353
+ const session = this.#session;
341
354
  const query = this.#model.find(filter, { _id: 1 }, { session }).lean();
342
355
  if (single) query.limit(1);
343
356
  const docs = await query.exec();
@@ -360,15 +373,14 @@ var MongooseRepository = class MongooseRepository {
360
373
  * @param ids - `_id` des documents supprimés.
361
374
  */
362
375
  async #releaseOptional(ids) {
363
- const session = this.#session ?? void 0;
364
376
  for (const referrer of this.#referrers ?? []) {
365
377
  if (referrer.required) continue;
366
- await referrer.model.updateMany({ [referrer.path]: { $in: ids } }, { $set: { [referrer.path]: null } }, { session });
378
+ await referrer.model.updateMany({ [referrer.path]: { $in: ids } }, { $set: { [referrer.path]: null } }, this.#sessionOptions());
367
379
  }
368
380
  }
369
381
  async count(criteria) {
370
382
  const filter = this.#filter(criteria);
371
- return this.#prof(() => this.#descr("countDocuments", filter), () => this.#model.countDocuments(filter, { session: this.#session ?? void 0 }));
383
+ return this.#prof(() => this.#descr("countDocuments", filter), () => this.#model.countDocuments(filter, this.#sessionOptions()));
372
384
  }
373
385
  /**
374
386
  * `COUNT(DISTINCT …)` en agrégation — `$group` puis `$count`, donc la
@@ -0,0 +1,17 @@
1
+ //#region nodefony/src/writeCount.ts
2
+ /**
3
+ * Compte rendu par une écriture MongoDB (`deletedCount`, `modifiedCount`), 0 s'il manque.
4
+ *
5
+ * Le pilote le type `number`, mais le recopie de la réponse du serveur
6
+ * (`res.n`) : une écriture NON acquittée (`w: 0`) n'en reçoit aucune, et le
7
+ * compte arrive `undefined`. Sans ce repli, un `gc()` rendrait `undefined` et
8
+ * un `> 0` conclurait « rien écrit » pour une raison qui n'est pas la bonne.
9
+ *
10
+ * @param count - le compte tel que le pilote le rend.
11
+ * @returns le compte, ou 0 quand le serveur n'en a rendu aucun.
12
+ */
13
+ function writeCount(count) {
14
+ return count ?? 0;
15
+ }
16
+ //#endregion
17
+ export { writeCount };
@@ -28,7 +28,7 @@ declare class MongooseService extends Service {
28
28
  */
29
29
  connectAll(connect?: boolean): Promise<void>;
30
30
  /** Assemble l'URI de connexion à partir de la config (`uri` ou composants). */
31
- static buildUri(cfg: IMongooseConnectorConfig): string;
31
+ static buildUri(cfg: Partial<IMongooseConnectorConfig>): string;
32
32
  /**
33
33
  * Options de connexion Mongoose d'un connecteur.
34
34
  *
@@ -45,7 +45,7 @@ declare class MongooseService extends Service {
45
45
  * @param cfg - configuration validée du connecteur.
46
46
  * @returns les options à passer à la connexion, ou `undefined` s'il n'y en a aucune.
47
47
  */
48
- static buildConnectOptions(cfg: IMongooseConnectorConfig): ConnectOptions | undefined;
48
+ static buildConnectOptions(cfg: Partial<IMongooseConnectorConfig>): ConnectOptions | undefined;
49
49
  /** Ferme toutes les connexions. */
50
50
  disconnectAll(): Promise<void>;
51
51
  /** Retourne l'ORM Mongoose d'un connecteur (défaut : `"nodefony"`). */
@@ -56,7 +56,7 @@ export declare class MongooseUserRepository implements IUserRepository {
56
56
  updateMany(criteria: Criteria<IPasswordAuthenticatedUser>, data: Partial<IPasswordAuthenticatedUser>): Promise<number>;
57
57
  delete(criteria: Criteria<IPasswordAuthenticatedUser>): Promise<number>;
58
58
  count(criteria?: Criteria<IPasswordAuthenticatedUser>): Promise<number>;
59
- countDistinct(field: keyof IPasswordAuthenticatedUser & string, criteria?: Criteria<IPasswordAuthenticatedUser>): Promise<number>;
59
+ countDistinct(field: keyof IPasswordAuthenticatedUser, criteria?: Criteria<IPasswordAuthenticatedUser>): Promise<number>;
60
60
  withTransaction(tx: ITransaction): IUserRepository;
61
61
  findByIdentifier(identifier: string): Promise<IPasswordAuthenticatedUser | null>;
62
62
  /**
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Compte rendu par une écriture MongoDB (`deletedCount`, `modifiedCount`), 0 s'il manque.
3
+ *
4
+ * Le pilote le type `number`, mais le recopie de la réponse du serveur
5
+ * (`res.n`) : une écriture NON acquittée (`w: 0`) n'en reçoit aucune, et le
6
+ * compte arrive `undefined`. Sans ce repli, un `gc()` rendrait `undefined` et
7
+ * un `> 0` conclurait « rien écrit » pour une raison qui n'est pas la bonne.
8
+ *
9
+ * @param count - le compte tel que le pilote le rend.
10
+ * @returns le compte, ou 0 quand le serveur n'en a rendu aucun.
11
+ */
12
+ export declare function writeCount(count: number | undefined): number;
@@ -267,7 +267,7 @@ ainsi qu'une application lit une base métier et écrit dans une base d'archives
267
267
  L'adresse se donne de deux façons, jamais les deux à la fois utilement :
268
268
 
269
269
  - **En pièces détachées** — `host`, `port`, `dbname` : lisible, adapté au développement. Le service
270
- les assemble en `mongodb://hôte:port/base` (`MongooseService.buildUri()` (`MongooseService.ts:102`)).
270
+ les assemble en `mongodb://hôte:port/base` (`MongooseService.buildUri()` (`MongooseService.ts:117`)).
271
271
  - **En une URI** — `uri` (`config.ts:41`) : la seule forme capable d'exprimer un replica set, un
272
272
  `mongodb+srv`, des options de requête. **Dès qu'`uri` est présent, les trois autres champs ne sont
273
273
  plus lus du tout.**
@@ -295,7 +295,7 @@ publique** de ton application : le changer déplace des entités.
295
295
  ### `options` — ce que Mongoose sait faire et Nodefony ne re-décrit pas
296
296
 
297
297
  `options` (`config.ts:86`) est transmis tel quel au driver
298
- (`MongooseService.#connectOne()` (`MongooseService.ts:141`)). On y met tout ce qui touche au
298
+ (`MongooseService.#connectOne()` (`MongooseService.ts:156`)). On y met tout ce qui touche au
299
299
  **transport** plutôt qu'à l'adresse : taille du pool, délais, TLS, identifiants séparés.
300
300
 
301
301
  ```typescript
@@ -320,7 +320,7 @@ description des options du driver.
320
320
 
321
321
  ### `debug` — voir passer les requêtes
322
322
 
323
- `debug` (`config.ts:100`) active la trace intégrée de Mongoose (`connectAll()` (`MongooseService.ts:81`)).
323
+ `debug` (`config.ts:100`) active la trace intégrée de Mongoose (`connectAll()` (`MongooseService.ts:104`)).
324
324
  Chaque opération part sur la sortie standard, avec sa collection, son filtre et ses champs.
325
325
 
326
326
  C'est un **réglage de processus, pas de connecteur** : il vaut pour toutes les connexions à la fois.
@@ -395,7 +395,7 @@ Trois précisions qui évitent les mauvaises surprises :
395
395
  ### L'override générique du framework
396
396
 
397
397
  Toute clé de config d'un module se surcharge par `NF__<MODULE>__<CHEMIN>`, le double tiret bas
398
- séparant les niveaux (`parseNfEnvOverrides()` (`envOverride.ts:80`)). Le segment de module est le nom
398
+ séparant les niveaux (`parseNfEnvOverrides()` (`envOverride.ts:92`)). Le segment de module est le nom
399
399
  court : `MONGOOSE`.
400
400
 
401
401
  ```bash
@@ -405,7 +405,7 @@ NF__MONGOOSE__CONNECTORS__NODEFONY__DBNAME=recette # champ imbriqué
405
405
  NF__MONGOOSE__CONNECTORS__NODEFONY__PORT=27018 # coercé en nombre
406
406
  ```
407
407
 
408
- Ces overrides sont posés **avant** la validation Zod (`Kernel.applyEnvConfigOverrides()` (`Kernel.ts:1807`)) :
408
+ Ces overrides sont posés **avant** la validation Zod (`Kernel.applyEnvConfigOverrides()` (`Kernel.ts:1895`)) :
409
409
  une valeur aberrante est donc rejetée comme si tu l'avais écrite dans ton fichier. C'est voulu — un
410
410
  réglage d'environnement invalide doit casser aussi fort qu'un réglage de code.
411
411
 
@@ -436,7 +436,7 @@ De la plus faible à la plus forte priorité :
436
436
 
437
437
  1. **Les défauts du schéma** — `localhost:27017/nodefony`, `debug: false`, `frameworkEntities: true`.
438
438
  2. **Ta config d'app** — `use("@nodefony/mongoose", { … })`, fusionnée en profondeur sous les défauts
439
- (`Kernel.loadModulesFromManifest()` (`Kernel.ts:1656`)).
439
+ (`Kernel.loadModulesFromManifest()` (`Kernel.ts:1714`)).
440
440
  3. **Un override venu d'un autre module** — la clé `module-mongoose` dans la config d'un module tiers.
441
441
  4. **`NF__MONGOOSE__…`** — l'override générique d'environnement.
442
442
  5. **La validation Zod** — types, bornes, défauts des champs restés absents.
@@ -549,9 +549,9 @@ use("@nodefony/mongoose", {
549
549
  ```
550
550
 
551
551
  Les deux connexions s'ouvrent en série au démarrage, dans l'ordre de déclaration
552
- (`connectAll()` (`MongooseService.ts:81`)), et se ferment toutes à l'arrêt
553
- (`disconnectAll()` (`MongooseService.ts:195`)). Un service peut demander l'une ou l'autre par son nom
554
- (`getOrm()` (`MongooseService.ts:203`)), mais l'usage courant reste le registre d'ORM.
552
+ (`connectAll()` (`MongooseService.ts:104`)), et se ferment toutes à l'arrêt
553
+ (`disconnectAll()` (`MongooseService.ts:210`)). Un service peut demander l'une ou l'autre par son nom
554
+ (`getOrm()` (`MongooseService.ts:218`)), mais l'usage courant reste le registre d'ORM.
555
555
 
556
556
  ## 🔐 Le secret de connexion
557
557
 
@@ -568,7 +568,7 @@ données d'un coup. La règle est donc sans nuance.
568
568
  - **Les identifiants passés par `options`** (`user`, `pass`) suivent la même règle : ils viennent de
569
569
  l'environnement, pas du fichier. Le framework rédige d'ailleurs la valeur de tout override
570
570
  d'environnement dont le chemin ressemble à un secret, avant de le journaliser
571
- (`pathLooksSecret()` (`envOverride.ts:375`)).
571
+ (`pathLooksSecret()` (`envOverride.ts:417`)).
572
572
 
573
573
  > [!TIP]
574
574
  > Vérifie ta redaction en une commande : démarre l'application et lis la ligne de connexion. Elle doit
@@ -653,7 +653,7 @@ Un port hors bornes, un type erroné, un champ vide : le démarrage s'arrête av
653
653
  le champ**, pas une pile d'appels.
654
654
 
655
655
  ```
656
- [@nodefony/mongoose] Invalid config: connectors.x.port: Too small: expected number to be >=1
656
+ [@nodefony/mongoose] configuration invalide — connectors.x.port : Too small: expected number to be >=1
657
657
  ```
658
658
 
659
659
  Le message est assemblé à partir des remontées de validation, chemin de champ compris
@@ -665,7 +665,7 @@ coûteux à diagnostiquer qu'un serveur qui refuse de démarrer.
665
665
 
666
666
  Le cas courant : la config est parfaite, mais Mongo n'est pas joignable — conteneur pas encore prêt,
667
667
  réseau coupé, identifiants périmés. Le comportement **dépend de l'environnement**, arbitré par la
668
- politique de boot du cœur (`Kernel.isBootErrorFatal()` (`Kernel.ts:3130`)) :
668
+ politique de boot du cœur (`Kernel.isBootErrorFatal()` (`Kernel.ts:3221`)) :
669
669
 
670
670
  | Environnement | Ce qui se passe |
671
671
  | ------------------- | -------------------------------------------------------------------------------------- |
@@ -697,7 +697,7 @@ vaut mieux qu'une erreur 500 et un rejet non capturé. À l'inverse, une entité
697
697
  La configuration ne se contente pas d'être validée : elle se **montre**.
698
698
 
699
699
  - Le module publie son schéma en JSON Schema (`Mongoose.configSchema()` (`mongoose/index.ts:55`) →
700
- `mongooseConfigJsonSchema()` (`defineModuleConfig.ts:72`)). C'est ce qui permet à Studio d'afficher
700
+ `mongooseConfigJsonSchema()` (`defineModuleConfig.ts:78`)). C'est ce qui permet à Studio d'afficher
701
701
  chaque clé avec son type, son défaut et son texte d'aide — sans qu'une seule ligne de description
702
702
  soit recopiée quelque part.
703
703
  - L'écran `/nodefony/config` montre la configuration **effective** après toutes les couches, et la
package/docs/index.md CHANGED
@@ -492,8 +492,8 @@ global de Mongoose : c'est ce qui permet à plusieurs bases — voire plusieurs
492
492
  le même processus.
493
493
 
494
494
  Le service orchestre ce cycle de bout en bout : il ouvre une connexion par connecteur déclaré au
495
- démarrage (`MongooseService.connectAll()` (`MongooseService.ts:89`)) et referme tout à l'arrêt
496
- (`MongooseService.disconnectAll()` (`MongooseService.ts:195`)). Le module se déclare **non critique**
495
+ démarrage (`MongooseService.connectAll()` (`MongooseService.ts:104`)) et referme tout à l'arrêt
496
+ (`MongooseService.disconnectAll()` (`MongooseService.ts:210`)). Le module se déclare **non critique**
497
497
  (`Mongoose.critical` (`mongoose/index.ts:48`)) : une base injoignable ne tue pas le processus —
498
498
  l'application monte quand même, l'échec est journalisé, et c'est l'orchestrateur qui relèvera Mongo.
499
499
 
@@ -508,7 +508,7 @@ l'application monte quand même, l'échec est journalisé, et c'est l'orchestrat
508
508
 
509
509
  `repo.find({ views: { $gte: 10 } }, { limit: 20 })` traverse quatre étapes :
510
510
 
511
- 1. **Traduction du critère** (`MongooseRepository.#filter()` (`MongooseRepository.ts:244`)) : chaque
511
+ 1. **Traduction du critère** (`MongooseRepository.#filter()` (`MongooseRepository.ts:256`)) : chaque
512
512
  champ est résolu (`id` devient `_id`), chaque opérateur portable est converti.
513
513
  2. **Validation du champ** (`MongooseRepository.#resolveField()` (`MongooseRepository.ts:222`)) : un
514
514
  champ absent du schéma lève `UnknownCriteriaField` — plutôt que de renvoyer zéro résultat sans
@@ -539,11 +539,12 @@ drivers. Les signatures exactes vivent dans le graphe généré
539
539
  | `delete` / `deleteOne` | supprimer | `deleteMany` / `deleteOne` |
540
540
  | `findOneAndDelete` | supprimer **et** récupérer le document | `findOneAndDelete` |
541
541
  | `count` / `exists` | compter / tester l'existence | `countDocuments` / `exists` |
542
+ | `countDistinct` | compter les valeurs distinctes et non nulles d'un champ | agrégation `$match` → `$group` → `$count` |
542
543
  | `withTransaction` | rejouer les mêmes opérations dans une transaction | ajoute la `session` à chaque opération |
543
544
 
544
545
  Les écritures qui « lisent puis écrivent » sont **atomiques par construction**
545
- (`MongooseRepository.upsert()` (`MongooseRepository.ts:405`),
546
- `MongooseRepository.increment()` (`MongooseRepository.ts:491`)) : un seul aller-retour, la
546
+ (`MongooseRepository.upsert()` (`MongooseRepository.ts:422`),
547
+ `MongooseRepository.increment()` (`MongooseRepository.ts:507`)) : un seul aller-retour, la
547
548
  comparaison est faite par le serveur. Ce n'est pas une optimisation cosmétique — c'est ce qui évite
548
549
  que deux requêtes simultanées lisent le même état et s'écrasent mutuellement.
549
550
 
@@ -657,13 +658,16 @@ contrat est importé en `import type` (effacé à la compilation), et c'est le d
657
658
  auprès du module consommateur. Le module met en place tout ce câblage à son enregistrement, avant que
658
659
  la connexion ne s'ouvre.
659
660
 
660
- | Store | Contrat | Comment le choisir | Collections |
661
- | ------------ | -------------------------- | ----------------------------------- | -------------------------------------------------- |
662
- | Sessions | `ISessionStorage` (http) | `session: { store: "mongoose" }` | `session` |
663
- | Utilisateurs | `IUserRepository` (user) | via `provisionUsers` de ton app | `User` |
664
- | Jetons | `ITokenStore` (security) | `tokenStore: { store: "mongoose" }` | `access_token`, `denied_jti`, `subject_revocation` |
665
- | Passkeys | `IWebAuthnCredentialStore` | `passkeys: { store: "mongoose" }` | `webauthn_credential` |
666
- | Webhooks | `IWebhookStore` | `webhooks: { store: "mongoose" }` | `webhook_endpoint` |
661
+ | Store | Contrat | Comment le choisir | Collections |
662
+ | ------------ | ----------------------------- | ------------------------------------------------ | -------------------------------------------------- |
663
+ | Sessions | `ISessionStorage` (http) | `session: { store: "mongoose" }` | `session` |
664
+ | Utilisateurs | `IUserRepository` (user) | via `provisionUsers` de ton app | `User` |
665
+ | Jetons | `ITokenStore` (security) | `tokenStore: { store: "mongoose" }` | `access_token`, `denied_jti`, `subject_revocation` |
666
+ | Passkeys | `IWebAuthnCredentialStore` | `passkeys: { store: "mongoose" }` | `webauthn_credential` |
667
+ | Webhooks | `IWebhookStore` | `webhooks: { store: "mongoose" }` | `webhook_endpoint` |
668
+ | 2FA (TOTP) | `ITotpSecretStore` (security) | `totp: { store: "mongoose" }` | `totp_secret` |
669
+ | Audit | `IAuditStore` (security) | `audit: { store: "mongoose" }` | `audit_event` |
670
+ | Idempotence | `IIdempotencyStore` (cœur) | `idempotency: { store: "mongoose" }` (framework) | `idempotency_key` |
667
671
 
668
672
  L'auto-enregistrement peut être coupé (`frameworkEntities: false` (`config.ts:102`)) : le module
669
673
  devient alors un pur driver de données, sans schéma framework.
@@ -676,7 +680,7 @@ devient alors un pur driver de données, sans schéma framework.
676
680
 
677
681
  Trois comportements valent d'être connus :
678
682
 
679
- - **Purge à deux bornes** — `idleTimeoutS` et `absoluteTimeoutS` (`SessionStorage.gc()` (`SessionStorage.ts:32`)) :
683
+ - **Purge à deux bornes** — `idleTimeoutS` et `absoluteTimeoutS` (`SessionStorage.gc()` (`SessionStorage.ts:163`)) :
680
684
  l'inactivité (depuis la dernière activité) et l'âge absolu (depuis la création, **jamais prolongé** —
681
685
  la ré-authentification finit par être imposée, conformément aux recommandations NIST/OWASP).
682
686
  - **Prolongation sans réécriture** (`SessionStorage.touch()` (`SessionStorage.ts:195`)) : rafraîchir
@@ -695,15 +699,15 @@ vaut mieux qu'une erreur 500 et un rejet non capturé.
695
699
  `MongooseUserRepository` rend des objets `BaseUser` (avec leur comportement : rôles, actif, verrouillé),
696
700
  pas des documents nus. Deux recherches lui sont propres :
697
701
 
698
- - **par compte social lié** (`MongooseUserRepository.findBySocialProvider()` (`MongooseUserRepository.ts:264`)) :
702
+ - **par compte social lié** (`MongooseUserRepository.findBySocialProvider()` (`MongooseUserRepository.ts:238`)) :
699
703
  un `$elemMatch` sur un tableau libre de fournisseurs — le pendant Mongo du parcours JSON en SQL.
700
704
  C'est ce qui porte le motif « Shadow User » d'OAuth (un compte créé à la volée au premier login social),
701
705
  **sans colonne par fournisseur** : ajouter GitHub demain n'est pas une migration.
702
- - **listing paginé** (`MongooseUserRepository.listPage()` (`MongooseUserRepository.ts:299`)) : requête
706
+ - **listing paginé** (`MongooseUserRepository.listPage()` (`MongooseUserRepository.ts:281`)) : requête
703
707
  native bornée (`skip`/`limit + 1`), tri sur liste blanche, `_id` en départage. Une page est une page,
704
708
  jamais la collection entière rapatriée en mémoire.
705
709
 
706
- Le comptage des administrateurs actifs (`MongooseUserRepository.countActiveAdmins()` (`MongooseUserRepository.ts:354`))
710
+ Le comptage des administrateurs actifs (`MongooseUserRepository.countActiveAdmins()` (`MongooseUserRepository.ts:328`))
707
711
  compte côté serveur — c'est le garde-fou qui empêche de supprimer le dernier administrateur.
708
712
 
709
713
  ### Jetons
@@ -719,7 +723,7 @@ Deux invariants de sécurité sont tenus **par la requête**, pas par du code Ja
719
723
  déconnexions simultanées pourraient reposer un seuil **plus ancien** — et des jetons révoqués
720
724
  redeviendraient valides. Ici, c'est structurellement impossible.
721
725
 
722
- La purge, bornée par `retentionRevokedMs` (`MongooseTokenStore.gc()` (`MongooseTokenStore.ts:350`)), s'appuie sur une particularité utile
726
+ La purge, bornée par `retentionRevokedMs` (`MongooseTokenStore.gc()` (`MongooseTokenStore.ts:356`)), s'appuie sur une particularité utile
723
727
  de Mongo : une comparaison numérique **ignore** les documents dont le champ est `null`. Les jetons sans
724
728
  expiration ne sont donc jamais balayés par erreur ; ils partent par une règle de rétention distincte.
725
729
 
@@ -748,7 +752,7 @@ serait pire qu'une erreur.
748
752
 
749
753
  ## 🗃 Ce qui est stocké
750
754
 
751
- Les cinq schémas portés par le module. Les collections sont créées à la volée par MongoDB — il n'y a
755
+ Les schémas portés par le module — dix collections. Les collections sont créées à la volée par MongoDB — il n'y a
752
756
  ni migration ni DDL à jouer, ce qui est l'un des vrais conforts du modèle documentaire.
753
757
 
754
758
  > ⚠️ **Le revers de ce confort : ce qui n'est pas déclaré est JETÉ, sans un mot.** Mongoose valide en
@@ -763,15 +767,18 @@ ni migration ni DDL à jouer, ce qui est l'un des vrais conforts du modèle docu
763
767
  > partagé (`USER_COLUMNS`), et le module en dérive un schéma Mongoose. Un champ métier que tu ajoutes
764
768
  > à ton utilisateur suit le même chemin — déclaré, donc écrit ; oublié, donc perdu en silence.
765
769
 
766
- | Collection | Clé primaire (`_id`) | Contenu | Horodatages |
767
- | --------------------- | ---------------------------- | ------------------------------------------------------------------ | ------------------ |
768
- | `session` | `ObjectId` (auto) | identifiant de session, contenu, messages flash, méta, utilisateur | nombres (ms) |
769
- | `User` | `ObjectId` (auto) | identifiant, mot de passe haché, rôles, comptes sociaux, méta | gérés par Mongoose |
770
- | `access_token` | le `jti` (texte) | type, porteur, périmètres, empreinte du secret, révocation | nombres (ms) |
771
- | `denied_jti` | le `jti` (texte) | expiration | nombres (ms) |
772
- | `subject_revocation` | le porteur (texte) | seuil `invalidBefore` | nombres (ms) |
773
- | `webauthn_credential` | l'identifiant du credential | clé publique, compteur, transports, état de sauvegarde | nombres (ms) |
774
- | `webhook_endpoint` | l'identifiant `wh_…` (texte) | URL, secret chiffré, événements, état des livraisons | nombres (ms) |
770
+ | Collection | Clé primaire (`_id`) | Contenu | Horodatages |
771
+ | --------------------- | -------------------------------------- | ------------------------------------------------------------------------------------ | ------------------ |
772
+ | `session` | `ObjectId` (auto) | identifiant de session, contenu, messages flash, méta, utilisateur | nombres (ms) |
773
+ | `User` | `ObjectId` (auto) | identifiant, mot de passe haché, rôles, comptes sociaux, méta | gérés par Mongoose |
774
+ | `access_token` | le `jti` (texte) | type, porteur, périmètres, empreinte du secret, révocation | nombres (ms) |
775
+ | `denied_jti` | le `jti` (texte) | expiration | nombres (ms) |
776
+ | `subject_revocation` | le porteur (texte) | seuil `invalidBefore` | nombres (ms) |
777
+ | `webauthn_credential` | l'identifiant du credential | clé publique, compteur, transports, état de sauvegarde | nombres (ms) |
778
+ | `webhook_endpoint` | l'identifiant `wh_…` (texte) | URL, secret chiffré, événements, état des livraisons | nombres (ms) |
779
+ | `totp_secret` | l'identifiant de l'utilisateur (texte) | secret chiffré, algorithme, chiffres, période, codes de secours, dernier pas utilisé | nombres (ms) |
780
+ | `audit_event` | l'identifiant d'événement (texte) | catégorie, action, issue, acteur, ressource, raison, IP, requête | nombre (`ts`, ms) |
781
+ | `idempotency_key` | la clé d'idempotence (texte) | empreinte de la requête, état, réponse rejouée | expiration (ms) |
775
782
 
776
783
  Deux choix structurants s'y lisent :
777
784
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodefony/mongoose",
3
- "version": "10.0.0-alpha.9",
3
+ "version": "10.0.0-beta.1",
4
4
  "description": "Moteur MongoDB de Nodefony sur Mongoose : dépôts typés conformes au contrat orm-core",
5
5
  "nodefony": {
6
6
  "storeKind": "durable",
@@ -55,23 +55,23 @@
55
55
  "zod": "^4.6.5"
56
56
  },
57
57
  "devDependencies": {
58
- "@nodefony/framework": "^10.0.0-alpha.9",
59
- "@nodefony/http": "^10.0.0-alpha.9",
60
- "@nodefony/orm-core": "^10.0.0-alpha.9",
61
- "@nodefony/security": "^10.0.0-alpha.9",
62
- "@nodefony/user": "^10.0.0-alpha.9",
63
- "@types/node": "26.6.2",
58
+ "@nodefony/framework": "^10.0.0-beta.1",
59
+ "@nodefony/http": "^10.0.0-beta.1",
60
+ "@nodefony/orm-core": "^10.0.0-beta.1",
61
+ "@nodefony/security": "^10.0.0-beta.1",
62
+ "@nodefony/user": "^10.0.0-beta.1",
63
+ "@types/node": "26.6.3",
64
64
  "mongodb-memory-server": "11.3.0",
65
- "nodefony": "^10.0.0-alpha.9",
66
- "vitest": "5.0.1"
65
+ "nodefony": "^10.0.0-beta.1",
66
+ "vitest": "5.0.2"
67
67
  },
68
68
  "peerDependencies": {
69
- "@nodefony/framework": "^10.0.0-alpha.9",
70
- "@nodefony/http": "^10.0.0-alpha.9",
71
- "@nodefony/orm-core": "^10.0.0-alpha.9",
72
- "@nodefony/security": "^10.0.0-alpha.9",
73
- "@nodefony/user": "^10.0.0-alpha.9",
74
- "nodefony": "^10.0.0-alpha.9"
69
+ "@nodefony/framework": "^10.0.0-beta.1",
70
+ "@nodefony/http": "^10.0.0-beta.1",
71
+ "@nodefony/orm-core": "^10.0.0-beta.1",
72
+ "@nodefony/security": "^10.0.0-beta.1",
73
+ "@nodefony/user": "^10.0.0-beta.1",
74
+ "nodefony": "^10.0.0-beta.1"
75
75
  },
76
76
  "repository": {
77
77
  "type": "git",