Coder Social home page Coder Social logo

hayes / pothos Goto Github PK

View Code? Open in Web Editor NEW
2.3K 12.0 151.0 70.41 MB

Pothos GraphQL is library for creating GraphQL schemas in typescript using a strongly typed code first approach

Home Page: https://pothos-graphql.dev

License: ISC License

TypeScript 80.48% JavaScript 1.15% MDX 18.37%
graphql typescript

pothos's Introduction

Pothos

Pothos GraphQL

Pothos is a plugin based GraphQL schema builder for typescript.

It makes building graphql schemas in typescript easy, fast and enjoyable. The core of Pothos adds 0 overhead at runtime, and has graphql as its only dependency.

Pothos is the most type-safe way to build GraphQL schemas in typescript, and by leveraging type inference and typescript's powerful type system Pothos requires very few manual type definitions and no code generation.

Pothos has a unique and powerful plugin system that makes every plugin feel like its features are built into the core library. Plugins can extend almost any part of the API by adding new options or methods that can take full advantage of the Pothos type system.

Hello, World

import { createYoga } from 'graphql-yoga';
import { createServer } from 'node:http';
import SchemaBuilder from '@pothos/core';

const builder = new SchemaBuilder({});

builder.queryType({
  fields: (t) => ({
    hello: t.string({
      args: {
        name: t.arg.string(),
      },
      resolve: (parent, { name }) => `hello, ${name || 'World'}`,
    }),
  }),
});

const yoga = createYoga({
  schema: builder.toSchema(),
});

const server = createServer(yoga);

server.listen(3000);

What sets Pothos apart

  • Pothos was built from the start to leverage typescript for best-in-class type-safety.
  • Pothos has a clear separation between the shape of your external GraphQL API, and the internal representation of your data.
  • Pothos comes with a large plugin ecosystem that provides a wide variety of features while maintaining great interoperability between plugins.
  • Pothos does not depend on code-generation or experimental decorators for type-safety.
  • Pothos has been designed to work at every scale from small prototypes to huge Enterprise applications, and is in use at some of the largest tech companies including Airbnb and Netflix.

Plugins that make Pothos even better

  • Add GraphQL

    Add existing GraphQL types to your schema

  • Auth

    Add global, type level, or field level authorization checks to your schema

  • Complexity

    A plugin for defining and limiting complexity of queries

  • Directives

    Integrate with existing schema graphql directives in a type-safe way.

  • Errors

    A plugin for easily including error types in your GraphQL schema and hooking up error types to resolvers.

  • Dataloader

    Quickly define data-loaders for your types and fields to avoid n+1 queries.

  • Mocks

    Add mock resolvers for easier testing

  • Prisma

    A plugin for more efficient integration with prisma that can help solve n+1 issues and more efficiently resolve queries

  • Relay

    Easy to use builder methods for defining relay style nodes and connections, and helpful utilities for cursor based pagination.

  • Simple Objects

    Define simple object types without resolvers or manual type definitions.

  • Smart Subscriptions

    Make any part of your graph subscribable to get live updates as your data changes.

  • Sub-Graph

    Build multiple subsets of your graph to easily share code between internal and external APIs.

  • Tracing

    Add tracing for resolver execution, with support for opentelemetry, newrelic, century, logging, and custom tracers

  • Validation

    Validating your inputs and arguments

  • With-Input

    Define fields with inline input objects

Sponsors

Pothos development supported by sponsorships from these generous people and organizations:

pothos's People

Contributors

3nk1du avatar andreasthoelke avatar baristikir avatar ben-bosmans avatar benbender avatar cauen avatar chenzn1 avatar delaneyj avatar emilios1995 avatar fabsrc avatar furkandmrblk avatar github-actions[bot] avatar hayes avatar jipsterk avatar jomik avatar julioxavierr avatar liam-tait avatar martinpelcat avatar maxast avatar n1ru4l avatar nathanchapman avatar noxify avatar p4sca1 avatar psirenny avatar s1owjke avatar smolattack avatar tgriesser avatar timsuchanek avatar tladd avatar tom751 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pothos's Issues

