Typescript with MongoDB findOne method not working with generic - mongodb

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>);

Related

How to update composite type model in Prisma?

I am trying to implement updation in a composite-type model in Prisma.
Here is my data structure:
{
"name":"toy",
"data":{
"sports":{
"currentState":"false"
},
"business":{
"currentState":"false"
}
}
}
Here I my code for updating:
const updatedSource = await prisma.sources.update({
where: {
name: 'toy'
},
data: {
data: {
sports: {
currentState: "true"
}
}
},
})
Here is my schema file
type SourcesData {
business SourcesDataState
sports SourcesDataState
}
type SourcesDataState {
currentState StateData[]
}
type StateData {
title String
url String
}
model sources {
id String #id #default(auto()) #map("_id") #db.ObjectId
data SourcesData
name String #unique
}
When I execute the above logic I get error as:Unknown arg `sports` in data.data.sports for type SourcesDataUpdateEnvelopeInput. Did you mean `set`? Available args:
Please guide what I am missing while updating.
The TypeScript should be pretty helpful in telling you what arguments you can or cannot use when interacting with Prisma. I strongly recommend using a code editor that includes TypeScript typehinting/Intellisense so you can see errors and warnings about your TypeScript usage as you are developing with Prisma.
Where it says Available args in your error, that should tell you the arguments that prisma.sports.update actually expects. If I had to guess (this may not be accurate, but you HAVE to look at the TypeScript to know exactly what it's supposed to be), it should look something like this:
const updatedSource = await prisma.sources.update({
where: {
name: 'toy'
},
data: {
data: {
update: {
sports: {
update: {
currentState: {
set: ["true"]
}
}
}
}
}
},
})
I strongly recommend reading Prisma's documentation on updating related/nested records: https://www.prisma.io/docs/concepts/components/prisma-client/relation-queries#update-a-specific-related-record
let typeEncounter = await prisma.encounter.update({
where: {
id
},
data: {
[property]: {
update: {
[subProperty] : value,
},
},
},
}
)
I get a receive the error Unknown arg update in data..update
I have seen some people mention nesting updates but no official documentation and can't seem to get this straightened out. Anybody have any ideas? The property and subproperty are largely irrelevant here, just examples. The code works fine aside from updated a subfield of a type (mongoDB prisma). Without the update the entire type gets overwritten rather than the selected field.

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.

Meteor Mongo Collection is not using typescript interface

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

Mocking data from MongoDB to satisfy TypeScript in Jest

I am creating a TypeScript interface for each model that extends mongoose.Document.
import mongoose, { Document } from 'mongoose';
export interface IAccount extends Document {
_id: mongoose.Types.ObjectId;
name: string;
industry: string;
}
The schema is then exported with the interface:
export default mongoose.model<IAccount>('Account', accountSchema);
The problem is that in Jest, creating an object with the properties required for the function being testing isn't enough, TypeScript complains about all the missing fields.
function getData(account: IAccount){
return account;
}
const account = {
name: 'Test account name',
industry: 'test account industry'
}
getData(account);
Argument of type '{ name: string; industry: string; }' is not assignable to parameter of type 'IAccount'.
Type '{ name: string; industry: string; }' is missing the following properties from type 'IAccount': _id, increment, model, and 52 more.ts(2345)
What is the simplest way to create an object that satisfies TypeScript for testing purposes?
One option is to create a mock that matches the expected type...
...but that can be very difficult for highly complex types.
The other option is to tell TypeScript you want your mock to pass through compile-time type checking.
You can do that by assigning your mock the type any:
function getData(account: IAccount){
return account;
}
const account: any = { // <= use type "any"
name: 'Test account name',
industry: 'test account industry'
}
getData(account); // <= no error

Do Typescript Interfaces allow you to discard attributes not defined on the interface?

I have a function that performs a search. The search can be done a few different ways (by looking for an ID or by querying a few attributes). However, I want to limit what attributes can be passed in. I thought I could do something like:
interface Search {
_id?: string
people?: number
partyName?: string
otherField? string
}
function search(query: Search) {
myDbConnection.find(query).then(... // etc
}
The problem is that any object will conform to this, and query can contain extra attributes. For example, this could be passed:
search({otherField: "foo", aProtectedField: "bar"})
and aProtectedField would be passed along to find.
I am wondering if there is a typescript way of enforcing the attributes passed. Sort of strong-parameters from the Rails world. I know I can do things like pick form lodash or maybe even make a SearchObject class and use the constructor as a means of discarding the extra attributes, but I feel like there is a way to do this within Typescript that I just don't know about.
You could make all the properties required then do an assertion to pass in a subset of the properties.
For example:
interface Search {
_id: string;
people: number;
partyName: string;
otherField: string;
}
function search(query: Search) {
// code here
}
search({ people: 2 } as Search); // ok
search({ otherField: "foo", aProtectedField: "bar" }); // error, good
search({ otherField: "foo", aProtectedField: "bar" } as Search); // error, good
What version of Typescript are you on? Since Typescript 1.6 there has been improved checking for object literals.
On TS 1.8 when I try to run your code I get:
error TS2345: Argument of type '{ otherField: string; aProtectedField: string; }' is not assignable to parameter of type 'Search'.
Object literal may only specify known properties, and 'aProtectedField' does not exist in type 'Search'.
This and noImplicitAny should catch the errors you're worried about.
I want to limit what attributes can be passed in. [...] query can contain extra attributes.
The way I see it, this is controversial. If you limit what attributes a given object might contain, then it is, by definition, limited to that set of attributes, and cannot contain others not allowed by its specs.
Since there is practically nothing you can do about the any type, my recommendation is to resolve this the type-safe way, by defining an option for additional attributes:
interface Search {
_id?: string;
people?: number;
partyName?: string;
additionalFields?: { [key: string]: any };
}
search({ people: 2 }); // ok
search({ _id: "jd", people: 2 }); // ok
search({ _id: "jd", additionalFields: { otherField: "foo" } }); // ok