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

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()) {
// ^^^^^^^^^
...
}

Related

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.

Haxe - Why can I not access a child's attribute without getting an error that the parent does not have the given attribute?

I've recently been getting into Haxe and just started to use HaxeFlixel to load a Tiled .TMX file.
I am creating a TiledMap object and passing it the TMX file path, then I want to iterate over the layers in that object to add them to the game scene. However when I try to access .tileArray (which is a property of TiledTileLayer) I get the following error :-
flixel.addons.editors.tiled.TiledLayer has no field tileArray
Here is the code:
package;
import flixel.FlxState;
import flixel.tile.FlxTilemap;
import flixel.addons.editors.tiled.TiledMap;
import openfl.Assets;
class PlayState extends FlxState
{
private var _tiled_map:TiledMap;
override public function create():Void
{
_tiled_map = new TiledMap("assets/data/Map1.tmx");
for(layer in _tiled_map.layers){
var layerData:Array<Int> = layer.tileArray;
}
super.create();
}
override public function update(elapsed:Float):Void
{
super.update(elapsed);
}
}
I've found the following example - http://coinflipstudios.com/devblog/?p=182 which seems to work fine for people.
So I wanted to check whether the layer object was a TiledTileLayer as it should be, or TiledLayer, with the following:
trace(Type.typeof(layer));
Which sure enough yields:
PlayState.hx:24: TClass([class TiledTileLayer])
So if it is a TiledTileLayer which has the field tileArray why is it moaning?
I had a look at the source code (https://github.com/HaxeFlixel/flixel-addons/blob/dev/flixel/addons/editors/tiled/TiledMap.hx#L135) and TiledTileLayer inherits from TiledLayer. Layers is an array of type TiledLayer, so I think this is why it is moaning. I can clearly see that the array is storing child objects of TiledLayer, but as soon as I access any props/methods of those children, it complains that the parent does not have that field? Very confusing!
To run I'm using this command: C:\HaxeToolkit\haxe\haxelib.exe run lime test flash -debug -Dfdb
Thank you!
So if it is a TiledTileLayer which has the field tileArray why is it moaning?
It may be a TiledTileLayer in this case, but that may not always be the case. layers is an Array<TileLayer> after all, so it could be a TiledObjectLayer or a TiledImageLayer as well (which don't have a tileArray field). This can nicely be seen in the code you linked. The concrete type can only be known at runtime, but the error you get happens at compile-time.
If you know for sure there won't be any object or image layers, you can just cast it to a TiledTileLayer. However, just to be safe, it's good practice to check the type beforehand anyway:
for (layer in _tiled_map.layers) {
if (Std.is(layer, TiledTileLayer)) {
var tileLayer:TiledTileLayer = cast layer;
var layerData:Array<Int> = tileLayer.tileArray;
}
}
It works without this for the tutorial you linked because it was made for an older version of flixel-addons.

Using `lean` on mongoose queries that return arrays in TypeScript

I have two Mongoose queries and decided it would be best to use .lean() on them.
For the one that returns a single document, it seems to work fine:
let something:Something;
SomethingDocument.findOne({_id:theId}).lean().then( (res) => { something = res;});
The problem is when I try to use it with a query that returns multiple results:
let somethings:Something[];
SomethingDocument.find({color:'blue'}).lean().then( (res) => { somethings = res;});
The second call gives the error:
Type 'Object' is not assignable to type 'Something[]'.
The 'Object' type is assignable to very few other types. Did you mean to use the 'any' type instead?
Property 'length' is missing in type 'Object'.
If I try to do a type conversion it just complains the the 'length' property is missing in type 'Object'.
How do I use lean when I expect an array of results?
...note that if I simply omit lean it all works.
Mongoose type definitions are not so good IMHO, so you can fix it using this:
let somethings:Something[];
SomethingDocument.find({color:'blue'}).lean().then((res) => { somethings = res as any;});
And by the way, I would suggest to use await if you are able to (you have to compile TS to a modern Ecma version):
const somethings = await SomethingDocument.find({color:'blue'}).lean() as Something[];
Note that the former version catches errors on .catch, but the second one will throw an exception.

Type hints of flow not stripped by babel

I have a React.JS project which uses a custom 'theme' with UI components.
This theme also provides build scripts (webpack config, babel configs, etc.).
I want to start using Flow in this project.
I installed the needed npm packages and added flow to babel's presets, then I added props = {mytestprop: string} to one of my React` classes.
Webpack compiled my code successfully, but the type hints were not stripped! Of course, the browser was not able to execute this code - when I try to run it, it raisesReferenceError: string is not defined.
The current list of presets from .babelrc is: ["es2015", "react", "stage-2", "flow"]. I'm sure that this is the actual list used by babel because if I delete any of the first 3 presets, compilation fails.
Do you have any ideas on what could lead to this behavior when stripping Flow types?
It's not that type annotations are not being stripped. It's that { mytestprop: string } is not a valid type annotation on the right-hand side of an assignment because it clashes with the syntax for defining an object.
Specifically, when Flow's parser sees the statement { mytestprop: string } it will interpret this as an attempt to create an object with a field named mytestprop with its value set to the value of the variable string, so it will leave the statement alone as it is, and you'll get the error you've seen in the browser.
The correct way to type object declarations is to type the left-hand side of the declaration.
For instance,
let myProps: { myTestProp: string } = { myTestProp: "testProp" };
if you aren't declaring your props separately, you could declare a custom type:
type myPropType = { myTestProp: string }
// ...
const myComponent = (props: myPropType) => //render your component
Since the type statement is exclusive to Flow and not a valid JavaScript statement, it will be stripped correctly.

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