Skip to content

feat(NODE-4059): ChangeStreamDocument not fully typed to specification #3191

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 4, 2022

Conversation

nbbeeken
Copy link
Contributor

@nbbeeken nbbeeken commented Apr 6, 2022

Description

What is changing?

ChangeStreamDocument had some errors and friction in type info. I've restructured it to be a descriminated union of the possible change stream events which lends itself well to TS.

This does introduce compilation changes since properties that used to be optional now do not exist depending on the change stream event type. We could consider mitigating this by bringing back the properties but typing them as something that indicates they shouldn't have been used with a particular event (for example delete has no fullDocument field)

What is the motivation for this change?

Type hinting on change stream documents helps sort through the variety of events that can occur.

Double check the following

  • Ran npm run check:lint script
  • Self-review completed using the steps outlined here
  • PR title follows the correct format: <type>(NODE-xxxx)<!>: <description>
  • Changes are covered by tests
  • New TODOs have a related JIRA ticket

@nbbeeken nbbeeken force-pushed the NODE-4059/changeStream-document-type branch from ae8f97c to 7aaf33d Compare April 6, 2022 21:08
Copy link
Contributor

@baileympearson baileympearson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some questions and suggestions

@@ -84,16 +85,16 @@ export interface ResumeOptions {
}

/**
* Represents the logical starting point for a new or resuming {@link https://docs.mongodb.com/manual/changeStreams/#std-label-change-stream-resume| Change Stream} on the server.
* Represents the logical starting point for a new or resuming on the server.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Represents the logical starting point for a new or resuming on the server.
* Represents the logical starting point for a new ChangeStream or resuming a ChangeStream on the server.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

export interface ChangeStreamDocument<TSchema extends Document = Document> {
export interface ChangeStreamFullNameSpace {
/** The namespace */
ns: { db: string; coll: string };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I notice further down in the rename interface, you reuse { db: string; coll: string }. Ultimately, { db: string; coll: string } is the namespace. Could we create the type as

interface ChangeStreamFullNamespace { db: string; coll: string }

so we can reuse it where needed? Then we can add ns: ChangeStreamFullNamespace to the ChangeStreamDocumentCommon object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this, we can't add it to common because it's missing from a number of the events

* this will contain all the components of the shard key in order,
* followed by the _id if the _id isn’t part of the shard key.
* The transaction number.
* Only present if the operation is part of a multi-document transaction.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we note here that the type of transaction number depends on the promoteLongs flag?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if it's more friction that it's worth to include this in the type annotation and instead document it as a possiblity based on the option.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So include this as part of the documentation for the promoteLongs flag? I'm okay with that

* @public
* @see https://www.mongodb.com/docs/manual/reference/change-events/#insert-event
*/
export interface InsertChangeStreamDocument<TSchema extends Document = Document>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think ChangeStream<Command>Result might make more sense than <Command>ChangeStreamDocument as a general format for these interfaces but I don't feel too strongly. Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed them all, I'm still using Document at the end because I don't want to change the name of the ChangeStreamDocument type and we should keep these consistent, but reordering the indicator makes sense.

entity.collection.collectionName,
patchCollectionOptions(entity.collection.collectionOptions)
);
const collection = await db
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: is this logic added to handle the scenario where a user has two entities with the same collection name in a single test suite? If so, would it make more sense to flip the logic - list collections while querying for the collection, and then create it if it doesn't exist?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Until this PR we haven't used the unified runner to run a drop db / rename collection. They both require the collection to exist in advance of the operations stage of the test. I could instead adjust the test to perform an insert to create the collection, but this seems like a reasonable adjustment to the runner.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh that makes sense. I was mainly wondering if the logic would be easier to follow if we flipped it around to look for collections first. But I don't feel super strongly about this, feel free to ignore this thought

let collection = await db.listCollections({ collectionName: entitiy.collection.collectionName })
    .map(({ name }) => db.collection(name))
    .toArray()[0]
if (!collection) {
  collection = db.createCollection( .... )
}

@@ -233,6 +233,6 @@ const testSuite = new UnifiedTestSuiteBuilder('listDatabases with comment option
)
.toJSON();

