Meteor Mongo Collection is not using typescript interface - mongodb

I want to describe Mongo.Collection schema via typescript interface to have a strict check for type on fetch, forEach, etc.
interface IChat {
_id: string;
name?: string
};
const Chats = new Mongo.Collection<IChat>('chat');
// (method) Mongo.Cursor<IChat>.forEach(callback: <T>(doc: T, index: number, cursor: Mongo.Cursor<T>) => void, thisArg?: any): void
Chats.find().forEach(c => {
console.log(c._id); // Property '_id' does not exist on type 'T'.
// Why "type T" if it should be the type IChat???
});
But facing next error: Property '_id' does not exist on type 'T'.
What I'm doing wrong?
Link to typescript playground
Error
Collection.find().forEach() definition

In this case you're missing the .fetch() call after .find().
It may be that mathamagically Meteor+Mongo can handle the syntax as you have it now but the easy way to make Typescript happy here is adjusting it to be the following:
interface IChat {
_id: string;
name?: string
};
const Chats = new Mongo.Collection<IChat>('chat');
Chats.find().fetch().forEach(c => {
console.log(c._id);
});
link to the solution in Typescript Playground

Related

Schema mutation for deleteMany({})

I have this simple resolver:
removeAllMovies: () => {
return prisma.movie.deleteMany({});
},
When I run my apollo client I get an error:
Mutation.removeAllMovies defined in resolvers, but not in schema
So I want to add the mutation to the schema but I can't find the correct syntax. I want to remove all the movies, not based on a id or a filter:
type Mutation {
removeAllMovies()
}
This shows an error while starting the Apollo server:
Syntax Error: Expected Name, found ")".
What's the correct schema syntax for a deleteMany({}) resolver?
This should do it:
type BatchPayload {
count: Int!
}
type Mutation {
removeAllMovies: BatchPayload
}
And the resolver is correct so no changes there.

An outer value of 'this' is shadowed by this container with Mongoose Schema Typescript

