@procedures/mongo 0.35.0 → 0.36.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +27 -0
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/index.d.ts +3 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +3 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/module.d.ts +2 -2
  9. package/dist/module.d.ts.map +1 -1
  10. package/dist/module.js +4 -5
  11. package/dist/module.js.map +1 -1
  12. package/dist/procedures/input-schemas.d.ts.map +1 -1
  13. package/dist/procedures/input-schemas.js +29 -2
  14. package/dist/procedures/input-schemas.js.map +1 -1
  15. package/dist/storage/document-storage.d.ts +162 -3
  16. package/dist/storage/document-storage.d.ts.map +1 -1
  17. package/dist/storage/document-storage.js +325 -7
  18. package/dist/storage/document-storage.js.map +1 -1
  19. package/dist/storage/index.d.ts +3 -1
  20. package/dist/storage/index.d.ts.map +1 -1
  21. package/dist/storage/index.js +3 -1
  22. package/dist/storage/index.js.map +1 -1
  23. package/dist/storage/materializer.d.ts.map +1 -1
  24. package/dist/storage/materializer.js +7 -0
  25. package/dist/storage/materializer.js.map +1 -1
  26. package/dist/storage/own-writes.d.ts +51 -0
  27. package/dist/storage/own-writes.d.ts.map +1 -0
  28. package/dist/storage/own-writes.js +130 -0
  29. package/dist/storage/own-writes.js.map +1 -0
  30. package/dist/storage/storage-hints.d.ts +230 -0
  31. package/dist/storage/storage-hints.d.ts.map +1 -0
  32. package/dist/storage/storage-hints.js +876 -0
  33. package/dist/storage/storage-hints.js.map +1 -0
  34. package/package.json +3 -3
  35. package/src/index.ts +4 -0
  36. package/src/module.ts +5 -5
  37. package/src/procedures/input-schemas.ts +29 -2
  38. package/src/storage/document-storage.ts +394 -7
  39. package/src/storage/index.ts +4 -0
  40. package/src/storage/materializer.ts +7 -0
  41. package/src/storage/own-writes.ts +157 -0
  42. package/src/storage/storage-hints.ts +971 -0
