@procedures/mongo 0.52.2 → 0.52.3
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 +17 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/storage/document-storage.d.ts.map +1 -1
- package/dist/storage/document-storage.js +20 -5
- package/dist/storage/document-storage.js.map +1 -1
- package/dist/storage/storage-hints.d.ts.map +1 -1
- package/dist/storage/storage-hints.js +11 -2
- package/dist/storage/storage-hints.js.map +1 -1
- package/package.json +5 -5
- package/src/storage/document-storage.ts +22 -5
- package/src/storage/storage-hints.ts +12 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@procedures/mongo",
|
|
4
|
-
"version": "0.52.
|
|
4
|
+
"version": "0.52.3",
|
|
5
5
|
"description": "MongoDB as typed procedures: documents, bulk writes, distinct, explain, collections, indexes, aggregation, change streams and database info, with safe update and delete targets.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mongodb",
|
|
@@ -68,8 +68,8 @@
|
|
|
68
68
|
"@mark1russell7/rigor-generators": "^0.1.5",
|
|
69
69
|
"@mark1russell7/rigor-statistics": "^0.1.9",
|
|
70
70
|
"@mark1russell7/service": "^0.6.0",
|
|
71
|
-
"@procedures/peer": "0.52.
|
|
72
|
-
"@procedures/store": "0.52.
|
|
71
|
+
"@procedures/peer": "0.52.3",
|
|
72
|
+
"@procedures/store": "0.52.3",
|
|
73
73
|
"@types/node": "^22.0.0",
|
|
74
74
|
"typescript": "^7.0.2",
|
|
75
75
|
"vitest": "^5.0.3"
|
|
@@ -78,8 +78,8 @@
|
|
|
78
78
|
"procedures": "./dist/register.js"
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
|
81
|
-
"@procedures/client": "0.52.
|
|
82
|
-
"@procedures/collections": "0.52.
|
|
81
|
+
"@procedures/client": "0.52.3",
|
|
82
|
+
"@procedures/collections": "0.52.3",
|
|
83
83
|
"mongodb": "^7.0.0",
|
|
84
84
|
"zod": "^4.6.5"
|
|
85
85
|
},
|
|
@@ -559,6 +559,20 @@ export interface RawStorageWrite {
|
|
|
559
559
|
/** The result of an adoption: `adopted`, `lost` (the document changed since: another adoption or write won), or `unknown` (no image and no stored `_id` tells the id and the partition). */
|
|
560
560
|
export type RawAdoption = "adopted" | "lost" | "unknown";
|
|
561
561
|
|
|
562
|
+
/**
|
|
563
|
+
* The key of a stored `_id` in a map: its extended JSON. Two object ids with other fields are two
|
|
564
|
+
* keys, and an `ObjectId` and its hex text are two keys too (audit 2026-11-08).
|
|
565
|
+
*/
|
|
566
|
+
function storedIdKey(id: unknown): string {
|
|
567
|
+
return BSON.EJSON.stringify({ v: id as never }, { relaxed: false });
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/** The order of two stored `_id` values of one place: two texts by their text, else by their keys. */
|
|
571
|
+
function idOrder(a: unknown, b: unknown, keyA: string, keyB: string): number {
|
|
572
|
+
const [x, y] = typeof a === "string" && typeof b === "string" ? [a, b] : [keyA, keyB];
|
|
573
|
+
return x < y ? -1 : x > y ? 1 : 0;
|
|
574
|
+
}
|
|
575
|
+
|
|
562
576
|
/** True when two values have the same BSON. */
|
|
563
577
|
function sameBson(a: unknown, b: unknown): boolean {
|
|
564
578
|
try {
|
|
@@ -1398,13 +1412,16 @@ export class MongoDocumentStorage<T> implements CollectionStorage<T> {
|
|
|
1398
1412
|
|
|
1399
1413
|
/** The listed documents with later documents over them, in the order of the entries. A tombstone removes its document. */
|
|
1400
1414
|
private withLaterDocs(listed: readonly Document[], later: readonly Document[]): Document[] {
|
|
1401
|
-
|
|
1402
|
-
const
|
|
1415
|
+
// The key knows the type of the `_id`: before, `String()` gave each object `_id` one key
|
|
1416
|
+
const keyOf = (doc: Document): string => storedIdKey(doc["_id"]);
|
|
1417
|
+
const docs = new Map(listed.map((doc) => [keyOf(doc), doc]));
|
|
1403
1418
|
for (const doc of later) {
|
|
1404
|
-
if (doc[TOMB] === true) docs.delete(
|
|
1405
|
-
else docs.set(
|
|
1419
|
+
if (doc[TOMB] === true) docs.delete(keyOf(doc));
|
|
1420
|
+
else docs.set(keyOf(doc), doc);
|
|
1406
1421
|
}
|
|
1407
|
-
return [...docs
|
|
1422
|
+
return [...docs]
|
|
1423
|
+
.sort(([keyA, a], [keyB, b]) => this.placeOf(a) - this.placeOf(b) || idOrder(a["_id"], b["_id"], keyA, keyB))
|
|
1424
|
+
.map(([, doc]) => doc);
|
|
1408
1425
|
}
|
|
1409
1426
|
|
|
1410
1427
|
async getChangesSince(since?: string): Promise<StorageChanges<T>> {
|
|
@@ -940,6 +940,8 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
940
940
|
}
|
|
941
941
|
stream = next;
|
|
942
942
|
const joiner = eventJoiner();
|
|
943
|
+
/** The token before the document of the last token: the token before the event at fragment 1. */
|
|
944
|
+
let tokenBefore: unknown = token;
|
|
943
945
|
/** The token at the open: a reopen at the same token is no success, so the backoff does not start again. */
|
|
944
946
|
const openToken = JSON.stringify(token ?? null);
|
|
945
947
|
let ended = false;
|
|
@@ -975,7 +977,11 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
975
977
|
next.on("resumeTokenChanged", (resumeToken: unknown) => {
|
|
976
978
|
if (resumeToken === undefined || resumeToken === null) return;
|
|
977
979
|
// In the middle of a split event, the token stays before the event: a stream that opens again gets all its fragments
|
|
978
|
-
if (!joiner.partial)
|
|
980
|
+
if (!joiner.partial) {
|
|
981
|
+
// The driver gives the token of a document before its change: fragment 1 puts the token back (audit 2026-11-08)
|
|
982
|
+
tokenBefore = token;
|
|
983
|
+
token = resumeToken;
|
|
984
|
+
}
|
|
979
985
|
opened = true;
|
|
980
986
|
if (JSON.stringify(resumeToken) !== openToken) attempt = 0;
|
|
981
987
|
markReady();
|
|
@@ -988,7 +994,11 @@ export function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish
|
|
|
988
994
|
live = true;
|
|
989
995
|
afterQueue();
|
|
990
996
|
const result = joiner.push(change);
|
|
991
|
-
if (result.kind === "part")
|
|
997
|
+
if (result.kind === "part") {
|
|
998
|
+
// The first fragment of a split event: the token goes back to the token before the event
|
|
999
|
+
if ((change["splitEvent"] as { fragment?: unknown } | undefined)?.fragment === 1) token = tokenBefore;
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
992
1002
|
if (result.kind === "missing") {
|
|
993
1003
|
// The stream started after a fragment: the event cannot be read, so the subscribers read the collection in full
|
|
994
1004
|
token = result.change["_id"];
|