describe('listDatabases w/ comment option', () => {
describe('listDatabases with comment option', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking: Feel free to use the new toMocha method you added on enumerate_databases and enumerate_indexes if you'd like, but not necessary for the review!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@dariakp dariakp changed the title fix(NODE-4059): ChangeStreamDocument not fully typed to specification feat(NODE-4059): ChangeStreamDocument not fully typed to specification Apr 7, 2022
@nbbeeken nbbeeken marked this pull request as ready for review April 11, 2022 20:13
@nbbeeken nbbeeken added the Primary Review In Review with primary reviewer, not yet ready for team's eyes label Apr 11, 2022
@nbbeeken nbbeeken requested a review from baileympearson April 15, 2022 15:37
@nbbeeken nbbeeken force-pushed the NODE-4059/changeStream-document-type branch 2 times, most recently from 86bb7f2 to b3610e5 Compare April 19, 2022 19:43
@PaulEndri
Copy link
Contributor

Hi, me again, hope it's okay. I'm actually really excited for this particular change as I've been using changestreams for a while, though looking at it, I do have a question: I think this change would break typescript validation if someone is trying to access to .fullDocument without first explicitly checking by type. Pretty sure the break is intended because this is technically more correct, but could it be done so that somehow the .watch can automatically limit the returned changeStream types? Maybe through an additional generic? I don't know if it's possible to dynamically pull based on the parameters, but I doubt it. It's just a weird developer experience if I specify to only listen to say chances and replacements and I have to additionally, in the execution logic, additionally specify it

@nbbeeken
Copy link
Contributor Author

Hi @PaulEndri always appreciate the feedback, something I hadn't originally considered when starting this work was the impact that the pipeline has on the change stream document as well, since currently the generic only controls the schema there isn't a way to account for a possible change like $addFields in the types. I've put this on the PR for the back burner for the moment while we consider how we could better support that possibility. It may come down to offering a new API that can capture the potential variability of the type. If we had something that better accounted for the pipeline then something like:

client.watchFor([{ $match: { operationType: 'insert' } }])

might be able to filter the actual change stream document type returned. Any ideas are welcome additions to the conversation 🙂

@nbbeeken
Copy link
Contributor Author

I'm proposing we add a second optional generic argument that can override the type of the whole change stream document. Unfortunately you can't skip arguments so the schema will need to be passed in first, but the second argument can be extended from our existing types making adding/omitting properties somewhat ergonomic.

The impact on users should still be the same as the change proposed so far where certain properties need narrowing to access. Those who want a solution that doesn't require runtime narrowing could use this second argument to strictly type their changes or pass in any removing all strictness.

type InsertChangeWithComment = ChangeStreamInsertDocument<Schema> & { comment: string };

const pipelineChangeStream = collection.watch<Schema, InsertChangeWithComment>([
  { $addFields: { comment: 'big changes' } }, 
  { $match: { operationType: 'insert' } },
]);

pipelineChangeStream.addListener('change', change => {
  expectType<string>(change.comment);
  // No need to narrow in code because the generics did that for us!
  expectType<Schema>(change.fullDocument);
});

@nbbeeken nbbeeken force-pushed the NODE-4059/changeStream-document-type branch from 981f105 to 68cbcac Compare April 22, 2022 15:44
@nbbeeken nbbeeken added the Blocked Blocked on other work label Apr 25, 2022
@nbbeeken
Copy link
Contributor Author

Waiting on #3215 to slim this PR down

@nbbeeken nbbeeken force-pushed the NODE-4059/changeStream-document-type branch from 853f968 to b022793 Compare April 27, 2022 21:44
@nbbeeken nbbeeken removed the Blocked Blocked on other work label Apr 27, 2022
@nbbeeken nbbeeken force-pushed the NODE-4059/changeStream-document-type branch 2 times, most recently from c10b98e to f2bfbaf Compare April 29, 2022 20:44
@nbbeeken nbbeeken force-pushed the NODE-4059/changeStream-document-type branch from f2bfbaf to acebbfe Compare April 29, 2022 20:50
baileympearson
baileympearson previously approved these changes May 2, 2022
Copy link
Contributor

@baileympearson baileympearson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@baileympearson baileympearson added Team Review Needs review from team and removed Primary Review In Review with primary reviewer, not yet ready for team's eyes labels May 2, 2022
@nbbeeken nbbeeken requested a review from dariakp May 3, 2022 21:21
@baileympearson baileympearson merged commit 8b24212 into main May 4, 2022
@baileympearson baileympearson deleted the NODE-4059/changeStream-document-type branch May 4, 2022 16:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Team Review Needs review from team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants