Skip to content

DOCSP-25723 Updates to Mutually Recursive Schema Types #459

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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 2 additions & 11 deletions source/fundamentals/typescript.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ For more information on object types, see the
Type Parameters that Extend Document
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The following classes accept all types that both extend
the ``Document`` interface and are not mutually recursive:
The following classes accept all types that extend the ``Document`` interface:

.. _node-mongodb-type-parameters-extend-document:

Expand All @@ -71,14 +70,10 @@ You can pass a type parameter that extends the ``Document`` interface like this:
:start-after: start-no-key
:end-before: end-no-key

To view an example of a mutually recursive type, which is not supported by the
:ref:`preceding classes <node-mongodb-type-parameters-extend-document>`,
see the :ref:`<node-driver-limitations-mutual-recursion>` section.

Type Parameters of Any Type
~~~~~~~~~~~~~~~~~~~~~~~~~~~

The following classes accept all type parameters that are not mutually recursive:
The following classes accept all type parameters:

.. _node-mongodb-type-parameters-any-type:

Expand All @@ -89,10 +84,6 @@ You can find a code snippet that shows how to specify a type for the ``FindCurso
class in the
:ref:`Find Multiple Documents Usage Example <node-driver-find-usage-example-code-snippet>`.

To view an example of a mutually recursive type, which is not supported by the
:ref:`preceding classes <node-mongodb-type-parameters-any-type>`,
see the :ref:`<node-driver-limitations-mutual-recursion>` section.


Type Safety and Dot Notation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
61 changes: 35 additions & 26 deletions source/includes/limitations/limits.rst
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
Learn about the following TypeScript specific limitations of the {+driver-short+}:

- :ref:`No type safety for dot notation references to nested instances of recursive types <node-driver-recursive-types-dot-notation>`
- :ref:`No mutually recursive types <node-driver-limitations-mutual-recursion>`
- :ref:`Depth limitations on type safety for mutually recursive types <node-driver-limitations-mutual-recursion>`

.. _node-driver-recursive-types-dot-notation:

Recursive Types and Dot Notation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. important:: Impacted Versions

- 4.3
Copy link
Collaborator Author

@ajhuh-mdb ajhuh-mdb Dec 1, 2022

Choose a reason for hiding this comment

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

Removed this admonition since this affects nested recursive types from all versions after v4.3.1

Copy link
Collaborator

Choose a reason for hiding this comment

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

Question: I thought v4.11 fixed this issue. What am I missing?

Copy link
Collaborator Author

@ajhuh-mdb ajhuh-mdb Dec 2, 2022

Choose a reason for hiding this comment

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

Ah so the "fix" I meant to refer to when we met was not type safety.

For clarification, the "issue" from 4.3.0 was that, previously, just the presence of a self-referential recursive schema type at all would cause a compilation error. The "fix" in 4.3.1 is that they allowed for the code to compile with a recursive schema type present, but their solution, in doing so, couldn't guarantee type checking (which becomes a separate "issue" from the one originally mentioned).

Sorry about that confusion 😅

- 4.4

The {+driver-short+} cannot provide type safety within nested instances of
The {+driver-short+} cannot provide type safety within nested instances of
**recursive types** referenced through dot notation.

A recursive type is a type that references itself. You can update
Expand Down Expand Up @@ -80,40 +75,54 @@ in the MongoDB manual.
Mutual Recursion
~~~~~~~~~~~~~~~~

.. important:: Impacted Versions

- 4.3
- 4.4

You cannot specify a **mutually recursive** type as a type parameter.

A mutually recursive type exists when two types contain a property that is of
the other's type. You can update the
:ref:`Pet <mongodb-node-typescript-pet-interface>` interface
to be mutually recursive by allowing a pet to have a handler, and defining a
handler to have a pet. The following are the mutually
A **mutually recursive** type exists when two types contain a property that is of
the other's type. You can update the :ref:`Pet <mongodb-node-typescript-pet-interface>`
interface to be mutually recursive by allowing a pet to have a handler, and
defining a handler to have a pet. The following examples reference the mutually
recursive ``Pet`` and ``Handler`` interfaces:

.. code-block:: typescript
:emphasize-lines: 2, 8

