Overview
En esta guía, puedes aprender sobre las características de TypeScript y las limitaciones del controlador de MongoDB Node.js. TypeScript es un lenguaje de programación fuertemente tipado que se compila a JavaScript.
El compilador de TypeScript ofrece comprobación de tipos en tiempo real. Los editores de código con soporte para TypeScript pueden ofrecer sugerencias de autocompletado, mostrar documentación en línea e identificar errores relacionados con tipos.
Todas las características de TypeScript del controlador son opcionales. Todo el código JavaScript válido escrito con el controlador es también código TypeScript válido.
Para obtener más información, consulte la Sitio web de TypeScript.
Características
Si usa TypeScript, puede especificar un tipo para algunas clases en el controlador. Todas las clases que aceptan un parámetro de tipo en el controlador tienen el tipo predeterminado.
DocumentLa interfaz Document tiene la siguiente definición:
interface Document { [key: string]: any; }
Todos los tipos de objetos extienden la interfaz Document.
Para obtener más información sobre los tipos de objetos, consulta el manual de TypeScript.
Parámetros de tipo que extienden documento
Las siguientes clases aceptan todos los tipos que implementan la interfaz Document:
Puedes pasar un parámetro de tipo que extienda la interfaz Document de esta manera:
1 interface Pet { 2 name: string; 3 age: number; 4 } 5 6 const database = client.db("<your database>"); 7 const collection = database.collection<Pet>("<your collection>");
Importante
Las claves que no están en el parámetro de tipo pueden recibir cualquier tipo
Las claves que no están listadas en su parámetro de tipo especificado reciben el tipo any. El siguiente fragmento de código demuestra este comportamiento:
1 interface User { 2 email: string; 3 } 4 5 const database = client.db("<your database>"); 6 const myColl = db.collection<User>("<your collection>"); 7 myColl.find({ age: "Accepts any type!" });
Parámetros de tipo de cualquier tipo
Las siguientes clases aceptan todos los parámetros de tipo:
Puede encontrar un fragmento de código que muestra cómo especificar un tipo para la clase FindCursor en el find() Ejemplo: Sección Archivo completo de la guía Buscar documentos.
Seguridad de tipos y notación de punto
A partir de la versión 5.0, por defecto, el driver de Node.js no proporciona seguridad de tipos para las operaciones que buscan en campos expresados en notación de puntos. La notación de puntos es una sintaxis que puede utilizar para navegar por objetos JSON anidados. Cuando se construye un filtro para pasar a una query, el driver no generará un error de tipo incluso si especifica un valor con un tipo incorrecto para un campo expresado en notación de puntos.
El siguiente fragmento de código define la interfaz ClassificationPet, que incluye un campo classification que le permite especificar el género y el color de perros y gatos:
interface ClassificationPet { name: string; age: number; classification: { genus: "Canis" | "Felis"; color: string }; }
El controlador no genera un error de tipo para el siguiente ejemplo de código, aunque el valor de classification.color sea un booleano en lugar de un string:
await myColl.findOneAndDelete({ "classification.color": false });
Puede activar la comprobación de tipos construyendo filtros como tipos StrictFilter o StrictUpdateFilter.
Advertencia
Los tipos StrictFilter y StrictUpdateFilter son experimentales y podrían mostrar incorrectamente errores de tipo en queries válidas.
En la siguiente muestra de código, se asigna al filtro un tipo StrictFilter. Dado este tipo de filtro, el controlador de Node.js informa de un error de tipo porque el valor de classification.color es un booleano en lugar de un string.
const filterPredicate: StrictFilter<ClassificationPet> = { "classification.color": false }; await myColl.findOneAndDelete(filterPredicate);
El siguiente ejemplo asigna un tipo StrictUpdateFilter a un filtro para actualizar. El controlador de Node.js informa de un error de tipo porque el valor de classification.color es un booleano en lugar de un string.
const updateFilter: StrictUpdateFilter<ClassificationPet> = { $set: { "classification.color": false } } await pets.updateOne({}, updateFilter);
Referencias a claves que incorporan variables
Para hacer una query, una colección o realizar otra operación con una clave que incorpora variables, se debe usar una aserción as const al especificar la clave. Este mecanismo permite que el código se compile con éxito si los tipos de entrada son correctos.
El siguiente fragmento de código define la interfaz ClassificationPet y la interfaz Mealtime. ClassificationPet incluye un campo mealtimes que contiene un arreglo de interfaces Mealtime, cada una de las cuales incluye un campo time:
interface ClassificationPet { name: string; mealtimes: Mealtime[]; } interface Mealtime{ time: string; amount: number; }
El siguiente fragmento de código realiza una Operación de búsqueda y actualización en una colección de ClassificationPet documentos. La operación actualiza el campo time anidado de la instancia Mealtime en el índice 1. La posición del índice se especifica por la variable mealCounter:
const mealCounter = 1; await myColl.findOneAndUpdate( { name: "Lassie" }, { $set: { [`mealtimes.${mealCounter}.time` as const]: '04:00 PM' } }, );
Para obtener más información sobre la notación de puntos, consulta notación de puntos en el manual de MongoDB.
Para aprender más sobre las limitaciones de la notación de puntos en el controlador de Nodo.js, consulte la sección Tipos recursivos y notación de puntos.
Trabajar con el campo **_id**
MongoDB no recomienda especificar el _id como parte de su modelo. Omitir el campo _id hace que el modelo sea más genérico y reutilizable, y modela con mayor precisión los datos importantes para una aplicación. La integración de TypeScript del controlador de Node.js se encarga de añadir el campo _id a los tipos de retorno para los métodos relevantes.
Las siguientes secciones proporcionan información sobre las operaciones de guardado y lectura que utilizan el campo _id.
Operaciones de inserción y el campo _id
La manera en que especificas el campo _id en los parámetros de tipo pasados a su instancia Collection influye en el comportamiento de las operaciones de inserción. La siguiente tabla describe cómo las diferentes especificaciones de campo _id afectan a las operaciones de inserción:
_id tipo de campo | Tipo de ejemplo | Requerido al insertar | Comportamiento al insertar |
|---|---|---|---|
Unspecified | Not applicable | No | The driver creates an
ObjectId
value for each inserted document. |
Specified | { _id: number }; | Yes | If you do not specify a value for the _id field in an insert operation,
the driver raises an error. |
Specified as optional | { _id?: number }; | No | If you do not specify the _id field in an insert operation,
the driver adds an _id field value generated by the
primary key factory. |
Si se debe especificar el campo _id como requerido en el tipo que define para representar documentos en la colección, pero no se desea especificar valores para el campo _id en las operaciones de inserción, se debe usar el tipo de asistente OptionalId cuando se cree la colección. El tipo OptionalId acepta un parámetro de tipo como argumento y devuelve ese tipo con un campo _id opcional.
El siguiente fragmento de código define la interfaz IdPet, que incluye un tipo para el campo _id:
interface IdPet { _id: ObjectId; name: string; age: number; }
El siguiente código utiliza la interfaz anterior y el tipo OptionalId para insertar un documento sin especificar un valor para el campo _id:
const database = client.db("<your database>"); const collection = db.collection<OptionalId<IdPet>>("<your collection>"); myColl.insertOne({ name: "Spot", age: 2 });
Para obtener más información sobre el campo _id, consulta The _id campo en el manual de MongoDB.
Para obtener más información sobre los tipos, las interfaces y las clases discutidos en esta sección, consulta los siguientes recursos:
OptionalId API documentation
PkFactory Documentación de la API
ObjectId código fuente
Métodos de búsqueda y el campo _id
Los métodos find y findOne de la clase Collection incluyen el campo _id en su tipo de retorno. El controlador infiere el tipo del campo _id devuelto en función del parámetro de tipo que pasaste a su instancia de Collection.
Si el parámetro de tipo que se ha pasado a la instancia Collection incluye el campo _id en el esquema, el driver infiere que el campo _id devuelto por el método es del tipo especificado en el esquema.
Sin embargo, si el parámetro de tipo que se ha pasado a la instancia Collection no incluye el campo _id en su esquema, el driver infiere que el tipo del campo _id devuelto por el método es ObjectId.
Tip
El parámetro de tipo pasado a Collection influye únicamente en la inferencia de tipo de los campos devueltos por el método. El driver no convierte el campo al tipo especificado. El tipo de cada campo en el esquema del parámetro de tipo debe coincidir con el tipo del campo correspondiente en la colección.
El siguiente código utiliza la interfaz Pet para devolver un documento con un _id inferido como de tipo ObjectId:
const database = client.db("<your database>"); const collection = db.collection<Pet>("<your collection>"); const document = await myColl.findOne({ name: "Spot", }); const id : ObjectId = document._id;
El siguiente código utiliza la interfaz IdNumberPet para devolver un documento con un _id inferido como de tipo number:
interface IdNumberPet { _id: number; name: string; age: number; } const database = client.db("<your database>"); const collection = db.collection<IdNumberPet>("<your collection>"); const document = await myColl.findOne({ name: "Spot", }); const id : number = document._id;
Importante
Proyección
Si se especifica una proyección en un método de búsqueda, se debe pasar un parámetro de tipo al método de búsqueda que refleje la estructura de los documentos proyectados. Sin un parámetro de tipo, TypeScript no puede verificar en tiempo de compilación que usan los documentos proyectados de manera segura.
Para mostrar este comportamiento, el siguiente fragmento de código pasa la comprobación de tipos, pero genera un error en tiempo de ejecución:
const doc = await myColl.findOne( {}, { projection: { _id: 0, name: 1 } } ); console.log(doc._id.generationTime);
Para detectar este error en tiempo de compilación, pase un parámetro de tipo que no incluya el campo _id a su método find:
interface ProjectedDocument { name: string } const doc = await myColl.findOne<ProjectedDocument>( {}, { projection: { _id: 0, name: 1 } } ); // Compile time error: Property '_id' does not exist on type 'ProjectedDocument'. console.log(doc._id.generationTime);
Para ver un ejemplo ejecutable de TypeScript que incluye un método find que aplica una proyección, consulta la página Find a documento.
Para aprender más sobre las clases y métodos discutidos en esta sección, consulta la siguiente documentación de la API:
Limitaciones conocidas
Aprende sobre las siguientes limitaciones específicas de TypeScript del controlador de Node.js:
Tipos recursivos y notación de punto
El controlador de Node.js no puede garantizar la seguridad de tipos dentro de instancias anidadas de tipos recursivos referenciadas mediante notación de puntos.
Un tipo recursivo es un tipo que se refiere a sí mismo. Se puede actualizar la interfaz de Pet para que sea recursiva permitiendo que una mascota tenga su propia mascota. La siguiente es la interfaz Pet recursiva:
interface RecursivePet { pet?: RecursivePet; name: string; age: number; }
Nota
Límite de profundidad
El controlador de Nodo.js no atraviesa tipos recursivos anidados al verificar las claves de notación de puntos para evitar alcanzar el límite de profundidad recursiva de TypeScript.
El siguiente fragmento de código hace referencia a una instancia anidada de la interfaz RecursivePet con un tipo incorrecto utilizando notación de puntos, pero el compilador de TypeScript no genera un error de tipo:
database .collection<RecursivePet>("<your collection>") .findOne({ "pet.age": "Spot" });
El siguiente fragmento de código hace referencia a una instancia de nivel superior de la interfaz RecursivePet con un tipo incorrecto y genera un error de tipo:
database .collection<RecursivePet>("<your collection>") .findOne({ pet: "Spot" });
El error generado por el fragmento de código anterior es el siguiente:
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<Pet>'.
Si debe tener seguridad de tipos dentro de instancias anidadas de tipos recursivos, debe guardar su query o actualizar sin notación de puntos.
Para obtener más información sobre la notación de puntos, consulta notación de puntos en el manual de MongoDB.
Recursión mutua
Un tipo mutuamente recursivo existe cuando dos tipos contienen una propiedad que es del tipo del otro. Puedes actualizar la interfaz Pet para que sea mutuamente recursiva permitiendo que un Pet tenga un controlador, y definiendo un controlador para que tenga un Pet. Los siguientes ejemplos hacen referencia a las interfaces Pet y Handler que son mutuamente recursivas:
interface Pet { handler?: Handler; name: string; age: number; } interface Handler { pet: Pet; name: string; }
El controlador de Node.js proporciona seguridad de tipos para tipos mutuamente recursivos referenciados mediante notación de puntos hasta una profundidad de ocho. El siguiente fragmento de código asigna un string a un number y genera un error de tipo porque la propiedad referenciada está a una profundidad de cuatro:
database .collection<Pet>("<your collection>") .findOne({'handler.pet.handler.pet.age': "four"});
El error generado por el fragmento de código anterior es el siguiente:
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'.
A una profundidad mayor o igual a ocho, TypeScript compila el código, pero ya no verifica los tipos. El siguiente código asigna un string a una propiedad number, pero no provoca un error de compilación porque la propiedad referenciada está a una profundidad de 10:
database .collection<Pet>("<your collection>") .findOne({'handler.pet.handler.pet.handler.pet.handler.pet.handler.pet.age': "four"});