@@ -0,0 +1,130 @@
1
+ /**
2
+ * The writes of this process (roadmap CLI-77). Each write of a `MongoDocumentStorage` takes
3
+ * numbers of the sequence of its collection (`_seq`). While a watcher of the collection
4
+ * (`watchStorageChanges`) tracks the collection, the storage notes here each number that it takes,
5
+ * and the stored `_id` of each tombstone that it prunes. The watcher then knows the change events
6
+ * of the writes of this process.
7
+ *
8
+ * - The numbers: the collection procedures of this process publish the events of these writes
9
+ * already, so a watcher with `ownWrites: "skip"` skips them.
10
+ * - The pruned tombstones: the delete of a pruned tombstone deletes no item. Without the image
11
+ * before the change, the event of the delete has only the stored `_id`, so the watcher reads it
12
+ * here, and skips the event.
13
+ * - Each watcher has its own tracker. A tracker takes each noted entry once: the event of the
14
+ * write takes it. A later event with the same `_seq` (for example a raw `$set` that keeps the
15
+ * field) is not of this process, and the watcher reads it as a raw write.
16
+ * - The storage notes entries only while a tracker of the collection is open, so a process without
17
+ * a watcher keeps nothing.
18
+ * - The trackers of a collection are by the `MongoClient` of the database and the names of the
19
+ * database and of the collection: two `Db` objects of one database share them.
20
+ * - An entry without an event (for example of an aborted transaction) goes after
21
+ * `OWN_WRITE_TTL_MS` on the clock of the tracker, and a tracker keeps at most `MAX_OWN_WRITES`
22
+ * entries of each kind: the oldest go first. An entry that goes too early makes a second hint at
23
+ * most, never a lost hint.
24
+ * - The numbers of an adoption of a raw write (D39) are never noted: the watcher publishes the
25
+ * hint of the adoption.
26
+ */
27
+ import { BSON } from "mongodb";
28
+ /** The longest time that a noted entry waits for its change event: 5 minutes. */
29
+ const OWN_WRITE_TTL_MS = 5 * 60_000;
30
+ /** The most noted entries of each kind of one tracker. */
31
+ const MAX_OWN_WRITES = 100_000;
32
+ /** The trackers of each collection: by the client (else the `Db` object), then by the names of the database and the collection. */
33
+ const TRACKERS = new WeakMap();
34
+ function ownerOf(db) {
35
+ const client = db.client;
36
+ return typeof client === "object" && client !== null ? client : db;
37
+ }
38
+ function nameOf(db, collection) {
39
+ return `${db.databaseName}\u0000${collection}`;
40
+ }
41
+ function trackersOf(db, collection) {
42
+ return TRACKERS.get(ownerOf(db))?.get(nameOf(db, collection));
43
+ }
44
+ /** The key of a stored `_id` in a tracker: its extended JSON, so an `ObjectId` and a text never meet. */
45
+ function idKey(storedId) {
46
+ return BSON.EJSON.stringify({ v: storedId }, { relaxed: false });
47
+ }
48
+ /** This function notes some keys in a map with a time limit, and drops the old ones. */
49
+ function noteAll(map, keys, now) {
50
+ for (const key of keys) {
51
+ // A key noted again (an aborted transaction gives its numbers again) moves to the end
52
+ map.delete(key);
53
+ map.set(key, now + OWN_WRITE_TTL_MS);
54
+ }
55
+ // The map keeps the order of the notes: the oldest keys are first
56
+ for (const [key, until] of map) {
57
+ if (until > now && map.size <= MAX_OWN_WRITES)
58
+ break;
59
+ map.delete(key);
60
+ }
61
+ }
62
+ /** This function starts a tracker of the writes of this process in a collection. */
63
+ export function trackOwnNumbers(db, collection, options = {}) {
64
+ const owner = ownerOf(db);
65
+ const name = nameOf(db, collection);
66
+ const clock = options.clock;
67
+ const tracker = {
68
+ numbers: options.numbers === false ? undefined : new Map(),
69
+ pruned: new Map(),
70
+ now: clock ? () => clock.now() : Date.now,
71
+ };
72
+ let byName = TRACKERS.get(owner);
73
+ if (!byName) {
74
+ byName = new Map();
75
+ TRACKERS.set(owner, byName);
76
+ }
77
+ let trackers = byName.get(name);
78
+ if (!trackers) {
79
+ trackers = new Set();
80
+ byName.set(name, trackers);
81
+ }
82
+ trackers.add(tracker);
83
+ const live = (map, key) => {
84
+ const until = map.get(key);
85
+ if (until === undefined)
86
+ return false;
87
+ map.delete(key);
88
+ return until > tracker.now();
89
+ };
90
+ return {
91
+ take(seq) {
92
+ return tracker.numbers !== undefined && live(tracker.numbers, seq);
93
+ },
94
+ takePruned(storedId) {
95
+ return live(tracker.pruned, idKey(storedId));
96
+ },
97
+ close() {
98
+ const set = TRACKERS.get(owner)?.get(name);
99
+ set?.delete(tracker);
100
+ if (set?.size === 0)
101
+ TRACKERS.get(owner)?.delete(name);
102
+ tracker.numbers?.clear();
103
+ tracker.pruned.clear();
104
+ },
105
+ };
106
+ }
107
+ /** This function notes the numbers `first` to `first + count - 1` of a write of this process, for each open tracker of the collection. */
108
+ export function noteOwnNumbers(db, collection, first, count) {
109
+ const trackers = trackersOf(db, collection);
110
+ if (!trackers)
111
+ return;
112
+ for (const tracker of trackers) {
113
+ if (!tracker.numbers)
114
+ continue;
115
+ const numbers = [];
116
+ for (let seq = first; seq < first + count; seq++)
117
+ numbers.push(seq);
118
+ noteAll(tracker.numbers, numbers, tracker.now());
119
+ }
120
+ }
121
+ /** This function notes the stored `_id` of each tombstone that this process prunes, for each open tracker of the collection. Call it before the delete. */
122
+ export function notePrunedIds(db, collection, storedIds) {
123
+ const trackers = trackersOf(db, collection);
124
+ if (!trackers)
125
+ return;
126
+ const keys = storedIds.map(idKey);
127
+ for (const tracker of trackers)
128
+ noteAll(tracker.pruned, keys, tracker.now());
129
+ }
130
+ //# sourceMappingURL=own-writes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"own-writes.js","sourceRoot":"","sources":["../../src/storage/own-writes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,230 @@
1
+ /**
2
+ * The change hints of a `MongoDocumentStorage` collection (roadmap CLI-77). `watchStorageChanges()`
3
+ * watches the change stream of the collection, and publishes each change as a
4
+ * `CollectionChangeEvent` on the topic of the collection procedures. Thus a subscriber also gets
5
+ * the writes that did not go through the procedures: a raw write, a script, another process, a
6
+ * view.
7
+ *
8
+ * - The topic is `collectionChangeTopic(name, partition)`, as the procedures publish it: the
9
+ * partition of a document comes from `storage.form.partitionOf()`.
10
+ * - The event is a hint, without `since` and without `version` (the version of the change log): the
11
+ * subscriber reads the changes with `changes`, as for each event without `since`. The event has
12
+ * the id in `changed` or in `deleted`, and the version of the item in `versions` (a field of
13
+ * `CollectionChangeEvent`): the version that `storage.versionOf(id)` gives after the write.
14
+ * - A write through the storage gives the document a new `_seq`. The watcher reads that from the
15
+ * event, never from the `fullDocument` of the lookup: an update with `_seq` in
16
+ * `updateDescription.updatedFields`, an insert or a replace with `_seq`. With the image before
17
+ * the change, a replace whose `_seq` is the `_seq` before is a raw write (a copy). Without the
18
+ * image, a replace with `_seq` counts as a write of the storage.
19
+ * - A raw write (D39), with `rawWrites: "adopt"` (the default): the watcher publishes nothing for
20
+ * its event. The storage adopts it: one conditional write through the path of each write gives
21
+ * the document a new `_seq` (and the storage fields and partition fields that a raw replace took
22
+ * away), so `changes(since)` has it. The event of the adoption is a write of the storage: its
23
+ * hint goes out, with the new version. When two watchers adopt the same write, or a write of the
24
+ * storage comes first, one write wins and the others do nothing. A raw write never gets the old
25
+ * version. With `rawWrites: "ignore"` (for example for a user that can only read), the hint of a
26
+ * raw write goes out without a version, and `changes(since)` does not have the write.
27
+ * - The adoptions wait in one queue for each watcher: one adoption at a time, and the raw writes
28
+ * that wait together take one block of numbers for each partition. An adoption that fails with
29
+ * an error that can go away tries again after the backoff. When the queue is full, the hint of
30
+ * the next raw write goes out without a version. The watcher keeps the ids of the raw writes that
31
+ * did not fit or that failed (at most 10,000), and adopts them again when the queue is empty,
32
+ * after a sweep. An adoption that fails for good (after its last try, or a second time), or an id
33
+ * over that limit, gives its partition a reset hint (`reset: true`): its subscribers read it in full.
34
+ * - The adoption is exact only with the images before the change (`changeStreamPreAndPostImages`).
35
+ * An insert and a replace show their document, but an update shows only its fields: without the
36
+ * image, the `_seq` that the update kept comes from the lookup. When a write of the storage comes
37
+ * after the raw update, the adoption can then give that write a newer version, and a client that
38
+ * holds the version of that write gets a conflict of its precondition.
39
+ * - The sweep: after each gap (`sweep`, on by default with `rawWrites: "adopt"`), after a full
40
+ * queue, and when the stream opens (`sweepAtOpen`, off by default), the watcher sends each
41
+ * document without `_seq` to the same queue, in pages. Thus a document without `_seq` gets its
42
+ * number. A raw update of a document with `_seq` keeps that `_seq`: no sweep finds it, so a gap
43
+ * publishes a reset hint. The scan walks the index of `_id` and reads each document of the
44
+ * collection: a partial index cannot express `$exists: false`. A document that the sweep cannot
45
+ * place (no id, or another partition) goes to the log once, and the next sweeps leave it.
46
+ * - A raw hard delete of an item: with the image before the change, the storage adopts it as a
47
+ * tombstone. Without the image (no `changeStreamPreAndPostImages` on the collection), the hint
48
+ * goes out without a version, and the delete stays out of `changes(since)`.
49
+ * - A delete of a tombstone (the storage prunes the oldest ones) deletes no item: no hint, no
50
+ * adoption. The image before the change tells it. Without the image, the watcher knows the
51
+ * prunes of this process only.
52
+ * - The writes of this process through the storage: with `ownWrites: "skip"` (the default), the
53
+ * watcher skips their events, because the collection procedures publish them already, with
54
+ * `since`. Thus a subscriber gets one event for each write. With `ownWrites: "publish"`, the
55
+ * watcher publishes each change. Use it when the writes of this process do not go through the
56
+ * procedures, or when the procedures publish no events (`changeEvents: false`). An adoption is
57
+ * never an own write.
58
+ * - After an error of the stream, the watcher opens the stream again after a backoff, from the
59
+ * resume token of the last event: it loses no change. When the server does not have the token
60
+ * any more, or the collection was dropped or renamed, the watcher cannot know what it missed: it
61
+ * publishes a reset hint (a gap: no ids, `reset: true`) on the plain topic and on the topic of
62
+ * each partition that it knows: the partitions of the counters, of `forPartition()`, of the
63
+ * hints, of the subscribed topics of the bus, and of `gapPartitions`. The counters have a floor
64
+ * document for each partition with a write through a storage. The subscribers read the
65
+ * collection in full.
66
+ * - The pipeline of the stream is `[]`: the storage of a partition (`forPartition()`) watches the
67
+ * whole collection and drops the events of other partitions.
68
+ * - Change streams need a replica set or a sharded cluster. The watcher asks the server (`hello`)
69
+ * before the first open: on a single server, `ready` rejects (code 40573).
70
+ */
71
+ import { type Document } from "mongodb";
72
+ import { type Clock, type CollectionChangeEvent, type ResubscribeBackoff } from "@procedures/client";
73
+ import { type MongoDocumentStorage, type StoredForm } from "./document-storage.js";
74
+ /** The change of one item of a change event of a `MongoDocumentStorage` collection, from `storageChangeOf()`. */
75
+ export interface StorageChange {
76
+ /** The id of the item. */
77
+ readonly id: string;
78
+ /** The partition of the document. */
79
+ readonly partition: string | undefined;
80
+ /** True for a delete: a tombstone, or a delete of the document. */
81
+ readonly deleted: boolean;
82
+ /**
83
+ * True for a raw write: a write that did not give the document a new `_seq` (see the module
84
+ * comment), and each delete of a document that is not a tombstone.
85
+ */
86
+ readonly raw: boolean;
87
+ /** The version of the item after a write of the storage (`versionOfStored()`). A delete and a raw write have no version. */
88
+ readonly version?: string | undefined;
89
+ /** The new `_seq` of a write of the storage, or the `_seq` that a raw write kept (none for a document without `_seq`). */
90
+ readonly seq?: number | undefined;
91
+ }
92
+ /**
93
+ * The change event of `watchStorageChanges()`: a `CollectionChangeEvent` without `since` (a hint),
94
+ * with the version of each changed item in `versions`. A subscriber of the collection procedures
95
+ * reads it as each other event. It is an alias of `CollectionChangeEvent`.
96
+ */
97
+ export type StorageChangeHint = CollectionChangeEvent;
98
+ /**
99
+ * The function or the bus that gets the events of `watchStorageChanges()`, for example the bus of
100
+ * the procedures. A bus with `channels()` (the `EventBus` of `@procedures/client`) also tells the
101
+ * subscribed topics: the hint of a gap goes to their partitions.
102
+ */
103
+ export type StorageHintPublisher = ((topic: string, event: StorageChangeHint) => void | Promise<void>) | {
104
+ emit(topic: string, event: StorageChangeHint): void;
105
+ channels?(): readonly string[];
106
+ };
107
+ /** The options of `watchStorageChanges()`. */
108
+ export interface WatchStorageChangesOptions {
109
+ /**
110
+ * The name of the collection in the topic: the name of `createCollectionProcedures()`.
111
+ * @default the MongoDB collection of the storage
112
+ */
113
+ collection?: string | undefined;
114
+ /**
115
+ * The topic of the events of a partition, or undefined for no event. Give the same rule as the
116
+ * option `changeEvents.topic` of the procedures.
117
+ * @default `collectionChangeTopic(collection, partition)`
118
+ */
119
+ topic?: ((collection: string, partition: string | undefined) => string | undefined) | undefined;
120
+ /**
121
+ * `skip`: the watcher skips the events of the writes of this process through the storage, because
122
+ * the collection procedures publish them. `publish`: the watcher publishes each change.
123
+ * @default "skip"
124
+ */
125
+ ownWrites?: "skip" | "publish" | undefined;
126
+ /**
127
+ * `adopt`: the storage gives each raw write a new `_seq`, so `changes(since)` has it, and the hint
128
+ * goes out with the new version (D39). The watcher needs write access. `ignore`: the hint of a raw
129
+ * write goes out without a version, and the storage writes nothing (for a user that can only read).
130
+ * @default "adopt"
131
+ */
132
+ rawWrites?: "adopt" | "ignore" | undefined;
133
+ /**
134
+ * More partitions for the hint of a gap. The host gives the partitions that its subscribers
135
+ * subscribed to, when the publisher cannot tell them (a function, or a bus without
136
+ * `channels()`). The watcher adds the plain topic, the partitions of the counters and of
137
+ * `forPartition()`, the partitions of the hints, and the subscribed topics of the bus. Each
138
+ * partition with a write through a storage (in any process) has a floor document in the
139
+ * counters (roadmap O66). So give `gapPartitions` only for the partitions that only raw writes
140
+ * wrote.
141
+ */
142
+ gapPartitions?: (() => Iterable<string | undefined>) | undefined;
143
+ /**
144
+ * True: after each gap, the watcher adopts each document without `_seq` (a raw write during the
145
+ * gap), in pages, through the queue of the adoptions. It needs `rawWrites: "adopt"`. With
146
+ * `false`, a sweep still runs when the watcher had more raw writes to adopt again than it can
147
+ * keep.
148
+ * @default true with `rawWrites: "adopt"`, else false
149
+ */
150
+ sweep?: boolean | undefined;
151
+ /**
152
+ * True: a sweep also runs when the stream opens first, for the raw writes before the watcher
153
+ * started. It needs `rawWrites: "adopt"`. The first watcher on a legacy collection adopts every
154
+ * legacy document, and the next delta of every subscriber is the whole collection.
155
+ * @default false
156
+ */
157
+ sweepAtOpen?: boolean | undefined;
158
+ /**
159
+ * The backoff before the stream opens again after an error.
160
+ * @default { initialDelay: ms(250), maxDelay: seconds(30), backoffMultiplier: 2, jitter: 1 }
161
+ */
162
+ backoff?: ResubscribeBackoff | undefined;
163
+ /** 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. */
164
+ clock?: Clock | undefined;
165
+ /** The log (default `console`, with the prefix `[storage-hints]`). */
166
+ log?: ((level: "info" | "warn" | "error", message: string) => void) | undefined;
167
+ }
168
+ /** The handle of `watchStorageChanges()`. */
169
+ export interface StorageChangesWatch {
170
+ /**
171
+ * It resolves when the first stream is open (each later change reaches the publisher), or when
172
+ * `stop()` runs. It rejects when the first open fails with an error that does not go away (a
173
+ * single server, a refused authentication or permission): the watcher then stops. The watcher
174
+ * opens the stream again after the other errors.
175
+ */
176
+ readonly ready: Promise<void>;
177
+ /** This method stops the watcher: it closes the stream and cancels the timer of a backoff. */
178
+ stop(): Promise<void>;
179
+ }
180
+ /**
181
+ * This function reads the change of an item from a change event of a `MongoDocumentStorage`
182
+ * collection (roadmap CLI-77), for an app that watches the collection itself. It gives `undefined`
183
+ * for an event of no item: another operation (for example `drop`), an update whose document is
184
+ * gone (its delete event follows), the delete of a tombstone, or a document whose id neither
185
+ * `idOf` nor `_key` tells.
186
+ *
187
+ * - The partition is `form.partitionOf()` of `fullDocumentBeforeChange`, else of `fullDocument`,
188
+ * else of `documentKey`: the first whose partition gives the id with `form.idOf()` of the stored
189
+ * `_id`. Else the id is `_key`, in the partition of the first document.
190
+ * - A tombstone and a delete are deletes.
191
+ * - `raw` tells a raw write, from the event only (see the module comment). A write of the storage
192
+ * has the version of its new `_seq`. A raw write has no version.
193
+ *
194
+ * @param change - A change event of the collection, with `fullDocument: "updateLookup"` for the updates
195
+ * @param form - The stored form of the storage, for example `storage.form` (default `DEFAULT_STORED_FORM`)
196
+ */
197
+ export declare function storageChangeOf(change: Document, form?: StoredForm): StorageChange | undefined;
198
+ /**
199
+ * This function watches the change stream of the collection of a `MongoDocumentStorage`, and
200
+ * publishes a hint of each change of an item on the topic of the collection procedures (roadmap
201
+ * CLI-77). The writes that do not go through the procedures (a raw write, another process, a
202
+ * script) then reach the subscribers too, and the storage adopts each raw write (D39). See the
203
+ * module comment for the rules.
204
+ *
205
+ * - The storage of a partition (`forPartition()`) publishes only the documents of its partition.
206
+ * - The watcher reads only the collection of the items. The documents of the counters
207
+ * (`__storage_counters`) are in another collection, and a change there is never a hint.
208
+ * - An error of the publisher goes to the log. It never stops the watcher.
209
+ * - The adoption of raw writes needs write access. A refused adoption goes to the log once, and
210
+ * the hint of that write goes out without a version: set `rawWrites: "ignore"` for a user that
211
+ * can only read.
212
+ * - The adoption is exact only with `changeStreamPreAndPostImages` on the collection. Without the
213
+ * image, a raw update and a later write of the storage can give that write an extra version.
214
+ *
215
+ * @param storage - The storage, for its collection, its database and its stored form
216
+ * @param publish - The function or the bus of the events, for example the bus of the procedures
217
+ * @param options - The topic, the own writes, the raw writes, the gap, the backoff, the clock and the log
218
+ * @returns The handle: `ready` and `stop()`
219
+ *
220
+ * @example
221
+ * ```typescript
222
+ * const notes = new MongoDocumentStorage<Note>({ collection: "notes" });
223
+ * const watch = watchStorageChanges(notes, bus); // the bus of the procedures
224
+ * await watch.ready;
225
+ * // ... a raw write of a note reaches the subscribers of collections.notes.<partition>
226
+ * await watch.stop();
227
+ * ```
228
+ */
229
+ export declare function watchStorageChanges<T>(storage: MongoDocumentStorage<T>, publish: StorageHintPublisher, options?: WatchStorageChangesOptions): StorageChangesWatch;
230
+ //# sourceMappingURL=storage-hints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-hints.d.ts","sourceRoot":"","sources":["../../src/storage/storage-hints.ts"],"names":[],"mappings":"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"}