edgedb / edgedb-js

The official TypeScript/JS client library and query builder for EdgeDB
https://edgedb.com
Apache License 2.0
509 stars 63 forks source link

__element__ incompatible between parent & child types #720

Open jvassev opened 11 months ago

jvassev commented 11 months ago

Code The code causing the error.

// fetches the configuration for anything that implements HasConfiguration
async function getConfigurationFor(type: e.default.HasConfiguration, ownerId: string): any {
  const found = await e
    .select(type, () => ({
      config: {
        e.default.Configuration.data,
      },
      filter_single: {
        id: ownerId,
      },
    }))
    .run(client);
   return found.config.data
}

// now, when I try to get the configuration for user userId:
const cfg = getConfigurationFor(e.default.User, userId);

// this compilation error occurs:
// Type '$expr_PathNode<TypeSet<$User, Cardinality.Many>, null>' is not assignable to type '$expr_PathNode<TypeSet<$HasConfiguration, Cardinality.Many>, null>'.
//   Type '$expr_PathNode<TypeSet<$User, Cardinality.Many>, null>' is not assignable to type '{ __element__: $HasConfiguration; __cardinality__: Cardinality.Many; __parent__: null; __kind__: ExpressionKind.PathNode; "*": { id: true; }; }'.
//     Types of property '__element__' are incompatible.

Schema

module default {
  type Configuration {
    data: json;
  }

  abstract type HasConfiguration {
    config: Configuration;
  }

  type User extending HasConfiguration {
    email: str;
  }

  type Project extending HasConfiguration {
    name: str;
  }
}

Generated EdgeQL

Error or desired behavior

I think it would be nice to treat EdgeQL subtypes as subtypes in TS code. If I ignore the compiler error from the example above (using getConfigurationFor(e.default.User as any, x) ), the code works.

The error has to do with the __element__ property being different for the parent (HasConfiguration) and child(User or Project);

A solution I could think of is making the __element__s compatible or have a "cast" method, for example:

// here asParent is generated and prepopulated with all the parents of User
// the resulting value has the same __element__ as HasConfiguration
// but 
const cfg = getConfigurationFor(e.default.User.asParent.HasConfiguration, x);

It's like a handle to the User type which lets you only touch the properties inherited from one of its parents.

Versions (please complete the following information):

scotttrinh commented 11 months ago

This is somewhat related to the work I've started in #606 . It's a little tricky because TypeScript and EdgeQL have slightly different type systems, so I'm trying to tread carefully here. Definitely makes sense from the TypeScript perspective of wanting to define some function that is polymorphic based on some superclass/interface.

jvassev commented 11 months ago

Thank you for the quick response! I tried to find a similar issue but did not discover #606. This is clearly a duplicate of it.