Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip - Pothos EdgeDB Plugin #539

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

baristikir
Copy link
Contributor

@baristikir baristikir commented Aug 19, 2022

WIP EdgeDB Plugin

Related Issue: #534

edgeDBObject

Naming Conventions

  • t.link(): Model property which is a relation to another model. (like prisma plugins t.relation())
    Could be one or many relation to model.

Open for any feedback and reviews!

@changeset-bot
Copy link

changeset-bot bot commented Aug 19, 2022

⚠️ No Changeset found

Latest commit: 8ec9e25

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Aug 19, 2022

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated
pothos ✅ Ready (Inspect) Visit Preview Aug 22, 2022 at 2:40PM (UTC)

: never
: never;

export type EdgeDBModelShape<
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EdgeDB's Query Builder contains a default which includes all db schema's with their properties and relations, or named as links in edgeDB, of the schemas and can be used to recreate the PrismaTypes structure for type safety.

Example of EdgeDBTypes

export interface types {
  "std": {
    ...
  };
  "schema": {
    ...
  };
  "cfg": {
    ...
  };
  "default": {
    "Comment": Comment;
    "Follow": Follow;
    "Media": Media;
    "Post": Post;
    "PostMedia": PostMedia;
    "Profile": Profile;
    "User": User;
  };
  "sys": {
     ....
  };
}

export interface Post extends std.$Object {
  "big_int_id": bigint;
  "content"?: string | null;
  "created_at": Date;
  "published": boolean;
  "title": string;
  "updated_at": Date;
  "author"?: User | null;
}

@baristikir
Copy link
Contributor Author

baristikir commented Aug 19, 2022

Current work only covers the edgeDBObject api. Showcase of how the type system could look like for the edgeDB schema. Type system is constructed out of the EdgeDB Query Builder types, specifically from the default export type. Output shape looks pretty similar to the PrismaTypes shape

Bildschirmfoto 2022-08-19 um 16 09 54

@hayes
Copy link
Owner

hayes commented Aug 19, 2022

This looks like an awesome start! Getting the type system working is often one of the hardest parts, and it looks like you are making great progress!

I'll try to find some time today to dive a little deeper. Initial skim looks like there are lots of pieces that are unused or copied from the prisma plugin that don't quite make sense yet, so I'll just ignore those for now and try to look specifically at types for defining objects and links

@@ -0,0 +1,340 @@
# Change Log
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably don't need this

"author": "Michael Hayes",
"license": "ISC",
"keywords": [
"giraphql",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably don't need giraphql in keywords anymore, I think pothos had gotten enough traction that there shouldn't be many people still looking for giraphql

| ({ [ModelKey in keyof Model]: Model[ModelKey] extends infer U ? U : never } & {
Fields: string | never;
Links: {
[Key in Model['Fields']]: { Shape: Model['Links'][Key] };
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious about this shape referencing itself here, what is that used for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had parent args, Fields and Links in one type which end up really nested. Will need to rethink that and construct it in a clean way, will probably refer more or less to PrismaTypes's structure. This Shape referencing isnt actually useful there indeed.

export interface PrismaModelTypes {
Name: string;
Shape: {};
Include: unknown;
Select: unknown;
Where: {};
Fields: string;
ListRelations: string;
RelationName: string;
Relations: Record<
string,
{
Shape: unknown;
Types: PrismaModelTypes;
}
>;
}

Curios is there a difference between Fields and RelationName?
Looks like they contain the same values.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope... That seems to be an oversight. I'll remove Fields from the prisma plugin

// description: getFieldDescription(this.model, this.builder, name, description),
extensions: {
...extensions,
pothosEdgeDBFallback:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the fallback resolver stuff in pothos is one of my least favorite parts. having a resolver option that only sometimes gets called is really confusing for users, so if this can be avoided for edgeDB that would be cool. I haven't seen if you have started working on any entity-reloading logic yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't really looked into it, just copy left from the prisma plugin. Will take a look into it, we could avoid it. No entity-reloading logic existing yet.

) => EdgeDBObjectRef<Types, Model>;
}

export interface EdgeDBObjectFieldBuilder<
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I noticed that there is something broken about this in the Prisma plugin. I tried referencing this global type to create a function that accepts a PrismaFieldBuilder, but it wasn't actually compatible with the PrismaFieldBuilder type. Not sure if this will have the same issue, but something that might be worth checking on. Not sure if you need this at all if you are not supporting extending EdgeDBObjectFieldBuilder from other plugins initially.

};
})
| never,
Shape extends object = Model,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shape is generally used as the shape of the parent arg

range = 'range',
}
export type tupleOf<T> = [T, ...T[]] | [];
export type cardinalityAssignable<C extends Cardinality> = C extends Cardinality.Empty
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could probably be modeled in a way thats more readable:

type CardinalityAssignable<C extends Cardinality> =  {
  Empty: Cardinality.Empty
  ... 
  AtLeastOne: Cardinality.One | Cardinality.AtLeastOne | Cardinality.Many
}[C]

@hayes
Copy link
Owner

hayes commented Aug 19, 2022

