GithubHelp home page GithubHelp logo

herbsjs / herbs2gql Goto Github PK

View Code? Open in Web Editor NEW
14.0 5.0 21.0 2.08 MB

Create a GraphQL endpoint based on Entities and Use Cases

License: MIT License

JavaScript 100.00%
herbsjs graphql apollo hacktoberfest

herbs2gql's Introduction

herbs2gql

​ herbs2gql creates GraphQL types based on herbs entities (gotu) and usecases (buchu), based on Apollo GraphQL. ​

Installing

$ npm install @herbsjs/herbs2gql ​

Using

​ All methods returns a string in GraphQL format representing the type based (gql) and a resolver (when expected). ​

Herbarium integration

If your project uses Herbarium as discovery service you can use herbs2gql with less code:

const { herbarium } = require('@herbsjs/herbarium')
const { herbs2gql } = require('@herbsjs/herbs2gql')

const { mutations, queries, types } = herbs2gql(herbarium)

GraphQL Type

​ To convert a Herbs Entity to GraphQL Type: ​

const { entity2type } = require('@herbsjs/herbs2gql')const entity = entity('User', {
    id: field(String),
    name: field(String),
    document: field(String),
    age: field(Number),
    active: field(Boolean),
})const gql = entity2type(entity)

GraphQL Input

​ To convert a Herbs Entity to GraphQL Input: ​

const { entity2input } = require('@herbsjs/herbs2gql')const entity = entity('UserFilter', {    
    name: field(String),    
    age: field(Number),    
})const gql = entity2input(entity)

GraphQL Query

​ To convert a Herbs Use Case to GraphQL Query: ​

const { usecase2query } = require('@herbsjs/herbs2gql')const usecase = usecase('Get User', {
    request: {
        id: Number,
        document: String
    },response: User
})const resolverFunc = (parent, args, context, info) => { }const [gql, resolver] = usecase2query(usecase, resolverFunc)

GraphQL Mutation

​ To convert a Herbs Use Case to GraphQL Mutation: ​

const { usecase2mutation } = require('@herbsjs/herbs2gql')const usecase = usecase('Update User', {
    request: {
        id: Number,
        name: String,
        age: Number,
        active: Boolean
    },response: User
})const resolverFunc = (parent, args, context, info) => { }const [gql, resolver] = usecase2mutation(usecase, resolverFunc)

GraphQL Subscription

​ To convert a Herbs Use Case to GraphQL Subscription: ​

const { usecase2subscription } = require('@herbsjs/herbs2gql')const usecase = usecase('New User Notification', {
    request: {
        id: Number,        
    },response: UserMessage
})const resolverFunc = () => { }const [gql, resolver] = usecase2subscription(usecase, resolverFunc)

GraphQL Resolvers

herbs2gql provides a generic resolver implementation for mutations and queries. ​

const { defaultResolver } = require('@herbsjs/herbs2gql')const updateUser = (injection) => usecase('Update User', {
    request: {
        id: Number,
        name: String,
        age: Number,
        active: Boolean
    },response: User
})const [gql, resolver] = usecase2mutation(updateUser(), defaultResolver(updateUser))

​ In case you need to implement your own resolver: ​

const usecase = usecase('Update User', {
    request: {
        id: Number,
        name: String,
        age: Number,
        active: Boolean
    },response: User
})const resolverFunc = (parent, args, context, info) => { }const [gql, resolver] = usecase2mutation(usecase, resolverFunc)

​ Or you can use herbs2gql defaultResolver implementation as a reference.

Error Handling

herbs2gql deals with errors in the default resolver. It translates the usecase's errors into graphql errors:

Usecase Error Apollo Error
Permission Denied ForbiddenError
Not Found ApolloError
Already Exists ApolloError
Unknown ApolloError
Invalid Arguments UserInputError
Invalid Entity UserInputError
Any other kind of errors UserInputError

However, it's behavior can be overridden in the errorHandler property of the options parameter:

const { defaultResolver } = require('@herbsjs/herbs2gql')

const myCustomErrorHandler = (usecaseResponse) => {
    // handle the errors on your own way
}

const options = {
    errorHandler: myCustomErrorHandler
}

const updateUser = usecase('Update User', {
    // usecase implementation
})

const [gql, resolver] = usecase2mutation(updateUser(), defaultResolver(updateUser, options))

Your custom error handler can also utilize the defaultErrorHandler as a fallback:

const { defaultResolver, defaultErrorHandler } = require('@herbsjs/herbs2gql')

const myCustomErrorHandler = (usecaseResponse) => {
    // handle the errors on your own way

    // use the default error handler when there is no need of a specific treatment
    return defaultErrorHandler(usecaseResponse)
}

const options = {
    errorHandler: myCustomErrorHandler
}

const updateUser = usecase('Update User', {
    // usecase implementation
})

