Typescript defination for Meteor.users collection

We add custome fields to users collection so we need to define type for that one. This is what i’m using:

/imports/types/users.ts

// ... some imports here

export type UserType = Meteor.User & {
  username: string;
  address?: AddressType;
  profile?: {
    nickname?: string;
    avatar?: string;
  };
  roles: RoleType[];
  // ... other custom fields here
};

/imports/api/users/schema.ts

// ... some imports here

const Users: Mongo.Collection<UserType> = Meteor.users;

export default Users;

Then we use Users collection in methods.ts file
/imports/api/users/server/methods.ts

import Users from "../../users/schema";

Meteor.methods({
  "users.findOne"(userId: string): UserType | undefined {
    return Users.findOne({ _id: userId });
  }
});

With this setup, it works fine except when I try to add options to find function, for example

return Users.findOne({ _id: userId }, { hint: "some.index.name.here" });

then I got the error:

Argument of type ‘{ hint: string; }’ is not assignable to parameter of type ‘Options’.
Object literal may only specify known properties, and ‘hint’ does not exist in type ‘Options’.

It works but I think there would be a better way to define types.
How do you define Meteor.users type?

Your code is correct, but the Options<T> type doesn’t have the hint property. Could you file a PR with this change?

@radekmie the strange thing is if I use that hint option with other colections, it works.

const UsersActivities = new Mongo.Collection<ActivityType>("users-activities");

then this code works without warning:

  const items = UsersActivities.find(conditions, {
      sort: {
        createdAt: -1,
      },
      limit: 50,
      hint: "get.user.history",
    })

That’s weird. As I look at it now, it should work, as find wants anything that extends Options<T> (source), and Meteor.users is a normal Mongo.Collection (source). I’ll check it later.

Yes, that’s weird.
On VSCode, when I hover the Users.find function I have this:

(method) Mongo.Collection<UserType, UserType>.find<Mongo.Options<UserType>>(selector?: string | Mongo.ObjectID | Mongo.Selector<UserType> | undefined, options?: Mongo.Options<UserType> | undefined): Mongo.Cursor<...> (+1 overload)

Find the documents in a collection that match the selector.

But when I hover the UsersActivities.find function, I have this:

(method) Mongo.Collection<ActivityType, ActivityType>.find<{
    sort: {
        createdAt: number;
    };
    limit: number;
    hint: string;
}>(selector?: string | Mongo.ObjectID | Mongo.Selector<ActivityType> | undefined, options?: {
    sort: {
        createdAt: number;
    };
    limit: number;
    hint: string;
} | undefined): Mongo.Cursor<...> (+1 overload)
Find the documents in a collection that match the selector.

Here are my findings:

type UserType = Meteor.User & { foo?: string };
const Users: Mongo.Collection<UserType> = Meteor.users;

// Both OK.
Meteor.users.find();
Users.find();

// Both OK.
Meteor.users.find({ _id: '' });
Users.find({ _id: '' });

// Both fail.
Meteor.users.find({ _id: '' }, { hint: '' });
Users.find({ _id: '' }, { hint: '' });

// Both OK.
Meteor.users.find({ _id: '' }, { hint: '', limit: 50 });
Users.find({ _id: '' }, { hint: '', limit: 50 });

The reason is, that the options are expected to extend the Options<T> type. This type has only optional properties, but due to the way how TypeScript work, { hint: string } is not considered to be extending Options<T>. By adding { limit: number } everything works, as then it’s Options<T> & { hint: string }.

The first step would be to add hint to Options<T> with a proper comment. Here I strongly encourage you to file a PR.

The second step would be to change the Options<T> type in a way that only the transform property would be parametrized. Here I believe it’s not as trivial and will take more time.

@radekmie Wow, that’s complicated. I confirm that by adding limit or other fields in Options<T> it solve the issue. Thank you very much.
I created a pull request but without additional test. It works on my local.
PR