@giraphql/core tsconfig.json strict error

{
	"resource": "/node_modules/@giraphql/core/tsconfig.json",
	"owner": "typescript",
	"severity": 8,
	"message": "Option 'declarationDir' cannot be specified without specifying option 'declaration' or option 'composite'.",
	"source": "ts",
	"startLineNumber": 1,
	"startColumn": 1,
	"endLineNumber": 1,
	"endColumn": 2
}

Duplicate field / Duplicate typename - With Vite/SvelteKit

Hey

Im running into issues with vite/sveltekit when i save a resolver file, i think because of some module caching in vite, idk the exact reason.
I managed to work around the Error: Duplicate typename: Another type with name LoginInput already exists. by

if (import.meta.env.DEV) {
   builder.configStore.typeConfigs.clear();
}

But then the next error was Error: Duplicate field definition for field email in LoginInput.

I don't know if maybe exposing a way to clear the configStore would be enough.

I can't really use giraphql without restarting the dev server which is not very productive.

Infer the exposed key from the key of the field?

This is probably a feature request.

Is it possible to infer the exposed key from the key of the field? So instead of this:

const ApplicationObjectType = builder.objectRef<Application>('Application').implement({
  fields: (t) => ({
    applicationFormConfigId: t.exposeID('applicationFormConfigId', {}),
    clientId: t.exposeID('clientId', {}),
    comments: t.expose('comments', { type: 'GraphQLJSON' }),
    destinations: t.exposeStringList('destinations', {}),
...

It'd be this

const ApplicationObjectType = builder.objectRef<Application>('Application').implement({
  fields: (t) => ({
    applicationFormConfigId: t.exposeID(), // inferred as `applicationFormConfigId`
    clientId: t.exposeID(), // inferred as `clientId`
    comments: t.expose({ type: 'GraphQLJSON' }),
    destinations: t.exposeStringList(),
...

Add converter to the docs

I should have noticed this instead of spending all day yesterday and today converting over ๐Ÿ˜…

I can add it to the docs if it's helpful!

Loadable nodes not extending interfaces other than node

The below code sample produces the second when I believe it should work the same as the non loadable version

export const UserEntity = builder.loadableNode('UserNode', {
  id: {
    resolve: (user) => user.id,
  },
  interfaces: [PageInterface],
  // ...
  fields: (t) => ({
    name: t.exposeString('name', {}),
   // ...
    posts: t.connection(
      {
        type: PostEntity,
        authScopes: {
          authenticated: true,
        },
        resolve: async ({ id }, args) =>
          // NOTE: This seems ineficient
          resolveOffsetConnection({ args }, async ({ limit, offset }) =>
            prisma.post.findMany({
              where: {
                creatorID: id,
              },
              include: {
                creator: true,
              },
              take: limit ?? undefined,
              skip: offset ?? undefined,
            }),
          ),
      },
      {},
      {},
    ),
  }),
});
type UserNode implements Node {
  avatarUrl: String!
  bio: String
  createdAt: DateTime!
  email: String!
  followRequests: [FollowRequest!]!
  followStatus: FriendShipStatus!
  followers: [UserNode!]!
  following: [UserNode!]!
  id: ID!
  linkedSite: String
  name: String!
  postCount: Int!
  posts(after: ID, before: ID, first: Int, last: Int): UserNodePostsConnection!
  username: String!
}

[Relay-Plugin]: EdgeType Resolver Should Return Edge as src, not the Connection Parent

Let's say I have an Organization Node that has an employees Connection. If I defined it as below, I'm not able to pass the edge properties from the query since they are embedded on the return object.

// Parent Object === 'Organization'
{
  employees: t.connection(
    { 
        type: Individual,
        resolve: async (src, args, ctx) => {
          const getEmployees = () => data.map(person => ({ 
            ...personStuff, 
            // append the relationship edge data to the node
            employment: { dateStarted, dateEnded }
          }))
          return resolveArrayConnection({ args }, getEmployees)
        }
    },
    {},
    {
       fields: () => ({
         dateStarted: t.string({
            nullable: true,
            // src here resolves to 'Organization' which is the parent type
            // src should be the returned Individual in order to access the edge properties
            resolve: (src) => src.employment.dateStarted 
          }),
          dateEnded: t.string({
            nullable: true,
            resolve: (src) => src.employment.dateEnded
          })
       })
    }
)

Possible to use graphql-scalars' UUID as ID

https://giraphql.com/guide/scalars shows creating your own scalars but not how to use as an ID.

Coming from gqlgen I used to being able to set the ID! fields to validate as UUID from both the query and mutation side. I did see how to use the validate plugin to check input args for uuids, but not how to set a type for IDs.

Specifically I was trying to add Graphql-Scalars UUID .

import { UUIDResolver } from 'graphql-scalars'

const builder = new SchemaBuilder<{
    Scalars: {
      UUID: {
        Input: typeof UUIDResolver
        Output: typeof UUIDResolver
      }
    }
  }>({
    plugins: [ValidationPlugin],
  })
const uuid = builder.addScalarType('UUID', UUIDResolver, {})

does not error out, however I'm unclear how to use in a t.exposeID.

fields: (t) => ({
      id: t.exposeID('id', {
        type: uuid,
      }),

Doesn't work but I'm not sure what it should look like. Just want to make sure it I can't give an invalid UUID as part of the resolver. In go UUID as an explicit type extending byte[16] and would like that level of explicitness. Was hoping using graphql-scalars would help enforce that.

ObjectRef.implements returns any

const Author = builder.objectRef<AuthorModel>('Author').implement({
   description: 'A writer.',
   fields: (t) => ({
     id: t.exposeID('id', {}),
     firstName: t.exposeString('firstName', {}),
     lastName: t.exposeString('lastName', {}),
     posts: t.field({
       type: [Post],
       args: {
         findTitle: t.arg.string({
           description: 'if empty then it grabs all',
         }),
       },
       resolve: (author, { findTitle }) => {
         let pp = db.posts.filter((p) => p.authorID === author.id)
         if (findTitle) {
           pp = pp.filter((p) => p.title === findTitle)
         }
         return pp
       },
     }),
   }),
 })

 const Post = builder.objectRef<PostModel>('Post').implement({
   description: 'Post from writer',
   fields: (t) => ({
     id: t.exposeID('id', {}),
     title: t.exposeString('title', {}),
     author: t.field({
       type: Author,
       resolve: (post) => {
         const author = db.authors.find((a) => a.id === post.authorID)
         if (!author) throw new Error('bad author id')
         return author
       },
     }),
   }),
 })

generates the error

{
	"resource": "src/graphql/giraph/test.ts",
	"owner": "typescript",
	"code": "7022",
	"severity": 8,
	"message": "'Author' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.",
	"source": "ts",
	"startLineNumber": 11,
	"startColumn": 9,
	"endLineNumber": 11,
	"endColumn": 15
}

However change to ...

...
const Author = builder.objectRef<AuthorModel>('Author')
Author.implement({...})
...

resolves the errors. The first way matches the docs and is cleaner. Am I missing something?

Change context type based on authScopes check

Not sure if this is possible to solve but I am noticing this challenge.

Give this builder:

type Context = { user?: User };

const builder = new SchemaBuilder<{
 Context: Context;
AuthScopes: {
    public: boolean;
    authenticated: boolean;
};
}>({
  plugins: [ScopeAuthPlugin, ValidationPlugin],
  authScopes: async ({ user, req }) => ({
    public: true,
    authenticated: !!user,
})

Then query:

builder.queryField('organizations', (t) =>
  t.field({
    type: [UserObject],

    authScopes: {
      authenticated: true,
    },
    resolve: (_root, _arg, { user }) => {
      console.log(user) // technically, because of auth scopes, the user should not be undefined 
    }
  })
);

Notice that we check authenticated authScope where we check for the presence of the user in the context. When using the context within a query, expectation (since we checked it on authScopes) is that user is not undefined.

Adding functionality to arguments

Ideally, I'd love to be able to wrap arguments somehow. This would make things like validation a lot easier (e.g. applying a schema to arguments directly, as opposed to the current workarounds involving attaching schemas to the field itself).

This would probably also make it possible to have a custom plugin that could define both the schema and the arguments at the same time.

[Error-Plugin] Documentation example errors are both returning as __typename BaseError

"@giraphql/core": "^2.9.0",
"@giraphql/plugin-errors": "^2.1.0",

Not sure if documentation error or an error in the plugin itself. From https://giraphql.com/plugins/errors#recommended-ussage are both giving me the __typename of BaseError. I have not had a chance to dive directly into the code just yet but since I was planning on switching to this type of error handing with graphql I wanted to give this a quick test.

I am also getting a type error for the empty errors: {} (documentation or type issue) object under the hello query, easily fixed with adding

  errors: { 
    types: [Error] 
 }

Capture

Overall, great work on the package. Love using it, and it just keeps getting better.

Guide on custom plugins?

I have a situation where I need to inject a minimum set of properties no matter what was queried. So

{
  foo{
    bar
  }
}

would become

{
  foo{
    id
    metadata
    bar
  }
}

I'm guessing a middleware style plugin would be great. Is there a guide of what would be necessary to create own plugins that work with giraphql?

resolveArrayConnection and null

The resolveArrayConnection helper is current resolving T to Foo | null without complaint. This will result in a runtime error because node is not nullable. As arrays can contain holes due to missing relations, I think the best thing to do is to map null to null edges in the result.

This is my current workaround:

        const connection = resolveArrayConnection({ args }, foos);

        if (connection == null) {
          return null
        }

        // work around bug in resolveArrayConnection
        return {
          ...connection,
          edges: connection.edges.map(edge => edge?.node == null ? null : edge) || []
        }

Relay mutation builder return type

I had a situation where I wanted to share an input type between a mutation created with the relay mutation helper and one without. The relay mutation builder currently returns void and the input type string was not resolving, irrespective of load order. Would it be possible to return a structure with the generated object refs or somehow resolve the string resolution?

Connections on interface

Is there any way of doing this yet?

import { builder } from '../builder';
import { PostEntity } from '../entities';

export const PageInterface = builder.interfaceRef('Page').implement({
  fields: (t) => ({
    // eslint-disable-next-line
    posts: t.connection({ type: PostEntity, resolve: () => ({} as any) }),
  }),
});

I tried the above but get this error when trying to use it with relay

Interface field Page.posts expects type PagePostsConnection! but UserNode.posts is type UserNodePostsConnection!.

Dataloader plugin load signature

The dataloader plugin uses the dataloader loadMany function, which forces the return type of the load function to be (T | Error)[]. Lee Byron has written that he regretted implementing what is essentially a convenience wrapper of Promise.all (graphql/dataloader#41 (comment)). It would be very nice to be able to return (T | null)[] to express not found, but that is only possible if the plugin used Promise.all and dataloader's load function. I'm still not sure what I think the right pattern is, but I find the Error mapping/handling to be quite noisy.

Document directives

I'm trying to use directives to implement this rate limiting middleware https://github.com/ravangen/graphql-rate-limit but I'm not sure how to do this, the types are telling me that the first argument for builder.toSchema({}) accepts an array of directives but how would I then attach these directives to specific fields?

[Relay]: Expose resolveOffsetConnection into two functions

Another request!

Is there any way to break the resolveOffsetConnection function into two parts, or change the return type on the resolveOffsetConnection callback?

The reason is that I get the totalCount and pageCursors from one aggregated database call. But I can't make that database call without the offset and limit ... which means I have to manually replicate the offsetForArgs function or roll my own. But then I have two offset/limit calculations which means that if they ever get out of sync, things will get dicey. Plus it just seems inefficient.

I tried returning the totalCount, etc. from the resolveOffsetConnection function but it's expecting an array of nodes, not extra connection info.

resolveArrayConnection type

Given the filtering of nulls within resolveArrayConnection, wouldn't you expect the array input type to be (T | null)[] and the output be T?

Relay connection cursor type

The relay connection plugin is using ID for cursors. Reading the relay connection spec, it clearly allows for this, but we are currently using String and this would be a breaking change for clients. Is it possible to configure this type? If not, we can look at a migration path.

Globally extend connections?

I have a use case where we add the totalCount and paginatedCursors to relay connections (inspired by artsy) such that the type for a "connection" field is roughly:

type PageCursors {
  ...
}

type SomeConnection {
  edges: [SomeEdge!]
  pageInfo: PageInfo!
  totalCount: Int!
  pageCursors: PageCursors!
}

I know that graphql-nexus lets you globally extend the connection type, but I could not figure out if that was an option with GiraphQL, and/or how one might go about extending the connection type globally.

Also, I'm actually enjoying building a Relay API with GiraphQL, so great work!

Redundant empty object when exposing fields

I find myself having to place an empty object for the second argument when exposing fields

clientId: t.exposeID('clientId', {}),

Instead of

clientId: t.exposeID('clientId'),

mutationField

Thank you for such a nice alternative to tools like nexus!

I might be mistaken, but it appears as though giraphql is unhappy if I build a mutationField without also building the mutationType (with empty args).

@giraphql/plugin-auth outdated peer depency

I'm using @giraphql/core ^1.5.1 and installing @giraphql/plugin-auth fails because it expects @giraphql/core ^0.20.3, here's the error message:

npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! Found: @giraphql/[email protected]
npm ERR! node_modules/@giraphql/core
npm ERR!   @giraphql/core@"^1.5.1" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer @giraphql/core@"^0.20.3" from @giraphql/[email protected]
npm ERR! node_modules/@giraphql/plugin-auth
npm ERR!   @giraphql/plugin-auth@"^1.3.1" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution

For now I can work around it by installing with the --legacy-peer-deps flag.

[Relay] Guidance of generalizing plugin usage

I've gotten a version of the relay spec working with a SQL database using pg-sql2. It's working great and the backend is generic enough that with a little magic I think it could work in 90% of my cases. The issue is I wanted to move t.connection into a helper function that filled out the specific but so far have gotten lost in a quagmire of types and have tried many ways but think I'm overcomplicating it.

Here is the working version

 const User = builder.loadableObject('User', {
    load: (ids: string[], ctx: Context) => loadByIDs<IUser>(ids, ctx),
    fields: (t) => ({
      id: t.exposeID('id', {}),
      name: t.exposeString('name', {}),
      ...
      fooBars: t.connection(
        {
          type: FooBar,
          resolve: async (parent, args, ctx) => {
            const { first, last, before, after }: DefaultConnectionArguments = args

            if (first && last) throw new Error(`shouldn't have both first and last in same query`)

            let filter = emptySQLQuery
            if (after && validate(after)) {
              filter = sql.query`AND r.from_agg_id > ${sql.value(after)}`
            } else if (before && validate(before)) {
              filter = sql.query`AND r.from_agg_id < ${sql.value(before)}`
            }

            let orderLimit = emptySQLQuery
            if (first && first > 0) {
              orderLimit = sql.query`ORDER BY r.updated_at ASC\nLIMIT ${sql.value(first + 1)}`
            } else if (last && last > 0) {
              orderLimit = sql.query`ORDER BY r.updated_at DESC\nLIMIT ${sql.value(last + 1)}`
            }
            const relationship = sql.value('owner')
            const fromType = sql.value(aggName.IFooBar)
            const toAggID = sql.value(parent.id)

            const q = sql.query`
    SELECT r.from_agg_id id
    FROM relationships r 
    WHERE r.relationship = ${relationship}
    AND r.from_type  = ${fromType}
    AND r.to_agg_id  = ${toAggID}
    ${filter}
    ${orderLimit}
    `
            const { text, values } = sql.compile(q)
            const cfg = { text, values }

            const { rows, rowCount } = await ctx.sql.transaction(async (tx) => await tx.query(cfg))

            let hasNextPage = false
            let hasPreviousPage = false

            if (first && first + 1 === rowCount) {
              hasNextPage = true
              rows.splice(-1)
            } else if (last && last + 1 === rowCount) {
              hasPreviousPage = true
              rows.splice(-1)
            }

            if (last) rows.reverse()

            const edges = rows.map(({ id }) => ({
              cursor: id,
              node: id,
            }))

            let startCursor = rows.length ? rows[0].id : null
            let endCursor = rows.length ? rows[rows.length - 1].id : null

            const pageInfo: PageInfoShape = {
              hasNextPage,
              hasPreviousPage,
              startCursor,
              endCursor,
            }

            return { pageInfo, edges }
          },
        },
        {},
        {},
      ),
    }),
  })

Closest to method types I've gotten is

async function makeRelationship<
Types extends SchemaTypes, Shape,
Type extends OutputType<Types>,
> (
  t: GiraphQLSchemaTypes.ObjectFieldBuilder<Types, Shape>,
fromType:Type,
fromTypeName:string
){
 return t.connection(
    {
      type: fromType,
      resolve: async (parent, args, ctx) => {
        const { first, last, before, after }: DefaultConnectionArguments = args
        ...

But the errors just keep stacking the more I try. I understand this is a very hard typing problem but would really like to extract this out as otherwise the c/p would be prohibitive (I'm having to deal with a 2500 table legacy db that i can not change). I <3 using GiraphQL as the type check is just amazing, but I feel like I'm stepping into a quagmire and feel like I'm missing some key info.

ID typescript type

It appears as though ID maps to string | number by default. By adding the following to the builder types, I appear to be able to constrain this to string. The question is whether it is safe at runtime or whether we risk being passed a number.

ID: {
  Input: string;
  Output: string;
};

Weird VsCode error with SimpleAuthPlugin

Error

Hey I am new to this and have started using GiraphQL recently.
While using SimpleAuthPlugin in my project, even though I am declaring the types in SchemaBuilder for my AuthScopes, I am getting this vs code error for authScopes inside the queryFields or mutationFields.

The plugin is working as expected when I make graphql requests, just vs code is showing this typescript error which is very weird.

Error Shown -
image

Text -

Type '{ user: true; }' is not assignable to type 'FieldAuthScopes<ExtendDefaultTypes<{ DefaultInputFieldRequiredness: true; Context: { req: IncomingMessage; res: OutgoingMessage; user?: User; session?: Session; }; Scalars: { ...; }; AuthScopes: { ...; }; }>, {}, InputShapeFromFields<...>>'.
  Object literal may only specify known properties, and 'user' does not exist in type 'FieldAuthScopes<ExtendDefaultTypes<{ DefaultInputFieldRequiredness: true; Context: { req: IncomingMessage; res: OutgoingMessage; user?: User; session?: Session; }; Scalars: { ...; }; AuthScopes: { ...; }; }>, {}, InputShapeFromFields<...>>'.ts(2322)
global-types.d.ts(35, 13): The expected type comes from property 'authScopes' which is declared here on type 'QueryFieldOptions<ExtendDefaultTypes<{ DefaultInputFieldRequiredness: true; Context: { req: IncomingMessage; res: OutgoingMessage; user?: User; session?: Session; }; Scalars: { ...; }; AuthScopes: { ...; }; }>, ObjectRef<...>, true, InputFieldMap, unknown>'

My Builder file

image

Can't define extra fields with relay plugin on connection or edge

Looking at the docs for the relay plugin at https://giraphql.com/plugins/relay#creating-connections and it shows a brief snippet about defining extra fields on Connection/Node (https://github.com/hayes/giraphql/blame/main/docs/plugins/relay.md#L140-L153).

Defining an addition field like below (snipped down to clean it up for visibility):

builder.queryField('test', (t) =>
    t.connection(
        {
            type: 'Test',
            resolve: (root, args, ctx) => {
                return resolveOffsetConnection(
                    {
                        args,
                    },
                    () => {
                        return [];
                    },
                );
            },
        },
        {
            fields: () => ({
                totalCount: t.int({
                    resolve: (root) => {
                        return 100;
                    },
                }),
            }),
        },
        {},
    ),
);

This results in no additional fields in the generated schema.

Is this an oversight in the documentation or am I doing something wrong/misunderstanding the docs?

Error while using simpleInterfaces

When not using interfaces created using the simple object plugin using objects created in the simple object plugin I get the error
type [...] not assignable to type '"Object shape must extends Interface shape"'

e.g.

export const PageInterface = builder.simpleInterface('Page', {
  fields: (t) => ({
    posts: t.string({ }),
  }),
});

export const UserEntity = builder
  .objectRef<User>('User')
  .implement({
    interfaces: [PageInterface],
    fields: (t) => ({
      id: t.exposeID('id', {}),
      posts: t.string({
        resolve: () => 'hello',
      }),
    }),
  });

Allow `registerPlugin` to be called multiple times.

In a HMR environment, reloading a plugin fails, because it results in multiple calls to registerPlugin it would be ideal for registerPlugin to be callable multiple times (maybe with some sort of flag that you're expecting that to happen?)

Apollo Federation support

Hello,

Firstly, huge thanks for such an amazing library.

Secondly, what would be the recommended way to add Apollo Federation support to the library?

Adding a scalar from a library

When using a library such as graphql-scalars for the JSON scalar, it's unclear how to add it to Giraphql. I tried this

const JsonType = builder.scalarType('GraphQLJSON', GraphQLJSON);

which resulted to error below:
image

Seems like the type needs to change to:

description?: string | null;
extensions?: Readonly<Record<string, unknown>> | null;

Narrowed it down to this:
https://github.com/hayes/giraphql/blob/1da1283475089311aed09a1c5079007e9abb7abf/packages/core/src/types/global/type-options.ts#L28

inputField method

There exist methods such as objectField and mutationField that allow iterative additions of fields to existing types.

Is there a way to do the same thing for inputType such that one could call a method inputField? I cannot find it in the API, is it not implemented or just done differently?

Export zod validator creation functions

It would be super useful to be able to re-use the same zod schema on the frontend and in graphQL. It seems like the best way to do this would be to export functions from the validation plugin which you can pass in the validation rules, and it would spit out a zod schema. For example...

const badPassword = createZodSchema({ minLength: 4, maxLength: 8 });

This would then allow us to do something like this:

// Shared.ts:
export const PasswordValidation = { minLength: 4, maxLength: 8 };

// Server.js
t.arg.string({ validate: PasswordValidation });

// Client.js
useForm({
  schema: zod.object({
    password: createZodSchema(PasswordValidation)
  })
})

Optional client mutation Id

The Input and Payload pattern of the relay mutation helper is very helpful, but the required clientMutationId makes it less generally applicable. An option to omit the client mutation id would be very much appreciated.

Passing parent, args and context into a custom perm auth scope

Couple of things:

  1. When to use customPerm, authScopes function and grantScopes? In the docs:
    customPerm
// scope loader with argument
 customPerm: (perm) => context.permissionService.hasPermission(context.User, perm),

authScopes

authScopes: (article, args, context, info) => {
        if (context.User.id === article.author.id) {
          // If user is author, let them see it
          // returning a boolean lets you set auth without specifying other scopes to check
          return true;
        }

        // If the user is not the author, require the employee scope
        return {
          employee: true,
        };
      },

grantScopes

grantScopes: (parent, args, context, info) => ['readArticle'],

Especially when coupled with CASL, I am a little confused with what's the best method to use when using an abilities library. What are the pros/cons of each and when should each of those methods be used? I want to say grantScopes is super powerful but not sure when to use it ๐Ÿ˜…

  1. In the authScopes initializer, only context is passed. The only way to get parent, args and context is to define authScopes function on the field level. Is this the preferred method when checking auth based on args? What if using customPerm where a field would pass in a perm, is it possible to have parent, args, context passed into that method?

  2. For return type, is there planned support? I guess best workaround is to throw ForbiddenError in the resolver or authScopes on the returned object?

Resolving circular dependency

I am having a circular dependency error happening. I have two files:

ApplicationObject.ts - has the Application object definition, references ClientObject
ClientObject.ts - has the Client object definition, references ApplicationObject

Weirdly, because of this dependency, both objects show up as any. Thoughts on how to solve this? I tried splitting objectRef and implement but didn't work:

export const ApplicationObjectRef = builder.objectRef<Application>('Application');

export const ApplicationObject = ApplicationObjectRef.implement({
  fields: (t) => ({
    id: t.exposeID('id', {}),
...

Relay Plugin: Make ID encoding optional

Right now there's a handful of cases where the ID of relay nodes / cursors are base64 encoded. While at times this makes sense, there are cases where the ID type is already entirely opaque, and does not require encoding. It would be good to have a option to disable the automatic encoding of these values

[React-Relay]: Smart Subscriptions Plugin with Relay

I was trying out the Smart Subscriptions Plugin on my Express + GraphQL Helix Server together with graphql-ws for the websocket connections and was facing some issues on the client side. The subscription works as expected on the GraphiQL Playground but fails on the client were I was using react-relay.

I tried to debug a bit and this was the output of the onOperation from the WebSocket Server. The connection seems alright and this was why I thought it might be related to the plugin.

    TypeError: Invalid value used as weak map key
        at WeakMap.set (<anonymous>)
        at GiraphQLSmartSubscriptionsPlugin.requestData (/Users/baristikir/tmp/giraphql-relay-subscription/node_modules/@giraphql/core/src/plugins/plugin.ts:160:27)
        at subscribe (/Users/baristikir/tmp/giraphql-relay-subscription/node_modules/@giraphql/plugin-smart-subscriptions/src/index.ts:88:20)
        at /Users/baristikir/tmp/giraphql-relay-subscription/node_modules/graphql/subscription/subscribe.js:177:19
        at new Promise (<anonymous>)
        at executeSubscription (/Users/baristikir/tmp/giraphql-relay-subscription/node_modules/graphql/subscription/subscribe.js:162:10)
        at /Users/baristikir/tmp/giraphql-relay-subscription/node_modules/graphql/subscription/subscribe.js:137:9
        at new Promise (<anonymous>)
        at createSourceEventStream (/Users/baristikir/tmp/giraphql-relay-subscription/node_modules/graphql/subscription/subscribe.js:130:10)
        at subscribeImpl (/Users/baristikir/tmp/giraphql-relay-subscription/node_modules/graphql/subscription/subscribe.js:69:23) {

Repository with minimal client-server setup to reproduce the issue:
https://github.com/baristikir/giraphql-relay-subscription

-> The websocket connection should close after connceting and requesting the subscription data with the same error above.

I'm currently using these versions:

{
	"dependencies": {
		"@giraphql/core": "^2.10.1",
		"@giraphql/plugin-relay": "^2.9.1",
		"@giraphql/plugin-scope-auth": "^2.5.1",
		"@giraphql/plugin-simple-objects": "^2.4.3",
		"@giraphql/plugin-smart-subscriptions": "^2.3.3",
		"@giraphql/plugin-validation": "^2.6.2",
      }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.