Skip to content

Commit 8b24212

Browse files
authored
feat(NODE-4059): ChangeStreamDocument not fully typed to specification (#3191)
1 parent ddb7d81 commit 8b24212

File tree

10 files changed

+777
-184
lines changed

10 files changed

+777
-184
lines changed

src/change_stream.ts

+226-98
Large diffs are not rendered by default.

src/collection.ts

+36-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { BSONSerializeOptions, Document, resolveBSONOptions } from './bson';
22
import type { AnyBulkWriteOperation, BulkWriteOptions, BulkWriteResult } from './bulk/common';
33
import { OrderedBulkOperation } from './bulk/ordered';
44
import { UnorderedBulkOperation } from './bulk/unordered';
5-
import { ChangeStream, ChangeStreamOptions } from './change_stream';
5+
import { ChangeStream, ChangeStreamDocument, ChangeStreamOptions } from './change_stream';
66
import { AggregationCursor } from './cursor/aggregation_cursor';
77
import { FindCursor } from './cursor/find_cursor';
88
import type { Db } from './db';
@@ -1418,21 +1418,52 @@ export class Collection<TSchema extends Document = Document> {
14181418
/**
14191419
* Create a new Change Stream, watching for new changes (insertions, updates, replacements, deletions, and invalidations) in this collection.
14201420
*
1421-
* @since 3.0.0
1421+
* @remarks
1422+
* watch() accepts two generic arguments for distinct usecases:
1423+
* - The first is to override the schema that may be defined for this specific collection
1424+
* - The second is to override the shape of the change stream document entirely, if it is not provided the type will default to ChangeStreamDocument of the first argument
1425+
* @example
1426+
* By just providing the first argument I can type the change to be `ChangeStreamDocument<{ _id: number }>`
1427+
* ```ts
1428+
* collection.watch<{ _id: number }>()
1429+
* .on('change', change => console.log(change._id.toFixed(4)));
1430+
* ```
1431+
*
1432+
* @example
1433+
* Passing a second argument provides a way to reflect the type changes caused by an advanced pipeline.
1434+
* Here, we are using a pipeline to have MongoDB filter for insert changes only and add a comment.
1435+
* No need start from scratch on the ChangeStreamInsertDocument type!
1436+
* By using an intersection we can save time and ensure defaults remain the same type!
1437+
* ```ts
1438+
* collection
1439+
* .watch<Schema, ChangeStreamInsertDocument<Schema> & { comment: string }>([
1440+
* { $addFields: { comment: 'big changes' } },
1441+
* { $match: { operationType: 'insert' } }
1442+
* ])
1443+
* .on('change', change => {
1444+
* change.comment.startsWith('big');
1445+
* change.operationType === 'insert';
1446+
* // No need to narrow in code because the generics did that for us!
1447+
* expectType<Schema>(change.fullDocument);
1448+
* });
1449+
* ```
1450+
*
14221451
* @param pipeline - An array of {@link https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/|aggregation pipeline stages} through which to pass change stream documents. This allows for filtering (using $match) and manipulating the change stream documents.
14231452
* @param options - Optional settings for the command
1453+
* @typeParam TLocal - Type of the data being detected by the change stream
1454+
* @typeParam TChange - Type of the whole change stream document emitted
14241455
*/
1425-
watch<TLocal extends Document = TSchema>(
1456+
watch<TLocal extends Document = TSchema, TChange extends Document = ChangeStreamDocument<TLocal>>(
14261457
pipeline: Document[] = [],
14271458
options: ChangeStreamOptions = {}
1428-
): ChangeStream<TLocal> {
1459+
): ChangeStream<TLocal, TChange> {
14291460
// Allow optionally not specifying a pipeline
14301461
if (!Array.isArray(pipeline)) {
14311462
options = pipeline;
14321463
pipeline = [];
14331464
}
14341465

1435-
return new ChangeStream<TLocal>(this, pipeline, resolveOptions(this, options));
1466+
return new ChangeStream<TLocal, TChange>(this, pipeline, resolveOptions(this, options));
14361467
}
14371468

14381469
/**

src/db.ts

+13-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Admin } from './admin';
22
import { BSONSerializeOptions, Document, resolveBSONOptions } from './bson';
3-
import { ChangeStream, ChangeStreamOptions } from './change_stream';
3+
import { ChangeStream, ChangeStreamDocument, ChangeStreamOptions } from './change_stream';
44
import { Collection, CollectionOptions } from './collection';
55
import * as CONSTANTS from './constants';
66
import { AggregationCursor } from './cursor/aggregation_cursor';
@@ -719,20 +719,27 @@ export class Db {
719719
* replacements, deletions, and invalidations) in this database. Will ignore all
720720
* changes to system collections.
721721
*
722+
* @remarks
723+
* watch() accepts two generic arguments for distinct usecases:
724+
* - The first is to provide the schema that may be defined for all the collections within this database
725+
* - The second is to override the shape of the change stream document entirely, if it is not provided the type will default to ChangeStreamDocument of the first argument
726+
*
722727
* @param pipeline - An array of {@link https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/|aggregation pipeline stages} through which to pass change stream documents. This allows for filtering (using $match) and manipulating the change stream documents.
723728
* @param options - Optional settings for the command
729+
* @typeParam TSchema - Type of the data being detected by the change stream
730+
* @typeParam TChange - Type of the whole change stream document emitted
724731
*/
725-
watch<TSchema extends Document = Document>(
726-
pipeline: Document[] = [],
727-
options: ChangeStreamOptions = {}
728-
): ChangeStream<TSchema> {
732+
watch<
733+
TSchema extends Document = Document,
734+
TChange extends Document = ChangeStreamDocument<TSchema>
735+
>(pipeline: Document[] = [], options: ChangeStreamOptions = {}): ChangeStream<TSchema, TChange> {
729736
// Allow optionally not specifying a pipeline
730737
if (!Array.isArray(pipeline)) {
731738
options = pipeline;
732739
pipeline = [];
733740
}
734741

735-
return new ChangeStream<TSchema>(this, pipeline, resolveOptions(this, options));
742+
return new ChangeStream<TSchema, TChange>(this, pipeline, resolveOptions(this, options));
736743
}
737744

738745
/** Return the db logger */

src/index.ts

+11
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,20 @@ export type {
173173
ChangeStreamAggregateRawResult,
174174
ChangeStreamCursor,
175175
ChangeStreamCursorOptions,
176+
ChangeStreamDeleteDocument,
176177
ChangeStreamDocument,
178+
ChangeStreamDocumentCommon,
179+
ChangeStreamDocumentKey,
180+
ChangeStreamDropDatabaseDocument,
181+
ChangeStreamDropDocument,
177182
ChangeStreamEvents,
183+
ChangeStreamInsertDocument,
184+
ChangeStreamInvalidateDocument,
185+
ChangeStreamNameSpace,
178186
ChangeStreamOptions,
187+
ChangeStreamRenameDocument,
188+
ChangeStreamReplaceDocument,
189+
ChangeStreamUpdateDocument,
179190
OperationTime,
180191
PipeOptions,
181192
ResumeOptions,

src/mongo_client.ts

+13-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { TcpNetConnectOpts } from 'net';
22
import type { ConnectionOptions as TLSConnectionOptions, TLSSocketOptions } from 'tls';
33

44
import { BSONSerializeOptions, Document, resolveBSONOptions } from './bson';
5-
import { ChangeStream, ChangeStreamOptions } from './change_stream';
5+
import { ChangeStream, ChangeStreamDocument, ChangeStreamOptions } from './change_stream';
66
import type { AuthMechanismProperties, MongoCredentials } from './cmap/auth/mongo_credentials';
77
import type { AuthMechanism } from './cmap/auth/providers';
88
import type { LEGAL_TCP_SOCKET_OPTIONS, LEGAL_TLS_SOCKET_OPTIONS } from './cmap/connect';
@@ -590,20 +590,27 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> {
590590
* replacements, deletions, and invalidations) in this cluster. Will ignore all
591591
* changes to system collections, as well as the local, admin, and config databases.
592592
*
593+
* @remarks
594+
* watch() accepts two generic arguments for distinct usecases:
595+
* - The first is to provide the schema that may be defined for all the data within the current cluster
596+
* - The second is to override the shape of the change stream document entirely, if it is not provided the type will default to ChangeStreamDocument of the first argument
597+
*
593598
* @param pipeline - An array of {@link https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/|aggregation pipeline stages} through which to pass change stream documents. This allows for filtering (using $match) and manipulating the change stream documents.
594599
* @param options - Optional settings for the command
600+
* @typeParam TSchema - Type of the data being detected by the change stream
601+
* @typeParam TChange - Type of the whole change stream document emitted
595602
*/
596-
watch<TSchema extends Document = Document>(
597-
pipeline: Document[] = [],
598-
options: ChangeStreamOptions = {}
599-
): ChangeStream<TSchema> {
603+
watch<
604+
TSchema extends Document = Document,
605+
TChange extends Document = ChangeStreamDocument<TSchema>
606+
>(pipeline: Document[] = [], options: ChangeStreamOptions = {}): ChangeStream<TSchema, TChange> {
600607
// Allow optionally not specifying a pipeline
601608
if (!Array.isArray(pipeline)) {
602609
options = pipeline;
603610
pipeline = [];
604611
}
605612

606-
return new ChangeStream<TSchema>(this, pipeline, resolveOptions(this, options));
613+
return new ChangeStream<TSchema, TChange>(this, pipeline, resolveOptions(this, options));
607614
}
608615

609616
/** Return the mongo client logger */

0 commit comments

Comments
 (0)