@ignex/ninox 0.1.5 → 0.1.7
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 +15 -8
- package/dist/index.d.ts +73 -26
- package/dist/index.js +235 -82
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +3 -14
- package/dist/utils.js +20 -10
- package/dist/utils.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -57,6 +57,17 @@ var probeMongoCapabilities = async (client) => {
|
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
// src/utils/redact.ts
|
|
61
|
+
var CONNECTION_CREDENTIALS = /([a-z][a-z0-9+.-]*:\/\/[^/\s:@]*):[^/\s@]*@/gi;
|
|
62
|
+
var SECRET_PAIR = /\b(password|passwd|pwd|secret|token|api[-_]?key|access[-_]?key)=([^&\s;,"')]+)/gi;
|
|
63
|
+
var maskCredentials = (value) => value.replace(CONNECTION_CREDENTIALS, "$1:***@");
|
|
64
|
+
var maskSecretPairs = (value) => value.replace(SECRET_PAIR, "$1=***");
|
|
65
|
+
var redactText = (value, max = 300) => {
|
|
66
|
+
const single = maskSecretPairs(maskCredentials(value)).split("\n").find((line) => line.trim().length > 0);
|
|
67
|
+
const text = (single ?? "").trim();
|
|
68
|
+
return text.length <= max ? text : `${text.slice(0, max - 1)}\u2026`;
|
|
69
|
+
};
|
|
70
|
+
|
|
60
71
|
// src/errors/classes.ts
|
|
61
72
|
var ERROR_HTTP_STATUS = {
|
|
62
73
|
BAD_REQUEST: 400,
|
|
@@ -68,22 +79,49 @@ var ERROR_HTTP_STATUS = {
|
|
|
68
79
|
VALIDATION_FAILED: 422,
|
|
69
80
|
MONGO_TIMEOUT: 504
|
|
70
81
|
};
|
|
82
|
+
var STATUS_MESSAGES = {
|
|
83
|
+
400: "Bad Request",
|
|
84
|
+
401: "Unauthorized",
|
|
85
|
+
403: "Forbidden",
|
|
86
|
+
404: "Not Found",
|
|
87
|
+
409: "Conflict",
|
|
88
|
+
422: "Unprocessable Entity",
|
|
89
|
+
429: "Too Many Requests",
|
|
90
|
+
500: "Internal Server Error",
|
|
91
|
+
502: "Bad Gateway",
|
|
92
|
+
503: "Service Unavailable",
|
|
93
|
+
504: "Gateway Timeout"
|
|
94
|
+
};
|
|
95
|
+
var genericStatusMessage = (statusCode) => STATUS_MESSAGES[statusCode] ?? (statusCode >= 500 ? "Internal Server Error" : "Request failed");
|
|
71
96
|
var AppError = class extends Error {
|
|
72
97
|
code;
|
|
73
98
|
statusCode;
|
|
74
99
|
extra;
|
|
75
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Whether `message`/`extra` may be sent to a CLIENT.
|
|
102
|
+
*
|
|
103
|
+
* Fail-closed: a 4xx message is the caller's contract, a 5xx message is an
|
|
104
|
+
* operator detail that may quote a driver, a host or a query. Pass `expose`
|
|
105
|
+
* explicitly to override — only for a 5xx whose text was written for callers
|
|
106
|
+
* (e.g. `new InfraError("DRAINING", "queue is draining", extra, true)`).
|
|
107
|
+
*/
|
|
108
|
+
expose;
|
|
109
|
+
constructor(code, message, statusCode = 500, extra, expose) {
|
|
76
110
|
super(message);
|
|
77
111
|
this.name = "AppError";
|
|
78
112
|
this.code = code;
|
|
79
113
|
this.statusCode = statusCode;
|
|
114
|
+
this.expose = expose ?? statusCode < 500;
|
|
80
115
|
if (extra !== void 0) this.extra = extra;
|
|
81
116
|
}
|
|
82
117
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
118
|
+
* Full, stack-free shape for LOGS and typed consumers:
|
|
119
|
+
* `{ name, code, message, statusCode, ...(extra) }`.
|
|
120
|
+
*
|
|
121
|
+
* It carries the real message, so it is NOT the client path — use
|
|
122
|
+
* {@link toClientPayload} (or `serializeError`) for anything a caller sees, and
|
|
123
|
+
* prefer `logger.error({ err })` so pino's own serializer keeps the stack in the
|
|
124
|
+
* log rather than in a payload.
|
|
87
125
|
*/
|
|
88
126
|
toJSON() {
|
|
89
127
|
return {
|
|
@@ -94,22 +132,42 @@ var AppError = class extends Error {
|
|
|
94
132
|
...this.extra !== void 0 ? { extra: this.extra } : {}
|
|
95
133
|
};
|
|
96
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* The client-safe payload — the same shape as {@link toJSON}, except that a
|
|
137
|
+
* non-exposed error's `message` is replaced by the canonical reason phrase and
|
|
138
|
+
* its `extra` is dropped. A framework can forward this (or `serializeError`'s
|
|
139
|
+
* result) without auditing what the message might contain.
|
|
140
|
+
*
|
|
141
|
+
* @param options - `expose: true` forces the real (masked) message + extra.
|
|
142
|
+
* @returns The client payload.
|
|
143
|
+
*/
|
|
144
|
+
toClientPayload(options = {}) {
|
|
145
|
+
const expose = options.expose === true || this.expose;
|
|
146
|
+
const payload = {
|
|
147
|
+
name: this.name,
|
|
148
|
+
code: this.code,
|
|
149
|
+
message: expose ? maskCredentials(this.message) : genericStatusMessage(this.statusCode),
|
|
150
|
+
statusCode: this.statusCode
|
|
151
|
+
};
|
|
152
|
+
if (expose && this.extra !== void 0) payload.extra = this.extra;
|
|
153
|
+
return payload;
|
|
154
|
+
}
|
|
97
155
|
};
|
|
98
156
|
var DomainError = class extends AppError {
|
|
99
|
-
constructor(code, message, extra) {
|
|
100
|
-
super(code, message, ERROR_HTTP_STATUS[code] ?? 400, extra);
|
|
157
|
+
constructor(code, message, extra, expose) {
|
|
158
|
+
super(code, message, ERROR_HTTP_STATUS[code] ?? 400, extra, expose);
|
|
101
159
|
this.name = "DomainError";
|
|
102
160
|
}
|
|
103
161
|
};
|
|
104
162
|
var InfraError = class extends AppError {
|
|
105
|
-
constructor(code, message, extra) {
|
|
106
|
-
super(code, message, ERROR_HTTP_STATUS[code] ?? 500, extra);
|
|
163
|
+
constructor(code, message, extra, expose) {
|
|
164
|
+
super(code, message, ERROR_HTTP_STATUS[code] ?? 500, extra, expose);
|
|
107
165
|
this.name = "InfraError";
|
|
108
166
|
}
|
|
109
167
|
};
|
|
110
168
|
var BadRequest = class extends DomainError {
|
|
111
|
-
constructor(message, extra) {
|
|
112
|
-
super("BAD_REQUEST", message, extra);
|
|
169
|
+
constructor(message, extra, expose) {
|
|
170
|
+
super("BAD_REQUEST", message, extra, expose);
|
|
113
171
|
this.name = "BadRequest";
|
|
114
172
|
}
|
|
115
173
|
};
|
|
@@ -279,13 +337,20 @@ var httpStatusForError = (err) => {
|
|
|
279
337
|
if (isMongoTransientError(err)) return 503;
|
|
280
338
|
return 500;
|
|
281
339
|
};
|
|
282
|
-
var serializeError = (err, ctx = {}) => {
|
|
340
|
+
var serializeError = (err, ctx = {}, options = {}) => {
|
|
283
341
|
const mapped = mapMongoDriverError(err, ctx);
|
|
284
|
-
if (mapped instanceof AppError) return mapped.
|
|
285
|
-
|
|
286
|
-
|
|
342
|
+
if (mapped instanceof AppError) return mapped.toClientPayload(options);
|
|
343
|
+
const statusCode = httpStatusForError(mapped);
|
|
344
|
+
const name = mapped instanceof Error ? mapped.name : "UnknownError";
|
|
345
|
+
if (options.expose !== true) {
|
|
346
|
+
return { name, code: "UNKNOWN", message: genericStatusMessage(statusCode), statusCode };
|
|
287
347
|
}
|
|
288
|
-
return {
|
|
348
|
+
return {
|
|
349
|
+
name,
|
|
350
|
+
code: "UNKNOWN",
|
|
351
|
+
message: redactText(mapped instanceof Error ? mapped.message : String(mapped)),
|
|
352
|
+
statusCode
|
|
353
|
+
};
|
|
289
354
|
};
|
|
290
355
|
var cloneDeep = (value) => {
|
|
291
356
|
if (value instanceof Date) return new Date(value.getTime());
|
|
@@ -525,6 +590,7 @@ var RefreshTicker = class {
|
|
|
525
590
|
this.host = host;
|
|
526
591
|
this.intervalMs = tickIntervalMs;
|
|
527
592
|
}
|
|
593
|
+
host;
|
|
528
594
|
timer;
|
|
529
595
|
intervalMs;
|
|
530
596
|
/** Whether the timer is currently running. */
|
|
@@ -618,16 +684,26 @@ var normalizeWatchId = (id) => {
|
|
|
618
684
|
};
|
|
619
685
|
|
|
620
686
|
// src/utils/watch-errors.ts
|
|
621
|
-
var
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
687
|
+
var STANDALONE = /only supported on replica sets|not supported on (?:a )?standalone/i;
|
|
688
|
+
var CREDENTIALS = /requires authentication|not authorized|unauthorized|authentication failed|Command .* requires authentication/i;
|
|
689
|
+
var explainPermanentWatchError = (message) => {
|
|
690
|
+
if (STANDALONE.test(message)) {
|
|
691
|
+
return {
|
|
692
|
+
reason: "standalone",
|
|
693
|
+
headline: "change streams need a replica set",
|
|
694
|
+
hint: "Point the connection at a replica set (a single-node replica set is fine for local dev), or set cacheWatch: false / cache: { ttlMs } to run write-through only."
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
if (CREDENTIALS.test(message)) {
|
|
698
|
+
return {
|
|
699
|
+
reason: "credentials",
|
|
700
|
+
headline: "the connection is not authorized for change streams",
|
|
701
|
+
hint: "Change streams need an authenticated user with the read + changeStream privilege (usually granted on the authSource database) \u2014 fix the credentials in .env, then restart."
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
return void 0;
|
|
705
|
+
};
|
|
706
|
+
var isPermanentWatchError = (message) => explainPermanentWatchError(message) !== void 0;
|
|
631
707
|
|
|
632
708
|
// src/cache/hot-cache/watcher.ts
|
|
633
709
|
var resolveDbSafe = (ref, logger) => {
|
|
@@ -649,6 +725,7 @@ var WatchCoordinator = class {
|
|
|
649
725
|
constructor(host) {
|
|
650
726
|
this.host = host;
|
|
651
727
|
}
|
|
728
|
+
host;
|
|
652
729
|
streams = /* @__PURE__ */ new Map();
|
|
653
730
|
failedStreams = /* @__PURE__ */ new Set();
|
|
654
731
|
stopped = true;
|
|
@@ -762,7 +839,7 @@ var WatchCoordinator = class {
|
|
|
762
839
|
}
|
|
763
840
|
}
|
|
764
841
|
if (isPermanentWatchError(message)) {
|
|
765
|
-
this.markStreamFailed(key);
|
|
842
|
+
this.markStreamFailed(key, explainPermanentWatchError(message));
|
|
766
843
|
return;
|
|
767
844
|
}
|
|
768
845
|
}
|
|
@@ -771,12 +848,15 @@ var WatchCoordinator = class {
|
|
|
771
848
|
await sleepJittered(backoff, 1e3, true);
|
|
772
849
|
}
|
|
773
850
|
}
|
|
774
|
-
markStreamFailed(key) {
|
|
851
|
+
markStreamFailed(key, verdict) {
|
|
775
852
|
this.failedStreams.add(key);
|
|
776
853
|
if (!this.host.isReplica()) return;
|
|
777
854
|
const planned = this.plannedStreamKeys();
|
|
778
855
|
if (planned.size > 0 && [...planned].every((k) => this.failedStreams.has(k))) {
|
|
779
|
-
this.host.logger.warn?.(
|
|
856
|
+
this.host.logger.warn?.(
|
|
857
|
+
verdict === void 0 ? {} : { reason: verdict.reason, hint: verdict.hint },
|
|
858
|
+
`hot cache: change streams unsupported${verdict === void 0 ? "" : ` \u2014 ${verdict.headline}`}, falling back to ticker`
|
|
859
|
+
);
|
|
780
860
|
this.host.fallbackToStandalone();
|
|
781
861
|
}
|
|
782
862
|
}
|
|
@@ -1787,6 +1867,7 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
1787
1867
|
constructor(ctx) {
|
|
1788
1868
|
this.ctx = ctx;
|
|
1789
1869
|
}
|
|
1870
|
+
ctx;
|
|
1790
1871
|
_filter = {};
|
|
1791
1872
|
_sort;
|
|
1792
1873
|
_skip;
|
|
@@ -2814,13 +2895,19 @@ var CacheInvalidator = class {
|
|
|
2814
2895
|
} catch {
|
|
2815
2896
|
}
|
|
2816
2897
|
}
|
|
2817
|
-
|
|
2898
|
+
const verdict = explainPermanentWatchError(message);
|
|
2899
|
+
if (verdict) {
|
|
2818
2900
|
this.disabled = true;
|
|
2819
2901
|
if (!this.warnedUnsupported) {
|
|
2820
2902
|
this.warnedUnsupported = true;
|
|
2821
2903
|
this.logger.warn?.(
|
|
2822
|
-
{
|
|
2823
|
-
|
|
2904
|
+
{
|
|
2905
|
+
collection: ref.collection,
|
|
2906
|
+
reason: verdict.reason,
|
|
2907
|
+
error: message,
|
|
2908
|
+
hint: verdict.hint
|
|
2909
|
+
},
|
|
2910
|
+
`cacheWatch: disabled \u2014 ${verdict.headline}; the query cache stays write-through only`
|
|
2824
2911
|
);
|
|
2825
2912
|
}
|
|
2826
2913
|
return;
|
|
@@ -3142,13 +3229,28 @@ var probeDebugQuery = async () => {
|
|
|
3142
3229
|
debugQueryCache = null;
|
|
3143
3230
|
}
|
|
3144
3231
|
};
|
|
3232
|
+
var sanitizeDebugParams = (value) => {
|
|
3233
|
+
try {
|
|
3234
|
+
return JSON.parse(JSON.stringify(value));
|
|
3235
|
+
} catch {
|
|
3236
|
+
try {
|
|
3237
|
+
return String(value);
|
|
3238
|
+
} catch {
|
|
3239
|
+
return "[unserializable]";
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
};
|
|
3145
3243
|
var traceDbOp = async (logger, meta, fn, options = {}) => {
|
|
3146
3244
|
const start = performance.now();
|
|
3147
3245
|
logger.debug({ ...meta, operation: "start" }, "start");
|
|
3148
3246
|
try {
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3247
|
+
let debugQuery = options.debugQuery;
|
|
3248
|
+
if (debugQuery === void 0) {
|
|
3249
|
+
if (debugQueryCache === void 0) await probeDebugQuery();
|
|
3250
|
+
debugQuery = debugQueryCache;
|
|
3251
|
+
}
|
|
3252
|
+
const params = meta.params === void 0 ? void 0 : sanitizeDebugParams(meta.params);
|
|
3253
|
+
const result = debugQuery ? await debugQuery(`${meta.collection}.${meta.op}`, params, () => fn()) : await fn();
|
|
3152
3254
|
const durationMs = performance.now() - start;
|
|
3153
3255
|
logger.info({ ...meta, durationMs }, "ok");
|
|
3154
3256
|
return result;
|
|
@@ -3164,11 +3266,12 @@ var traceDbOp = async (logger, meta, fn, options = {}) => {
|
|
|
3164
3266
|
// src/service/op-deps.ts
|
|
3165
3267
|
var makeOpDeps = (logger, dbLabel, resolve, wrapMongoErrors) => {
|
|
3166
3268
|
const trace = (meta2, fn) => traceDbOp(logger, meta2, fn, { wrapMongoErrors });
|
|
3167
|
-
const meta = (collection, op) => ({
|
|
3269
|
+
const meta = (collection, op, params) => ({
|
|
3168
3270
|
collection: String(collection),
|
|
3169
3271
|
physicalCollection: resolve(String(collection)),
|
|
3170
3272
|
db: dbLabel,
|
|
3171
|
-
op
|
|
3273
|
+
op,
|
|
3274
|
+
...params !== void 0 ? { params } : {}
|
|
3172
3275
|
});
|
|
3173
3276
|
return { trace, meta };
|
|
3174
3277
|
};
|
|
@@ -3214,11 +3317,11 @@ var resolveDriftMode = (perOp, serviceDefault) => {
|
|
|
3214
3317
|
};
|
|
3215
3318
|
|
|
3216
3319
|
// src/service/crud-op.ts
|
|
3217
|
-
var defineCrudOp = async (deps2, collection, opName, execute, options, write = false) => {
|
|
3320
|
+
var defineCrudOp = async (deps2, collection, opName, execute, options, write = false, params) => {
|
|
3218
3321
|
const resolved = resolveQueryOptions(options);
|
|
3219
3322
|
const maxAttempts = write && resolved.retryWrites !== true ? 1 : resolved.maxAttempts;
|
|
3220
3323
|
return deps2.trace(
|
|
3221
|
-
deps2.meta(collection, opName),
|
|
3324
|
+
deps2.meta(collection, opName, params),
|
|
3222
3325
|
async () => withRetry(async () => execute(resolved), {
|
|
3223
3326
|
maxAttempts,
|
|
3224
3327
|
delayMs: resolved.retryDelayMs
|
|
@@ -3232,6 +3335,8 @@ var PipelineBuilder = class _PipelineBuilder {
|
|
|
3232
3335
|
this.deps = deps2;
|
|
3233
3336
|
this.stages = stages;
|
|
3234
3337
|
}
|
|
3338
|
+
deps;
|
|
3339
|
+
stages;
|
|
3235
3340
|
/** Raw accumulated stages — embeddable into `$lookup` / `$facet` branches. */
|
|
3236
3341
|
raw() {
|
|
3237
3342
|
return this.stages;
|
|
@@ -3323,7 +3428,9 @@ var PipelineBuilder = class _PipelineBuilder {
|
|
|
3323
3428
|
this.deps.logical,
|
|
3324
3429
|
"mongo.pipeline",
|
|
3325
3430
|
run,
|
|
3326
|
-
this.deps.options
|
|
3431
|
+
this.deps.options,
|
|
3432
|
+
false,
|
|
3433
|
+
{ pipeline: this.stages }
|
|
3327
3434
|
);
|
|
3328
3435
|
}
|
|
3329
3436
|
/** Run the pipeline and return the first output document (or null). */
|
|
@@ -3350,7 +3457,9 @@ var PipelineBuilder = class _PipelineBuilder {
|
|
|
3350
3457
|
this.deps.logical,
|
|
3351
3458
|
"mongo.pipeline.first",
|
|
3352
3459
|
run,
|
|
3353
|
-
this.deps.options
|
|
3460
|
+
this.deps.options,
|
|
3461
|
+
false,
|
|
3462
|
+
{ pipeline: this.stages }
|
|
3354
3463
|
);
|
|
3355
3464
|
}
|
|
3356
3465
|
/** Streaming cursor (raw driver cursor; no retry / trace). */
|
|
@@ -3470,7 +3579,9 @@ var makeAggregateOps = (ctx) => {
|
|
|
3470
3579
|
collection,
|
|
3471
3580
|
"mongo.aggregate",
|
|
3472
3581
|
(r) => coll(collection).aggregate(pipeline2, mergeAggOptions2(r.driverOpts, r.sdk)),
|
|
3473
|
-
options
|
|
3582
|
+
options,
|
|
3583
|
+
false,
|
|
3584
|
+
{ pipeline: pipeline2 }
|
|
3474
3585
|
);
|
|
3475
3586
|
};
|
|
3476
3587
|
const pipeline = (collection, options) => new PipelineBuilder({
|
|
@@ -3556,7 +3667,7 @@ var createCachedAggregate = (ctx) => {
|
|
|
3556
3667
|
const noSession = !resolved.sdk.session;
|
|
3557
3668
|
const useCache = cache !== void 0 && noSession && resolved.cache !== false && isCacheablePipeline(pipeline);
|
|
3558
3669
|
const shouldDedupe = resolved.dedupe !== false && (resolved.dedupe === true || opts.dedupeReads === true && noSession) && opts.inFlight !== void 0;
|
|
3559
|
-
const runDriver = () => defineCrudOp(deps2, collection, opName, execute, options);
|
|
3670
|
+
const runDriver = () => defineCrudOp(deps2, collection, opName, execute, options, false, { pipeline });
|
|
3560
3671
|
const key = useCache ? cache.key(
|
|
3561
3672
|
cacheCollectionKey(client.databaseName, physical),
|
|
3562
3673
|
stableHash([opName, pipeline, options])
|
|
@@ -3905,8 +4016,8 @@ var createCrudContext = (client, dbLabel, logger, opts) => {
|
|
|
3905
4016
|
);
|
|
3906
4017
|
const coll = (name) => client.collection(resolve(String(name)));
|
|
3907
4018
|
const toDriverFilter = (filter) => filter;
|
|
3908
|
-
const run = (collection, opName, execute, options) => defineCrudOp(deps2, collection, opName, execute, options);
|
|
3909
|
-
const writeRun = (collection, opName, execute, options) => defineCrudOp(deps2, collection, opName, execute, options, true);
|
|
4019
|
+
const run = (collection, opName, execute, options, params) => defineCrudOp(deps2, collection, opName, execute, options, false, params);
|
|
4020
|
+
const writeRun = (collection, opName, execute, options, params) => defineCrudOp(deps2, collection, opName, execute, options, true, params);
|
|
3910
4021
|
const invalidate = (collection) => {
|
|
3911
4022
|
opts.cache?.invalidateByCollection(
|
|
3912
4023
|
cacheCollectionKey(client.databaseName, resolve(String(collection)))
|
|
@@ -3942,6 +4053,9 @@ var createCrudContext = (client, dbLabel, logger, opts) => {
|
|
|
3942
4053
|
const read = async (collection, opName, filter, options, execute, shape = "none", keyExtra) => {
|
|
3943
4054
|
const physical = resolve(String(collection));
|
|
3944
4055
|
const resolved = resolveQueryOptions(options);
|
|
4056
|
+
const sentParams = { filter };
|
|
4057
|
+
if (Object.keys(resolved.driverOpts).length > 0) sentParams.options = resolved.driverOpts;
|
|
4058
|
+
if (keyExtra !== void 0) sentParams.field = keyExtra;
|
|
3945
4059
|
const noSession = !resolved.sdk.session;
|
|
3946
4060
|
const useCache = opts.cache !== void 0 && noSession && resolved.cache !== false;
|
|
3947
4061
|
const shouldDedupe = resolved.dedupe !== false && (resolved.dedupe === true || opts.dedupeReads === true && noSession) && opts.inFlight !== void 0;
|
|
@@ -3963,7 +4077,7 @@ var createCrudContext = (client, dbLabel, logger, opts) => {
|
|
|
3963
4077
|
);
|
|
3964
4078
|
};
|
|
3965
4079
|
const executeWrapped = () => deps2.trace(
|
|
3966
|
-
deps2.meta(collection, opName),
|
|
4080
|
+
deps2.meta(collection, opName, sentParams),
|
|
3967
4081
|
() => withRetry(() => execute(resolved), {
|
|
3968
4082
|
maxAttempts: resolved.maxAttempts,
|
|
3969
4083
|
delayMs: resolved.retryDelayMs
|
|
@@ -4292,7 +4406,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4292
4406
|
insertOpts
|
|
4293
4407
|
);
|
|
4294
4408
|
},
|
|
4295
|
-
options
|
|
4409
|
+
options,
|
|
4410
|
+
{ doc }
|
|
4296
4411
|
);
|
|
4297
4412
|
invalidate(collection);
|
|
4298
4413
|
await safeAfterHooks(String(collection), "afterCreate", {
|
|
@@ -4325,7 +4440,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4325
4440
|
bulkOpts
|
|
4326
4441
|
);
|
|
4327
4442
|
},
|
|
4328
|
-
options
|
|
4443
|
+
options,
|
|
4444
|
+
{ docs }
|
|
4329
4445
|
);
|
|
4330
4446
|
invalidate(collection);
|
|
4331
4447
|
await safeAfterHooks(String(collection), "afterCreate", {
|
|
@@ -4354,7 +4470,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4354
4470
|
updateOpts
|
|
4355
4471
|
);
|
|
4356
4472
|
},
|
|
4357
|
-
options
|
|
4473
|
+
options,
|
|
4474
|
+
{ filter, update: effective }
|
|
4358
4475
|
);
|
|
4359
4476
|
invalidate(collection);
|
|
4360
4477
|
await safeAfterHooks(String(collection), "afterUpdate", {
|
|
@@ -4383,7 +4500,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4383
4500
|
updateOpts
|
|
4384
4501
|
);
|
|
4385
4502
|
},
|
|
4386
|
-
options
|
|
4503
|
+
options,
|
|
4504
|
+
{ filter, update: effective }
|
|
4387
4505
|
);
|
|
4388
4506
|
invalidate(collection);
|
|
4389
4507
|
await safeAfterHooks(String(collection), "afterUpdate", {
|
|
@@ -4416,7 +4534,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4416
4534
|
fOpts
|
|
4417
4535
|
);
|
|
4418
4536
|
},
|
|
4419
|
-
options
|
|
4537
|
+
options,
|
|
4538
|
+
{ filter, update: effective }
|
|
4420
4539
|
);
|
|
4421
4540
|
invalidate(collection);
|
|
4422
4541
|
await safeAfterHooks(String(collection), "afterUpdate", {
|
|
@@ -4449,7 +4568,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4449
4568
|
fOpts
|
|
4450
4569
|
);
|
|
4451
4570
|
},
|
|
4452
|
-
options
|
|
4571
|
+
options,
|
|
4572
|
+
{ filter, replacement: effective }
|
|
4453
4573
|
);
|
|
4454
4574
|
invalidate(collection);
|
|
4455
4575
|
await safeAfterHooks(String(collection), "afterUpdate", {
|
|
@@ -4477,7 +4597,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4477
4597
|
repOpts
|
|
4478
4598
|
);
|
|
4479
4599
|
},
|
|
4480
|
-
options
|
|
4600
|
+
options,
|
|
4601
|
+
{ filter, replacement }
|
|
4481
4602
|
);
|
|
4482
4603
|
invalidate(collection);
|
|
4483
4604
|
await safeAfterHooks(String(collection), "afterUpdate", {
|
|
@@ -4501,7 +4622,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4501
4622
|
};
|
|
4502
4623
|
return await coll(collection).deleteOne(toDriverFilter(filter), delOpts);
|
|
4503
4624
|
},
|
|
4504
|
-
options
|
|
4625
|
+
options,
|
|
4626
|
+
{ filter }
|
|
4505
4627
|
);
|
|
4506
4628
|
invalidate(collection);
|
|
4507
4629
|
await safeAfterHooks(String(collection), "afterDelete", {
|
|
@@ -4525,7 +4647,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4525
4647
|
};
|
|
4526
4648
|
return await coll(collection).deleteMany(toDriverFilter(filter), delOpts);
|
|
4527
4649
|
},
|
|
4528
|
-
options
|
|
4650
|
+
options,
|
|
4651
|
+
{ filter }
|
|
4529
4652
|
);
|
|
4530
4653
|
invalidate(collection);
|
|
4531
4654
|
await safeAfterHooks(String(collection), "afterDelete", {
|
|
@@ -4554,7 +4677,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4554
4677
|
fOpts
|
|
4555
4678
|
);
|
|
4556
4679
|
},
|
|
4557
|
-
options
|
|
4680
|
+
options,
|
|
4681
|
+
{ filter }
|
|
4558
4682
|
);
|
|
4559
4683
|
invalidate(collection);
|
|
4560
4684
|
await safeAfterHooks(String(collection), "afterDelete", {
|
|
@@ -4586,7 +4710,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4586
4710
|
updateOpts
|
|
4587
4711
|
);
|
|
4588
4712
|
},
|
|
4589
|
-
options
|
|
4713
|
+
options,
|
|
4714
|
+
{ filter, update: effective }
|
|
4590
4715
|
);
|
|
4591
4716
|
invalidate(collection);
|
|
4592
4717
|
await safeAfterHooks(String(collection), "afterDelete", {
|
|
@@ -4612,7 +4737,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4612
4737
|
};
|
|
4613
4738
|
return await coll(collection).updateOne(toDriverFilter(filter), formatted, updateOpts);
|
|
4614
4739
|
},
|
|
4615
|
-
options
|
|
4740
|
+
options,
|
|
4741
|
+
{ filter, update: formatted }
|
|
4616
4742
|
);
|
|
4617
4743
|
invalidate(collection);
|
|
4618
4744
|
await safeAfterHooks(String(collection), "afterUpdate", {
|
|
@@ -4644,7 +4770,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4644
4770
|
};
|
|
4645
4771
|
return await coll(collection).bulkWrite(writes, bulkOpts);
|
|
4646
4772
|
},
|
|
4647
|
-
options
|
|
4773
|
+
options,
|
|
4774
|
+
{ operations }
|
|
4648
4775
|
);
|
|
4649
4776
|
invalidate(collection);
|
|
4650
4777
|
return result;
|
|
@@ -4665,7 +4792,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4665
4792
|
};
|
|
4666
4793
|
return await coll(collection).bulkWrite(operations, bulkOpts);
|
|
4667
4794
|
},
|
|
4668
|
-
options
|
|
4795
|
+
options,
|
|
4796
|
+
{ operations }
|
|
4669
4797
|
);
|
|
4670
4798
|
invalidate(collection);
|
|
4671
4799
|
return result;
|
|
@@ -4848,7 +4976,9 @@ var makePaginateCursor = (ctx) => {
|
|
|
4848
4976
|
};
|
|
4849
4977
|
return client.collection(resolve(String(collection))).find(filter, findOpts).sort(effectiveSort).limit(limit + 1).toArray();
|
|
4850
4978
|
},
|
|
4851
|
-
config.queryOptions
|
|
4979
|
+
config.queryOptions,
|
|
4980
|
+
false,
|
|
4981
|
+
{ filter, sort: effectiveSort, limit }
|
|
4852
4982
|
);
|
|
4853
4983
|
const hasMore = rows.length > limit;
|
|
4854
4984
|
const page = backwards ? rows.slice(0, limit).reverse() : rows.slice(0, limit);
|
|
@@ -4974,40 +5104,63 @@ var optionSignature = (o) => JSON.stringify([
|
|
|
4974
5104
|
]);
|
|
4975
5105
|
var indexKeyOf = (key, options) => `${JSON.stringify(Object.keys(key).map((k) => [k, key[k]]))}|${optionSignature(options)}`;
|
|
4976
5106
|
var makeSchemaOps = (client, _logger, opts) => {
|
|
5107
|
+
const readCollectionValidator = async (physical) => {
|
|
5108
|
+
const info = await client.listCollections({ name: physical }).next();
|
|
5109
|
+
return info?.options?.validator;
|
|
5110
|
+
};
|
|
5111
|
+
const ensureDeclaredIndexes = async (collection, physical, def) => {
|
|
5112
|
+
const declared = def?.indexes ?? [];
|
|
5113
|
+
if (declared.length === 0) return;
|
|
5114
|
+
const handle = client.collection(physical);
|
|
5115
|
+
const existing = new Set(
|
|
5116
|
+
(await handle.listIndexes().toArray()).filter((i) => i.name !== "_id_").map((i) => indexKeyOf(i.key, i))
|
|
5117
|
+
);
|
|
5118
|
+
for (const index of declared) {
|
|
5119
|
+
if (existing.has(indexKeyOf(index.key, index.options))) continue;
|
|
5120
|
+
try {
|
|
5121
|
+
await handle.createIndex(index.key, index.options);
|
|
5122
|
+
} catch (err) {
|
|
5123
|
+
if (err?.code === 85) {
|
|
5124
|
+
throw new Error(
|
|
5125
|
+
`Cannot apply declared index on "${collection}": an index with the same name/key already exists with different options. Run db.syncIndexes("${collection}") to reconcile index drift.`,
|
|
5126
|
+
{ cause: err }
|
|
5127
|
+
);
|
|
5128
|
+
}
|
|
5129
|
+
throw err;
|
|
5130
|
+
}
|
|
5131
|
+
}
|
|
5132
|
+
};
|
|
4977
5133
|
const createSchema = async (collection, createCollectionOptions) => {
|
|
4978
5134
|
const physical = opts.resolveCollectionName(collection);
|
|
4979
5135
|
const def = opts.getDefinition(collection);
|
|
4980
|
-
const
|
|
5136
|
+
const createOptions = {
|
|
5137
|
+
...createCollectionOptions ?? {},
|
|
5138
|
+
...def?.schema ? { validator: toMongoValidator(def.schema) } : {}
|
|
5139
|
+
};
|
|
5140
|
+
let created = true;
|
|
4981
5141
|
try {
|
|
4982
|
-
await client.createCollection(physical,
|
|
4983
|
-
// Caller options FIRST, derived validator LAST — a stale `validator`
|
|
4984
|
-
// key in the options must not clobber the schema-derived one.
|
|
4985
|
-
...createCollectionOptions ?? {},
|
|
4986
|
-
...validator ? { validator } : {}
|
|
4987
|
-
});
|
|
5142
|
+
await client.createCollection(physical, createOptions);
|
|
4988
5143
|
} catch (err) {
|
|
4989
|
-
if (err?.code
|
|
4990
|
-
|
|
4991
|
-
physicalCollection: physical
|
|
4992
|
-
});
|
|
4993
|
-
}
|
|
4994
|
-
throw err;
|
|
5144
|
+
if (err?.code !== 48) throw err;
|
|
5145
|
+
created = false;
|
|
4995
5146
|
}
|
|
4996
|
-
|
|
4997
|
-
if (indexes.length > 0) {
|
|
5147
|
+
if (created) {
|
|
4998
5148
|
try {
|
|
4999
|
-
await
|
|
5000
|
-
indexes.map((index) => ({
|
|
5001
|
-
key: index.key,
|
|
5002
|
-
...index.options ?? {}
|
|
5003
|
-
}))
|
|
5004
|
-
);
|
|
5149
|
+
await ensureDeclaredIndexes(collection, physical, def);
|
|
5005
5150
|
} catch (err) {
|
|
5006
5151
|
await client.dropCollection(physical).catch(() => {
|
|
5007
5152
|
});
|
|
5008
5153
|
throw err;
|
|
5009
5154
|
}
|
|
5155
|
+
return;
|
|
5156
|
+
}
|
|
5157
|
+
if (createOptions.validator) {
|
|
5158
|
+
const current = await readCollectionValidator(physical);
|
|
5159
|
+
if (JSON.stringify(current) !== JSON.stringify(createOptions.validator)) {
|
|
5160
|
+
await client.command({ collMod: physical, validator: createOptions.validator });
|
|
5161
|
+
}
|
|
5010
5162
|
}
|
|
5163
|
+
await ensureDeclaredIndexes(collection, physical, def);
|
|
5011
5164
|
};
|
|
5012
5165
|
const updateSchema = async (collection, schema) => {
|
|
5013
5166
|
const physical = opts.resolveCollectionName(collection);
|