@procedures/mongo 0.43.0 → 0.45.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/CHANGELOG.md +26 -0
- package/README.md +47 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/storage/database-changes.d.ts +120 -0
- package/dist/storage/database-changes.d.ts.map +1 -0
- package/dist/storage/database-changes.js +273 -0
- package/dist/storage/database-changes.js.map +1 -0
- package/dist/storage/index.d.ts +2 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +2 -0
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/materializer.d.ts +12 -2
- package/dist/storage/materializer.d.ts.map +1 -1
- package/dist/storage/materializer.js +27 -25
- package/dist/storage/materializer.js.map +1 -1
- package/dist/storage/storage-hints.d.ts +23 -2
- package/dist/storage/storage-hints.d.ts.map +1 -1
- package/dist/storage/storage-hints.js +45 -37
- package/dist/storage/storage-hints.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +3 -0
- package/src/storage/database-changes.ts +357 -0
- package/src/storage/index.ts +3 -0
- package/src/storage/materializer.ts +36 -25
- package/src/storage/storage-hints.ts +56 -37
|
@@ -77,7 +77,10 @@ import {
|
|
|
77
77
|
type Cancel,
|
|
78
78
|
type Clock,
|
|
79
79
|
type CollectionChangeEvent,
|
|
80
|
+
type LogFields,
|
|
81
|
+
type Logger,
|
|
80
82
|
type ResubscribeBackoff,
|
|
83
|
+
writeLog,
|
|
81
84
|
} from "@procedures/client";
|
|
82
85
|
import {
|
|
83
86
|
DEFAULT_STORED_FORM,
|
|
@@ -196,7 +199,17 @@ export interface WatchStorageChangesOptions {
|
|
|
196
199
|
backoff?: ResubscribeBackoff | undefined;
|
|
197
200
|
/** The clock of the timers and of the time limit of the own writes (default the clock of the platform). The timers do not keep a process alive. */
|
|
198
201
|
clock?: Clock | undefined;
|
|
199
|
-
/**
|
|
202
|
+
/**
|
|
203
|
+
* The logger of the watcher (CLI-88). The fields of each line hold its context: `collection`,
|
|
204
|
+
* `operationType`, `resumeToken` (true when the watcher has a token), `error`, `attempt` and
|
|
205
|
+
* `delayMs`. When both are set, `logger` wins over `log`.
|
|
206
|
+
* @default the console, with the prefix `[storage-hints]`
|
|
207
|
+
*/
|
|
208
|
+
logger?: Logger | undefined;
|
|
209
|
+
/**
|
|
210
|
+
* The log function of each line, with the context in the text.
|
|
211
|
+
* @deprecated Use `logger`. Without a `logger`, the watcher still uses `log`.
|
|
212
|
+
*/
|
|
200
213
|
log?: ((level: "info" | "warn" | "error", message: string) => void) | undefined;
|
|
201
214
|
}
|
|
202
215
|
|
|
@@ -227,7 +240,7 @@ const SEQ = "_seq";
|
|
|
227
240
|
const KEY = "_key";
|
|
228
241
|
|
|
229
242
|
/** The codes of the errors after which the server does not know the resume token: the watcher opens a new stream and publishes a gap. */
|
|
230
|
-
const LOST_TOKEN_CODES: ReadonlySet<number> = new Set([260, 280, 286]);
|
|
243
|
+
export const LOST_TOKEN_CODES: ReadonlySet<number> = new Set([260, 280, 286]);
|
|
231
244
|
|
|
232
245
|
/** The codes of a refused access: Unauthorized, AuthenticationFailed, and the access error of Atlas. */
|
|
233
246
|
const REFUSED_CODES: ReadonlySet<number> = new Set([13, 18, 8000]);
|
|
@@ -262,7 +275,7 @@ function resetHint(): StorageChangeHint {
|
|
|
262
275
|
}
|
|
263
276
|
|
|
264
277
|
/** The code of a command that the server does not know (an old server without `hello`). */
|
|
265
|
-
const COMMAND_NOT_FOUND = 59;
|
|
278
|
+
export const COMMAND_NOT_FOUND = 59;
|
|
266
279
|
|
|
267
280
|
function asDocument(value: unknown): Document | undefined {
|
|
268
281
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? (value as Document) : undefined;
|
|
@@ -354,7 +367,7 @@ export function storageChangeOf(change: Document, form: StoredForm = DEFAULT_STO
|
|
|
354
367
|
}
|
|
355
368
|
|
|
356
369
|
/** The delay before try `attempt` (0 for the first) of a backoff. */
|
|
357
|
-
function backoffOf(options: ResubscribeBackoff | undefined): (attempt: number) => number {
|
|
370
|
+
export function backoffOf(options: ResubscribeBackoff | undefined): (attempt: number) => number {
|
|
358
371
|
const initial = optionalMs(options?.initialDelay, "backoff.initialDelay") ?? 250;
|
|
359
372
|
const max = optionalMs(options?.maxDelay, "backoff.maxDelay") ?? 30_000;
|
|
360
373
|
const factor = options?.backoffMultiplier ?? 2;
|
|
@@ -369,11 +382,11 @@ function backoffOf(options: ResubscribeBackoff | undefined): (attempt: number) =
|
|
|
369
382
|
}
|
|
370
383
|
|
|
371
384
|
/** The message of an error. */
|
|
372
|
-
function messageOf(error: unknown): string {
|
|
385
|
+
export function messageOf(error: unknown): string {
|
|
373
386
|
return String((error as Error | undefined)?.message ?? error);
|
|
374
387
|
}
|
|
375
388
|
|
|
376
|
-
function codeOf(error: unknown): number | undefined {
|
|
389
|
+
export function codeOf(error: unknown): number | undefined {
|
|
377
390
|
const code = (error as { code?: unknown } | undefined)?.code;
|
|
378
391
|
return typeof code === "number" ? code : undefined;
|
|
379
392
|
}
|
|
@@ -385,7 +398,7 @@ function isRefused(error: unknown): boolean {
|
|
|
385
398
|
}
|
|
386
399
|
|
|
387
400
|
/** True for an error of a first open that does not go away: a single server, a refused access, a wrong option. */
|
|
388
|
-
function isPermanent(error: unknown): boolean {
|
|
401
|
+
export function isPermanent(error: unknown): boolean {
|
|
389
402
|
const code = codeOf(error);
|
|
390
403
|
if (code !== undefined && PERMANENT_CODES.has(code)) return true;
|
|
391
404
|
return error instanceof MongoAPIError || error instanceof MongoParseError;
|
|
@@ -441,7 +454,12 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
441
454
|
const name = options.collection ?? source.collection;
|
|
442
455
|
const topicOf = options.topic ?? ((collection: string, partition: string | undefined): string | undefined => collectionChangeTopic(collection, partition));
|
|
443
456
|
const clock = options.clock ?? realClock;
|
|
444
|
-
const
|
|
457
|
+
const { logger, log: oldLog } = options;
|
|
458
|
+
/** One line: to `logger` with the fields, else to the old `log` with the text, else to the console with the text. */
|
|
459
|
+
const log = (level: "info" | "warn" | "error", message: string, fields: LogFields, text: string): void => {
|
|
460
|
+
if (!logger && oldLog) oldLog(level, text);
|
|
461
|
+
else writeLog(logger, level, message, { component: "storage-hints", ...fields }, `[storage-hints] ${text}`);
|
|
462
|
+
};
|
|
445
463
|
const emit: (topic: string, event: StorageChangeHint) => void | Promise<void> = typeof publish === "function" ? publish : publish.emit.bind(publish);
|
|
446
464
|
// The subscribed topics of the bus name the partitions of a gap. A custom topic rule cannot be read back.
|
|
447
465
|
const channels = typeof publish === "object" && typeof publish.channels === "function" && options.topic === undefined ? publish.channels.bind(publish) : undefined;
|
|
@@ -510,9 +528,9 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
510
528
|
topic = topicOf(name, partition);
|
|
511
529
|
if (topic === undefined) return;
|
|
512
530
|
const result: unknown = emit(topic, event);
|
|
513
|
-
if (result instanceof Promise) result.catch((error: unknown) => log("error", `the publish on ${topic} failed: ${messageOf(error)}`));
|
|
531
|
+
if (result instanceof Promise) result.catch((error: unknown) => log("error", "The publish of a hint failed", { collection: name, topic, partition, error }, `the publish on ${topic} failed: ${messageOf(error)}`));
|
|
514
532
|
} catch (error) {
|
|
515
|
-
log("error", `the publish on ${topic ?? name} failed: ${messageOf(error)}`);
|
|
533
|
+
log("error", "The publish of a hint failed", { collection: name, topic, partition, error }, `the publish on ${topic ?? name} failed: ${messageOf(error)}`);
|
|
516
534
|
}
|
|
517
535
|
};
|
|
518
536
|
|
|
@@ -532,18 +550,18 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
532
550
|
if (parsed?.collection === name) into.add(parsed.partition);
|
|
533
551
|
}
|
|
534
552
|
} catch (error) {
|
|
535
|
-
log("warn", `the bus does not tell its channels: ${messageOf(error)}`);
|
|
553
|
+
log("warn", "The bus does not tell its channels", { collection: name, error }, `the bus does not tell its channels: ${messageOf(error)}`);
|
|
536
554
|
}
|
|
537
555
|
try {
|
|
538
556
|
for (const partition of options.gapPartitions?.() ?? []) into.add(partition);
|
|
539
557
|
} catch (error) {
|
|
540
|
-
log("warn", `gapPartitions failed: ${messageOf(error)}`);
|
|
558
|
+
log("warn", "gapPartitions failed", { collection: name, error }, `gapPartitions failed: ${messageOf(error)}`);
|
|
541
559
|
}
|
|
542
560
|
};
|
|
543
561
|
|
|
544
562
|
/** The reset hint of a gap: the subscribers read the collection in full. */
|
|
545
|
-
const gap = (reason: string): void => {
|
|
546
|
-
log("warn", `${reason}: the subscribers read the collection in full`);
|
|
563
|
+
const gap = (reason: string, fields: LogFields): void => {
|
|
564
|
+
log("warn", "A gap in the change stream: the subscribers read the collection in full", { collection: source.collection, reason, ...fields }, `${reason}: the subscribers read the collection in full`);
|
|
547
565
|
// The raw writes of the gap have no event: the sweep adopts the documents without _seq
|
|
548
566
|
if (sweepOn) {
|
|
549
567
|
sweepWanted = true;
|
|
@@ -557,7 +575,7 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
557
575
|
(known) => {
|
|
558
576
|
for (const partition of known) topics.add(partition);
|
|
559
577
|
},
|
|
560
|
-
(error: unknown) => log("warn", `the counters of ${source.collection} cannot be read (${messageOf(error)}): the gap goes to the partitions of the hints and of the subscribers only`)
|
|
578
|
+
(error: unknown) => log("warn", "The counters cannot be read: the gap goes to the partitions of the hints and of the subscribers only", { collection: source.collection, error }, `the counters of ${source.collection} cannot be read (${messageOf(error)}): the gap goes to the partitions of the hints and of the subscribers only`)
|
|
561
579
|
)
|
|
562
580
|
.then(() => {
|
|
563
581
|
if (stopped) return;
|
|
@@ -592,7 +610,7 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
592
610
|
redo.set(key, { item: job.item, raw: job.raw, tries: 0 });
|
|
593
611
|
return true;
|
|
594
612
|
}
|
|
595
|
-
if (!redoFull) log("warn", `more than ${redoLimit} raw writes of ${source.collection} wait to be adopted again: the partitions of the next ones get a reset hint, and a sweep runs`);
|
|
613
|
+
if (!redoFull) log("warn", "The set of the raw writes to adopt again is full: the next ones get a reset hint, and a sweep runs", { collection: source.collection, limit: redoLimit }, `more than ${redoLimit} raw writes of ${source.collection} wait to be adopted again: the partitions of the next ones get a reset hint, and a sweep runs`);
|
|
596
614
|
redoFull = true;
|
|
597
615
|
return false;
|
|
598
616
|
};
|
|
@@ -614,7 +632,7 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
614
632
|
if (result === "unknown") {
|
|
615
633
|
if (job.kind === "sweep") leave(job.raw.storedId, "neither its id nor its partition");
|
|
616
634
|
else {
|
|
617
|
-
log("warn", `the raw write of ${describeId(job.raw.storedId)} in ${source.collection} tells neither its id nor its partition: the storage leaves it`);
|
|
635
|
+
log("warn", "A raw write tells neither its id nor its partition: the storage leaves it", { collection: source.collection, storedId: describeId(job.raw.storedId) }, `the raw write of ${describeId(job.raw.storedId)} in ${source.collection} tells neither its id nor its partition: the storage leaves it`);
|
|
618
636
|
publishItem(job.item);
|
|
619
637
|
}
|
|
620
638
|
}
|
|
@@ -622,7 +640,7 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
622
640
|
}
|
|
623
641
|
const error = result.error;
|
|
624
642
|
if (isRefused(error)) {
|
|
625
|
-
if (!refused) log("error", `the adoption of a raw write of ${source.collection} was refused (${messageOf(error)}): the watcher needs write access, or set rawWrites: "ignore". The hints of raw writes go out without a version`);
|
|
643
|
+
if (!refused) log("error", "The adoption of a raw write was refused: the hints of raw writes go out without a version", { collection: source.collection, error }, `the adoption of a raw write of ${source.collection} was refused (${messageOf(error)}): the watcher needs write access, or set rawWrites: "ignore". The hints of raw writes go out without a version`);
|
|
626
644
|
refused = true;
|
|
627
645
|
publishItem(job.item);
|
|
628
646
|
return job.done?.();
|
|
@@ -662,7 +680,7 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
662
680
|
if (first) {
|
|
663
681
|
// The last try failed: a reset hint for each partition. The redo tries once more; a second failure is for good.
|
|
664
682
|
const names = [...new Set(failed.map(({ job }) => job.item.partition))].map((partition) => (partition === undefined ? "(none)" : JSON.stringify(partition)));
|
|
665
|
-
log("warn", `the adoption of ${failed.length} raw writes of ${source.collection} failed (${messageOf(first.error)}): the partitions ${names.join(", ")} get a reset hint`);
|
|
683
|
+
log("warn", "The adoption of raw writes failed: their partitions get a reset hint", { collection: source.collection, count: failed.length, partitions: names, error: first.error }, `the adoption of ${failed.length} raw writes of ${source.collection} failed (${messageOf(first.error)}): the partitions ${names.join(", ")} get a reset hint`);
|
|
666
684
|
for (const { job } of failed) if (job.kind !== "redo" && !remember(job)) sweepWanted = true;
|
|
667
685
|
resetPartitions(failed.map(({ job }) => job));
|
|
668
686
|
}
|
|
@@ -679,7 +697,7 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
679
697
|
const enqueue = (job: AdoptJob): void => {
|
|
680
698
|
if (stopped) return job.done?.();
|
|
681
699
|
if (queue.length >= queueLimit) {
|
|
682
|
-
if (!full) log("warn", `more than ${queueLimit} raw writes of ${source.collection} wait for an adoption: the next ones are adopted again when the queue is empty`);
|
|
700
|
+
if (!full) log("warn", "The queue of the adoptions is full: the next raw writes are adopted again when it is empty", { collection: source.collection, limit: queueLimit }, `more than ${queueLimit} raw writes of ${source.collection} wait for an adoption: the next ones are adopted again when the queue is empty`);
|
|
683
701
|
full = true;
|
|
684
702
|
if (remember(job)) {
|
|
685
703
|
// The hint of a raw write of a change event goes out now, without a version. Its adoption gives the version.
|
|
@@ -744,10 +762,10 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
744
762
|
if (page.length < sweepPage) break;
|
|
745
763
|
}
|
|
746
764
|
if (stopped) return;
|
|
747
|
-
if (count > 0) log("info", `the sweep of ${source.collection} sent ${count} documents without _seq to the adoption`);
|
|
748
|
-
if (leftCount > 0) log("warn", `the sweep of ${source.collection} cannot place ${leftCount} documents without _seq, for example ${leftFirst}: the next sweeps leave them`);
|
|
765
|
+
if (count > 0) log("info", "The sweep sent the documents without _seq to the adoption", { collection: source.collection, count }, `the sweep of ${source.collection} sent ${count} documents without _seq to the adoption`);
|
|
766
|
+
if (leftCount > 0) log("warn", "The sweep cannot place documents without _seq: the next sweeps leave them", { collection: source.collection, count: leftCount, first: leftFirst }, `the sweep of ${source.collection} cannot place ${leftCount} documents without _seq, for example ${leftFirst}: the next sweeps leave them`);
|
|
749
767
|
} catch (error) {
|
|
750
|
-
if (!stopped) log("warn", `the sweep of the documents without _seq of ${source.collection} failed: ${messageOf(error)}`);
|
|
768
|
+
if (!stopped) log("warn", "The sweep of the documents without _seq failed", { collection: source.collection, error }, `the sweep of the documents without _seq of ${source.collection} failed: ${messageOf(error)}`);
|
|
751
769
|
}
|
|
752
770
|
};
|
|
753
771
|
|
|
@@ -764,7 +782,7 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
764
782
|
docs = await source.storedDocuments(part.map((job) => job.raw.storedId));
|
|
765
783
|
} catch (error) {
|
|
766
784
|
if (stopped) return;
|
|
767
|
-
log("warn", `the documents of ${part.length} raw writes of ${source.collection} to adopt again cannot be read (${messageOf(error)}): their partitions get a reset hint`);
|
|
785
|
+
log("warn", "The documents of raw writes to adopt again cannot be read: their partitions get a reset hint", { collection: source.collection, count: part.length, error }, `the documents of ${part.length} raw writes of ${source.collection} to adopt again cannot be read (${messageOf(error)}): their partitions get a reset hint`);
|
|
768
786
|
resetPartitions(part);
|
|
769
787
|
continue;
|
|
770
788
|
}
|
|
@@ -799,7 +817,7 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
799
817
|
if (redo.size === 0) return;
|
|
800
818
|
redoing = true;
|
|
801
819
|
void adoptAgain()
|
|
802
|
-
.catch((error: unknown) => log("warn", `the redo of the raw writes of ${source.collection} failed: ${messageOf(error)}`))
|
|
820
|
+
.catch((error: unknown) => log("warn", "The redo of the raw writes failed", { collection: source.collection, error }, `the redo of the raw writes of ${source.collection} failed: ${messageOf(error)}`))
|
|
803
821
|
.finally(() => {
|
|
804
822
|
redoing = false;
|
|
805
823
|
afterQueue();
|
|
@@ -810,14 +828,14 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
810
828
|
token = change["_id"];
|
|
811
829
|
attempt = 0;
|
|
812
830
|
const type = change["operationType"];
|
|
813
|
-
if (type === "drop" || type === "rename" || type === "dropDatabase") return gap(`the collection ${source.collection} had a ${String(type)}
|
|
831
|
+
if (type === "drop" || type === "rename" || type === "dropDatabase") return gap(`the collection ${source.collection} had a ${String(type)}`, { operationType: type, resumeToken: true });
|
|
814
832
|
const ns = asDocument(change["ns"]);
|
|
815
833
|
if (ns?.["coll"] === source.counters) return;
|
|
816
834
|
let item: StorageChange | undefined;
|
|
817
835
|
try {
|
|
818
836
|
item = storageChangeOf(change, source.form);
|
|
819
837
|
} catch (error) {
|
|
820
|
-
log("warn", `a change of ${source.collection} has no item: ${messageOf(error)}`);
|
|
838
|
+
log("warn", "A change has no item", { collection: source.collection, operationType: type, error }, `a change of ${source.collection} has no item: ${messageOf(error)}`);
|
|
821
839
|
return;
|
|
822
840
|
}
|
|
823
841
|
if (!item) return;
|
|
@@ -834,10 +852,11 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
834
852
|
adopt(item, change, before);
|
|
835
853
|
};
|
|
836
854
|
|
|
837
|
-
const schedule = (reason: string): void => {
|
|
855
|
+
const schedule = (reason: string, error?: unknown): void => {
|
|
838
856
|
if (stopped) return;
|
|
839
|
-
const delay = delayOf(attempt
|
|
840
|
-
log("warn", `${reason}: the stream of ${source.collection} opens again in ${Math.round(delay)} ms`);
|
|
857
|
+
const delay = delayOf(attempt);
|
|
858
|
+
log("warn", "The change stream failed: it opens again after a backoff", { collection: source.collection, reason, ...(error !== undefined && { error }), attempt: attempt + 1, delayMs: Math.round(delay), resumeToken: token !== undefined }, `${reason}: the stream of ${source.collection} opens again in ${Math.round(delay)} ms`);
|
|
859
|
+
attempt++;
|
|
841
860
|
timer = clock.after(delay, () => {
|
|
842
861
|
timer = undefined;
|
|
843
862
|
void open();
|
|
@@ -858,7 +877,7 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
858
877
|
|
|
859
878
|
/** The end of a watcher whose first open failed with an error that does not go away. */
|
|
860
879
|
const fail = (error: unknown): void => {
|
|
861
|
-
log("error", `the stream of ${source.collection} cannot open (${messageOf(error)}): the watcher stops`);
|
|
880
|
+
log("error", "The change stream cannot open: the watcher stops", { collection: source.collection, error }, `the stream of ${source.collection} cannot open (${messageOf(error)}): the watcher stops`);
|
|
862
881
|
stopped = true;
|
|
863
882
|
endAdoptions();
|
|
864
883
|
own?.close();
|
|
@@ -873,7 +892,7 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
873
892
|
db = await source.db();
|
|
874
893
|
} catch (error) {
|
|
875
894
|
if (!opened && isPermanent(error)) return fail(error);
|
|
876
|
-
return schedule(`the database is not available (${messageOf(error)})
|
|
895
|
+
return schedule(`the database is not available (${messageOf(error)})`, error);
|
|
877
896
|
}
|
|
878
897
|
if (stopped) return;
|
|
879
898
|
if (!checked) {
|
|
@@ -885,7 +904,7 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
885
904
|
// An old server without hello: the stream tells the error
|
|
886
905
|
if (codeOf(error) !== COMMAND_NOT_FOUND) {
|
|
887
906
|
if (!opened && isPermanent(error)) return fail(error);
|
|
888
|
-
return schedule(`the server does not answer hello (${messageOf(error)})
|
|
907
|
+
return schedule(`the server does not answer hello (${messageOf(error)})`, error);
|
|
889
908
|
}
|
|
890
909
|
}
|
|
891
910
|
if (stopped) return;
|
|
@@ -895,7 +914,7 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
895
914
|
checked = true;
|
|
896
915
|
}
|
|
897
916
|
own ??= trackOwnNumbers(db, source.collection, { numbers: skipOwn, clock });
|
|
898
|
-
if (token === undefined && opened) gap(`the stream of ${source.collection} opens again without a resume token
|
|
917
|
+
if (token === undefined && opened) gap(`the stream of ${source.collection} opens again without a resume token`, { resumeToken: false });
|
|
899
918
|
let next: ChangeStream;
|
|
900
919
|
try {
|
|
901
920
|
next = db.collection(source.collection).watch([], {
|
|
@@ -905,7 +924,7 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
905
924
|
});
|
|
906
925
|
} catch (error) {
|
|
907
926
|
if (!opened && isPermanent(error)) return fail(error);
|
|
908
|
-
return schedule(`the stream cannot open (${messageOf(error)})
|
|
927
|
+
return schedule(`the stream cannot open (${messageOf(error)})`, error);
|
|
909
928
|
}
|
|
910
929
|
stream = next;
|
|
911
930
|
let ended = false;
|
|
@@ -920,10 +939,10 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
920
939
|
const code = codeOf(error);
|
|
921
940
|
if (code !== undefined && LOST_TOKEN_CODES.has(code) && token !== undefined) {
|
|
922
941
|
// The next stream starts now, without the token: it publishes a gap
|
|
923
|
-
log("warn", `the server does not know the resume token of ${source.collection} (code ${code})`);
|
|
942
|
+
log("warn", "The server does not know the resume token", { collection: source.collection, code, resumeToken: true }, `the server does not know the resume token of ${source.collection} (code ${code})`);
|
|
924
943
|
token = undefined;
|
|
925
944
|
}
|
|
926
|
-
schedule(`the stream ${reason}
|
|
945
|
+
schedule(`the stream ${reason}`, error);
|
|
927
946
|
};
|
|
928
947
|
next.on("resumeTokenChanged", (resumeToken: unknown) => {
|
|
929
948
|
if (resumeToken === undefined || resumeToken === null) return;
|