Giter Site home page Giter Site logo

gqlify / gqlify Goto Github PK

View Code? Open in Web Editor NEW
178.0 13.0 16.0 677 KB

An API integration framework using GraphQL

Home Page: https://www.gqlify.com

License: Apache License 2.0

JavaScript 0.02% TypeScript 99.98%
graphql graphql-server graphql-client graphql-api mongodb firebase firestore

gqlify's Introduction

npm version Join the community on Spectrum Twitter Follow


Businesses usually involving a broad spectrum of applications, that aren't integrated and requiring human intervention at almost every step of the process this lack of integration and automation. GQLify make API integration simple and easy.

Why GQLify: https://www.gqlify.com/docs/why-gqlify

FAQ - Compare to Prisma: https://www.gqlify.com/docs/vs-prisma

Installation

yarn add @gqlify/server

Demo

Edit GQLify Server @welcome page

Features

Supported data-sources

Documentation

👉 Full documentation

Join community

Join the community on Spectrum

License

Apache-2.0

footer banner

gqlify's People

Contributors

chilijung avatar frankyang0529 avatar jonesnc avatar wwwy3y3 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  avatar

gqlify's Issues

Support changing the payload when updating.

Is your feature request related to a problem? Please describe.
A common case is the field updatedAt which should be updated automatically.

Describe the solution you'd like
Maybe we can have a key called resolvers to transform the payload before the API is sent.

const gqlify = new Gqlify({
  // provide datamodel to gqlify
  sdl,

  // provide data-sources map to GQLify,
  // so GQLify would know how to create data-source for each model
  dataSources: {
    memory: args => new MemoryDataSource(defaultData[args.key]),
  },
  resolvers: {
    posts: payload => {...payload, updatedAt: (new Date()).toString()}
  }
});

Subscriptions?

Just wondering how this lib would work with graphql subscriptions?

I haven't tried it myself yet, but it seems from #13 that it may not given that issue seems to mention whitelisting server middlewares.

If you could shine some light on this that would be great

Hook in mutations

Goal

  • developers can implement business logic before/after data creation, or update the payload.

Use case

Send email after creation

type User @afterCreate(name: "mailgunSend") {
  # fields 
}

What we need

We need a mechanism to let developers create customized hook and pass to GQLify.

Performance improvement with memoize

Goal

  • Improve graphql performance by adding a memoize function to data-source query methods

Why

There will be lots of duplicated requests sent to data-source in one graphql if lots of same relationships are shared between records.

Gqlify Goal

Gqlify goal

Make Graphql easy to connect to different API interfaces.

Architecture

The architecture should be separated into two parts: Graphql auto-generate & Resolver

Graphql Auto-generate

screen shot 2018-10-15 at 12 11 31 pm

Graphql Resolver

screen shot 2018-10-15 at 12 19 32 pm

Technical background

Gqlify Model

Gqlify Model is a representation of the data model (fields, types...)

interface Field {
  name: string;
  type: GraphqlType;
  nonNull: boolean;
  list: boolean;
  nonNullItem: boolean;
  unique: boolean;
  readOnly: boolean;
}

For example:

type Article {
  id: ID! @unique
  title: String!
}

can be represented as:

ArticleModel<{
 fields: [{
   name: "id",
   type: GraphqlTypes.ID,
   nonNull: true,
   unique: true
 }, {
   name: "title",
   type: GraphqlTypes.STRING,
   nonNull: true
 }] 
}>

Plugin interface

A plugin is responsible for generating graphql and also create resolvers for apollo-server.

interface Plugin {
  // auto-generate graphql
  buildGraphql(graphqlAst);

  // resolvers
  resolveInQuery();
  resolveInMutation();
  resolveInRoot();
}

API interface Technical Background

API interface should be capable of dealing with the following issues:

  • Rate limit
  • Error Handling
  • Circuit breaker
  • Caching and batching requests
  • Retrying strategy: exponential backoff, timeouts

Security Features

Is your feature request related to a problem? Please describe.
Aside from graphql-rbac what other security features are you planning on implementing via Directives?

I recently discovered graphql-rate-limit which is interesting and supports custom data sources for the tracking data for the current rate/req.

Are there other or similar considerations in mind?

Edit: here are some considerations https://blog.apollographql.com/securing-your-graphql-api-from-malicious-queries-16130a324a6b

I can't seem to find the docs to contribute

Is your feature request related to a problem? Please describe.
I can't find the gqlify docs

Describe the solution you'd like
I would like to contribute to the docs but cannot find the source

Describe alternatives you've considered
Checked in the Canner org repos

Support more operators

Is your feature request related to a problem? Please describe.
We should support more operator like comparator for Int, contains for string.

Ref: https://www.opencrud.org/#sec-Data-types

Describe the solution you'd like
Based on the field type, we create a different comparator for it.
Ref: https://github.com/Canner/gqlify/blob/master/packages/gqlify/src/plugins/whereInput.ts#L91-L111

