Attempting to use the mongoose "findOne" build-in method in a generic function - node.js

I have a typescript generic function as following:
function foo<T>(model:T): Promise<Response>{
return model.findOne(email)
}
to call the function I do the following:
const result = await foo<'IUserModel>(User)'>
(* for those who are confused about the ' ', please ignore them *)
when I do this, I receive the following error which is:
*** Property 'findOne does not exist' on type 'T' ***
I read the mongoose documentation about the Model object but this did not help me.
Which interface or type or alternative solution is available to get rid of this typescript error?
Thank you in advance!

Using a generic function without a constraint (extends) in the parameter tells TypeScript that model can be anything. So you could use a number, for example, and a number doesn't have the .findOne() function:
function foo<T>(model: T): Promise<Response> {
return model.findOne() // error
}
foo(10) // no error
Using extends tells TypeScript that the model must have the Model properties, so it will always have the findOne() function:
import { Model } from 'mongoose';
function foo<T extends Model>(model: T): Promise<Response> {
return model.findOne() // no error
}
foo(10) // error, it must be a Model

#Lucas Surdi Franco had opened a way of solution for me.
to resolve my problem I had to extend T with Model
code solution:
import { Model } from 'mongoose';
function foo<T extends Model<T>>(model: T): Promise<Response> {
return model.findOne()
If there is a nicer solution for this, feel free to comment

Related

Wrap a problematic npm package, while maintaining type information

The Problem
We're authoring an npm package containing React components that will be used in various (internal) web sites. There is a problematic npm package dependency that we are forced to use in our react .tsx files, that has these problems:
It doesn't expose any useful types despite having .d.ts files in it... they're empty.
It tries to run when required or imported server-side, instead of waiting until called, so we have to avoid a top-level import and instead do if (window) { const module = require('package-name') } and then use it inside that block only.
It is a frequent source of errors so everything in that library needs to be run inside of a try ... catch block.
Well, At Least We Have Types
We have already created our own types file which addressed problem #1:
// problematic-package-types.d.ts
declare module 'problematic-package' {
function doErrorProneButNecessaryThing(
foo: Record<string, unknown>,
bar: string
): void
}
The Needed Solution
The long term solution is to fix this problematic library and we're looking into how to get that done (but it's not in our direct control).
In the short term, though, we need a solution now.
Note that we are configuring dynamic requires in our npm package bundler to import them only at use-time, not treating them like other imports/requires. As our package is consumed inside other applications, we don't have full control over how that application bundling works or when the components are required, so our components may end up being required server-side when they shouldn't, and we have to tolerate that. We're still learning about some aspects of this.
My Wild (But Failed) Stab
My goal is to do something more DRY like this, where we solve all three problems of strong typing, detecting server-side execution & doing nothing, and adding error handling:
// hoping to leverage our module declaration above without importing anything
import type * as ProblematicPackage from 'problematic-package'
import wrapProblematicRequire from '../utils/my-sanity-preserving-module'
const wrappedProblematicPackage = wrapProblematicRequire<ProblematicPackage>()
// then later...
const foo: Record<string, unknown> = { property1: 'yes', property2: false }
const bar = 'yodeling'
wrappedProblematicPackage.invokeIfWindowReady(
'doErrorProneButNecessaryThing',
foo,
bar
)
However, TypeScript doesn't like the import type which unfortunately makes sense:
Cannot use namespace 'ProblematicPackage' as a type.
The Plea
How do I get the type information we've placed into problematic-package-types.d.ts to use as desired?
Or ANYTHING else. Honestly, I'm open to whatever, no matter how crude or hacky, so long as we get some clarity and reliability at call sites, with full type information as described. Suggestions/advice?
Full Details
Here is the full implementation of the wrapProblematicRequire function. I haven't tested it. It's probably awful. I'm sure it could be far better but I don't have time to get this helper module super clean right now. (My attempt to handle function type information isn't quite right.)
type Func = (...args: any[]) => any
type FunctionNames<T, TName extends keyof T> = T[TName] extends Func ? TName : never
type FunctionNamesOf<T> = FunctionNames<T, keyof T>
const wrapProblematicRequire = <T>(packageName: string) => ({
invokeIfWindowReady<TName extends FunctionNamesOf<T>>(
name: T[TName] extends Func ? TName : never,
...args: T[TName] extends Func ? Parameters<T[TName]> : never
): T[TName] extends Func ? ReturnType<T[TName]> : never {
if (!window) {
// #ts-ignore
return undefined
}
try {
// #ts-ignore
return require(packageName)[name] as T[TName](...args)
} catch (error: unknown) {
// ToDo: Log errors
// #ts-ignore
return undefined
}
}
})
export default wrapProblematicRequire
P.S. await import('problematic-package') didn't seem to work. Yes, problems abound.
Cannot use namespace 'ProblematicPackage' as a type.
Well, you can get the typeof that namespace, which seems to be what you want.
To test this, I setup the following:
// problem.js
export function doErrorProneButNecessaryThing(n) {
return n;
}
export function doErrorProneButNecessaryThing2(s) {
return s;
}
console.log('did side effect');
// problem.d.ts
export function doErrorProneButNecessaryThing(n: number): number;
export function doErrorProneButNecessaryThing2(s: string): string;
And now you can do:
import type * as ProblemNs from './problem';
type Problem = typeof ProblemNs;
// works
type A = Problem['doErrorProneButNecessaryThing'] // type A = (n: number) => number
Then the wrapProblematicRequire function just takes the name of the function as a generic, pulls the args for it, and pulls the return type.
const wrapProblematicRequire = <TName extends FunctionNamesOf<Problem>>(
name: TName,
...args: Parameters<Problem[TName]>
): ReturnType<Problem[TName]> | undefined => {
if (!window) return;
const problem = require('./problem'); // type is any, but types are enforced above
try {
return problem[name](...args);
} catch (err) {
console.log('error!');
}
};
Here require('./problem') returns the any type, but the generics keep everything key safe as long as typeof ProblemNs can be trusted.
Now to test that:
console.log('start');
const result: number = wrapProblematicRequire(
'doErrorProneButNecessaryThing',
123
);
console.log('end');
Which logs:
start
did side effect
end
Which seems to work!
Codesandbox

Typescript error TS2504 when trying to use async iterators with Mongoose 5.11.14: must have a '[Symbol.asyncIterator]()'

I'm trying to move away from using #types/mongoose after realising that mongoose 5.11 has type information.
However I'm now encountering Typescript issues when running tsc on the following code:
for await (const document of db.myModel.find()) {
...
}
The error is:
Type 'Query<IMyDocumentType[], IMyDocumentType>' must have a '[Symbol.asyncIterator]()' method that returns an async iterator.
Ignoring the error and running the code works fine.
Am I missing something or is there missing type information in mongoose 5.11.14 that #types/mongoose had?
mongoose 5.11.14
node.js 12.19.0
Yes, there is missing type information.
#types/mongoose#5.10.3 defines [Symbol.asyncIterator] on DocumentQuery, and Query extends DocumentQuery.
mongoose#5.11.14 does not define [Symbol.asyncIterator] on Query.
This method must be defined on any interface that is used on the right-hand side of a for await...of statement.
I do notice that both packages define a QueryCursor which extends stream.Readable. This class does define a [Symbol.asyncIterator] method, and the cursor() method of the Query interface returns an instance of QueryCursor. Try to see if the following compiles and runs as you expect without using #types/mongoose:
for await (const document of db.myModel.find().cursor()) {
// ^^^^^^^^^
...
}

Use Firestore collection get() return type in a function

I'm currently trying to write some code that retrieves a collection from my Firestore instance.
My codebase uses the service repository pattern to keep business logic seperate from the code that retrieves data. For this reason I've made the following code:
import { injectable, inject } from "inversify";
import { IOfficeRepository, TYPES } from "../common/types";
import { Firestore } from "#google-cloud/firestore";
#injectable()
export default class OfficeRepository implements IOfficeRepository {
private fireStoreClient: Firestore;
constructor(#inject(TYPES.FireStoreFactory) firestoreFactory: () => Firestore) {
this.fireStoreClient = firestoreFactory();
};
public async getOffice(officeId: string): Promise<FirebaseFirestore.QueryDocumentSnapshot<FirebaseFirestore.DocumentData>> {
const officeCollection = "offices";
const document = await this.fireStoreClient.collection(officeCollection).get();
return document;
};
}
What I'd like to do is return the value from the get() call to my service, in the service I will be performing checks and executing the business logic that I need.
The get() returns a Promise<FirebaseFirestore.QuerySnapshot<FirebaseFirestore.DocumentData>>, but I am unable to use this as a return type for the function in my repository. I just get the following error:
Type 'QuerySnapshot' is missing the following properties from type 'QueryDocumentSnapshot': createTime, updateTime, data, exists, and 3 more.
I've already looked-up the error, but I wasn't able to find any solution or a post where someone was trying to return the result from the get() function before performing any logic on the result.
So my question is: How would I be able to make this setup work? Or is there something I am doing wrong with this setup? If so, what would be another approach to work this out while using the service repository pattern?
Your declared return type of QueryDocumentSnapshot doesn't match the actual return type of QuerySnapshot.
This line of code:
const document = await this.fireStoreClient.collection(officeCollection).get();
performs a query for all of the documents in the officeCollection collection. As you can see from the API documentation, CollectionReference.get() yields a QuerySnapshot object. The entire set of documents will be available in the returned docs property.
It seems that you expect getOffice to return a single document instead. I'm noticing that you never used the argument officeId to narrow down your query to just the one document you want. Perhaps you meant to do something like this instead to get a single document using its ID?
const document = await this.fireStoreClient
.collection(officeCollection)
.doc(officeId)
.get();
In this case, document will be a DocumentSnapshot object.

I get an error about not finding a matching constructor but the signatures match

class SharedWorld {
def db = Db(sql)
def help = Help(db)
}
class Db {
Sql sql
Db(def sql) {
this.sql = sql
}
}
class Help {
Help(){}
Db db
Help(Db db) {
this.db = db
}
}
I have this structure and for some reason when I compile my groovy I get an error that it can't find a matching constructor for Help(Db). Any ideas why? The signature obviously matches
You've got a few issues with your code.
First, class declarations don't take parameters or need parentheses immediately after the class name. Try making a constructor for SharedWorld inside the curly braces. In addition, you need to use the new keyword to instantiate classes (although there is a #Newify annotation to support the syntax you're using). Example:
class SharedWorld {
def db
def help
SharedWorld(sql) {
db = new Db(sql)
help = new Help(db)
}
}

Mongoose: use plugin in Schema static method

I use mongoose random plugin.
In my schema definition i call
GameSchema.plugin(random, { path: 'r' });
After that I have a custom static method who use the plugin:
GameSchema.statics.someMethod {
[...]
GameSchema.findRandom...
And I get the error
TypeError: Object #<Schema> has no method 'findRandom'
Is there a way to achieve what I am trying to do or should I implement some kind of repository ?
EDIT:
Ben's answer worked, I needed to use findRandom on the model and not the schema.
Precision for those in my case: you need to declare first your static function
GameSchema.statics.someMethod {
[...]
Game.findRandom...
then register your schema
var Game = mongoose.model('Game', GameSchema);
otherwise you'll get "Model .... has no method 'someMethod'"
Game variable in the static function is recognized event though it is defined only later in the script.
=> bonus question: does anyone know why it works ?
You're calling the method on the schema, whereas you need to be calling it on the model.
var Game = mongoose.model('Game', GameSchema);
Game.findRandom()...

Resources