@ignex/ninox 0.1.6 → 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/dist/index.d.ts +52 -22
- package/dist/index.js +121 -32
- 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.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import * as mongodb from 'mongodb';
|
|
|
2
2
|
import { Db, MongoClient, ClientSession, Document, Filter, Hint, CollationOptions, ReadPreferenceLike, ReadConcern, WriteConcern, WithId, Condition, RootFilterOperators, Collection, FindOptions, Sort, FindCursor, ObjectId, Decimal128, ReadConcernLike, AggregateOptions, AggregationCursor, CreateCollectionOptions, TransactionOptions, InsertOneOptions, InsertOneResult, InsertManyResult, UpdateOptions, UpdateResult, FindOneAndUpdateOptions, DeleteResult, ChangeStreamOptions, ChangeStream } from 'mongodb';
|
|
3
3
|
import { L as LoggerLike } from './timeout-B10CTiGm.js';
|
|
4
4
|
export { C as CacheOptions, a as LRU, b as LRUOptions, c as LogFn, d as LogLevel, e as createCachedAsyncFactory, f as createCachedFactory, g as createConsoleLogger, h as createNoopLogger, i as hashString, s as sleep, j as sleepJittered, k as stableHash, l as stableStringify, w as withTimeout } from './timeout-B10CTiGm.js';
|
|
5
|
-
import * as bson from 'bson';
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Types + constants for the HotCache layer. All type-only (plus the stream-key
|
|
@@ -411,14 +410,6 @@ declare const createMongoCapabilitiesStore: () => MongoCapabilitiesStore;
|
|
|
411
410
|
/** Probe a connected client/db via `hello`. */
|
|
412
411
|
declare const probeMongoCapabilities: (client: MongoClient | Db | null | undefined) => Promise<MongoCapabilities>;
|
|
413
412
|
|
|
414
|
-
/**
|
|
415
|
-
* The ORM's error classes (`AppError` → `DomainError` / `InfraError` /
|
|
416
|
-
* `BadRequest`), their type guards, and the `ERROR_HTTP_STATUS` table that sets
|
|
417
|
-
* each error's `statusCode` at construction.
|
|
418
|
-
*
|
|
419
|
-
* A leaf module: nothing else in `src/errors/` imports this (so `driver-map.ts`
|
|
420
|
-
* and `http-status.ts` can depend on it without cycles).
|
|
421
|
-
*/
|
|
422
413
|
interface ErrorContext {
|
|
423
414
|
db?: string;
|
|
424
415
|
collection?: string;
|
|
@@ -437,23 +428,47 @@ declare class AppError extends Error {
|
|
|
437
428
|
readonly code: string;
|
|
438
429
|
readonly statusCode: number;
|
|
439
430
|
readonly extra?: Record<string, unknown>;
|
|
440
|
-
constructor(code: string, message: string, statusCode?: number, extra?: Record<string, unknown>);
|
|
441
431
|
/**
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
*
|
|
432
|
+
* Whether `message`/`extra` may be sent to a CLIENT.
|
|
433
|
+
*
|
|
434
|
+
* Fail-closed: a 4xx message is the caller's contract, a 5xx message is an
|
|
435
|
+
* operator detail that may quote a driver, a host or a query. Pass `expose`
|
|
436
|
+
* explicitly to override — only for a 5xx whose text was written for callers
|
|
437
|
+
* (e.g. `new InfraError("DRAINING", "queue is draining", extra, true)`).
|
|
438
|
+
*/
|
|
439
|
+
readonly expose: boolean;
|
|
440
|
+
constructor(code: string, message: string, statusCode?: number, extra?: Record<string, unknown>, expose?: boolean);
|
|
441
|
+
/**
|
|
442
|
+
* Full, stack-free shape for LOGS and typed consumers:
|
|
443
|
+
* `{ name, code, message, statusCode, ...(extra) }`.
|
|
444
|
+
*
|
|
445
|
+
* It carries the real message, so it is NOT the client path — use
|
|
446
|
+
* {@link toClientPayload} (or `serializeError`) for anything a caller sees, and
|
|
447
|
+
* prefer `logger.error({ err })` so pino's own serializer keeps the stack in the
|
|
448
|
+
* log rather than in a payload.
|
|
446
449
|
*/
|
|
447
450
|
toJSON(): Record<string, unknown>;
|
|
451
|
+
/**
|
|
452
|
+
* The client-safe payload — the same shape as {@link toJSON}, except that a
|
|
453
|
+
* non-exposed error's `message` is replaced by the canonical reason phrase and
|
|
454
|
+
* its `extra` is dropped. A framework can forward this (or `serializeError`'s
|
|
455
|
+
* result) without auditing what the message might contain.
|
|
456
|
+
*
|
|
457
|
+
* @param options - `expose: true` forces the real (masked) message + extra.
|
|
458
|
+
* @returns The client payload.
|
|
459
|
+
*/
|
|
460
|
+
toClientPayload(options?: {
|
|
461
|
+
expose?: boolean;
|
|
462
|
+
}): Record<string, unknown>;
|
|
448
463
|
}
|
|
449
464
|
declare class DomainError extends AppError {
|
|
450
|
-
constructor(code: string, message: string, extra?: Record<string, unknown
|
|
465
|
+
constructor(code: string, message: string, extra?: Record<string, unknown>, expose?: boolean);
|
|
451
466
|
}
|
|
452
467
|
declare class InfraError extends AppError {
|
|
453
|
-
constructor(code: string, message: string, extra?: Record<string, unknown
|
|
468
|
+
constructor(code: string, message: string, extra?: Record<string, unknown>, expose?: boolean);
|
|
454
469
|
}
|
|
455
470
|
declare class BadRequest extends DomainError {
|
|
456
|
-
constructor(message: string, extra?: Record<string, unknown
|
|
471
|
+
constructor(message: string, extra?: Record<string, unknown>, expose?: boolean);
|
|
457
472
|
}
|
|
458
473
|
declare const isAppError: (err: unknown) => err is AppError;
|
|
459
474
|
declare const isDomainError: (err: unknown) => err is DomainError;
|
|
@@ -483,8 +498,16 @@ declare const isMappedMongoError: (error: unknown) => error is AppError;
|
|
|
483
498
|
|
|
484
499
|
/** Effective HTTP status for any thrown value: AppError-aware, raw → 500 (503 transient). */
|
|
485
500
|
declare const httpStatusForError: (err: unknown) => number;
|
|
501
|
+
/** Options for {@link serializeError}. */
|
|
502
|
+
interface SerializeErrorOptions {
|
|
503
|
+
/**
|
|
504
|
+
* Include the real message (and `extra`) even for a non-exposed error — the
|
|
505
|
+
* development / trusted-caller path. It is still credential-masked.
|
|
506
|
+
*/
|
|
507
|
+
expose?: boolean;
|
|
508
|
+
}
|
|
486
509
|
/**
|
|
487
|
-
* Reduce any thrown value to a stable,
|
|
510
|
+
* Reduce any thrown value to a stable, CLIENT-SAFE error payload:
|
|
488
511
|
* `{ name, code, message, statusCode, ...(extra) }`.
|
|
489
512
|
*
|
|
490
513
|
* Raw driver errors are mapped through `mapMongoDriverError` first, so a
|
|
@@ -495,8 +518,15 @@ declare const httpStatusForError: (err: unknown) => number;
|
|
|
495
518
|
* const payload = serializeError(err, { db, collection, op });
|
|
496
519
|
* res.status(payload.statusCode).json(payload);
|
|
497
520
|
* });
|
|
521
|
+
*
|
|
522
|
+
* Fail-closed by default: a 4xx message/extra is the caller's contract, while a
|
|
523
|
+
* 5xx message is replaced by the canonical reason phrase (`Internal Server
|
|
524
|
+
* Error`, `Service Unavailable`, …) and `extra` is dropped — a driver message
|
|
525
|
+
* that quotes a host, a query or a stored value never reaches a client. The
|
|
526
|
+
* detail stays on the error itself for logs; pass `{ expose: true }` (or a
|
|
527
|
+
* per-error `expose`) to opt out for a trusted caller.
|
|
498
528
|
*/
|
|
499
|
-
declare const serializeError: (err: unknown, ctx?: ErrorContext) => Record<string, unknown>;
|
|
529
|
+
declare const serializeError: (err: unknown, ctx?: ErrorContext, options?: SerializeErrorOptions) => Record<string, unknown>;
|
|
500
530
|
|
|
501
531
|
/**
|
|
502
532
|
* Transient-error detection: which MongoDB errors are safe to retry. This is a
|
|
@@ -2038,7 +2068,7 @@ interface CrudOpsOptions {
|
|
|
2038
2068
|
* and fire lifecycle hooks.
|
|
2039
2069
|
*/
|
|
2040
2070
|
declare const makeCrudOps: <TClients extends DbClientsDefinition, TDb extends ExtractDbNames<TClients>>(client: Db, dbLabel: string, logger: LoggerLike, opts: CrudOpsOptions) => {
|
|
2041
|
-
watchCollection: <X extends ColNames<TClients, TDb>>(collection: X, pipeline?:
|
|
2071
|
+
watchCollection: <X extends ColNames<TClients, TDb>>(collection: X, pipeline?: mongodb.Document[], options?: mongodb.ChangeStreamOptions) => mongodb.ChangeStream<CollectionDoc<TClients[TDb]["collections"][X]>, mongodb.ChangeStreamDocument<CollectionDoc<TClients[TDb]["collections"][X]>>>;
|
|
2042
2072
|
query: <X extends ColNames<TClients, TDb>>(collection: X) => QueryBuilder<CollectionDoc<TClients[TDb]["collections"][X]>, CollectionDoc<TClients[TDb]["collections"][X]>>;
|
|
2043
2073
|
insertOne: <X extends ColNames<TClients, TDb>>(collection: X, doc: InsertInput<CollectionDoc<TClients[TDb]["collections"][X]>>, options?: mongodb.InsertOneOptions & QueryOptions) => Promise<mongodb.InsertOneResult<CollectionDoc<TClients[TDb]["collections"][X]>>>;
|
|
2044
2074
|
insertMany: <X extends ColNames<TClients, TDb>>(collection: X, docs: readonly InsertInput<CollectionDoc<TClients[TDb]["collections"][X]>>[], options?: mongodb.BulkWriteOptions & QueryOptions) => Promise<mongodb.InsertManyResult<CollectionDoc<TClients[TDb]["collections"][X]>>>;
|
|
@@ -2054,8 +2084,8 @@ declare const makeCrudOps: <TClients extends DbClientsDefinition, TDb extends Ex
|
|
|
2054
2084
|
filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>;
|
|
2055
2085
|
update: UpdateInput<CollectionDoc<TClients[TDb]["collections"][X]>>;
|
|
2056
2086
|
upsert?: boolean;
|
|
2057
|
-
}[], options?: mongodb.BulkWriteOptions & QueryOptions) => Promise<
|
|
2058
|
-
bulkWrite: <X extends ColNames<TClients, TDb>>(collection: X, operations: mongodb.AnyBulkWriteOperation<CollectionDoc<TClients[TDb]["collections"][X]>>[], options?: mongodb.BulkWriteOptions & QueryOptions) => Promise<
|
|
2087
|
+
}[], options?: mongodb.BulkWriteOptions & QueryOptions) => Promise<mongodb.Document>;
|
|
2088
|
+
bulkWrite: <X extends ColNames<TClients, TDb>>(collection: X, operations: mongodb.AnyBulkWriteOperation<CollectionDoc<TClients[TDb]["collections"][X]>>[], options?: mongodb.BulkWriteOptions & QueryOptions) => Promise<mongodb.Document>;
|
|
2059
2089
|
updateWithVersion: <X extends ColNames<TClients, TDb>>(collection: X, filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, update: UpdateInput<CollectionDoc<TClients[TDb]["collections"][X]>>, options?: mongodb.FindOneAndUpdateOptions & QueryOptions) => Promise<VersionedUpdateResult<CollectionDoc<TClients[TDb]["collections"][X]>>>;
|
|
2060
2090
|
findOneAndDelete: <X extends ColNames<TClients, TDb>>(collection: X, filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, options?: mongodb.FindOneAndDeleteOptions & QueryOptions) => Promise<CollectionDoc<TClients[TDb]["collections"][X]> | null>;
|
|
2061
2091
|
replaceOne: <X extends ColNames<TClients, TDb>>(collection: X, filter: FilterInput<CollectionDoc<TClients[TDb]["collections"][X]>>, replacement: InsertInput<CollectionDoc<TClients[TDb]["collections"][X]>>, options?: mongodb.ReplaceOptions & QueryOptions) => Promise<mongodb.UpdateResult<CollectionDoc<TClients[TDb]["collections"][X]>>>;
|
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;
|
|
@@ -3248,6 +3335,8 @@ var PipelineBuilder = class _PipelineBuilder {
|
|
|
3248
3335
|
this.deps = deps2;
|
|
3249
3336
|
this.stages = stages;
|
|
3250
3337
|
}
|
|
3338
|
+
deps;
|
|
3339
|
+
stages;
|
|
3251
3340
|
/** Raw accumulated stages — embeddable into `$lookup` / `$facet` branches. */
|
|
3252
3341
|
raw() {
|
|
3253
3342
|
return this.stages;
|