@classytic/mongokit 3.6.3 → 3.7.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/dist/{PaginationEngine-CunXkR9p.mjs → PaginationEngine-CPmZv7Ds.mjs} +8 -5
- package/dist/actions/index.d.mts +1 -1
- package/dist/actions/index.mjs +1 -1
- package/dist/ai/index.d.mts +23 -1
- package/dist/ai/index.mjs +194 -12
- package/dist/{cursor-CHToazHy.mjs → cursor-DZ9JSoMM.mjs} +11 -7
- package/dist/error-Lw9GEAro.mjs +46 -0
- package/dist/{index-BSRSsRRx.d.mts → index-C-qZUqv8.d.mts} +1 -1
- package/dist/index.d.mts +31 -9
- package/dist/index.mjs +53 -12
- package/dist/logger-DJ7egMnd.mjs +30 -0
- package/dist/{mongooseToJsonSchema-MlaD3C0B.mjs → mongooseToJsonSchema-DUwXPStK.mjs} +25 -21
- package/dist/{mongooseToJsonSchema-D2YjHo7W.d.mts → mongooseToJsonSchema-Du-UopRK.d.mts} +18 -2
- package/dist/pagination/PaginationEngine.d.mts +3 -1
- package/dist/pagination/PaginationEngine.mjs +1 -1
- package/dist/plugins/index.d.mts +2 -2
- package/dist/plugins/index.mjs +2 -2
- package/dist/query/primitives/coercion.d.mts +1 -1
- package/dist/query/primitives/coercion.mjs +1 -1
- package/dist/query/primitives/indexes.d.mts +1 -1
- package/dist/{sort-C-BJEWUZ.mjs → sort-ffe-9JBg.mjs} +19 -4
- package/dist/{types-DiQfDesD.d.mts → types-BgdeBXI1.d.mts} +68 -0
- package/dist/{update-AVfKWNGt.mjs → update-BDsQ3cdy.mjs} +2 -1
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.mjs +5 -4
- package/dist/{validation-chain.plugin-BsFzKVnO.d.mts → validation-chain.plugin-Be9rG04B.d.mts} +25 -2
- package/dist/{validation-chain.plugin-7A5qGEQq.mjs → validation-chain.plugin-CEyJssZl.mjs} +129 -17
- package/package.json +8 -3
- package/dist/error-Bpbi_NKo.mjs +0 -63
- /package/dist/{coercion-B19MD38s.d.mts → coercion-oM8G4b6m.d.mts} +0 -0
- /package/dist/{field-selection-C5o7jyyA.mjs → field-selection-dI0m4RBs.mjs} +0 -0
- /package/dist/{id-resolution-LZ-cCwR5.mjs → id-resolution-e4BT9zAV.mjs} +0 -0
- /package/dist/{indexes-qIBCZ3mY.d.mts → indexes-C26GNZRU.d.mts} +0 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as exists, c as getOrCreate, d as deleteByQuery, g as upsert, i as count, m as createMany, n as updateByQuery, o as getById, p as create, s as getByQuery, t as update, u as deleteById, v as distinct, y as LookupBuilder } from "./update-
|
|
3
|
-
import { t as
|
|
1
|
+
import { n as debug, r as warn } from "./logger-DJ7egMnd.mjs";
|
|
2
|
+
import { a as exists, c as getOrCreate, d as deleteByQuery, g as upsert, i as count, m as createMany, n as updateByQuery, o as getById, p as create, s as getByQuery, t as update, u as deleteById, v as distinct, y as LookupBuilder } from "./update-BDsQ3cdy.mjs";
|
|
3
|
+
import { n as parseDuplicateKeyError, t as createError } from "./error-Lw9GEAro.mjs";
|
|
4
|
+
import { t as PaginationEngine } from "./PaginationEngine-CPmZv7Ds.mjs";
|
|
4
5
|
import { hasNearOperator, rewriteNearForCount } from "./query/primitives/geo.mjs";
|
|
5
|
-
import { r as isValidIdForType, t as getSchemaIdType } from "./id-resolution-
|
|
6
|
-
import { a as byIdKey, c as listQueryKey, l as modelPattern, o as byQueryKey, r as getFieldsForUser, u as versionKey } from "./field-selection-
|
|
6
|
+
import { r as isValidIdForType, t as getSchemaIdType } from "./id-resolution-e4BT9zAV.mjs";
|
|
7
|
+
import { a as byIdKey, c as listQueryKey, l as modelPattern, o as byQueryKey, r as getFieldsForUser, u as versionKey } from "./field-selection-dI0m4RBs.mjs";
|
|
7
8
|
import mongoose from "mongoose";
|
|
9
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
10
|
//#region src/plugins/aggregate-helpers.plugin.ts
|
|
9
11
|
/**
|
|
10
12
|
* Aggregate helpers plugin
|
|
@@ -1228,6 +1230,40 @@ function ensureLookupProjectionIncludesCursorFields(projection, sort) {
|
|
|
1228
1230
|
return nextProjection;
|
|
1229
1231
|
}
|
|
1230
1232
|
/**
|
|
1233
|
+
* Ordered pairs that produce wrong behavior when registered out of order.
|
|
1234
|
+
* Each tuple is `[mustComeFirst, mustComeAfter, reason]`.
|
|
1235
|
+
*
|
|
1236
|
+
* Plugin names are read from `plugin.name` (Plugin objects). Plain-function
|
|
1237
|
+
* plugins without a name are skipped — no false positives.
|
|
1238
|
+
*/
|
|
1239
|
+
const PLUGIN_ORDER_CONSTRAINTS = [[
|
|
1240
|
+
"soft-delete",
|
|
1241
|
+
"batch-operations",
|
|
1242
|
+
"soft-delete must precede batch-operations so bulk deletes/updates see the soft-delete filter"
|
|
1243
|
+
], [
|
|
1244
|
+
"multi-tenant",
|
|
1245
|
+
"cache",
|
|
1246
|
+
"multi-tenant must precede cache so tenant scoping is baked into cache keys (prevents cross-tenant cache poisoning)"
|
|
1247
|
+
]];
|
|
1248
|
+
function pluginName(plugin) {
|
|
1249
|
+
if (!plugin || typeof plugin === "function") return void 0;
|
|
1250
|
+
const name = plugin.name;
|
|
1251
|
+
return typeof name === "string" ? name : void 0;
|
|
1252
|
+
}
|
|
1253
|
+
function validatePluginOrder(plugins, modelName, mode) {
|
|
1254
|
+
if (mode === "off") return;
|
|
1255
|
+
const names = plugins.map(pluginName);
|
|
1256
|
+
for (const [first, after, reason] of PLUGIN_ORDER_CONSTRAINTS) {
|
|
1257
|
+
const firstIdx = names.indexOf(first);
|
|
1258
|
+
const afterIdx = names.indexOf(after);
|
|
1259
|
+
if (firstIdx === -1 || afterIdx === -1) continue;
|
|
1260
|
+
if (firstIdx < afterIdx) continue;
|
|
1261
|
+
const message = `[mongokit] Repository "${modelName}": plugin order issue — ${reason}. Got: [..., '${after}' at index ${afterIdx}, '${first}' at index ${firstIdx}]. Swap them, or pass { pluginOrderChecks: 'off' } to silence.`;
|
|
1262
|
+
if (mode === "throw") throw new Error(message);
|
|
1263
|
+
warn(message);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1231
1267
|
* Plugin phase priorities (lower = runs first)
|
|
1232
1268
|
* Policy hooks (multi-tenant, soft-delete, validation) MUST run before cache
|
|
1233
1269
|
* to ensure filters are injected before cache keys are computed.
|
|
@@ -1262,6 +1298,7 @@ var Repository = class {
|
|
|
1262
1298
|
this.searchMode = options.searchMode ?? "text";
|
|
1263
1299
|
this.searchFields = options.searchFields;
|
|
1264
1300
|
if (this.searchMode === "regex" && (!this.searchFields || this.searchFields.length === 0)) warn(`[mongokit] Repository "${this.model}" configured with searchMode: 'regex' but no searchFields provided. getAll({ search }) will throw until searchFields is set.`);
|
|
1301
|
+
validatePluginOrder(plugins, this.model, options.pluginOrderChecks ?? "warn");
|
|
1265
1302
|
plugins.forEach((plugin) => {
|
|
1266
1303
|
this.use(plugin);
|
|
1267
1304
|
});
|
|
@@ -2063,12 +2100,13 @@ var Repository = class {
|
|
|
2063
2100
|
};
|
|
2064
2101
|
if (isKeyset && sort) {
|
|
2065
2102
|
const parsedSort = this._parseSort(sort);
|
|
2066
|
-
const { validateKeysetSort } = await import("./sort-
|
|
2067
|
-
const { encodeCursor, resolveCursorFilter } = await import("./cursor-
|
|
2068
|
-
const { getPrimaryField } = await import("./sort-
|
|
2069
|
-
const normalizedSort = validateKeysetSort(parsedSort);
|
|
2103
|
+
const { validateKeysetSort } = await import("./sort-ffe-9JBg.mjs").then((n) => n.n);
|
|
2104
|
+
const { encodeCursor, resolveCursorFilter } = await import("./cursor-DZ9JSoMM.mjs").then((n) => n.t);
|
|
2105
|
+
const { getPrimaryField } = await import("./sort-ffe-9JBg.mjs").then((n) => n.n);
|
|
2106
|
+
const normalizedSort = validateKeysetSort(parsedSort, this._pagination.config.strictKeysetSortFields);
|
|
2070
2107
|
const cursorVersion = this._pagination.config.cursorVersion ?? 1;
|
|
2071
|
-
const
|
|
2108
|
+
const minCursorVersion = this._pagination.config.minCursorVersion ?? 1;
|
|
2109
|
+
const matchFilters = after ? resolveCursorFilter(after, normalizedSort, cursorVersion, { ...filters || {} }, minCursorVersion) : { ...filters || {} };
|
|
2072
2110
|
if (projection) {
|
|
2073
2111
|
if (Object.values(projection).some((v) => v === 1)) {
|
|
2074
2112
|
for (const sortField of Object.keys(normalizedSort)) if (!(sortField in projection)) projection[sortField] = 1;
|
|
@@ -2415,6 +2453,28 @@ var Repository = class {
|
|
|
2415
2453
|
* ```
|
|
2416
2454
|
*/
|
|
2417
2455
|
/**
|
|
2456
|
+
* Resolve the user's jitter config into a deterministic `ttl -> ttl` function.
|
|
2457
|
+
*
|
|
2458
|
+
* - `undefined` / `0`: identity (no jitter).
|
|
2459
|
+
* - number in (0, 1]: symmetric fractional jitter. `0.1` → uniform in [0.9, 1.1].
|
|
2460
|
+
* - function: used as-is; caller owns the math. Clamped to >= 1s below.
|
|
2461
|
+
*
|
|
2462
|
+
* The final TTL is always clamped to a whole second >= 1 so adapters that
|
|
2463
|
+
* treat 0 as "no expiry" (Redis SETEX semantics) don't accidentally persist
|
|
2464
|
+
* entries forever.
|
|
2465
|
+
*/
|
|
2466
|
+
function resolveJitter(jitter) {
|
|
2467
|
+
if (!jitter) return (ttl) => ttl;
|
|
2468
|
+
if (typeof jitter === "function") return (ttl) => Math.max(1, Math.round(jitter(ttl)));
|
|
2469
|
+
const fraction = Math.min(1, Math.max(0, jitter));
|
|
2470
|
+
if (fraction === 0) return (ttl) => ttl;
|
|
2471
|
+
return (ttl) => {
|
|
2472
|
+
const delta = ttl * fraction;
|
|
2473
|
+
const jittered = ttl - delta + Math.random() * 2 * delta;
|
|
2474
|
+
return Math.max(1, Math.round(jittered));
|
|
2475
|
+
};
|
|
2476
|
+
}
|
|
2477
|
+
/**
|
|
2418
2478
|
* Cache plugin factory
|
|
2419
2479
|
*
|
|
2420
2480
|
* @param options - Cache configuration
|
|
@@ -2428,7 +2488,8 @@ function cachePlugin(options) {
|
|
|
2428
2488
|
queryTtl: options.queryTtl ?? options.ttl ?? 60,
|
|
2429
2489
|
prefix: options.prefix ?? "mk",
|
|
2430
2490
|
debug: options.debug ?? false,
|
|
2431
|
-
skipIfLargeLimit: options.skipIf?.largeLimit ?? 100
|
|
2491
|
+
skipIfLargeLimit: options.skipIf?.largeLimit ?? 100,
|
|
2492
|
+
applyJitter: resolveJitter(options.jitter)
|
|
2432
2493
|
};
|
|
2433
2494
|
const stats = {
|
|
2434
2495
|
hits: 0,
|
|
@@ -2468,7 +2529,7 @@ function cachePlugin(options) {
|
|
|
2468
2529
|
async function bumpVersion() {
|
|
2469
2530
|
const newVersion = Date.now();
|
|
2470
2531
|
try {
|
|
2471
|
-
await config.adapter.set(versionKey(config.prefix, model), newVersion, config.ttl * 10);
|
|
2532
|
+
await config.adapter.set(versionKey(config.prefix, model), newVersion, config.applyJitter(config.ttl * 10));
|
|
2472
2533
|
stats.invalidations++;
|
|
2473
2534
|
log(`Bumped version for ${model} to:`, newVersion);
|
|
2474
2535
|
} catch (e) {
|
|
@@ -2647,7 +2708,7 @@ function cachePlugin(options) {
|
|
|
2647
2708
|
populate: context.populate,
|
|
2648
2709
|
lean: context.lean
|
|
2649
2710
|
});
|
|
2650
|
-
const ttl = context.cacheTtl ?? config.byIdTtl;
|
|
2711
|
+
const ttl = config.applyJitter(context.cacheTtl ?? config.byIdTtl);
|
|
2651
2712
|
try {
|
|
2652
2713
|
await config.adapter.set(key, result, ttl);
|
|
2653
2714
|
trackByIdKey(id, key);
|
|
@@ -2671,7 +2732,7 @@ function cachePlugin(options) {
|
|
|
2671
2732
|
select: context.select,
|
|
2672
2733
|
populate: context.populate
|
|
2673
2734
|
});
|
|
2674
|
-
const ttl = context.cacheTtl ?? config.queryTtl;
|
|
2735
|
+
const ttl = config.applyJitter(context.cacheTtl ?? config.queryTtl);
|
|
2675
2736
|
try {
|
|
2676
2737
|
await config.adapter.set(key, result, ttl);
|
|
2677
2738
|
stats.sets++;
|
|
@@ -2693,7 +2754,7 @@ function cachePlugin(options) {
|
|
|
2693
2754
|
populate: context.populate
|
|
2694
2755
|
});
|
|
2695
2756
|
try {
|
|
2696
|
-
await config.adapter.set(key, result, context.cacheTtl ?? config.queryTtl);
|
|
2757
|
+
await config.adapter.set(key, result, config.applyJitter(context.cacheTtl ?? config.queryTtl));
|
|
2697
2758
|
stats.sets++;
|
|
2698
2759
|
} catch (e) {
|
|
2699
2760
|
log(`Failed to cache getOne:`, e);
|
|
@@ -2726,7 +2787,7 @@ function cachePlugin(options) {
|
|
|
2726
2787
|
countStrategy: context.countStrategy
|
|
2727
2788
|
};
|
|
2728
2789
|
const key = listQueryKey(config.prefix, model, collectionVersion, params);
|
|
2729
|
-
const ttl = context.cacheTtl ?? config.queryTtl;
|
|
2790
|
+
const ttl = config.applyJitter(context.cacheTtl ?? config.queryTtl);
|
|
2730
2791
|
try {
|
|
2731
2792
|
await config.adapter.set(key, result, ttl);
|
|
2732
2793
|
stats.sets++;
|
|
@@ -4215,6 +4276,57 @@ function subdocumentPlugin() {
|
|
|
4215
4276
|
};
|
|
4216
4277
|
}
|
|
4217
4278
|
//#endregion
|
|
4279
|
+
//#region src/plugins/tenant-context.ts
|
|
4280
|
+
/**
|
|
4281
|
+
* Request-scoped tenant context via AsyncLocalStorage.
|
|
4282
|
+
*
|
|
4283
|
+
* The multi-tenant plugin already accepts a `resolveContext` callback for
|
|
4284
|
+
* reading tenant ID from an external store. This helper wraps Node's
|
|
4285
|
+
* `AsyncLocalStorage` into a tiny, batteries-included API so apps don't need
|
|
4286
|
+
* to wire the plumbing by hand — a common source of cross-tenant leaks.
|
|
4287
|
+
*
|
|
4288
|
+
* @example
|
|
4289
|
+
* import { createTenantContext, multiTenantPlugin } from '@classytic/mongokit';
|
|
4290
|
+
*
|
|
4291
|
+
* const tenantContext = createTenantContext();
|
|
4292
|
+
*
|
|
4293
|
+
* // In your HTTP middleware (Express / Fastify / NestJS):
|
|
4294
|
+
* app.use((req, _res, next) => {
|
|
4295
|
+
* tenantContext.run({ tenantId: req.auth.organizationId }, next);
|
|
4296
|
+
* });
|
|
4297
|
+
*
|
|
4298
|
+
* // In your repository definition:
|
|
4299
|
+
* const repo = new Repository(Invoice, [
|
|
4300
|
+
* multiTenantPlugin({
|
|
4301
|
+
* tenantField: 'organizationId',
|
|
4302
|
+
* resolveContext: () => tenantContext.getTenantId(),
|
|
4303
|
+
* }),
|
|
4304
|
+
* ]);
|
|
4305
|
+
*
|
|
4306
|
+
* // In your handler — no need to pass organizationId manually:
|
|
4307
|
+
* await repo.getAll({ filters: { status: 'paid' } });
|
|
4308
|
+
*/
|
|
4309
|
+
function createTenantContext() {
|
|
4310
|
+
const storage = new AsyncLocalStorage();
|
|
4311
|
+
return {
|
|
4312
|
+
storage,
|
|
4313
|
+
run(store, fn) {
|
|
4314
|
+
return storage.run(store, fn);
|
|
4315
|
+
},
|
|
4316
|
+
getStore() {
|
|
4317
|
+
return storage.getStore();
|
|
4318
|
+
},
|
|
4319
|
+
getTenantId() {
|
|
4320
|
+
return storage.getStore()?.tenantId;
|
|
4321
|
+
},
|
|
4322
|
+
requireTenantId() {
|
|
4323
|
+
const id = storage.getStore()?.tenantId;
|
|
4324
|
+
if (!id) throw new Error("[mongokit] No tenant context active. Wrap the request in tenantContext.run({ tenantId }, fn).");
|
|
4325
|
+
return id;
|
|
4326
|
+
}
|
|
4327
|
+
};
|
|
4328
|
+
}
|
|
4329
|
+
//#endregion
|
|
4218
4330
|
//#region src/plugins/timestamp.plugin.ts
|
|
4219
4331
|
/**
|
|
4220
4332
|
* Timestamp plugin that auto-injects timestamps
|
|
@@ -4388,4 +4500,4 @@ function uniqueField(field, errorMessage) {
|
|
|
4388
4500
|
};
|
|
4389
4501
|
}
|
|
4390
4502
|
//#endregion
|
|
4391
|
-
export {
|
|
4503
|
+
export { AuditTrailQuery as A, cachePlugin as C, withTransaction as D, isTransactionUnsupported as E, auditLogPlugin as M, aggregateHelpersPlugin as N, AggregationBuilder as O, cascadePlugin as S, Repository as T, customIdPlugin as _, uniqueField as a, prefixedId as b, createTenantContext as c, observabilityPlugin as d, multiTenantPlugin as f, elasticSearchPlugin as g, fieldFilterPlugin as h, requireField as i, auditTrailPlugin as j, batchOperationsPlugin as k, subdocumentPlugin as l, methodRegistryPlugin as m, blockIf as n, validationChainPlugin as o, mongoOperationsPlugin as p, immutableField as r, timestampPlugin as s, autoInject as t, softDeletePlugin as u, dateSequentialId as v, HOOK_PRIORITY as w, sequentialId as x, getNextSequence as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/mongokit",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0",
|
|
4
4
|
"description": "Production-grade MongoDB repositories with zero dependencies - smart pagination, events, and plugins",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -101,8 +101,12 @@
|
|
|
101
101
|
"scripts": {
|
|
102
102
|
"build": "tsdown",
|
|
103
103
|
"dev": "tsdown --watch",
|
|
104
|
-
"test": "vitest run",
|
|
105
|
-
"test:
|
|
104
|
+
"test": "vitest run --project unit --project integration",
|
|
105
|
+
"test:unit": "vitest run --project unit",
|
|
106
|
+
"test:integration": "vitest run --project integration",
|
|
107
|
+
"test:e2e": "vitest run --project e2e",
|
|
108
|
+
"test:all": "vitest run --project unit --project integration --project e2e",
|
|
109
|
+
"test:watch": "vitest --project unit --project integration",
|
|
106
110
|
"test:coverage": "vitest run --coverage",
|
|
107
111
|
"typecheck": "tsc --noEmit",
|
|
108
112
|
"lint": "biome check src/",
|
|
@@ -124,6 +128,7 @@
|
|
|
124
128
|
"@vitest/coverage-v8": "^3.2.4",
|
|
125
129
|
"ajv": "^8.18.0",
|
|
126
130
|
"ajv-formats": "^3.0.1",
|
|
131
|
+
"dotenv": "^17.4.2",
|
|
127
132
|
"knip": "^6.3.0",
|
|
128
133
|
"mongodb-memory-server": "^10.4.3",
|
|
129
134
|
"mongoose": "^9.4.1",
|
package/dist/error-Bpbi_NKo.mjs
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
//#region src/utils/logger.ts
|
|
2
|
-
const noop = () => {};
|
|
3
|
-
let current = {
|
|
4
|
-
warn: console.warn.bind(console),
|
|
5
|
-
debug: noop
|
|
6
|
-
};
|
|
7
|
-
/**
|
|
8
|
-
* Configure the internal logger.
|
|
9
|
-
* Pass `false` to silence all output.
|
|
10
|
-
*/
|
|
11
|
-
function configureLogger(config) {
|
|
12
|
-
if (config === false) current = {
|
|
13
|
-
warn: noop,
|
|
14
|
-
debug: noop
|
|
15
|
-
};
|
|
16
|
-
else current = {
|
|
17
|
-
...current,
|
|
18
|
-
...config
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
/** Emit a warning — security blocks, config issues, performance hints */
|
|
22
|
-
function warn(message, ...args) {
|
|
23
|
-
current.warn(message, ...args);
|
|
24
|
-
}
|
|
25
|
-
/** Emit debug info — only visible when debug is enabled */
|
|
26
|
-
function debug(message, ...args) {
|
|
27
|
-
current.debug(message, ...args);
|
|
28
|
-
}
|
|
29
|
-
//#endregion
|
|
30
|
-
//#region src/utils/error.ts
|
|
31
|
-
/**
|
|
32
|
-
* Creates an error with HTTP status code
|
|
33
|
-
*
|
|
34
|
-
* @param status - HTTP status code
|
|
35
|
-
* @param message - Error message
|
|
36
|
-
* @returns Error with status property
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* throw createError(404, 'Document not found');
|
|
40
|
-
* throw createError(400, 'Invalid input');
|
|
41
|
-
* throw createError(403, 'Access denied');
|
|
42
|
-
*/
|
|
43
|
-
function createError(status, message) {
|
|
44
|
-
const error = new Error(message);
|
|
45
|
-
error.status = status;
|
|
46
|
-
return error;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Detect and convert a MongoDB E11000 duplicate-key error into
|
|
50
|
-
* a 409 HttpError with an actionable message.
|
|
51
|
-
*
|
|
52
|
-
* Returns `null` when the error is not a duplicate-key error.
|
|
53
|
-
*/
|
|
54
|
-
function parseDuplicateKeyError(error) {
|
|
55
|
-
if (!error || typeof error !== "object") return null;
|
|
56
|
-
const mongoErr = error;
|
|
57
|
-
if (mongoErr.code !== 11e3) return null;
|
|
58
|
-
const fields = mongoErr.keyPattern ? Object.keys(mongoErr.keyPattern) : [];
|
|
59
|
-
const values = mongoErr.keyValue ? Object.entries(mongoErr.keyValue).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ") : "";
|
|
60
|
-
return createError(409, fields.length ? `Duplicate value for ${fields.join(", ")}${values ? ` (${values})` : ""}` : "Duplicate key error");
|
|
61
|
-
}
|
|
62
|
-
//#endregion
|
|
63
|
-
export { warn as a, debug as i, parseDuplicateKeyError as n, configureLogger as r, createError as t };
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|