Use TypeScript types for check in methods

I’m moving my code base to TypeScript and was wondering if there is any type support for the check package? I am aware that check is running at runtime, not at compile time. But it would still be handy if you could use TypeScript types instead of re-formulating your check rules from scratch. Not sure if this even possible, though.

It is possible to do this in TypeScript with type guards.

However, check would need to be modified to work differently. It would need to return a boolean, and its type definition would need to use the is operator. The usage would need to be like this, and then TypeScript would be able to narrow types:

// here the type of roomId is string|null

if (!check(roomId, String))
  throw new Error('Expected a string')

// here the type of roomId is string

The whole purpose of check is that it should throw if the type is not correct. But TypeScript does not support this type of narrowing. It’s great for plain JS.

However! There is Match.test!

If we look here, we see it is using the is expression in the return type:

So, for TypeScript, just use that all the time along with a conditional and your own error every time:

if (!Match.test(obj, pattern))
  throw new Error('Expected a string')

// here the type of obj is narrowed 
1 Like

I have this in my meteor.d.ts file:

declare function check<T extends Match.Pattern>(value: any, pattern: T): asserts value is Match.PatternMatch<T>;

which I think does what you want:

Edit: actually this type defintion for check is also in node_modules\@types\Meteor\globals\check.d.ts - have you installed that package? (the only reason I have it in my meteor.d.ts file is because I’ve replaced the default method with my own which takes a 3rd errMsg parameter).

Just FYI if you’re migrating to TS, these wrappers I provided for Methods and Publications might be useful.

There are other, probably better solutions out there, but these provided an easy migration path when I implemented them.

1 Like

Just use zod!

2 Likes

@wildhart That approach does not do type narrowing. After you call check(), the type of the variable is not narrowed.

Here’s an example (in Solid Playground, which can be useful as a TypeScript playground too): Solid Playground

Here is what we want: Solid Playground

(that’s only a sample limited to number types, but Match.test should cover the pattern passed in)

@trusktr there is a small difference in your Solid Playground compared with the type definition I provided. Here’s a working example which matches the error thrown by check().

image

2 Likes

Ooooh, asserts. I somehow missed that TS feature update and hadn’t stumbled on it until now! Super nifty!

I see that check.d.ts is using that already in Meteor source. So, it should already be working then.

2 Likes

Ok, but how exactly would I use this in a method then?

I don’t know why you need a “special version” of check function to work with typescript.
I’m using typescript and it just work.

async "sample.method"({ strIn, numIn }: { strIn: string; numIn:  number; }) {
  check(strIn, String);
  // or you can use Match.test
  if (!Match.test(numIn, Number)) {
    throw new Meteor.Error("someCode", "some message");
  }
}

my tsconfig.json file:

{
  "compilerOptions": {
    /* Basic Options */
    "target": "es2018",
    "module": "esNext",
    "lib": ["esnext", "dom"],
    "allowJs": true,
    "checkJs": false,
    "jsx": "preserve",
    "incremental": true,
    "noEmit": true,

    /* Strict Type-Checking Options */
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,

    /* Additional Checks */
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": false,
    "noFallthroughCasesInSwitch": false,

    /* Module Resolution Options */
    "baseUrl": ".",
    "paths": {
      /* Support absolute /imports/* with a leading '/' */
      "/*": ["*"]
    },
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "types": ["node", "mocha"],
    "esModuleInterop": true,
    "preserveSymlinks": true
  },
  "exclude": ["./.meteor/**", "./packages/**"]
}

@waldgeist you shouldn’t need to do anything extra to use this. In any normal code (including Meteor.methods), after you’ve called the built-in check() method on a variable, typescript should know what type the variable is:

The only thing you might need to do is npm install -D @types/meteor

@minhna we’re not using a “special version” of anything. The code examples above were contrived simplifications to demonstrate how the type-narrowing definition of the existing check() method works.

@waldgeist if you were to use a strongly-typed method wrapper, such as the one in my post (or Zodern:relay)…

image

…then you would get type checking goodness when calling a method too:

1 Like

@waldgeist he is asking what to do. With my experience all you need to do is install the @types/meteor package.

I am using Zoderns relay package in my typescript projects at the moment and really happy with the results.

It checks the parsed parameters with zod and returns an error when they don’t match, so no need for the check package anymore.

It also creates functions instead of Meteor.call() that have types infered to them so you know which props to parse etc.

2 Likes

You’re still doing double checks here. One at compile time (TS) and one on runtime (check, Match).

Ah, ok, thanks. This is exactly what I was looking for. Sweet that check automatically hands the types over to TypeScript.

That’s also nice! Thanks for the hint.

Relay is also great because it proxies return types between server and client.

1 Like

Oh, cool, so it’s like EJSON?

@waldgeist no, it’s just the types, so basically if Typescript knows the return type for the server return, the code you call on the client will have knowledge of that type in the client code. So if your method returns an Number the client code will have knowledge that the return is a Number.

I’ll second zodern:relay. Amazing improvement to developer experience. @zodern @copleykj

As others have said, the check function will handle coercion from one type to another automatically with Meteor’s typescript support set up correctly and after you start up your app (for zodern:types)*

The type signature of check basically says, “if I return successfully without throwing an error, the variable passed into me is definitely of type T, where T is inferred from the schema object passed in”.

In addition to the solutions provided by others, there is also (on npm) the following you can use for type assertions at runtime that have a corresponding effect on your static types:

  • @sinclair/typebox (supposedly faster than Zod)
  • purify-ts (for functional programmers, check out the “Coerce” module)

(Amongst many others)

Edit: regarding the asterisk*, I believe that’s the case at least. If not, I had the behaviour with an up to date version of the npm package @types/meteor - either way, I didn’t have to configure any special type overrides or anything, I just needed a strict TSConfig set up with appropriate “includes” references to the corresponding npm/.meteor/local .d.ts files, and it infers the types for me from the schema passed into check.