Isa operator not correctly identified by static typing validation

,

I have the following FSL:

@role(role_refresh)
function refreshAccessToken() {
  let user = Query.identity()

  if(user != null && user isa User) {

    // Delete Token to prevent reuse
    Query.token()!.delete()
    
    {
      user: user,
      token: createAccessAndRefreshToken(user)
    } 
  }
}

But if I try to push it with the fauna cli via fauna schema push I’m getting this error:

 ›   Error: error: Type `{ *: Any } | Null` is not a subtype of `User`
 ›   at src/lib/db/schema/fsl/functions/auth/tokens/refreshAccessToken.fsl:12:42
 ›      |
 ›   12 |       token: createAccessAndRefreshToken(user)
 ›      |                                          ^^^^
 ›      |

The function createAccessAndRefreshToken expects a prop of type User, but I’m surprised, that

if(user != null && user isa User) { }

is not satisfying the validation. With Query.identity()! I can get at least workaround the | Null option, but I’m not able to find a workaround to classify it as User.
Any help appreciated. :+1:


Besides of that, is there something similar to TypeScripts “as” keyword? I can guarantee that Query.identity() is not null and from type User as this will be already validated as part of the ABAC in role_refresh, so it would be great to simply say Query.identity() as User

Hi @Mike!

I would first try to assert user is non-null and see if that works. The error is that { *: Any } | Null is not a subtype of User, so let’s eliminate the Null as a part of the type.

token: createAccessAndRefreshToken(user!) // assert not null with '!'

The FQL type checker does not perform “type narrowing” for null checks or isa checks, yet, though that’s on our roadmap. Type narrowing would definitely have a huge impact on type inference, and it’s something we want to deliver.

If null assertion doesn’t fix it 100%, we don’t have explicit casts with as, but you can try setting an intermediate variable.

    let user: User = user
    // or use Any if necessary
    // let user: Any = user    

    {
      user: user,
      token: createAccessAndRefreshToken(user!)
    } 

Hi @ptpaterson,

thanks for the suggestions!
Seems casting it first to Any let identity: Any = Query.identity()! is the only way getting it working for now.

@role(server)
function refreshAccessToken() {
  let identity: Any = Query.identity()!
  let user: User = identity
  
  {
    user: user,
    token: createAccessAndRefreshToken(user)
  } 
}