With more and more operators on api, data-source without too much API field might have some problem dealing with it.

Additional context
We should also think about how comparator should be added for custom scalar like DateTime.

Relationship customization

Goal

  • developers should be able to customize the foreign key name
  • developers should be able to decide how to save the relationship in data-source
  • developers should be able to override relation implementation

Encapsulate payload in update passed to dataSource

Is your feature request related to a problem? Please describe.
For issues below:

  • #34: Atomic operation on nested array in GraphQL API
  • #35: Support firestore.FieldValue.serverTimestamp()

Also for future possible features like:

  • Increase/decrease a field value
  • update nested object field without overriding the whole object field

Describe the solution you'd like
We should somehow encapsulate the payload that passed to create/update methods in dataSource.

Implementaion detail

enum ArrayOperator {
  set = 'set',
  add = 'add',
  remove = 'remove',
}

interface ArrayUpdateOperation {
  fieldName: string;
  operator:  ArrayOperator
}

interface Mutation {
  getData(): Record<string, any>;
  getArrayOperations(): ArrayUpdateOperation[];
}

interface ListMutable {
  // origin:
  // update(where: Where, payload: any): Promise<any>;
  // new:
  update(where: Where, mutation: Mutation): Promise<any>;
}

API

getData(): Record<string, any>

Should directly replace the original values with payload got from getData()

getArrayOperations(): ArrayUpdateOperation[]

getArrayOperations() will provide atomic operations like add, set, remove to dataSource

Remove some mutations or queries for a specific model

Describe the solution you'd like
Remove some mutations or queries for a specific model

Describe alternatives you've considered
Maybe add directives to model like

type User @queryOnly {
  name: String
}

and mutation plugins will know that it's a queryOnly model, hence skip mutation generation.

graphql-i18n middleware

User story

  • User can specify which adapter stores i18n. (MongoDB, MySQL, Firebase, ...)
  • User can specify which type and column need to use i18n.
  • User can specify default language.
  • User can CRUD entries in i18n.
  • User can use multiple way to specify which language should be retrieved. (Header, GraphQL Decorator)

Design

declare class II18n {
  private adapter;
  constructor({
    adapter,
    schema,
    default
  }: {
    adapter: IAdapterParam,
    schema: Record<string, string[]>,
    default: string
  });

  private setupAdapter(adapter: IAdapterParam): void;
  private setupSchema(schema: Record<string, string[]>): void;

  public sync(where: { id: string, type: string }, data: Record<string, string>, language: string): Promise<void>;
  public find(where: { ids: string[], type: string }, language: string): Promise<Record<string, any>>;
  public findOne(where: { id: string, type: string }, language: string): Promise<any>;
  public destroy(where: { id: string, type: string }, language: string): Promise<void>;
}

interface IAdapterParam {
  type: string,
  uri?: string
}

declare class IAdapter {
  constructor(adapter: IAdapterParam);

  public create(where: { id: string, type: string }, data: Record<string, string>, language: string): Promise<any>;
  public update(where: { id: string, type: string }, data: Record<string, string>, language: string): Promise<any>;
  public find(where: { ids: string[], type: string }, language: string): Promise<any[]>;
  public findOne(where: { id: string, type: string }, language: string): Promise<any>;
  public destroy(where: { id: string, type: string }, language: string): Promise<void>;
  private isTableExistent(table: string): Promise<Boolean>;
}

Use case

import { I18n } from 'graphql-i18n';

const adapter = {
  type: 'memory'
}

const schema = {
  User: ['name'],
  Book: ['name', 'author.name']
}

const i18n = new I18n({adapter, schema, default: 'en'})

// sync user
i18n.sync({ id: '1', type: 'User' }, { name: 'Tom' }, 'en')
i18n.sync({ id: '1', type: 'User' }, { name: '湯姆' }, 'zh')
i18n.sync({ id: '2', type: 'User' }, { name: 'John' }, 'en')
i18n.sync({ id: '2', type: 'User'}, { name: '約翰' }, 'zh')

i18n.sync({ id: '1', type: 'Book' }, { name: 'Fooled by Randomness', author: { name: 'Nassim Nicholas Taleb' }}, 'en')
i18n.sync({ id: '1', type: 'Book' }, { name: '隨機騙局', author: { name: '納西姆‧尼可拉斯‧塔雷伯' }}, 'zh')

// find one
i18n.findOne({ id: '1', type: 'User' }, 'zh') // { name: '湯姆' }
i18n.findOne({ id: '1', type: 'Book'}, 'en') // { name: 'Fooled by Randomness', author: { name: 'Nassim Nicholas Taleb' }}

// find
i18n.find({ ids: ['1', '2'], type: 'User' }, 'zh') // { 1: { name: '湯姆' }, 2: { name: '約翰' } }

// destroy user
i18n.destroy('1', fields: ['name'], 'zh')

Filter records with custom scalar

Describe the solution you'd like
How should GQLify deal with filtering with custom scalar? like DateTime.

