@classytic/mongokit 3.29.0 → 3.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/dist/{create-repository-D4ZU94b3.d.mts → Repository-BXHkUtLk.d.mts} +30 -57
- package/dist/{Repository-DThvFCaV.mjs → Repository-X7U2d0kU.mjs} +296 -42
- package/dist/actions/index.mjs +1 -1
- package/dist/adapter/index.mjs +1 -1
- package/dist/{adapter-CLvUHiTW.mjs → adapter-BitHg-CN.mjs} +1 -1
- package/dist/ai/index.d.mts +1 -1
- package/dist/better-auth/index.d.mts +13 -0
- package/dist/better-auth/index.mjs +76 -6
- package/dist/create-repository-CvD4wzuN.d.mts +54 -0
- package/dist/{create-repository-uhKt_oUT.mjs → create-repository-DYe5YEie.mjs} +3 -2
- package/dist/index.d.mts +275 -41
- package/dist/index.mjs +80 -7
- package/dist/kernel-conformance/index.d.mts +217 -0
- package/dist/kernel-conformance/index.mjs +603 -0
- package/dist/method-registry.plugin-DblM7V6j.mjs +112 -0
- package/dist/model-registry-ClKtWiHa.mjs +67 -0
- package/dist/{mongooseToJsonSchema-CByt1VhQ.mjs → mongooseToJsonSchema-DKbnKduN.mjs} +14 -3
- package/dist/{multi-tenant.plugin-DVyVGREb.d.mts → multi-tenant.plugin-2VtSSm1Y.d.mts} +1 -1
- package/dist/outbox/index.d.mts +63 -0
- package/dist/outbox/index.mjs +239 -0
- package/dist/{plugin-options-vFufQZMZ.d.mts → plugin-options-BsFH0sTi.d.mts} +1 -1
- package/dist/plugins/index.d.mts +4 -4
- package/dist/plugins/index.mjs +3 -2
- package/dist/{repository-DCHRWRXw.d.mts → repository-Q16ErTu8.d.mts} +19 -1
- package/dist/testkit/index.d.mts +8 -1
- package/dist/testkit/index.mjs +1 -1
- package/dist/{timestamp.plugin-CFL6LlYZ.mjs → timestamp.plugin-ChFRcLRJ.mjs} +32 -143
- package/dist/{to-plain-DkF5px0R.d.mts → to-plain-BuX1RUZV.d.mts} +12 -4
- package/dist/{update-CI2NdAW6.mjs → update-DHdXdOk_.mjs} +21 -2
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.mjs +1 -1
- package/dist/{validation-chain.plugin-BLecaKa0.d.mts → validation-chain.plugin-BSshLJTA.d.mts} +3 -3
- package/dist/{validation-chain.plugin-CwOcUJC1.mjs → validation-chain.plugin-BxTl_dYR.mjs} +4 -3
- package/package.json +191 -185
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as isValidIdForType, t as getSchemaIdType } from "./id-resolution-BqDrk0S2.mjs";
|
|
2
|
-
import { a as count, b as LookupBuilder, c as getByQuery, d as deleteById, f as deleteByQuery, h as createMany, l as getOrCreate, m as create, n as update, o as exists, r as updateByQuery, s as getById, t as findOneAndUpdate, x as compileFilterToMongo, y as distinct } from "./update-
|
|
2
|
+
import { a as count, b as LookupBuilder, c as getByQuery, d as deleteById, f as deleteByQuery, h as createMany, l as getOrCreate, m as create, n as update, o as exists, r as updateByQuery, s as getById, t as findOneAndUpdate, x as compileFilterToMongo, y as distinct } from "./update-DHdXdOk_.mjs";
|
|
3
3
|
import { n as warn } from "./logger-K7dPmu2U.mjs";
|
|
4
4
|
import { n as isDuplicateKeyError, r as parseDuplicateKeyError, t as createError } from "./error-BuByZwKU.mjs";
|
|
5
5
|
import { t as PaginationEngine } from "./PaginationEngine-CczbvCAq.mjs";
|
|
@@ -80,9 +80,7 @@ function createKeysetPurgePort(repo, baseFilter, session) {
|
|
|
80
80
|
[strategy.deletedAtField ?? "deletedAt"]: /* @__PURE__ */ new Date()
|
|
81
81
|
} }, baseOpts);
|
|
82
82
|
break;
|
|
83
|
-
case "anonymize":
|
|
84
|
-
await repo.updateMany(chunkFilter, { $set: strategy.fields }, baseOpts);
|
|
85
|
-
break;
|
|
83
|
+
case "anonymize": await repo.updateMany(chunkFilter, { $set: strategy.fields }, baseOpts);
|
|
86
84
|
}
|
|
87
85
|
cursor = ids[ids.length - 1];
|
|
88
86
|
return idDocs.length;
|
|
@@ -127,21 +125,44 @@ async function purgeAnonymizeFunctional(repo, selectFilter, fields, limit, sessi
|
|
|
127
125
|
}
|
|
128
126
|
//#endregion
|
|
129
127
|
//#region src/capabilities.ts
|
|
130
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Read the provenance of a capability descriptor's `transactions` flag.
|
|
130
|
+
* A descriptor without the field is a static declaration (`'declared'`) —
|
|
131
|
+
* which is exactly right for sqlitekit / prismakit / any kit whose answer
|
|
132
|
+
* genuinely does not depend on a live deployment.
|
|
133
|
+
*/
|
|
134
|
+
function transactionResolutionOf(caps) {
|
|
135
|
+
const value = caps.transactionsResolution;
|
|
136
|
+
return value === "observed" || value === "unknown" ? value : "declared";
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* What MongoDB supports through mongokit's surface, DECLARED (not observed).
|
|
140
|
+
*
|
|
141
|
+
* Deployment-dependent flags (`transactions`, `nestedTransactions`,
|
|
142
|
+
* `changeStreams` — all three need a replica set or mongos) are `true` here
|
|
143
|
+
* because they describe the product, not the server you happen to be pointed
|
|
144
|
+
* at. **Do not read this constant to decide whether a live connection can run
|
|
145
|
+
* a transaction** — that is {@link resolveMongoCapabilities}, which is what
|
|
146
|
+
* `Repository#capabilities` returns. This one is for the cross-kit conformance
|
|
147
|
+
* harness (which overrides environment-specific limits where it is
|
|
148
|
+
* constructed) and as the base every observed descriptor is derived from.
|
|
149
|
+
*/
|
|
150
|
+
const MONGOKIT_CAPABILITIES = Object.freeze({
|
|
151
|
+
transactionsResolution: "declared",
|
|
131
152
|
transactions: true,
|
|
132
153
|
nestedTransactions: true,
|
|
133
154
|
upsert: true,
|
|
134
155
|
duplicateKeyError: true,
|
|
135
156
|
distinct: true,
|
|
136
157
|
aggregate: true,
|
|
137
|
-
aggregateOps: {
|
|
158
|
+
aggregateOps: Object.freeze({
|
|
138
159
|
percentile: true,
|
|
139
160
|
stddev: true,
|
|
140
161
|
topN: true,
|
|
141
162
|
customDateBuckets: true,
|
|
142
163
|
dateBucketSubMinute: true,
|
|
143
164
|
cache: true
|
|
144
|
-
},
|
|
165
|
+
}),
|
|
145
166
|
getOrCreate: true,
|
|
146
167
|
countAndExists: true,
|
|
147
168
|
purgeByField: true,
|
|
@@ -153,7 +174,195 @@ const MONGOKIT_CAPABILITIES = {
|
|
|
153
174
|
lean: true,
|
|
154
175
|
lookupPopulate: true,
|
|
155
176
|
streaming: true
|
|
156
|
-
};
|
|
177
|
+
});
|
|
178
|
+
/**
|
|
179
|
+
* Driver `ServerType`s that positively imply a session-capable, transaction-
|
|
180
|
+
* capable deployment. Verified against mongodb driver 7.x: a single-node
|
|
181
|
+
* replica set reports `RSPrimary` (topology `ReplicaSetWithPrimary`, or
|
|
182
|
+
* `Single` when reached with `directConnection`), a standalone `mongod`
|
|
183
|
+
* reports `Standalone`, and a mongos reports `Mongos`.
|
|
184
|
+
*
|
|
185
|
+
* **This is why the check reads SERVER type, not just topology type.** The
|
|
186
|
+
* pre-existing heuristic (`topology.description.type !== 'Single'`) has a
|
|
187
|
+
* false NEGATIVE: a directly-connected single-node replica set is `Single`
|
|
188
|
+
* yet runs transactions fine.
|
|
189
|
+
*/
|
|
190
|
+
const TRANSACTION_CAPABLE_SERVER_TYPES = /* @__PURE__ */ new Set([
|
|
191
|
+
"RSPrimary",
|
|
192
|
+
"RSSecondary",
|
|
193
|
+
"RSArbiter",
|
|
194
|
+
"RSOther",
|
|
195
|
+
"RSGhost",
|
|
196
|
+
"Mongos",
|
|
197
|
+
"LoadBalancer"
|
|
198
|
+
]);
|
|
199
|
+
/** Topology types that positively imply transactions regardless of server detail. */
|
|
200
|
+
const TRANSACTION_CAPABLE_TOPOLOGY_TYPES = /* @__PURE__ */ new Set([
|
|
201
|
+
"ReplicaSetWithPrimary",
|
|
202
|
+
"ReplicaSetNoPrimary",
|
|
203
|
+
"Sharded",
|
|
204
|
+
"LoadBalanced"
|
|
205
|
+
]);
|
|
206
|
+
function clientOf(connection) {
|
|
207
|
+
const probe = connection;
|
|
208
|
+
if (!probe || typeof probe !== "object") return void 0;
|
|
209
|
+
try {
|
|
210
|
+
return probe.getClient?.() ?? probe.client ?? (probe.topology !== void 0 ? probe : void 0);
|
|
211
|
+
} catch {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function serverTypes(description) {
|
|
216
|
+
const servers = description.servers;
|
|
217
|
+
if (!servers) return [];
|
|
218
|
+
return (typeof servers.values === "function" ? [...servers.values()] : Object.values(servers)).map((s) => s?.type).filter((t) => typeof t === "string");
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Read whether a connection's LIVE deployment supports multi-document
|
|
222
|
+
* transactions, synchronously, from the driver's SDAM topology description.
|
|
223
|
+
*
|
|
224
|
+
* Free (a few property reads against an object the driver keeps current), so
|
|
225
|
+
* it is deliberately NOT memoized — a connection that opens after the repo was
|
|
226
|
+
* constructed upgrades its own answer with no invalidation step. What IS
|
|
227
|
+
* memoized is the async {@link probeMongoCapabilities} result, which this
|
|
228
|
+
* function consults first so an exotic deployment that answered `hello` but
|
|
229
|
+
* exposes no topology still reports definitively.
|
|
230
|
+
*
|
|
231
|
+
* @returns `'yes'` / `'no'` when the topology is conclusive, `'unknown'`
|
|
232
|
+
* otherwise — never optimistic. See the module docblock on rule 3.
|
|
233
|
+
*/
|
|
234
|
+
function resolveTransactionSupport(connection) {
|
|
235
|
+
const cached = readProbeCache(connection);
|
|
236
|
+
if (cached) return cached;
|
|
237
|
+
const description = clientOf(connection)?.topology?.description;
|
|
238
|
+
if (!description) return "unknown";
|
|
239
|
+
const types = serverTypes(description);
|
|
240
|
+
if (types.some((t) => TRANSACTION_CAPABLE_SERVER_TYPES.has(t))) return "yes";
|
|
241
|
+
if (types.length > 0 && types.every((t) => t === "Standalone")) return "no";
|
|
242
|
+
const topologyType = description.type;
|
|
243
|
+
if (topologyType && TRANSACTION_CAPABLE_TOPOLOGY_TYPES.has(topologyType)) return "yes";
|
|
244
|
+
return "unknown";
|
|
245
|
+
}
|
|
246
|
+
function deriveCapabilities(support) {
|
|
247
|
+
const available = support === "yes";
|
|
248
|
+
return Object.freeze({
|
|
249
|
+
...MONGOKIT_CAPABILITIES,
|
|
250
|
+
transactionsResolution: support === "unknown" ? "unknown" : "observed",
|
|
251
|
+
transactions: available,
|
|
252
|
+
nestedTransactions: available,
|
|
253
|
+
changeStreams: available
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
/** Three possible answers ⇒ three frozen singletons; the getter allocates nothing. */
|
|
257
|
+
const DERIVED = Object.freeze({
|
|
258
|
+
yes: deriveCapabilities("yes"),
|
|
259
|
+
no: deriveCapabilities("no"),
|
|
260
|
+
unknown: deriveCapabilities("unknown")
|
|
261
|
+
});
|
|
262
|
+
/**
|
|
263
|
+
* The capability descriptor for a LIVE connection — what
|
|
264
|
+
* `Repository#capabilities` returns.
|
|
265
|
+
*
|
|
266
|
+
* Every deployment-dependent flag reflects the observed topology; everything
|
|
267
|
+
* else comes from {@link MONGOKIT_CAPABILITIES}. Cheap enough to call per
|
|
268
|
+
* access (topology read + one frozen-object lookup).
|
|
269
|
+
*/
|
|
270
|
+
function resolveMongoCapabilities(connection) {
|
|
271
|
+
return DERIVED[resolveTransactionSupport(connection)];
|
|
272
|
+
}
|
|
273
|
+
/** The three derived descriptors, for tests and for gates that want to compare identity. */
|
|
274
|
+
function capabilitiesForSupport(support) {
|
|
275
|
+
return DERIVED[support];
|
|
276
|
+
}
|
|
277
|
+
/** Definitive results only. Keyed on the connection object; never holds `'unknown'`. */
|
|
278
|
+
const PROBE_RESULTS = /* @__PURE__ */ new WeakMap();
|
|
279
|
+
/** In-flight de-duplication so N repos on one connection issue ONE `hello`. */
|
|
280
|
+
const PROBE_INFLIGHT = /* @__PURE__ */ new WeakMap();
|
|
281
|
+
function readProbeCache(connection) {
|
|
282
|
+
if (!connection || typeof connection !== "object") return void 0;
|
|
283
|
+
return PROBE_RESULTS.get(connection);
|
|
284
|
+
}
|
|
285
|
+
/** Test seam — drop memoized probe answers (a reused connection object across suites). */
|
|
286
|
+
function resetCapabilityProbeCache(connection) {
|
|
287
|
+
if (connection && typeof connection === "object") {
|
|
288
|
+
PROBE_RESULTS.delete(connection);
|
|
289
|
+
PROBE_INFLIGHT.delete(connection);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function withTimeout(promise, ms, onTimeout) {
|
|
293
|
+
return new Promise((resolve) => {
|
|
294
|
+
const timer = setTimeout(() => resolve(onTimeout), ms);
|
|
295
|
+
timer.unref?.();
|
|
296
|
+
promise.then((value) => {
|
|
297
|
+
clearTimeout(timer);
|
|
298
|
+
resolve(value);
|
|
299
|
+
}, () => {
|
|
300
|
+
clearTimeout(timer);
|
|
301
|
+
resolve(onTimeout);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Classify a `hello` (or legacy `isMaster`) reply.
|
|
307
|
+
*
|
|
308
|
+
* - `msg: 'isdbgrid'` ⇒ mongos ⇒ transactions (Mongo 4.2+ sharded).
|
|
309
|
+
* - `setName` ⇒ a replica-set member ⇒ transactions.
|
|
310
|
+
* - a reply that IS a hello (`isWritablePrimary` / `ismaster` present) with
|
|
311
|
+
* neither marker ⇒ standalone ⇒ **no**. Verified: a memory-server standalone
|
|
312
|
+
* returns `{ isWritablePrimary: true }` and nothing else.
|
|
313
|
+
* - anything unrecognised ⇒ `'unknown'`. A reply we cannot parse is not a yes.
|
|
314
|
+
*/
|
|
315
|
+
function classifyHelloReply(reply) {
|
|
316
|
+
if (!reply || typeof reply !== "object") return "unknown";
|
|
317
|
+
const { msg, setName } = reply;
|
|
318
|
+
if (msg === "isdbgrid") return "yes";
|
|
319
|
+
if (typeof setName === "string" && setName.length > 0) return "yes";
|
|
320
|
+
return "isWritablePrimary" in reply || "ismaster" in reply ? "no" : "unknown";
|
|
321
|
+
}
|
|
322
|
+
async function runProbe(connection, timeoutMs) {
|
|
323
|
+
if (connection.readyState === 2 && typeof connection.asPromise === "function") await withTimeout(connection.asPromise().then(() => void 0, () => void 0), timeoutMs, void 0);
|
|
324
|
+
const fromTopology = resolveTransactionSupport(connection);
|
|
325
|
+
if (fromTopology !== "unknown") return fromTopology;
|
|
326
|
+
const admin = connection.db?.admin?.();
|
|
327
|
+
if (!admin) return "unknown";
|
|
328
|
+
return classifyHelloReply(await withTimeout(admin.command({ hello: 1 }).catch(async () => admin.command({ isMaster: 1 }).catch(() => void 0)), timeoutMs, void 0));
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Resolve a connection's real capabilities, waiting for (and if necessary
|
|
332
|
+
* asking) the deployment. Memoized per connection: N repositories and N
|
|
333
|
+
* kernels on one connection issue at most ONE `hello`.
|
|
334
|
+
*
|
|
335
|
+
* Hosts do not have to call this — `Repository#capabilities` reads the live
|
|
336
|
+
* topology synchronously, which is already conclusive once
|
|
337
|
+
* `await mongoose.connect()` has returned (the Kernel Construction Standard's
|
|
338
|
+
* Describe → **Bind** ordering guarantees that). It exists for the cases the
|
|
339
|
+
* sync read cannot cover: binding while a connection is still opening, or a
|
|
340
|
+
* deployment whose driver surfaces no topology description. Calling it makes
|
|
341
|
+
* the SUBSEQUENT synchronous reads definitive, so nothing else needs rewiring:
|
|
342
|
+
*
|
|
343
|
+
* ```ts
|
|
344
|
+
* await probeMongoCapabilities(connection); // once, before bind
|
|
345
|
+
* const engine = defineWallet().bind(connection); // gate now sees the truth
|
|
346
|
+
* ```
|
|
347
|
+
*
|
|
348
|
+
* Only definitive answers are cached — an `'unknown'` may be re-probed later
|
|
349
|
+
* (the deployment may simply not have been up yet).
|
|
350
|
+
*/
|
|
351
|
+
async function probeMongoCapabilities(connection, options = {}) {
|
|
352
|
+
if (!connection || typeof connection !== "object") return DERIVED.unknown;
|
|
353
|
+
const key = connection;
|
|
354
|
+
const cached = PROBE_RESULTS.get(key);
|
|
355
|
+
if (cached) return DERIVED[cached];
|
|
356
|
+
const inflight = PROBE_INFLIGHT.get(key);
|
|
357
|
+
if (inflight) return DERIVED[await inflight];
|
|
358
|
+
const run = runProbe(connection, options.timeoutMs ?? 2e3).catch(() => "unknown").then((support) => {
|
|
359
|
+
if (support !== "unknown") PROBE_RESULTS.set(key, support);
|
|
360
|
+
PROBE_INFLIGHT.delete(key);
|
|
361
|
+
return support;
|
|
362
|
+
});
|
|
363
|
+
PROBE_INFLIGHT.set(key, run);
|
|
364
|
+
return DERIVED[await run];
|
|
365
|
+
}
|
|
157
366
|
//#endregion
|
|
158
367
|
//#region src/operations.ts
|
|
159
368
|
/**
|
|
@@ -705,7 +914,16 @@ function buildAggPipeline(req, schema) {
|
|
|
705
914
|
const aliasSet = new Set((req.lookups ?? []).map((l) => l.as ?? l.from));
|
|
706
915
|
const { base: baseFilter, joined: joinedFilter } = splitFilterByAlias(schema !== void 0 && req.filter !== void 0 ? castFilterToSchema(req.filter, schema) : req.filter, aliasSet);
|
|
707
916
|
if (baseFilter !== void 0) {
|
|
708
|
-
|
|
917
|
+
/**
|
|
918
|
+
* The SCHEMA is passed so the date coercion can consult it.
|
|
919
|
+
*
|
|
920
|
+
* `castFilterToSchema` above already casts operands to their declared types; without
|
|
921
|
+
* the schema here, `compileFilterToMongo`'s coercion then guessed from value shape
|
|
922
|
+
* alone and OVERRODE that cast — turning a `'2026-08-02'` bound on a String column
|
|
923
|
+
* into a Date, which BSON refuses to compare against the stored string. The aggregate
|
|
924
|
+
* returned zero rows and nothing indicated why.
|
|
925
|
+
*/
|
|
926
|
+
const match = compileFilterToMongo(baseFilter, schema);
|
|
709
927
|
if (Object.keys(match).length > 0) stages.push({ $match: match });
|
|
710
928
|
}
|
|
711
929
|
if (req.lookups && req.lookups.length > 0) appendLookupStages$1(stages, req.lookups);
|
|
@@ -993,7 +1211,7 @@ async function countAggGroups(Model, req, options = {}) {
|
|
|
993
1211
|
const [row] = await aggregation.exec();
|
|
994
1212
|
return row?.n ?? 0;
|
|
995
1213
|
}
|
|
996
|
-
const match = req.filter ? compileFilterToMongo(castFilterToSchema(req.filter, Model.schema)) : {};
|
|
1214
|
+
const match = req.filter ? compileFilterToMongo(castFilterToSchema(req.filter, Model.schema), Model.schema) : {};
|
|
997
1215
|
if (groupCols.length === 0) {
|
|
998
1216
|
const pipeline = [];
|
|
999
1217
|
if (Object.keys(match).length > 0) pipeline.push({ $match: match });
|
|
@@ -1899,29 +2117,29 @@ function isTransactionUnsupported(error) {
|
|
|
1899
2117
|
/**
|
|
1900
2118
|
* PROACTIVELY report whether a connection's deployment supports multi-document
|
|
1901
2119
|
* transactions — the read-side companion to {@link isTransactionUnsupported}
|
|
1902
|
-
* (which classifies a FAILED attempt).
|
|
1903
|
-
*
|
|
2120
|
+
* (which classifies a FAILED attempt). Lets a caller skip a doomed transaction
|
|
2121
|
+
* attempt on standalone dev Mongo rather than starting one just to catch the
|
|
2122
|
+
* error.
|
|
2123
|
+
*
|
|
2124
|
+
* Delegates to {@link resolveTransactionSupport} so mongokit has exactly ONE
|
|
2125
|
+
* topology reader. That also fixes a false NEGATIVE this function used to
|
|
2126
|
+
* have: it tested `description.type !== 'Single'`, but a single-node replica
|
|
2127
|
+
* set reached with `directConnection` is `Single` with a server type of
|
|
2128
|
+
* `RSPrimary` and runs transactions fine. The shared reader looks at the
|
|
2129
|
+
* SERVER descriptions first.
|
|
1904
2130
|
*
|
|
1905
|
-
*
|
|
1906
|
-
*
|
|
1907
|
-
*
|
|
1908
|
-
* still
|
|
1909
|
-
*
|
|
1910
|
-
*
|
|
2131
|
+
* **`unknown` is optimistic HERE and pessimistic in `capabilities.ts` — the
|
|
2132
|
+
* difference is deliberate.** This answers "should I bother ATTEMPTING a
|
|
2133
|
+
* transaction?", where guessing yes costs one caught error (`allowFallback`
|
|
2134
|
+
* still recovers). The capability descriptor answers "may this deployment be
|
|
2135
|
+
* trusted with money?", where guessing yes is the bug class this repo keeps
|
|
2136
|
+
* hitting (AGENTS.md FAIL LOUD rule 3). Do not "unify" them.
|
|
1911
2137
|
*
|
|
1912
2138
|
* Accepts a Mongoose `Connection` (or anything exposing `getClient()`/`client`
|
|
1913
2139
|
* with a driver `topology`), so it stays decoupled from mongoose types.
|
|
1914
2140
|
*/
|
|
1915
2141
|
function supportsTransactions(connection) {
|
|
1916
|
-
|
|
1917
|
-
if (!probe) return true;
|
|
1918
|
-
let client;
|
|
1919
|
-
try {
|
|
1920
|
-
client = probe.getClient?.() ?? probe.client ?? void 0;
|
|
1921
|
-
} catch {
|
|
1922
|
-
return true;
|
|
1923
|
-
}
|
|
1924
|
-
return client?.topology?.description?.type !== "Single";
|
|
2142
|
+
return resolveTransactionSupport(connection) !== "no";
|
|
1925
2143
|
}
|
|
1926
2144
|
//#endregion
|
|
1927
2145
|
//#region src/Repository.ts
|
|
@@ -1986,8 +2204,31 @@ var Repository = class extends RepositoryBase {
|
|
|
1986
2204
|
* Runtime capability descriptor required by `StandardRepo<TDoc>`
|
|
1987
2205
|
* (repo-core 0.6.0). Hosts feature-detect once at boot instead of
|
|
1988
2206
|
* try/catching `UnsupportedOperationError` per call.
|
|
2207
|
+
*
|
|
2208
|
+
* **OBSERVED, not declared** (changed 2026-08-05). Until then this was a
|
|
2209
|
+
* class field pointing at the static `MONGOKIT_CAPABILITIES`, so
|
|
2210
|
+
* `transactions` read `true` for every mongoose-backed repository —
|
|
2211
|
+
* including one pointed at a standalone `mongod`, which cannot run a
|
|
2212
|
+
* transaction at all. Every kernel boot gate that reads it (wallet, party,
|
|
2213
|
+
* invoice, purchase, flow, …) was therefore unfalsifiable decoration
|
|
2214
|
+
* (AGENTS.md FAIL LOUD rule 4).
|
|
2215
|
+
*
|
|
2216
|
+
* Now it derives the deployment-dependent flags (`transactions`,
|
|
2217
|
+
* `nestedTransactions`, `changeStreams`) from THIS model's connection and
|
|
2218
|
+
* its live SDAM topology. A getter rather than a constructor snapshot on
|
|
2219
|
+
* purpose: a repository built before `mongoose.connect()` resolves upgrades
|
|
2220
|
+
* its own answer the moment the connection is up, with no invalidation step.
|
|
2221
|
+
* When the topology cannot be read the answer is `transactions: false` +
|
|
2222
|
+
* `transactionsResolution: 'unknown'` — fail closed, never an optimistic
|
|
2223
|
+
* yes (rule 3). See `capabilities.ts` and `probeMongoCapabilities`.
|
|
2224
|
+
*
|
|
2225
|
+
* `options.capabilities` overrides the whole descriptor for hosts/tests that
|
|
2226
|
+
* genuinely know better (a proxied connection, a non-mongoose driver seam).
|
|
1989
2227
|
*/
|
|
1990
|
-
capabilities
|
|
2228
|
+
get capabilities() {
|
|
2229
|
+
return this._capabilitiesOverride ?? resolveMongoCapabilities(this.Model?.db);
|
|
2230
|
+
}
|
|
2231
|
+
_capabilitiesOverride;
|
|
1991
2232
|
Model;
|
|
1992
2233
|
/**
|
|
1993
2234
|
* Mongoose model name. Duplicated on the instance for BC — arc / catalog /
|
|
@@ -2020,6 +2261,7 @@ var Repository = class extends RepositoryBase {
|
|
|
2020
2261
|
warn(m);
|
|
2021
2262
|
});
|
|
2022
2263
|
this.Model = Model;
|
|
2264
|
+
this._capabilitiesOverride = options.capabilities;
|
|
2023
2265
|
this.model = Model.modelName;
|
|
2024
2266
|
this._pagination = new PaginationEngine(Model, paginationConfig);
|
|
2025
2267
|
this.idField = options.idField ?? "_id";
|
|
@@ -2318,7 +2560,8 @@ var Repository = class extends RepositoryBase {
|
|
|
2318
2560
|
return result;
|
|
2319
2561
|
}
|
|
2320
2562
|
const wantsThrow = context.throwOnNotFound === true || options.throwOnNotFound === true;
|
|
2321
|
-
|
|
2563
|
+
const idType = getSchemaIdType(this.Model.schema);
|
|
2564
|
+
if (!isValidIdForType(id, idType)) {
|
|
2322
2565
|
if (wantsThrow) throw createError(404, "Document not found");
|
|
2323
2566
|
return null;
|
|
2324
2567
|
}
|
|
@@ -2822,8 +3065,9 @@ var Repository = class extends RepositoryBase {
|
|
|
2822
3065
|
/**
|
|
2823
3066
|
* Update a document by id and return the post-update doc.
|
|
2824
3067
|
*
|
|
2825
|
-
* Compiles to `Model.findOneAndUpdate(filter, { $set: data },
|
|
2826
|
-
*
|
|
3068
|
+
* Compiles to `Model.findOneAndUpdate(filter, { $set: data },
|
|
3069
|
+
* { returnDocument: 'after', runValidators: true })` under the hood and
|
|
3070
|
+
* routes through the
|
|
2827
3071
|
* full plugin pipeline — `multiTenantPlugin` injects the tenant scope,
|
|
2828
3072
|
* `auditTrailPlugin` records the change, `cachePlugin` invalidates,
|
|
2829
3073
|
* `softDeletePlugin` etc. all compose. Returns `null` on miss (the
|
|
@@ -2838,7 +3082,7 @@ var Repository = class extends RepositoryBase {
|
|
|
2838
3082
|
* const doc = await CampaignModel.findOneAndUpdate(
|
|
2839
3083
|
* { _id: id, organizationId: ctx.orgId }, // hand-rolled tenant filter
|
|
2840
3084
|
* { $set: patch, $currentDate: { updatedAt: 1 } },
|
|
2841
|
-
* {
|
|
3085
|
+
* { returnDocument: 'after', runValidators: true }
|
|
2842
3086
|
* );
|
|
2843
3087
|
* return doc ? toEntity(doc) : null;
|
|
2844
3088
|
*
|
|
@@ -2867,7 +3111,7 @@ var Repository = class extends RepositoryBase {
|
|
|
2867
3111
|
* const doc = await CampaignModel.findOneAndUpdate(
|
|
2868
3112
|
* { _id: id, organizationId: ctx.orgId, status: 'pending' },
|
|
2869
3113
|
* { $set: { status: 'sent', sentAt: new Date() } },
|
|
2870
|
-
* {
|
|
3114
|
+
* { returnDocument: 'after' }
|
|
2871
3115
|
* );
|
|
2872
3116
|
* if (!doc) throw new ConcurrentTransitionError(id);
|
|
2873
3117
|
*
|
|
@@ -2921,7 +3165,8 @@ var Repository = class extends RepositoryBase {
|
|
|
2921
3165
|
const wantsThrow = context.throwOnNotFound === true || options.throwOnNotFound === true;
|
|
2922
3166
|
const effectiveIdField = options.idField ?? this.idField;
|
|
2923
3167
|
if (effectiveIdField === "_id") {
|
|
2924
|
-
|
|
3168
|
+
const idType = getSchemaIdType(this.Model.schema);
|
|
3169
|
+
if (!isValidIdForType(id, idType)) {
|
|
2925
3170
|
if (wantsThrow) throw createError(404, "Document not found");
|
|
2926
3171
|
await this._emitHook("after:update", {
|
|
2927
3172
|
context,
|
|
@@ -3211,7 +3456,8 @@ var Repository = class extends RepositoryBase {
|
|
|
3211
3456
|
const finalQuery = context.query || filter;
|
|
3212
3457
|
const finalUpdate = context.data || update;
|
|
3213
3458
|
if (effectiveIdField === "_id" && !options.upsert) {
|
|
3214
|
-
|
|
3459
|
+
const idType = getSchemaIdType(this.Model.schema);
|
|
3460
|
+
if (!isValidIdForType(id, idType)) return null;
|
|
3215
3461
|
}
|
|
3216
3462
|
if (Object.keys(finalUpdate).length === 0) return await this.Model.findOne(finalQuery, null, { session: options.session });
|
|
3217
3463
|
return await findOneAndUpdate(this.Model, finalQuery, finalUpdate, {
|
|
@@ -3444,7 +3690,8 @@ var Repository = class extends RepositoryBase {
|
|
|
3444
3690
|
const finalQuery = context.query || filter;
|
|
3445
3691
|
const finalUpdate = context.data || operatorUpdate;
|
|
3446
3692
|
if (effectiveIdField === "_id") {
|
|
3447
|
-
|
|
3693
|
+
const idType = getSchemaIdType(this.Model.schema);
|
|
3694
|
+
if (!isValidIdForType(id, idType)) return null;
|
|
3448
3695
|
}
|
|
3449
3696
|
return await findOneAndUpdate(this.Model, finalQuery, finalUpdate, {
|
|
3450
3697
|
returnDocument: "after",
|
|
@@ -3497,7 +3744,8 @@ var Repository = class extends RepositoryBase {
|
|
|
3497
3744
|
const effectiveIdField = options.idField ?? this.idField;
|
|
3498
3745
|
const wantsThrow = context.throwOnNotFound === true || options.throwOnNotFound === true;
|
|
3499
3746
|
if (effectiveIdField === "_id") {
|
|
3500
|
-
|
|
3747
|
+
const idType = getSchemaIdType(this.Model.schema);
|
|
3748
|
+
if (!isValidIdForType(id, idType)) {
|
|
3501
3749
|
if (wantsThrow) throw createError(404, "Document not found");
|
|
3502
3750
|
await this._emitHook("after:delete", {
|
|
3503
3751
|
context,
|
|
@@ -3630,7 +3878,8 @@ var Repository = class extends RepositoryBase {
|
|
|
3630
3878
|
* the port implementation.
|
|
3631
3879
|
*/
|
|
3632
3880
|
async purgeByField(field, value, strategy, options = {}) {
|
|
3633
|
-
|
|
3881
|
+
const port = createMongoPurgePort(this, field, value, options.session);
|
|
3882
|
+
return runChunkedPurge(strategy, options, port);
|
|
3634
3883
|
}
|
|
3635
3884
|
/**
|
|
3636
3885
|
* Range/filter-scoped purge — see `StandardRepo.purgeByFilter` for the
|
|
@@ -3648,7 +3897,8 @@ var Repository = class extends RepositoryBase {
|
|
|
3648
3897
|
*/
|
|
3649
3898
|
async purgeByFilter(filter, strategy, options = {}) {
|
|
3650
3899
|
const compiled = compileFilterToMongo(filter);
|
|
3651
|
-
|
|
3900
|
+
const port = createMongoPurgePortFromFilter(this, compiled, options.session);
|
|
3901
|
+
return runChunkedPurge(strategy, options, port);
|
|
3652
3902
|
}
|
|
3653
3903
|
/**
|
|
3654
3904
|
* Chunked cold-storage extraction — see `StandardRepo.archiveByFilter`
|
|
@@ -3663,7 +3913,8 @@ var Repository = class extends RepositoryBase {
|
|
|
3663
3913
|
*/
|
|
3664
3914
|
async archiveByFilter(filter, sink, options = {}) {
|
|
3665
3915
|
const compiled = compileFilterToMongo(filter);
|
|
3666
|
-
|
|
3916
|
+
const port = createMongoArchivePort(this, compiled, options.session);
|
|
3917
|
+
return runChunkedArchive(options, sink, port);
|
|
3667
3918
|
}
|
|
3668
3919
|
/**
|
|
3669
3920
|
* Kit-native aggregation pipeline — takes a MongoDB stage array and
|
|
@@ -4410,7 +4661,10 @@ var Repository = class extends RepositoryBase {
|
|
|
4410
4661
|
const labels = error.errorLabels;
|
|
4411
4662
|
const hasLabel = error.hasErrorLabel;
|
|
4412
4663
|
if (Array.isArray(labels) && (labels.includes("TransientTransactionError") || labels.includes("UnknownTransactionCommitResult")) || typeof hasLabel === "function" && (hasLabel.call(error, "TransientTransactionError") || hasLabel.call(error, "UnknownTransactionCommitResult"))) return error;
|
|
4413
|
-
if (error instanceof mongoose.Error.ValidationError)
|
|
4664
|
+
if (error instanceof mongoose.Error.ValidationError) {
|
|
4665
|
+
const messages = Object.values(error.errors).map((err) => err.message);
|
|
4666
|
+
return createError(400, `Validation Error: ${messages.join(", ")}`);
|
|
4667
|
+
}
|
|
4414
4668
|
if (error instanceof mongoose.Error.CastError) return createError(400, `Invalid ${error.path}: ${error.value}`);
|
|
4415
4669
|
const duplicateErr = parseDuplicateKeyError(error);
|
|
4416
4670
|
if (duplicateErr) return duplicateErr;
|
|
@@ -4419,4 +4673,4 @@ var Repository = class extends RepositoryBase {
|
|
|
4419
4673
|
}
|
|
4420
4674
|
};
|
|
4421
4675
|
//#endregion
|
|
4422
|
-
export { supportsTransactions as a, ALL_OPERATIONS as c, READ_OPERATIONS as d, operationsByPolicyKey as f, isTransactionUnsupported as i, MUTATING_OPERATIONS as l,
|
|
4676
|
+
export { resetCapabilityProbeCache as _, supportsTransactions as a, transactionResolutionOf as b, ALL_OPERATIONS as c, READ_OPERATIONS as d, operationsByPolicyKey as f, probeMongoCapabilities as g, classifyHelloReply as h, isTransactionUnsupported as i, MUTATING_OPERATIONS as l, capabilitiesForSupport as m, Repository as n, withTransaction as o, MONGOKIT_CAPABILITIES as p, batchTransaction as r, AggregationBuilder as s, HOOK_PRIORITY$1 as t, OP_REGISTRY as u, resolveMongoCapabilities as v, createMongoPurgePort as x, resolveTransactionSupport as y };
|
package/dist/actions/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "../rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import { g as create_exports, i as update_exports, p as delete_exports, u as read_exports, v as aggregate_exports } from "../update-
|
|
2
|
+
import { g as create_exports, i as update_exports, p as delete_exports, u as read_exports, v as aggregate_exports } from "../update-DHdXdOk_.mjs";
|
|
3
3
|
//#region src/actions/index.ts
|
|
4
4
|
var actions_exports = /* @__PURE__ */ __exportAll({
|
|
5
5
|
aggregate: () => aggregate_exports,
|
package/dist/adapter/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as createMongooseAdapter, r as isMongooseModel, t as MongooseAdapter } from "../adapter-
|
|
1
|
+
import { n as createMongooseAdapter, r as isMongooseModel, t as MongooseAdapter } from "../adapter-BitHg-CN.mjs";
|
|
2
2
|
export { MongooseAdapter, createMongooseAdapter, isMongooseModel };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as warn } from "./logger-K7dPmu2U.mjs";
|
|
2
|
-
import { t as buildCrudSchemasFromModel } from "./mongooseToJsonSchema-
|
|
2
|
+
import { t as buildCrudSchemasFromModel } from "./mongooseToJsonSchema-DKbnKduN.mjs";
|
|
3
3
|
import { matchesRecordFilter } from "@classytic/repo-core/filter";
|
|
4
4
|
import { mergeFieldRuleConstraints } from "@classytic/repo-core/schema";
|
|
5
5
|
import { asRepositoryLike, isRepository } from "@classytic/repo-core/adapter";
|
package/dist/ai/index.d.mts
CHANGED
|
@@ -58,6 +58,19 @@ interface RegisterBetterAuthStubsOptions {
|
|
|
58
58
|
usePlural?: boolean;
|
|
59
59
|
/** Per-collection model name override (mirrors BA's `user.modelName`). */
|
|
60
60
|
modelOverrides?: Partial<Record<string, string>>;
|
|
61
|
+
/**
|
|
62
|
+
* Canonical collection names to SKIP — because something else owns their model.
|
|
63
|
+
*
|
|
64
|
+
* The normal reason is `createBetterAuthOverlay`: a host wants stubs for the collections it merely
|
|
65
|
+
* REFERENCES (`organization`, `member`) and a full overlay for the one it exposes CRUD on (`user`).
|
|
66
|
+
* Without this the two helpers collide — the stub registers `user` first, and the overlay then
|
|
67
|
+
* THROWS, correctly, because mongoose locks a schema on first `model()` and its `additionalFields`
|
|
68
|
+
* would be silently dropped.
|
|
69
|
+
*
|
|
70
|
+
* Names are CANONICAL (`'user'`, not a pluralised or overridden model name); exclusion is applied
|
|
71
|
+
* before `usePlural` / `modelOverrides`, so a caller does not have to predict the final name.
|
|
72
|
+
*/
|
|
73
|
+
exclude?: string[];
|
|
61
74
|
}
|
|
62
75
|
/**
|
|
63
76
|
* Register stub Mongoose models for Better Auth's collections so that
|
|
@@ -1,8 +1,45 @@
|
|
|
1
|
-
import { n as Repository } from "../Repository-
|
|
2
|
-
import { n as createMongooseAdapter } from "../adapter-
|
|
1
|
+
import { n as Repository } from "../Repository-X7U2d0kU.mjs";
|
|
2
|
+
import { n as createMongooseAdapter } from "../adapter-BitHg-CN.mjs";
|
|
3
3
|
import { pluralizeBetterAuthCollection, resolveBetterAuthCollections } from "@classytic/repo-core/better-auth";
|
|
4
4
|
//#region src/better-auth/index.ts
|
|
5
5
|
/**
|
|
6
|
+
* Field key with ORDER PRESERVED — a compound index is identified by its key sequence.
|
|
7
|
+
* `{ organizationId: 1, createdAt: -1 }` is a DIFFERENT index from `{ createdAt: -1, organizationId: 1 }`
|
|
8
|
+
* (they serve different queries), so sorting the keys here would wrongly equate them.
|
|
9
|
+
*/
|
|
10
|
+
function indexFieldsKey(fields) {
|
|
11
|
+
return Object.entries(fields).map(([k, v]) => `${k}:${String(v)}`).join(",");
|
|
12
|
+
}
|
|
13
|
+
/** Deterministic canonical form (sorted object keys, recursive) for value equality. */
|
|
14
|
+
function canonical(value) {
|
|
15
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
16
|
+
if (value && typeof value === "object") {
|
|
17
|
+
const obj = value;
|
|
18
|
+
return Object.keys(obj).sort().reduce((acc, k) => {
|
|
19
|
+
acc[k] = canonical(obj[k]);
|
|
20
|
+
return acc;
|
|
21
|
+
}, {});
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Is every DECLARED option satisfied by the existing index's options? A subset check, not
|
|
27
|
+
* equality: Mongoose augments stored index options with defaults (e.g. `background`), so the
|
|
28
|
+
* existing side legitimately carries extra keys. We require each declared key to deep-equal the
|
|
29
|
+
* existing value — covering ALL declared options (unique, sparse, expireAfterSeconds, collation,
|
|
30
|
+
* name, weights, default_language, partialFilterExpression, …) with no hand-maintained subset.
|
|
31
|
+
*/
|
|
32
|
+
function declaredOptionsSatisfied(declared = {}, existing = {}) {
|
|
33
|
+
for (const [key, value] of Object.entries(declared)) if (JSON.stringify(canonical(value)) !== JSON.stringify(canonical(existing[key]))) return false;
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
/** Which declared indexes are NOT already present on the existing model's schema. */
|
|
37
|
+
function missingIndexesOn(existing, declared) {
|
|
38
|
+
if (declared.length === 0) return [];
|
|
39
|
+
const existingIndexes = (existing?.schema)?.indexes?.() ?? [];
|
|
40
|
+
return declared.filter((d) => !existingIndexes.some(([fields, options]) => indexFieldsKey(fields) === indexFieldsKey(d.fields) && declaredOptionsSatisfied(d.options ?? {}, options ?? {})));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
6
43
|
* Register stub Mongoose models for Better Auth's collections so that
|
|
7
44
|
* Mongoose-based resources can `.populate()` references to BA-owned
|
|
8
45
|
* documents. Idempotent — safe to call multiple times.
|
|
@@ -22,10 +59,14 @@ import { pluralizeBetterAuthCollection, resolveBetterAuthCollections } from "@cl
|
|
|
22
59
|
* already existed on `mongoose.models`).
|
|
23
60
|
*/
|
|
24
61
|
function registerBetterAuthStubs(mongoose, options = {}) {
|
|
25
|
-
const
|
|
62
|
+
const excluded = new Set(options.exclude ?? []);
|
|
63
|
+
const names = resolveBetterAuthCollections({
|
|
64
|
+
...options,
|
|
65
|
+
...excluded.size ? { exclude: [...excluded] } : {}
|
|
66
|
+
});
|
|
26
67
|
const registered = [];
|
|
27
68
|
for (const finalName of names) {
|
|
28
|
-
if (mongoose.models
|
|
69
|
+
if (finalName in mongoose.models) continue;
|
|
29
70
|
const schema = new mongoose.Schema({}, {
|
|
30
71
|
strict: false,
|
|
31
72
|
collection: finalName,
|
|
@@ -59,8 +100,37 @@ async function createBetterAuthOverlay(options) {
|
|
|
59
100
|
const tableConfig = ctx.tables[collection];
|
|
60
101
|
if (!tableConfig) throw new Error(`[mongokit:better-auth] Better Auth has no table named '${collection}'. Available: ${Object.keys(ctx.tables).join(", ")}. Did you enable the right plugin in your betterAuth() config?`);
|
|
61
102
|
const finalName = usePlural ? pluralizeBetterAuthCollection(tableConfig.modelName) : tableConfig.modelName;
|
|
62
|
-
if (mongoose.models
|
|
63
|
-
|
|
103
|
+
if (finalName in mongoose.models) {
|
|
104
|
+
const declared = Object.keys(additionalFields);
|
|
105
|
+
if (declared.length > 0 || indexes.length > 0) {
|
|
106
|
+
/**
|
|
107
|
+
* Distinguish "a STUB got here first" from "this same overlay already ran in this process".
|
|
108
|
+
*
|
|
109
|
+
* The guard exists because mongoose locks a schema on first `model()`, so declared
|
|
110
|
+
* `additionalFields` would be silently dropped. That danger is real when a
|
|
111
|
+
* `registerBetterAuthStubs()` call registered an EMPTY `strict: false` schema first.
|
|
112
|
+
*
|
|
113
|
+
* It is NOT real on a re-boot: a process that composes the app twice (every integration suite,
|
|
114
|
+
* and any host that recomposes) hits an existing model whose schema ALREADY carries these exact
|
|
115
|
+
* paths, because an identical overlay call put them there. Throwing then just makes the second
|
|
116
|
+
* boot impossible — which is precisely why hosts hand-rolled `models.X || model(X, schema)` and
|
|
117
|
+
* inherited the silent-drop bug this guard was written to stop.
|
|
118
|
+
*
|
|
119
|
+
* So the test is the SCHEMA, not the mere presence of a model: if every declared field is
|
|
120
|
+
* already a path, the fields are applied and reuse is safe. If any is missing, something else
|
|
121
|
+
* owns this model and the throw stands.
|
|
122
|
+
*/
|
|
123
|
+
const existing = mongoose.models[finalName];
|
|
124
|
+
const paths = existing?.schema?.paths ?? {};
|
|
125
|
+
const missing = declared.filter((f) => !(f in paths));
|
|
126
|
+
const missingIdx = missingIndexesOn(existing, indexes);
|
|
127
|
+
if (missing.length > 0 || missingIdx.length > 0) {
|
|
128
|
+
const parts = [];
|
|
129
|
+
if (missing.length > 0) parts.push(`${missing.length === 1 ? "field" : "fields"} ${missing.map((f) => `'${f}'`).join(", ")}`);
|
|
130
|
+
if (missingIdx.length > 0) parts.push(`${missingIdx.length === 1 ? "index" : "indexes"} on ${missingIdx.map((i) => `{${Object.keys(i.fields).join(",")}}`).join(", ")}`);
|
|
131
|
+
throw new Error(`[mongokit:better-auth] '${finalName}' already registered on mongoose.models WITHOUT ${parts.join(" and ")}. Cannot apply additionalFields / indexes from this createBetterAuthOverlay() call — mongoose locks schema on first model() call, so they would be SILENTLY DROPPED. Either: (a) exclude '${finalName}' from the prior registerBetterAuthStubs() call (\`exclude: ['${collection}']\`), or (b) move additionalFields / indexes there.`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
64
134
|
}
|
|
65
135
|
let Model = mongoose.models[finalName];
|
|
66
136
|
if (!Model) {
|