Curious about your goals here, and what you are looking for. Is this something you want to get merged in the not too distant future, or more just sharing/collecting feedback while you iterate?

Long term, is this something you want have live in the main repo and have maintained with the rest of the plugins, or something you want to own in your own repo. I have been meaning to create some docs pages for linking out to community maintained projects. It might also be interesting do have a model similar to https://github.com/opentracing-contrib where there is an official place for people to add their own plugins/packages/examples etc in a way that is discoverable.

Open to any ideas and suggestions here, and don't feel too strongly about it. I would say that the bar for what I would expect in this repo would likely be a lot higher, and include things like making sure that this works well with all the other plugins, thorough documentation, test coverage, and solutions for things like aliasing fields, or defining multiple fields that use the same link in the database.

Regardless of how you want to work on this, and where you would like it to live, I am happy to help out when I have time, and give feedback/reviews or answer any questions you have about how the internals of pothos work. Looks like you are already making a lot of progress and have figured out a lot of the weird patterns needed to make something like this work.

@baristikir
Copy link
Contributor Author

Agreed, this is right now just not enough for a plugin imo. Long term I would love to see it in the main repo as a plugin. But also looking for more features, supporting other plugins and enabling integrations, like relay nodes and so on. A lot of work needs to be done, not finished at all.

I am not very familiar with the architecture and type system of Pothos, still learning the patterns and experimenting with the types :)

I was porting a lot of the prisma plugins system into this plugin therefore still unused and unnecessary code / type definitions around here. Will clean that up.

@baristikir
Copy link
Contributor Author

Will need some feedback on this topic.

Edgedb queries return type depends on the select shape. Even ...e.User["*"] won't load the whole db schema. That would be a problem for edgeDBField resolve functions return type since Model["Shape"] won't be the return type at any time. Instead the return type needs to be composed of optional fields. We might not wanna loose the actual nullability of model properties in Model["Shape"], important for edgeDBObject field definitions. So defining another Shape type as the optional reflection looks like a solution.

Example

// EdgeDB Generated QueryBuilder
import e from '../../client';

builder.queryType({
  fields: (t) => ({
    users: t.edgeDBField({
      type: ['User'],
      nullable: true,
      resolve: async (_query, _parent, _args, ctx) => {
        const users = await e
          .select(e.User, (user) => ({
            id: true,
            email: true,
            name: true,
          }))
          .run(db);

        return users;
        //      ^? { id: string; email:string; name: string | null; }[]
        // This would not match with  Users Model["Shape"], comments | posts ... are missing
      },
    }),
  }),
});

interface EdgeDBModelTypes {
  User: {
    Shape: {
      "comments": Comment[];
      "posts": Post[];
      "email": string;
      "name"?: string | null;
      "followers": Follow[];
      "following": Follow[];
      "media": Media[];
      "profile"?: Profile | null;
    }
  }
}

@baristikir
Copy link
Contributor Author

baristikir commented Aug 22, 2022

Added ReturnShape to reflect Shape props as optional fields.

ReturnShape: {
[K in keyof ModelProperties]?: ModelProperties[K] extends ObjectType
? ModelProperties[K] extends infer Link
? { [K in keyof Link]?: Link[K] }
: ModelProperties[K]
: ModelProperties[K];
};

Type extends TypeParam extends [unknown]
? [ObjectRef<Model['ReturnShape']>]
: ObjectRef<Model['ReturnShape']>,

@baristikir
Copy link
Contributor Author

Added ReturnShape to EdgeDBModelTypes to reflect optional fields from Shape.

ReturnShape: {
[K in keyof ModelProperties]?: ModelProperties[K] extends ObjectType
? ModelProperties[K] extends infer Link
? { [K in keyof Link]?: Link[K] }
: ModelProperties[K]
: ModelProperties[K];
};

Actually supposed to be a deep partial type, updated version:

https://github.com/baristikir/giraphql/blob/8ec9e25d91d12fa9059a8ec0314c8b493944444e/packages/plugin-edgedb/src/types.ts#L72-L88

@hayes
Copy link
Owner

hayes commented Aug 22, 2022

Edgedb queries return type depends on the select shape. Even ...e.User["*"] won't load the whole db schema. That would be a problem for edgeDBField resolve functions return type since Model["Shape"] won't be the return type at any time. Instead the return type needs to be composed of optional fields. We might not wanna loose the actual nullability of model properties in Model["Shape"], important for edgeDBObject field definitions. So defining another Shape type as the optional reflection looks like a solution.

I think the technically correct approach here would be to model this after the "select" mode from the prisma plugin. Basically the prisma plugin supports having a default set of selections defined on the "prismaObject", when you do this, the "parent" type for all resolvers is limited to that selection. You can still "expose" fields that are not part of the selection (which will automatically select them when the expsosed field is queried) . You can then also define selections on individual fields, which will extend the parent type in the resolver for that field.

You can see an example of this here: https://github.com/hayes/pothos/blob/main/packages/plugin-prisma/tests/example/schema/index.ts#L38-L59

The implementation for this gets a lot more complicated, so not sure if you need to explore this in the initial version. Took me a few iterations of the prisma plugin before I figured out how to make that work properly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants