(Typescript) Extending the type returned by a call to Meteor.user()

As per the title - I have properties for each user on my application - extended beyond the default ones that are created by Meteor Accounts. This works well for me but one issue that is starting to annoy me a little bit is that every call similar to:

		const { emails, customApplicationProperty } = Meteor.user();

…results in typescript giving an error that customApplicationProperty does not exist on the return from Meteor.user(). This obviously makes sense - and I’ve been getting around it by typecasting the return value. But is there a better way to do this by letting Meteor/TS know the extra properties to expect when a call is made to Meteor.user()?

I hope this makes sense, please let me know if not. Thanks in advance!

You should be able to do something like the following in a project level type def file.

declare module "meteor/meteor" {
    module Meteor {
        interface User {
            customApplicationProperty: String
        }
    }
}

You can of course also do this by extending the User interface with your own interface and and casting the return to the new interface, but it’s cleaner with the prior solution.

3 Likes

Ah ye, cheers for that @copleykj

I see the pattern here and I think you’ve set me on the right path. I can’t quite get your snippet to work - it’s just not recognising it/merging it for some reason. But my variations haven’t worked either (including the example below), so not sure where I’m going wrong. I’m reading the TS handbook to see if I’m making any obvious errors anywhere.


// Below not working - structure copied from the official TS bindings at meteor.d.ts
declare module Meteor {
    interface User {
            customApplicationProperty: String
        }
}

I wonder whether it isn’t so much the code that isn’t working vs Typescript not recognising the meteorDefs.d.ts file and loading it at all. I’ll keep chipping away at it - if anyone has any ideas, I’m all ears. I’ll update this post if/when I have a solution.

What worked for me was adding the def. to a file called typings/index.d.ts and then adding "typeRoots": ["./typings"] inside the compilerOptions section of tsconfig.json

2 Likes

Cheers @hexsprite - based on your post I’ve been studying up on the typeRoots property on tsconfig. The examples below I tried putting in the @types folder as well as, as per your example, within a /typings folder. In both variations I also tested by adding an explicit typeRoots key in tsconfig.

Ultimately, I kind of got things working, and I’ve found the difference between a file that is compiled and one that is not - but I have zero idea as to why:

If the file in question imports a type, then that file will not be compiled by TS

So this works - ./imports/@types/custom-user.d.ts:

declare namespace Meteor {
  interface User {
    exampleProp: string;
    anotherProp: number;
  }
}

But this does not - ./imports/@types/custom-user.d.ts:

import { ModifiedUser } from "./main";

declare namespace Meteor {
    // the intention of the import would be to do:
    // interface User extends ModifiedUser {
  interface User {
    exampleProp: string;
    anotherProp: number;
  }
}

As yet unanswered questions:

  1. Funnily enough, modifying or not modifying the tsconfig with the typeRoots file did not make any difference at all. I.e. - Even if the file above was within a ‘typings/custom-user.d.ts’ path, it still worked as long as there was no import from another file. This seems contrary to the docs where a file not under @types would need to be explicitly mentioned in tsconfig - but I’m sure I have misunderstood the docs.
  2. Why does an import from another file break things? Edit - Found this SO qn addressing this.

Updated solution:

Piecing together the two answers above and that SO thread, this works:

declare namespace Meteor {
  type CustomUser = import('./main').CustomUser;
  interface User extends CustomUser{} // Empty {} needed?*
}

*Final unanswered question - can one interface extend another without the empty curly brackets at the end? I get a warning/error when I try. Not a big deal though.

Cheers all for the help.