Describe alternatives you've considered
Maybe data-source should provide some ways to serialize the value from our resolver to their own api interfaces. For DateTime, GQLify resolver might pass them ISO8601 or Date, and we need to make sure data-source understand the format and know how to transfer from our type to theirs.

RBAC middleware

User story

  • User can define role.
  • User can restrict which role has API access permission.
  • User can restrict which role has read permission for a field.

Schema

enum Role @RBAC {
  ADMIN
  DEVELOPER
}

type User 
    @protect(permissions: {
      ADMIN: ["mutation.create", "query", "mutation.update", "mutation.delete"],
      DEVELOPER: ["mutation.create", "query", "mutation.update"]
    })
{
  id: ID!
  name: String!
  role: Role!
  secretKey: String! @protectField(roles: [ADMIN])
}

Implement

import RBAC from 'graphql-rbac'

const roles = ['ADMIN', 'DEVELOPER'];

const schema = {
  Query: {
    user: ["ADMIN", "DEVELOPER"]
  },
  Mutation: {
    createUser: ["ADMIN", "DEVELOPER"],
    updateUser: ["ADMIN", "DEVELOPER"],
    deleteUser: ["ADMIN"]
  },
  User: {
    secretKey: ["ADMIN"]
  }
}

const getUser = async (ctx) => {
  // get user
  // ...
  return {
    role: user.role // must require
  }
}

const rbac = new RBAC({roles, schema, getUser})

const server = new GraphQLServer({
  // typeDefs,
  // resolvers,
  middlewares: [rbac.middleware()],
  context: req => ({
    ...rbac.context(req)
  }),
})

Datamodel types without scalar fields break GQLify generation

Describe the bug
When creating a data model type without any scalar fields (ie. only relation fields), code generation breaks.

To Reproduce
Steps to reproduce the behavior:

  1. Create a data model type without scalar fields, eg:
type Foo {
  bar: Bar
}
  1. Run generator
  2. See error
return new _GraphQLError.GraphQLError(
       ^
Syntax Error: Expected Name, found }

GraphQL request (1:37)
1: input UserSubscriptionsCreateInput {}

Expected behavior
No error when running the generation.

Desktop

  • OS: OS X
  • OS version: 10.14.3 Beta (18D32a)
  • GQLify package versions: @gqlify/firestore: 3.1.0, @gqlify/server: 3.1.2,

Additional context
This is exactly the same issue that is happening with Prisma on this issue, so I'm kinda copy/pasting it here.

latest graphql causes error again!

Describe the bug
when try quickstart in codesandbox,
the latest dependency graphql 14.2.1 throws the following error:

/sandbox/node_modules/@gqlify/server/lib/rootNode.js:247
        this.defBuilder._typeDefinitionsMap[name] = typeDefNode;
                                                  ^

TypeError: Cannot set property 'PageInfo' of undefined
    at RootNode.buildObjectType (/sandbox/node_modules/@gqlify/server/lib/rootNode.js:247:51)
    at RootNode.addObjectType (/sandbox/node_modules/@gqlify/server/lib/rootNode.js:93:23)
    at RelayPlugin.init (/sandbox/node_modules/@gqlify/server/lib/plugins/relay.js:58:14)
    at /sandbox/node_modules/@gqlify/server/lib/generator.js:22:24
    at Array.forEach (<anonymous>)
    at Generator.generate (/sandbox/node_modules/@gqlify/server/lib/generator.js:17:22)
    at Gqlify.createServerConfig (/sandbox/node_modules/@gqlify/server/lib/gqlify.js:107:34)
    at Gqlify.createApolloConfig (/sandbox/node_modules/@gqlify/server/lib/gqlify.js:116:33)
    at Object.<anonymous> (/sandbox/index.js:28:40)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
[nodemon] app crashed - waiting for file changes before starting...

To Reproduce
run on codesadgbox

Expected behavior
run as normal

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • Browser chrome

Additional context
explicit downgrade to graphql 14.1.1 fixed the problem

Delete cascade

Is your feature request related to a problem? Please describe.
Delete mutation should have cascade feature.

Describe the solution you'd like
When deleting a record, the relationship data at other places (relation table, foreign key) should be deleted as well.

Support firestore.FieldValue.serverTimestamp()

Is your feature request related to a problem? Please describe.
We should provide a way to let developers annotate a field that tells GQLify to put firestore.FieldValue.serverTimestamp() in it when creating data.

Describe the solution you'd like

type Book @GQLifyModel(dataSource: "firestore", key: "Book") {
  id: ID! @unique @autoGen
  name: String!
  time: DateTime @autoGen(value: SERVER_TIMESTAMP)
}

GraphQL middleware integration

Goal

  • to support graphql-18n & graphql-rbac

What we need?

  • Plugin will need to know the directives on SDL
  • A mechanism to pass generated queries/mutations to middleware

.

.

Data-source directives

Goal

  • DataSource instance can understand directives in datamodel

Use case

@firebase_map