interface MutuallyRecursivePet {
interface Pet {
handler?: Handler;
name: string;
age: number;
}

interface Handler {
pet: MutuallyRecursivePet;
pet: Pet;
name: string;
}

The {+driver-short+} provides type safety for mutually recursive types
referenced through dot notation up to a depth of eight. The following code
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

v4.11 release notes say nine, but tests of original PR say eight

Copy link
Collaborator

Choose a reason for hiding this comment

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

Definitely worth confirming with the devs

Copy link
Collaborator

Choose a reason for hiding this comment

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

The tests are correct, I'll be updating the notes

snippet assigns a ``string`` to a ``number`` and raises a type error because
the referenced property is at a depth of four:

.. code-block:: typescript
:emphasize-lines: 3

database
.collection<Pet>("<your collection>")
.findOne({'handler.pet.handler.pet.age': "four"});

If you specify a mutually recursive type, the TypeScript compiler raises the
following error:
The error raised by the preceding code snippet is as follows:

.. code-block:: none

error TS2615: Type of property 'r' circularly references itself in mapped type '{ [Key in keyof MutuallyRecursive]...
index.ts(19,59): error TS2769: No overload matches this call.
The last overload gave the following error.
Type 'string' is not assignable to type 'Condition<number> | undefined'.

At a depth greater than or equal to eight, TypeScript compiles your code but no
longer type checks it. The following code assigns a ``string`` to a ``number``
property but does not cause a compilation error because the referenced property
is at a depth of 10:

If you must apply a mutually recursive type to your classes, use version 4.2 of
the {+driver-short+}.
.. code-block:: typescript
:emphasize-lines: 3

database
.collection<Pet>("<your collection>")
.findOne({'handler.pet.handler.pet.handler.pet.handler.pet.handler.pet.age': "four"});
43 changes: 24 additions & 19 deletions source/whats-new.txt
Original file line number Diff line number Diff line change
Expand Up @@ -64,46 +64,51 @@ To learn more, see the `v4.12.0 Release Highlights <https://github.com/mongodb/n
What's New in 4.11
------------------

The 4.11 {+driver-short+} release includes **recursive schema support**,
meaning that you can use recursive types as the schema in the
``Collection`` class. The driver also provides type safety for dot
notation queries up to a depth of nine in this release. Typescript compiles
your code beyond a depth of nine, but types fall back to ``any`` at
this level. The recursive type depth limit is a current limitation in
TypeScript.
The 4.11 {+driver-short+} release includes added support for **mutually
recursive** collection schema types. The driver also provides type safety for
dot-notation queries up to a depth of eight in this release. At a depth greater
than or equal to eight, Typescript successfully compiles your code but does not
provide type safety. This depth limit on recursive types is a current limitation
of TypeScript.

Recursive Schema Type Checking Example
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Mutually Recursive Schema Type Checking Example
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Suppose we have a collection of type ``Collection<CircularSchema>``:
Suppose we have a collection of type ``Collection<Author>`` that contains the
following mutually recursive types:

.. code-block:: js
:copyable: false

interface CircularSchema {
interface Author {
name: string;
ref: CircularSchema;
bestBook: Book;
}

TypeScript enforces type checking below a depth of nine. The following
interface Book {
title: string;
author: Author;
}

TypeScript enforces type checking up to a depth of eight. The following
code causes a TypeScript compilation error because the ``name`` property
value must be a ``string`` type:

.. code-block:: js
:copyable: false

collection.findOne({ 'ref.ref.ref.name': 25 })
collection.findOne({ 'bestBook.author.bestBook.title': 25 })

At a depth greater than nine, TypeScript compiles your code but no
longer type checks it. The following code assigns an ``int`` to a ``string``
property but does not cause a compilation error because the
referenced property is at a depth of 11:
At a depth greater than or equal to eight, TypeScript compiles your code but no
longer type checks it. For example, the following code assigns a ``number`` to a
``string`` property but does not cause a compilation error because the
referenced property is at a depth of 10:

.. code-block:: js
:copyable: false

collection.findOne({
'ref.ref.ref.ref.ref.ref.ref.ref.ref.ref.name': 25
'bestBook.author.bestBook.author.bestBook.author.bestBook.author.bestBook.author.name': 25
})

To learn more, see the `v4.11.0 Release Highlights <https://github.com/mongodb/node-mongodb-native/releases/tag/v4.11.0>`__.
Expand Down