const [gql, resolver] = usecase2mutation(updateUser(), defaultResolver(updateUser, options))

Custom Names or Conventions

In Herbs it is possible to include personalized names for queries, mutations, inputs and types custom names are always prioritized ​

Custom Names

const options = { inputName: 'An-Entity' }// for entity2input
const gql = entity2input(givenAnInput, options)// for entity2type
const gql = entity2type(givenAnEntity, options)//for mutation, query or subscription example using mutation
const [gql, resolver] = usecase2mutation(givenAnUseCase, resolverFunc, options)

Conventions

At the convention, a function must be sent, it must return a text formatted according to the sended convention

const options = { convention: { inputNameRule: (str) => `snake_case_returned` }}// for entity2input
const gql = entity2input(givenAnInput, options)// for entity2type
const gql = entity2type(givenAnEntity, options)//for mutation, query or subscription example using mutation
const [gql, resolver] = usecase2mutation(givenAnUseCase, resolverFunc, options)

Apollo Errors and Err

Herbs2gql translates Herbs Known Errors​ to Apollo Errors as described in the documentation.

Example

​ Additionally you can view a simple demo application of this library in todolist-on-herbs. ​

How to contribute

​ If you would like to help contribute to this repository, please see CONTRIBUTING

License

herbs2gql's People

Contributors

brspontes-vortx avatar dalssoft avatar dependabot[bot] avatar euduardo avatar italojs avatar jhomarolo avatar jhomarolo-vortx avatar m7vicente avatar maikmb avatar maikvortx avatar mtperesvx avatar pamellaas avatar pedromarquesfr avatar rayellyv avatar rodrigodosanjosoliveira avatar semantic-release-bot avatar vitorgamer58 avatar vx-nico avatar

Stargazers

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

Watchers

 avatar  avatar  avatar  avatar  avatar

herbs2gql's Issues

Issue with "requestFieldType2gql" function and GraphQL Input types

An issue has been identified with the function "requestFieldType2gql". This function takes an entity as a parameter, and if that entity extends BaseEntity, we append the string "Input" to the property type in GraphQL. For example:

mutation createUser(name: String, age: Number, Address: EntityAddressInput)

The relevant line of code can be found here.

However, when attempting to execute this operation, GraphQL fails to find the "Input" type, resulting in the following error:

Error: Unknown type "CustomerSubscriptionInput". Did you mean "CustomerSubscription"?

A preliminary solution could be to create Inputs for entities and also create an Input for request objects within usecases. However, this might not be the best approach, as we might end up creating Inputs that aren't necessary or that don't necessarily fit what's expected in a usecase's "request" field.