type user @GQLifyModel {
  tags: [String!]! @firebase_map(value: true)
}

It will save tags in Map<string, boolean> instead of string[]

How to make a many-to-many relation in default data for memory datasource

Describe the bug
Hi!! I'm doing a demo of GQLify and I can't find an example of how to represent a many-to-many relation in the default data for a memory datasource. I have tried with the following options without luck:

To Reproduce

demo.graphql

enum Language {
  en_US,
  es_MX,
  de_DE
}
type User @GQLifyModel(dataSource: "memory", key: "users") {
  id: ID! @unique @autoGen
  username: String!
  email: String!
  preferedLanguage: Language!
  pokemons: [Pokemon!]!
  books: [Book!]!
}
type Book @GQLifyModel(dataSource: "memory", key: "books") {
  id: ID! @unique @autoGen
  name: String!
  author: User!
}
type Pokemon @GQLifyModel(dataSource: "memory", key: "pokemons") {
  id: ID! @unique
  name: String!
  base_experience: Int!
  order: Int!
  types: [String]
  moves: [String]
  owners: [User]
}

index.js

Option 1:

const defaultData = {
  users: [
    {id: '1', username: 'Alice', email: '[email protected]'},
    {id: '2', username: 'Bob', email: '[email protected]'},
  ],
  books: [
    {id: '1', name: 'book1', userId: '1'},
    {id: '2', name: 'book2', userId: '2'},
  ],
  pokemons: [
    {id: '1', name: 'bulbasaur', base_experience: 64, order: 1, types: ['poison', 'grass'], moves: ['razor-wind', 'swords-dance', 'vine-whip'], owners: ['2']}
  ],
};

Option 2:

const defaultData = {
  users: [
    {id: '1', username: 'Alice', email: '[email protected]'},
    {id: '2', username: 'Bob', email: '[email protected]'},
  ],
  books: [
    {id: '1', name: 'book1', userId: '1'},
    {id: '2', name: 'book2', userId: '2'},
  ],
  pokemons: [
    {id: '1', name: 'bulbasaur', base_experience: 64, order: 1, types: ['poison', 'grass'], moves: ['razor-wind', 'swords-dance', 'vine-whip'], userIds: ['2']}
  ],
};

Option 3:

const defaultData = {
  users: [
    {id: '1', username: 'Alice', email: '[email protected]', pokemons:[]},
    {id: '2', username: 'Bob', email: '[email protected]', pokemons:['1']},
  ],
  books: [
    {id: '1', name: 'book1', userId: '1'},
    {id: '2', name: 'book2', userId: '2'},
  ],
  pokemons: [
    {id: '1', name: 'bulbasaur', base_experience: 64, order: 1, types: ['poison', 'grass'], moves: ['razor-wind', 'swords-dance', 'vine-whip'], owners: ['2']}
  ],
};

Option 4:

const defaultData = {
  users: [
    {id: '1', username: 'Alice', email: '[email protected]', pokemonIds:[]},
    {id: '2', username: 'Bob', email: '[email protected]', pokemonIds:['1']},
  ],
  books: [
    {id: '1', name: 'book1', userId: '1'},
    {id: '2', name: 'book2', userId: '2'},
  ],
  pokemons: [
    {id: '1', name: 'bulbasaur', base_experience: 64, order: 1, types: ['poison', 'grass'], moves: ['razor-wind', 'swords-dance', 'vine-whip'], userIds: ['2']}
  ],
};

Error:

Query

query PokemonMasters{
  users{
    username
    pokemons{
      name
    }
  }
}

Result

{
  "data": {
    "users": [
      {
        "username": "Alice",
        "pokemons": []
      },
      {
        "username": "Bob",
        "pokemons": []
      }
    ]
  }
}

Expected behavior

Query

query PokemonMasters{
  users{
    username
    pokemons{
      name
      types
    }
  }
}

Result

{
  "data": {
    "users": [
      {
        "username": "Alice",
        "pokemons": []
      },
      {
        "username": "Bob",
        "pokemons": ["bulbasaur"]
      }
    ]
  }
}

Desktop (please complete the following information):

  • OS: OSX 10.14.4 (Mojave)
  • Browser: Chrome 73.0.3683.86
  • Version: GQLify 3.2.1 & graphql 14.1.1

Additional context
Memory datasource

Support Firestore sub-collections

Is your feature request related to a problem? Please describe.
It is unclear at this time how to use firestore subcollections with @gqlify/firestore.

Describe the solution you'd like
I imagine creating the type like so:

type Book @GQLifyModel(dataSource: "firestore", key: "books") {
  id: ID! @unique @autoGen
  title: String!
  author: Author!
}

type Author @GQLifyModel(dataSource: "firestore", key: "books/*/authors") {
  id: ID! @unique @autoGen
  name: String!
}

and then updating or creating a field requires a where params list like so:

type AuthorWhereUniqueInupt {
  bookId: ID!
  authorId: ID!
}

