ftrackhq / ftrack-ts-schema-generator

Gets the schema from an ftrack instance and generates typescript interfaces for all the entity types in the instance.
Apache License 2.0
2 stars 1 forks source link

fix: Adjust type inheritance #3

Open gismya opened 1 year ago

gismya commented 1 year ago

The ftrack API is built with an inheritance structure such that a returned type is the specified type or any of the types inheriting from that type.

For example, a Resource can be a Resource, a Group, a BaseUser, or a User. Currently, the generated types only accept that it returns a Resource and gives errors if you try to check for any properties from the other types. We need to decide what strategy to implement to remedy this.

gismya commented 1 year ago

Looking at something like:

interface ResourceTypeMap {
  User: User;
  Group: Group;
  Resource: Resource;
  BaseUser: BaseUser;
}

type AbstractResource = keyof ResourceTypeMap;

export interface Appointment<T extends AbstractResource = AbstractResource> {
  context?: Context;
  context_id?: string;
  readonly id?: string;
  resource?: ResourceTypeMap[T];
  resource_id?: string;
  type?: string;
  __entity_type__?: "Appointment";
  __permissions?: Record<string, any>;
}
gismya commented 1 year ago

Another way to handle it:

export interface Appointment {
  context?: Context;
  context_id?: string;
  readonly id?: string;
  resource?: Resource|User|Group|BaseUser;
  resource_id?: string;
  type?: string;
  __entity_type__?: "Appointment";
  __permissions?: Record<string, any>;
}
ffMathy commented 1 year ago

Another way to handle it:

export interface Appointment {
  context?: Context;
  context_id?: string;
  readonly id?: string;
  resource?: Resource|User|Group|BaseUser;
  resource_id?: string;
  type?: string;
  __entity_type__?: "Appointment";
  __permissions?: Record<string, any>;
}

Yeah I'd definitely prefer this. It allows for type discrimination.

Also, is there a way to make __entity_type__ non-optional? Not sure, but I think that actually messes with type discrimination as well.

gismya commented 1 year ago

I don't think the optionality affects type discrimination, but I can make some quick tests on that.

How would the first one break type discrimination? The upside to that approach is the possibility to explicitly add what type of resource will be returned since that's sometimes known.

ffMathy commented 1 year ago

I think the reason it could break it, is because I am also thinking about it in the context of this: #11

ffMathy commented 2 months ago

Another way to handle it:

export interface Appointment {
  context?: Context;
  context_id?: string;
  readonly id?: string;
  resource?: Resource|User|Group|BaseUser;
  resource_id?: string;
  type?: string;
  __entity_type__?: "Appointment";
  __permissions?: Record<string, any>;
}

At first glance, this is the way I'd prefer. Does it have any downsides?

gismya commented 2 months ago

Another way to handle it:

export interface Appointment {
  context?: Context;
  context_id?: string;
  readonly id?: string;
  resource?: Resource|User|Group|BaseUser;
  resource_id?: string;
  type?: string;
  __entity_type__?: "Appointment";
  __permissions?: Record<string, any>;
}

At first glance, this is the way I'd prefer. Does it have any downsides?

Not that I know of.

ffMathy commented 2 months ago

Alright. Maybe I'll pick this up next time I get bored.

Is it only for Resources, or does it need to be fixed for other types too?

gismya commented 2 months ago

It's for anything that inherits. Another one is I think there are instances of Context that (most commonly) is TypedContext

gismya commented 2 months ago

There could also be multiple levels of inheritance, for example User inherits from BaseUser, which inherits from Resources

ffMathy commented 2 months ago

Ah. If I am to contribute to this, where can I find this hierarchy?

gismya commented 2 months ago

In the schemas. That's what we're using to build the extends with when generating the interfaces.

For example

export interface User
  extends Omit<BaseUser, "__entity_type__" | "__permissions"> {
[...]
}
export interface BaseUser
  extends Omit<Resource, "__entity_type__" | "__permissions"> {
[...]
}
export interface Resource {
[...]
}