usecase('create user'
[...]
request: { 
 name: String
 age: Number,
 address: AddressEntity
}
[...]

For instance, in the usecase create user, where the request includes an entity-based field (e.g., address: AddressEntity), creating an Input for it solves the problem, but this Input would also include an "ID" field. This is a potential issue as we don't want the ID in the address field during user creation.

My proposed solution works for most scenarios, but not all. The suggestion is to proceed with this preliminary solution for the beta release and open up a discussion for a more definitive solution.

Return usecase errors in defaultResolver

Situation that defaultResolver did not meet my need

1

Recently I needed to return an custom error to my API requester but to do it I needed to implement my own resolver, the problem here is that my resolver is identical to defaultResolver, the unique difference is the error handler

2

I would like to return the error in my own pattern, e.g { message, code, stacktrace }, but i cant do it with defaultResolver because it forces to return a fix error format (result.err only)

Problem

In defaultResolver we are returning an userInputError every time the usecase return an Err, buuuuut my usecase can return an Err for many reasons that not includes the user input

defaultResolver.js

[...]
        const request = args2request(args, uc)
        const response = await uc.run(request)

        // eslint-disable-next-line no-console
        console.info(uc.auditTrail)

        /* I dont have another option to return an Err to my api-requester */
        if (response.isErr) throw new UserInputError(null, { invalidArgs: response.err })
        return response.ok
[...]

solution

defaultResolver.js

function defaultResolver(usecase, errorHandler) {
[...]
        const request = args2request(args, uc)
        const response = await uc.run(request)

        // eslint-disable-next-line no-console
        console.info(uc.auditTrail)

        /* using the await because if this function be async, we dont will problem with  */
        if(response.isErr && errorCallback) return await errorHandler(response.err)
         // I still believe we can remove this UserInputError but i'm not sure if this can broke others apps
        if (response.isErr) throw new UserInputError(null, { invalidArgs: response.err })
        return response.ok
[...]

my gql app

// it can be async or not
function errorHandler(err) {
     // here I'm free to handler MYYY errors like I need
      return new ApolloError(err.message, err.code, err.stackTrace)
}
const mutations = usecases.map(usecase => usecase2mutation(usecase, defaultResolver(usecase, errorHandler)))

Add dependencies of the lib from devDependencies to peerDependencies

Is your feature request related to a problem? Please describe.
Some dependencies of the lib like ( graphql, apollo etc) are listed as devDependencies, but are a peerDependencies as well (https://nodejs.org/es/blog/npm/peer-dependencies/) and should be added in this section too

Describe the solution you'd like
Like herbs2knex(https://github.com/herbsjs/herbs2knex/blob/master/package.json) the lib should list the peer dependency

discord test

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Describe the solution you'd like
A clear and concise description of what you want to happen.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

Error using a entity as a field of another entity

Describe the bug
When I use a entity as a field of another entity, for some reason it threw an Unknow type error.

To Reproduce
Create an entity
Import this entity into another entity
Use this imported entity as a field

example:

const { field, entity } = require('@herbsjs/herbs')
const B = require('../entities')

const A = entity("A", {
    B: field(B)
})

module.exports = A

Update the entities index

example:

module.exports = {
    A: require('./A'),
    B: require('./B')
}

Check the log when you run the application

Expected behavior
Graphql should create type and input without error

Screenshots
imagem erro

Herbarium - Use it to create mutations, queries and types

With the new release of Herbarium, herbs2gql can generate mutations, queries and types using it.

Instead of custom files (ex: mutations.js, queries.js, types.js) on the project, using Herbarium will be much simpler since it is a standardized way to access Herbs objects.

Instead of:
queries

const { usecase2query, defaultResolver } = require('@herbsjs/herbs2gql')
const { herbarium } = require('@herbsjs/herbarium')

const usecases = herbarium.usecases
    .findBy({ operation: [herbarium.crud.read, herbarium.crud.readAll] })
    .map(e => e.usecase)

const queries = usecases.map(usecase => usecase2query(usecase(), defaultResolver(usecase)))

/* Custom Queries */
// queries.push(require('./custom/getItems'))

module.exports = queries

it could be something like:

herbs2gql(herbarium)   // where `herbarium` constains all Herbs objects and its metadatas.

Handle Known Erros for better outputs

Is your feature request related to a problem? Please describe.
Improve Known Errors support for use case return.

Describe the solution you'd like
Handle Err.xxxx, aka known erros, and return the proper GQL error.

Add possibility to write how you want the method name to return (Name resolvers)

Problem:
In the application of VxNotification of resolvers' names are written in the format where the first letter of each word is capitalized, the first letter every word
SendWebHook
Herbal2gql returns in a sendWebHook camel case, this way we will have to change several applications that consume the notification

Proposed Solution
The solution at first would be to be able to optionally provide the name that could be returned together, or keep writing and not perform a conversion for the camel case

required usecase-response into usecase2mutation

Problem

I'm trying to use usecase2mutation with an usecase that don't needs a response, but the usecase2mutation requires it

my usecase:

const useCase = ({ profileRepository }) =>
  usecase('Delete Profile', {
    request: { indexer: String },
 
    'Update the Profile': step(async ctx => {
      await profileRepository.delete(ctx.req.indexer)
      return Ok()
    })
  })

when I transform it usingusecase2mutation(usecase, defaultResolver(usecase) I got this error:

> [email protected] start /Users/italojs/dev/herbjs/herbs-cli/lab
> node src/index.js

/Users/italojs/dev/herbjs/herbs-cli/lab/node_modules/herbs2gql/src/usecase2type.js:14
        throw error
        ^

InvalidUseCase: {"response":[{"cantBeEmpty":true},{"cantBeNull":true}]}
    at usecase2type (/Users/italojs/dev/herbjs/herbs-cli/lab/node_modules/herbs2gql/src/usecase2type.js:10:23)
    at usecase2mutation (/Users/italojs/dev/herbjs/herbs-cli/lab/node_modules/herbs2gql/src/usecase2mutation.js:4:12)
    at /Users/italojs/dev/herbjs/herbs-cli/lab/src/infra/api/graphql/mutations.js:5:47
    at Array.map (<anonymous>)
    at Object.factory (/Users/italojs/dev/herbjs/herbs-cli/lab/src/infra/api/graphql/mutations.js:5:32)
    at Object.<anonymous> (/Users/italojs/dev/herbjs/herbs-cli/lab/src/infra/api/graphql/index.js:29:75)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14) {
  invalidArgs: { response: [ { cantBeEmpty: true }, { cantBeNull: true } ] }
}

it says that my usecase needs an response type,I tried use null but I got the same error.

Solution

I believe we can create an scalar Void type by default into the default schema, so we can use it when a usecase return nothing.

Inside useCaseValidator we can remove the line 16 const responseValidation = validate(useCase.responseSchema, defaultValidator). So when we get a usecase without response type into usecaseResponse2gql we can return Void

the final result will be somethinkg like

type Mutation {
  deleteProfile(indexer: string): Void
}

here we have an clear explanation about the solution https://stackoverflow.com/a/61714123

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.