or could we use a Firestore sub-collection specific where param where the id field is the document path? Eg:

type AuthorWhereUniqueInupt {
  parentPath: String!
  authorId: ID!
}

where the parentPath is more like the current path usage in Firestore SDK:

variables = {
  parentPath: `books/${someBookId}`
}

Issues with this solution:

  • how do we query sub-collections?
  • what syntax do we use to represent the document in the collection/document/collection path key? That is, does books/*/authors work well enough?

Describe alternatives you've considered
Using root-level Firestore collections is a viable alternative since relationships/foreign-keys are already supported. There are performance implications with doing so though where sub-collections would be desirable.

Additional context
Just starting a discussion of the idea 😄

add relation tests

Goal

  • add one-to-many test suite
  • add many-to-many test suite

Improvement

  • See if we can separate a package to let developer import in their own tests
import {createTest} from '@gqlify/test-suite';

Many-Many relation

Describe the bug
Hello guys, I'm currently developing a new data source for DynamoDB and I encountered a barrier which seems to also happens to other data sources. (I've tested with memory and firebase realtime)

When dealing with many-to-many relations the hook that calls the method addIdToManyRelation it is not providing the correct atributes, at lest of of the parameters are always undefined, such as sourceSideId or targetSideId and by that I'm not able to create a many-many scenario.

Is that already mapped at another issue? If so, I'm sorry for reporting that again. 🤔

To Reproduce
Steps to reproduce the behavior:

// model.graphql
type Book @GQLifyModel(dataSource: "firebase", key: "Book") {
  id: ID! @unique @autoGen
  name: String!
  categories: [Category]
}


type Category @GQLifyModel(dataSource: "firebase", key: "Category") {
  id: ID! @unique @autoGen
  name: String!
  books: [Book]
}

Than using either a createBook or updateBook with the create action at the categories that happens.

Expected behavior
1 - It should save in a intermediary table (or 2), with the names in lower case and a _ in between the table names.
2 - It should call this method with all the 4 parameters correctly

Additional context
I'm really in love with this project and I'm planing to create a couple more data sources, if you guys need help at the project please shoot me and I'll do my best to contribute to this project!!

(The only missing key from the DynamoDB data source is the many-many relation =S)

Latest graphql causes error

Describe the bug
the auto-installed dependency graphql 14.1.1 throws the following error:

log
firebase-gcp-examples/fb-functions-graphql/gqlify/api on  graphql-examples [?] is 📦 v1.0.0 via ⬢ v8.15.0 took 30s
➜ node src/index.js
Starting Gqlify...

  - Gqlify Models
    - Model User (generated from 'User')
    Field: id `ID!`
    Field: username `String!`
    Field: email `String`
    Field: books `[Book!]!` @relation(name: UserAndBookOnbooks)


    - Model Book (generated from 'Book')
    Field: id `ID!`
    Field: name `String!`
    Field: author `User!` @relation(name: UserAndBookOnbooks)


  - Relations
    Relation UserAndBookOnbooks
      * Type: Bidirectional One-to-Many
      * Relationship: 1-* on `User`-`Book`


/home/jthegedus/projects/firebase-gcp-examples/fb-functions-graphql/gqlify/api/node_modules/graphql/type/schema.js:290
          throw _iteratorError3;
          ^

TypeError: _this.defBuilder._makeInputValues is not a function
    at fields (/home/jthegedus/projects/firebase-gcp-examples/fb-functions-graphql/gqlify/api/node_modules/@gqlify/server/lib/rootNode.js:172:72)
    at resolveThunk (/home/jthegedus/projects/firebase-gcp-examples/fb-functions-graphql/gqlify/api/node_modules/graphql/type/definition.js:374:40)
    at defineInputFieldMap (/home/jthegedus/projects/firebase-gcp-examples/fb-functions-graphql/gqlify/api/node_modules/graphql/type/definition.js:857:18)
    at GraphQLInputObjectType.getFields (/home/jthegedus/projects/firebase-gcp-examples/fb-functions-graphql/gqlify/api/node_modules/graphql/type/definition.js:838:27)
    at typeMapReducer (/home/jthegedus/projects/firebase-gcp-examples/fb-functions-graphql/gqlify/api/node_modules/graphql/type/schema.js:302:61)
    at typeMapReducer (/home/jthegedus/projects/firebase-gcp-examples/fb-functions-graphql/gqlify/api/node_modules/graphql/type/schema.js:243:12)
    at Array.reduce (<anonymous>)
    at typeMapReducer (/home/jthegedus/projects/firebase-gcp-examples/fb-functions-graphql/gqlify/api/node_modules/graphql/type/schema.js:275:38)
    at Array.reduce (<anonymous>)
    at new GraphQLSchema (/home/jthegedus/projects/firebase-gcp-examples/fb-functions-graphql/gqlify/api/node_modules/graphql/type/schema.js:108:28)
Explicitly installing [email protected] solves the issue.

To Reproduce
attempt to use the quickstart locally.

Expected behavior
It should not throw this schema error.

Desktop (please complete the following information):

  • OS: Ubuntu 18.10

Additional context
It seems the changes in 14.1.0 about schema assertion may be the cause:
https://github.com/graphql/graphql-js/releases/tag/v14.1.0

Potentially related: graphql/graphql-js#1668

@unique dosen't work when I use it in another attibut

I create this schema:

export const Article = `
type Article @GQLifyModel(dataSource: "mongodb", key: "articles"){ 
       url: ID! @unique 
       title: String! 
       img: String 
       content: String 
       post: Boolean 
}; 

but when I create a new article with the a just existent url, the mutation pass and create a new registry with a same url. @unique dosen't work?

TypeError: Cannot set property 'PageInfo' of undefined

Describe the bug
Cannot start the server because of RelayPlugin

To Reproduce

  1. Create a fresh new app as the quick start
mkdir gqlify-demo
cd gqlify-demo
yarn init -y
yarn add @gqlify/server graphql apollo-server
  1. Create demo.graphql and index.js file follow the quick start guide
  2. Start the server by running node index.js
  3. Error occurred
Starting Gqlify...

  - Gqlify Models
    - Model User (generated from 'User')
    Field: id `ID!`
    Field: username `String!`
    Field: email `String`
    Field: books `[Book!]!` @relation(name: UserAndBookOnbooks)


    - Model Book (generated from 'Book')
    Field: id `ID!`
    Field: name `String!`
    Field: author `User!` @relation(name: UserAndBookOnbooks)


  - Relations
    Relation UserAndBookOnbooks
      * Type: Bidirectional One-to-Many
      * Relationship: 1-* on `User`-`Book`


/workspace/gqlify-demo/node_modules/@gqlify/server/lib/rootNode.js:247
        this.defBuilder._typeDefinitionsMap[name] = typeDefNode;
                                                  ^

TypeError: Cannot set property 'PageInfo' of undefined
    at RootNode.buildObjectType (/workspace/gqlify-demo/node_modules/@gqlify/server/lib/rootNode.js:247:51)
    at RootNode.addObjectType (/workspace/gqlify-demo/node_modules/@gqlify/server/lib/rootNode.js:93:23)
    at RelayPlugin.init (/workspace/gqlify-demo/node_modules/@gqlify/server/lib/plugins/relay.js:58:14)
    at /workspace/gqlify-demo/node_modules/@gqlify/server/lib/generator.js:22:24
    at Array.forEach (<anonymous>)
    at Generator.generate (/workspace/gqlify-demo/node_modules/@gqlify/server/lib/generator.js:17:22)
    at Gqlify.createServerConfig (/workspace/gqlify-demo/node_modules/@gqlify/server/lib/gqlify.js:107:34)
    at Gqlify.createApolloConfig (/workspace/gqlify-demo/node_modules/@gqlify/server/lib/gqlify.js:116:33)
    at Object.<anonymous> (/workspace/gqlify-demo/index.js:33:40)
    at Module._compile (module.js:653:30)

Expected behavior
Server start normally

Screenshots

  • package.json
{
  "name": "gqlify-demo",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "@gqlify/server": "^3.2.1",
    "apollo-server": "^2.4.8",
    "graphql": "^14.2.1"
  }
}

Desktop (please complete the following information):

  • OS: [e.g. iOS] Docker image node:8
  • Browser [e.g. chrome, safari]
  • Version [e.g. 22] latest

Additional context

Data model relationship generation

Describe the bug
A Type containing multiple fields that contain relationships on the Data Model create a single expected Input Type in all the fields of said Type, as below:

To Reproduce
Steps to reproduce the behavior:

  1. Given the following data model:
  type UserContact {
    email: String
    telephone: String
  }

  type UserSession {
    session_start_timestamp: Float
    session_end_timestamp: Float
  }

  type Article @GQLifyModel(dataSource: "firestore", key: "articles") {
    id: ID! @unique @autoGen
    title: String
    content: String
  }

  type Video @GQLifyModel(dataSource: "firestore", key: "videos") {
    id: ID! @unique @autoGen
    title: String
    url: String
  }

  type User @GQLifyModel(dataSource: "firestore", key: "users") {
    id: ID! @unique @autoGen
    user_contact: UserContact!
    user_sessions: [UserSession!]!
    bookmarked_articles: [Article!]! @relation(name: "UserOnArticles")
    bookmarked_videos: [Video!]! @relation(name: "UserOnVideos")
  }
  1. Open the schema generated by GQLify
  2. Observe that the inputFields of both bookmarked_articles and bookmarked_videos refer to UserCreateManyInput
{
    "name": "bookmarked_articles",
    "description": "",
    "type": {
      "kind": "INPUT_OBJECT",
      "name": "UserCreateManyInput",
      "ofType": null
    },
    "defaultValue": null
  }
{
    "name": "bookmarked_videos",
    "description": "",
    "type": {
      "kind": "INPUT_OBJECT",
      "name": "UserCreateManyInput",
      "ofType": null
    },
    "defaultValue": null
  }
  1. Look at UserCreateManyInput and observe the inputFields of create and connect
{
  "kind": "INPUT_OBJECT",
  "name": "UserCreateManyInput",
  "description": "",
  "fields": null,
  "inputFields": [
    {
      "name": "create",
      "description": "",
      "type": {
        "kind": "LIST",
        "name": null,
        "ofType": {
          "kind": "INPUT_OBJECT",
          "name": "VideoCreateInput",
          "ofType": null
        }
      },
      "defaultValue": null
    },
    {
      "name": "connect",
      "description": "",
      "type": {
        "kind": "LIST",
        "name": null,
        "ofType": {
          "kind": "INPUT_OBJECT",
          "name": "VideoWhereUniqueInput",
          "ofType": null
        }
      },
      "defaultValue": null
    }
  ],
  "interfaces": null,
  "enumValues": null,
  "possibleTypes": null
}
  1. They refer, respectively, to VideoCreateInput and VideoWhereUniqueInput.

Expected behavior
Each relationship should create a unique inputField value, e.g. UserCreateManyArticlesInput and UserCreateManyVideosInput, where each refer to the correct input shape.

Desktop (please complete the following information):

  • OS: OSX
  • Version 10.14.2

Additional context
No matter how many field relationships you have above 1, the Input Object referred to by UserCreateManyInput is always the last field.
In the example above, if I were to add a third relation field e.g. bookmarked_xs, the UserCreateManyInput would then start referring to XCreateInput and XWhereUniqueInput.

Firestore create mutation gives me "is not a valid Document" error

Describe the bug
My GraphQL Schema looks like this with FirebaseDataSource set up.

type User @GQLifyModel(dataSource: "firestore", key: "users") {
  id: ID! @unique @autoGen # auto generate unique id
  username: String!
  email: String
}

Here's my index.js file:

const { Gqlify } = require('@gqlify/server');
const { ApolloServer } = require('apollo-server');
const { readFileSync } = require('fs');
const { FirestoreDataSource } = require('@gqlify/firestore');
const cert = require('./gqlify-firebase-adminsdk-a22pq-f37440b45b.json');
const databaseUrl = 'https://gqlify.firebaseio.com';

// Read datamodel
const sdl = readFileSync(__dirname + '/demo.graphql', { encoding: 'utf8' });

// construct gqlify
const gqlify = new Gqlify({
    sdl,

    dataSources: {
        firestore: args => new FirestoreDataSource(cert, databaseUrl, args.key)
    }
});

// GQLify will provide GraphQL apis & resolvers to apollo-server
const server = new ApolloServer(gqlify.createApolloConfig());

// start server
server.listen().then(({ url }) => {
    console.log(`🚀 Server ready at ${url}`);
});

When I try to run a createUser migration with:

mutation {
  createUser(
    data: {
      username: "newuser",
      email: "[email protected]"
    }) {
      id
    }
}

Gives me this error:

{
  "data": {
    "createUser": null
  },
  "errors": [
    {
      "message": "Argument \"data\" is not a valid Document. obj.hasOwnProperty is not a function",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "createUser"
      ],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR",
        "exception": {
          "stacktrace": [
            "Error: Argument \"data\" is not a valid Document. obj.hasOwnProperty is not a function",
            "    at Validator.(anonymous function).values [as isDocument] (/sandbox/node_modules/@google-cloud/firestore/build/src/validate.js:92:27)",
            "    at CollectionReference.add (/sandbox/node_modules/@google-cloud/firestore/build/src/reference.js:1680:25)",
            "    at FirestoreDataSource.<anonymous> (/sandbox/node_modules/@gqlify/firestore/lib/index.js:129:70)",
            "    at step (/sandbox/node_modules/@gqlify/firestore/lib/index.js:32:23)",
            "    at Object.next (/sandbox/node_modules/@gqlify/firestore/lib/index.js:13:53)",
            "    at /sandbox/node_modules/@gqlify/firestore/lib/index.js:7:71",
            "    at new Promise (<anonymous>)",
            "    at __awaiter (/sandbox/node_modules/@gqlify/firestore/lib/index.js:3:12)",
            "    at FirestoreDataSource.create (/sandbox/node_modules/@gqlify/firestore/lib/index.js:125:16)",
            "    at CreatePlugin.<anonymous> (/sandbox/node_modules/@gqlify/server/lib/plugins/create.js:137:55)"
          ]
        }
      }
    }
  ]
}

The obj.hasOwnProperty is not a function error may be due to graphql's usage of Object.create(null) when constructing args. See: graphql/express-graphql#177 (comment)

To Reproduce
Steps to reproduce the behavior:
2. Click on the Execute Query button in the GraphQL Playground window
3. See error

Expected behavior
A new users document to be created.

Versions:

"@gqlify/firestore": "^1.0.10",
"@gqlify/server": "^1.0.10",
"apollo-server": "^2.3.1"

Relation foreign key field name duplicate

Describe the bug
When a model has several relation fields, there might be chances that foreign key field name duplicated.

To Reproduce
Construct a model with same to-many relation fields to another model.

Expected behavior
Foreign key fields should be unique across relationships.

Version

  • GQLify v1.0.6

implement apollo-server-lambda with MongoDataSource

I use AWS Lambda serverless for my graphql apis, but now I want to use GQLify, it's great, and in this days I have been implement apollo-lambda-server whit GQLify, but I have not managed to do it.

This is my serverless-main.ts configuration without GQLfy

// GRAPHQL LAMBDA SERVER CONFIG
import {ApolloServer} from 'apollo-server-lambda';
import {typeDefs} from './schemas/schema';
import {resolvers} from './resolvers';
import {formatError} from 'apollo-errors';

const server = new ApolloServer({
    typeDefs,
    resolvers,
    formatError,
});

export const graphqlHandler = server.createHandler({
    cors: {
        origin: '*',
        credentials: false,
    },
});

if you can help me, I would appreciate it

use @gqlify/firestore without cert & dbUrl

Is your feature request related to a problem? Please describe.
I am using GQLify in a Firebase Cloud Function and would like to avoid uploading and storing my service account json file.

Describe the solution you'd like
When in a Google secure environment, like Cloud Functions, you are not required to provide cert: admin.ServiceAccount or dbUrl: string to admin.initializeApp().

I would like to change the constructor of the firestore plugin to check for
process.env.FIREBASE_CONFIG to see if it's in a Cloud Function environment and call initializeApp() without the params, otherwise, continue as it does now.

  constructor(cert: admin.ServiceAccount, dbUrl: string, path: string) {
    this.db = isEmpty(admin.apps)
      ? admin.initializeApp({
        credential: admin.credential.cert(cert),
        databaseURL: dbUrl,
      }).firestore()
      : admin.app().firestore();
    this.path = path;
  }

would become something like this

  constructor(cert: admin.ServiceAccount, dbUrl: string, path: string) {
	this.db = isEmpty(admin.apps)
	  ? process.env.FIREBASE_CONFIG
	    ? admin.initializeApp().firestore()
	    : admin
	        .initializeApp({
	          credential: admin.credential.cert(cert),
	          databaseURL: dbUrl
	        })
	        .firestore()
	  : admin.app().firestore();
    this.path = path;
  }

and I when using inside a Cloud Function it could be called like so:

const gqlify = new Gqlify({
    sdl,
    dataSources: {
      firestore: args => new FirestoreDataSource(null, null, args.key),
    },
    skipPrint: true,
  });

using null is not ideal, but it's backwards compatible.

Additional Information

The information about env vars, see here - https://firebase.google.com/docs/functions/config-env#automatically_populated_environment_variables

About initializing the SDK using the Google Application Default Credentials, see here - https://firebase.google.com/docs/admin/setup#initialize_the_sdk

most importantly:

The Admin SDKs can alternatively be authenticated with a different credential type. For example, if you are running your code within Google Cloud Platform, you can make use of Google Application Default Credentials to have the Admin SDKs themselves fetch a service account on your behalf:

The SDK can also be initialized with no parameters. In this case, the SDK uses Google Application Default Credentials...

I can create a PR for this tomorrow myself, was just curious if there was any other input for a better way to achieve the same outcome.

Thanks

Possibility to use other field as primary key

Describe the solution you'd like
For example:

type User @GQLifyModel(dataSource: "firestore", key: "employees") {
  email: String! @unique
  name: String!
}

email field as primary key instead of firestore generated id.

Additional context

  • Does it affect relationship implementation?
  • On the other hand, do we allow composite primary keys?

graphql auto-gen prior art

postgraphile

postgraphile seems to have some kind of plugins

prisma

cruddl

cruddl - create a cuddly GraphQL API for your database, using the GraphQL SDL to model your schema.
https://github.com/AEB-labs/cruddl

schema generation: https://github.com/AEB-labs/cruddl/tree/master/src/schema-generation

vulcanjs

Vulcan is a framework that gives you a set of tools for quickly building React & GraphQL-based web applications. Out of the box, it can handle data loading, automatically generate forms, handle email notifications, and much more.

type-graphql

Graphql generation with typescript
https://19majkel94.github.io/type-graphql/

Atomic operation on nested array in GraphQL API

Is your feature request related to a problem? Please describe.
We should provide atomic operation like add, remove, set operation for nested array type.

Describe the solution you'd like

type Tag {
  name: String
}

type Book @GQLifyModel(dataSource: "firebase", key: "Book") {
  id: ID! @unique @autoGen
  name: String!
  tags: [Tag]
}

Right now, we can only override the whole field with another array.

We should provide graphQL api like:

mutation {
  # add a new tag
  updateBook(where: {id: "1"}, data: {tags: {add: {name: "new tag"}}}) {
    id
  }
}

Additional context
There are things we need to consider, for example:

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.