I have the following for a schema validator for MongoDB:{
UserSchema.path('email').validate(async function (email: string) {
const count = await User.count({ email, _id: { $ne: this._id } })
return !count
}, 'Email already exists')
I'm getting the following error:
'this' implicitly has type 'any' because it does not have a type annotation.ts(2683)
User.ts(171, 35): An outer value of 'this' is shadowed by this container.
This is defined in my User.ts file. Everything works exactly as expected but this Typescript error is blocking CI from continuing. Is there anyway to get around this (no pun intended).
Try:
UserSchema.path('email').validate(async function (this:any, email: string) {
const count = await User.count({ email, _id: { $ne: this._id } })
return !count
}, 'Email already exists')
You can use your type instead of "any".
And the link to the docu: https://www.typescriptlang.org/docs/handbook/functions.html#this-parameters

Typescript with MongoDB findOne method not working with generic

I am writing a class to clean up a lot of my existing database code. The first step for this was writing a class to manage Collections for me, which I attempted to do using a class with a generic.
interface MongodbItem {
_id?: ObjectID
}
class CollectionManager<DataType extends MongodbItem> {
database: Database;
collection: Collection<DataType>;
// ...
async get(id?: ObjectID) {
if (!id) return await this.collection.find({}).toArray();
return await this.collection.findOne({ _id: id });
}
}
However, despite defining the DataType generic as having that _id Typescript gives me the following error (On the line with the .findOne):
Argument of type '{ _id: ObjectID; }' is not assignable to parameter of type 'FilterQuery<DataType>'.
Type '{ _id: ObjectID; }' is not assignable to type '{ [P in keyof DataType]?: Condition<DataType[P]>; }'
reading through the handbook it looks like extending the generic in the way I am should enforce that it has an _id property, so I do not know why this error is still occuring.
Appears to be a but with Typescript itself:
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/40584
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/39358
Solved using workaround
return this.collection.findOne({ _id: id } as FilterQuery<DataType>);

How to implement GraphQL Scalar for MongoDB sort property found in collection.find() function of MongoDB's?

I'm trying to create an argument type using type-graphql to accept arguments through the GraphQL query. The sort I'm talking about is a property of the options found on MongoDB's native NodeJS driver documentation.
#ArgsType()
export class FindAllArgs implements FindOneOptions {
#Field(type => Int, { defaultValue: 0, description: 'Sets the limit of documents returned in the query.' })
#Min(0)
limit?: number;
// This is where the custom sort would go about.
#Field(type => SortScalar)
sort?: sortScalar;
#Field(type => Int, { defaultValue: 0, description: 'Set to skip N documents ahead in your query (useful for pagination).' })
#Min(0)
skip?: number;
}
As you can see, simple types are fine, but when it comes to something like the sort object, I'm not sure how to go on about it.
NestJS implements a date scalar like so:
import { Scalar, CustomScalar } from '#nestjs/graphql';
import { Kind, ValueNode } from 'graphql';
#Scalar('Date', type => Date)
export class DateScalar implements CustomScalar<number, Date> {
description = 'Date custom scalar type';
parseValue(value: number): Date {
return new Date(value); // value from the client
}
serialize(value: Date): number {
return value.getTime(); // value sent to the client
}
parseLiteral(ast: ValueNode): Date {
if (ast.kind === Kind.INT) {
return new Date(ast.value);
}
return null;
}
}
Even in the example, it uses a returnTypeFunction #Scalar('Date', type => Date). What am I supposed to replace Date with? What do I put in for parseValue, serialize, and parseLiteral?
You can just use #InputType to nest objects in args - you don't need scalars for that, they are designed only for serializable things like timestamp -> number, mongo id -> ObjectId instance, etc.

Missing Subdocument Methods in Mongoose with Typescript

I'm working on a project and need to retrieve specific subdocuments from a model by their subdocument _id's. I then plan on making updates to those subdocuments and saving the main document. The mongoose subdocument documentation lists a number of methods you can call on the parent.children array, but methods that don't already exist in Javascript for arrays give an error saying they do not exist and it doesn't compile. I'm referencing this documentation: https://mongoosejs.com/docs/subdocs.html
I understand that should be able to use .findOneAndUpdate to make my updates, and using the runValidators option everything should still be validated, but I would also like to just retrieve the subdocument itself as well.
I looked at this post: MongoDB, Mongoose: How to find subdocument in found document? , and I will comment that the answer is incorrect that if a subdocument schema is registered it automatically creates a collection for that schema, the collection is only made if that schema is saved separately. You cannot use ChildModel.findOne() and retrieve a subdocument, as the collection does not exist, there is nothing in it.
Having IChildModel extend mongoose.Types.Subdocument and having the IParent interface reference that instead of IChild and not registering the ChildModel does not change anything other than no longer allowing calls to .push() to not accept simple objects (missing 30 or so properties). Also trying mongoose.Types.Array<IChild> in the IParent interface with this method does not change anything.
Changing the IParent interface to use mongoose.Types.Array<IChild> for the children property allows addToSet() to work, but not id() or create()
I'm using Mongoose version 5.5.10, MongoDb version 4.2.0 and Typescript version 3.4.5
import mongoose, { Document, Schema } from "mongoose";
// Connect to mongoDB with mongoose
mongoose.connect(process.env.MONGO_HOST + "/" + process.env.DB_NAME, {useNewUrlParser: true, useFindAndModify: false});
// Interfaces to be used throughout application
interface IParent {
name: string;
children: IChild[];
}
interface IChild {
name: string;
age: number;
}
// Model interfaces which extend Document
interface IParentModel extends IParent, Document { }
interface IChildModel extends IChild, Document { }
// Define Schema
const Child: Schema = new Schema({
name: {
type: String,
required: true
},
age: {
type: Number,
required: true
}
});
const ChildSchema: Schema = Child;
const Parent: Schema = new Schema({
name: {
type: String,
required: true
},
children: [ChildSchema]
});
const ParentSchema: Schema = Parent;
// Create the mongoose models
const ParentModel = mongoose.model<IParentModel>("Parent", Parent);
const ChildModel = mongoose.model<IChildModel>("Child", Child);
// Instantiate instances of both models
const child = new ChildModel({name: "Lisa", age: 7});
const parent = new ParentModel({name: "Steve", children: [child]});
const childId = child._id;
// Try to use mongoose subdocument methods
const idRetrievedChild = parent.children.id(childId); // Property 'id' does not exist on type 'IChild[]'.ts(2339)
parent.children.addToSet({ name: "Liesl", age: 10 }); // Property 'addToSet' does not exist on type 'IChild[]'.ts(2339)
parent.children.create({ name: "Steve Jr", age: 2 }); // Property 'create' does not exist on type 'IChild[]'.ts(2339)
// If I always know the exact position in the array of what I'm looking for
const arrayRetrievedChild = parent.children[0]; // no editor errors
parent.children.unshift(); // no editor errors
parent.children.push({ name: "Emily", age: 18 }); // no editor errors
Kind of a late response, but I looked through the typings and found the DocumentArray
import { Document, Embedded, Types } from 'mongoose';
interface IChild extends Embedded {
name: string;
}
interface IParent extends Document {
name: string;
children: Types.DocumentArray<IChild>;
}
Just wanted to put this here incase anyone else needs it.
Gross:
For now, I'm going with a very quick and dirty polyfill solution that doesn't actually answer my question:
declare module "mongoose" {
namespace Types {
class Collection<T> extends mongoose.Types.Array<T> {
public id: (_id: string) => (T | null);
}
}
}
then we declare IParent as such:
interface IParent {
name: string;
children: mongoose.Types.Collection<IChild>;
}
Because the function id() already exists and typescript just doesn't know about it, the code works and the polyfill lets it compile.
Even Grosser: Otherwise for an even quicker and dirtier solution, when you create the parent model instance simply typecast it to any and throw out all typescript checks:
const child = new ChildModel({name: "Lisa", age: 7});
const parent: any = new ParentModel({name: "Steve", children: [child]});
const idRetrievedChild = parent.children.id(childId); // works because declared any