Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

.graphqlrc redesign proposal #16

Closed
asiandrummer opened this issue Jan 27, 2017 · 35 comments
Closed

.graphqlrc redesign proposal #16

asiandrummer opened this issue Jan 27, 2017 · 35 comments

Comments

@asiandrummer
Copy link
Collaborator

@schickling and a group of contributors including myself discussed how a common GraphQL configuration would look like, and thought of redesigning the current implementation of graphql-config as well as including some new suggestions. Since GraphQL-powered environments usually include information about the common set of GraphQL artifacts (such as the location and specifications within the application directory), the idea is basically to recommend a commonly-used format/options within .graphqlrc, and provide some helper methods and use cases from other applications that already incorporate this concept.

A minimal .graphqlrc config file would consist of a JSON blob such as:

// Let's assume there is a directory that acts as one repository.
// For a single application within a directory,
{
  "schema-path": "url/to/your/endpoint/or/local/schema/path",
}
// For more than one application within a directory,
{
  "app-1-name": {
    "schema-path": "url/to/your/schema"
  },
  "app-2-name": {
    "schema-path": "url/to/your/schema"
  }
}

To support more use cases, .graphqlrc would have a potential to evolve into something a bit more complicated. For instance, user might need to specify which sub-directories to look into for .graphql files and other GraphQL-related artifacts. Also, schema-path can be a local path to the GraphQL schema definition within the application, or an URL to the endpoint that would be used to fetch GraphQL introspection.

To list these capabilities below:

{
  "app-1-name": {
    "schema-path": "url/to/your/schema",
    // the list of directories to look for GraphQL artifacts/queries
    "input-dirs": [
      "foo/bar",
      "foo/bar/baz"
    ],
    // the list of directories to ignore while looking for GraphQL artifacts/queries
    "exclude-dirs": [
      "a/b/c"
    ],
    // specifying customized validation rules if available
    "custom-validation-rules": "./customRules.js"
  },
  "app-2-name": {
    "schema-path": {
      "endpoint-url": "https://url",
      "local-schema-path": "./schema.graphql"
    },
    ...
  }
}

In a pseudo-flowtype format:

type GraphQLRC = {
  [appName: string]: GraphQLAppConfig
} | GraphQLAppConfig;

type GraphQLAppConfig = {
  schemaPath: URL | FilePath | {
    endPoint?: URL,
    localSchemaPath?: FilePath
  },
  inputDirs?: Array<DirPath>,
  excludeDirs?: Array<DirPath>,
  customValidationRules?: FilePath | Array<FilePath>
};

Note that these are not at all requirements - these specifications, I believe, will serve as an example for how to set up configurations for various GraphQL app use cases. From the above format I've suggested all config properties to be optional except for schemaPath.

@wincent and @jimkyndemeyer made a great suggestion that we provide some helpful tools/guidelines related to the configuration details we're proposing. Some examples are described below:

  • How would one discover .graphqlrc file programmatically from the top-level/sub-directories?
  • How would one fetch/parse/build a GraphQL schema from the introspection endpoint specified in schema-path?
  • Given a .graphql file in a repository, how would one recognize which app config to use?
  • How would configured custom validation rules be used?
  • How could existing GraphQL configurations be migrated into using this proposed .graphqlrc approach?

Finally, we're planning to propose this concept as one of "Best Practices" in graphql.org once it matures.

Please feel free to add/let me know if I've missed anything from our conversation @schickling and @wincent! I'm looking forward to see how this shapes up.

@wincent
Copy link

wincent commented Jan 27, 2017

Great write-up, @asiandrummer.

I think the main thing that I'd want to see clearly defined is how to resolve paths:

  • foo/bar/baz: relative path, but relative to what? Seems that relative to the .graphqlrc file is the most robust option; compared to say, current working directory (which may change) or repo root (which has a slippery definition).
  • /foo/bar/baz: obviously an absolute path.
  • ./foo/bar/baz: obviously a relative path.
  • https://example.com/schema: an introspection endpoint; may need to expand the definition to encode auth etc.

A question: might we want to allow arrays here, with a first-round-wins resolution algorithm.

I'd also like to make sure the discovery algorithm for the files themselves is very clearly spelt out (like you allude to above). We already have a working example internally, so that would be a good starting point to kick off the discussion.

@schickling
Copy link
Contributor

Thanks a lot @wincent for kicking this off! Additionally to your proposed design I'd like to bring up the following thoughts:

  • HTTP headers for URL endpoints: I'm aware that quite a few people are using the HTTP header configuration option to implement authentication mechanisms to fetch the schema. This configuration option is also present in the js-graphql-intellij-plugin. (It will be a question where we draw the line on what to make configurable within .graphqlrc and what should be solved by out-side tooling.)

  • Environment variable for endpoint: Especially for smaller apps, the most common use case for apps using a GraphQL backend seems to be using a URL endpoint. With tooling like webpack where it's possible to inject env variables at build time, it's getting more and more popular to configure frontend apps using environment variables (following the idea of 12 factor apps). This approach makes apps easier to configure (like used here) and also simplifies CI. (We're using the pattern quite heavily at Graphcool.)

  • graphql-js: As a lot of people are building GraphQL servers with graphql-js, @akre54 created a PR to introduce support for graphql-js out-of-the-box. I'm not a strong pro-/opponent of this but wanted to bring this up while discussing the new format.

Regarding the resolution of an URL endpoint:
We've implemented a first version of this here. It would be great if we find a good solution that allows for schema re-fetching as it's changing along the way.

I also want to loop in @stubailo as he's maintaining eslint-plugin-graphql.

@akre54
Copy link
Contributor

akre54 commented Jan 27, 2017

I could honestly go either way on this project continuing to support graphql-js. I think the most maintainable method long-term is to go through the public http graphql api. The main advantage for our architecture was that accessing the schema.js file directly avoids having to deal with the extra cruft that comes with authenticating / permissioning a request from the babel plugin.

We could rearchitect our app to special case a request from the babel-plugin (either checking for localhost or maybe set a 'X-Babel-Relay-Plugin' header with some kind of verifiable secret?) But this also seems fraught and viral.

This brings us back to generating a schema.json file on every save, either manually or hooked into our watcher, which brings us back to where we were in the first place a year ago. Would love any guidance from you guys on best practices here.

@bdougie
Copy link

bdougie commented Feb 2, 2017

I am for the having the ability to pass my endpoints into an ENV and populate via webpack, this provides flexibility when making a simple boilerplates or prototypes. Also provides flexibility when deploying, being able to switch endpoints based on a NODE_ENV would be nice.

Also having the ability to point to production and dev endpoints in a .graphqlrc would be a nice addition, though I haven't had a use case as I have mainly only used graphql for side projects. I could see myself need to point to set two separate endpoints in a single app.

@asiandrummer
Copy link
Collaborator Author

asiandrummer commented Feb 3, 2017

Thanks for your opinions! I'll try addressing each comment below:

cc @wincent:

Path resolving

Your explanation makes sense. I agree that resolving foo/bar/baz should be relative to the .graphqlrc directory. The introspection endpoint would just be what you'd use to query an introspection, so if you want to embed an authentication method to facilitate the introspection, I'm okay with that I think.

It is worth mentioning, though, that GraphQL recommends the authorization to reside under the business logic layer - if possible I'd encourage the authentication before introspection (cc @akre54).

A question: might we want to allow arrays here, with a first-round-wins resolution algorithm.

input-dirs and exclude-dirs are already arrays - do you mean to allow arrays for schema paths?

.graphqlrc discovery algorithm

graphql-language-service certainly has one implemented! The current algorithm is to start from the directory where .graphqlrc is, and walk up the directory path until .graphqlrc file is found or the directory hits the root directory (/).

I'll mention this later, but to support the case that .graphqlrc is located somewhere else in the repository (and not in the direct line of descendants), I think we can use environment variables to specify where .graphqlrc to use might be.

cc @schickling

HTTP headers for URL endpoints

Although I think this is a good example for each app to customize their settings, I'm not sure if we should recommend the custom HTTP headers in a .graphqlrc file. I think at the time when .graphqlrc configurations are needed, users should already have been authenticated to use it. Obviously I'm only thinking a couple of use cases here, and would love to hear some other cases where including auth options in .graphqlrc would help a lot.

Environment variable for endpoint && graphql-js

I'm not super experienced with working with env variables, but read through the 12 factor apps and the idea sounds cool. So would the GraphQL configurations be stored within whenever we store them, such as process.env and etc?

One concern I have is that we don't have one unified place to store them - for example Java/Python GraphQL services have different ways/conventions to use env variables. Maybe we could store the .graphqlrc location path in the env vars to begin with - it's definitely a new idea to me!

I like including a way to generate GraphQL schema from js file(s) - it's pretty cool how you've done it in the PR 👍

cc @bdougie

prod/dev schema endpoints

I like the idea:

{
  "schema-path": {
    "prod": {
      "endpoint-url": ...,
      "local-schema-path": ...
    },
    "dev": {
    }
  }
}

I like to have one source of truth for my environment, but I could see how one might use this to generate a boilerplate schema and test mutations, for example.

@stubailo
Copy link

stubailo commented Feb 3, 2017

If we have something like prod/dev, we should definitely not limit it to exactly two options. Alternatively, people can just have multiple .graphqlrc files and there could be some mechanism to select one.

@asiandrummer
Copy link
Collaborator Author

@stubailo currently the suggested mechanism to choose which .graphqlrc to choose is to take the "closest" .graphqlrc file found.

I'd like to see what people think about prod/dev. I guess the workaround for prod/dev is to have different app configurations:

{
  "app-prod": { ... },
  "app-dev": { ... }
}

Maybe that's more desired.

@nodkz
Copy link
Contributor

nodkz commented Feb 3, 2017

1. Env instead of sub apps

I think it is not good to allow schema-path be an object with subapps. Maybe borrow how works babel envs?

So if we have one schema in app, we may use standard config:

{
  "schema-path": "url/to/your/schema",
  "input-dirs": [
    "foo/bar",
    "foo/bar/baz"
  ],
  "exclude-dirs": [
    "a/b/c"
  ],
  "custom-validation-rules": "./customRules.js"
}

If we need multiple schema setups, we may add prod/dev/... environments, via adding env option which will be merged into and overwrite non-env specific options.

{
  "schema-path": "url/to/your/schema",
  "endpoint-url": "http://localhost:3000/graphql",
  "exclude-dirs": [
    "a/b/c"
  ],
  "env": {
     "production": {
        "endpoint-url": "http://example.com/graphql",
     },
     // if we want to add additional schemas, it may be done also as environment
     "app2dev": {
        "schema-path": "url/to/your/schema-app-2",
     },
     "app2prod": {
        "schema-path": "url/to/your/schema-app-2",
     }
  }
}

And we will have ability to introduce GRAPHQL_ENV env variable and eg. run development build with production database:

GRAPHQL_ENV=production && NODE_ENV=development && babel-node ./server.js

Anyway sub app division is a bad idea (in one .graphqlrc file). If you have several schemas in your app, then you will have several servers that serve them. And you may start each server under its own environment. If you want to run all schemas under one server - just read .graphqlrc file when start your server and read needed setup/startup options manually. Or, last but not least, may be used multiple .graphqlrc files.

2. Camel-case instead of kebab-case

Also it will be cool if config options' names migrated from kebab-case to camel-case. To have ability seamlessly add .graphqlrc.js in future. And have an ability of comfortable writing and reading options. Eg. accessing object props in JS via dots opts.schemaPath, not opts['schema-path'].

@schickling
Copy link
Contributor

Thanks a lot for your great input @nodkz. I completely agree and am a big fan of GRAPHQL_ENV instead of NODE_ENV since it makes it easier to use this format for non-js projects. 👍

@asiandrummer what are your thoughts on multiple .graphqlrc files vs multiple sub-apps?

@asiandrummer
Copy link
Collaborator Author

asiandrummer commented Feb 3, 2017

@schickling I'd like to think that each .graphqlrc config file should represent one organization of products/apps, and for each .graphqlrc there should be configurations for each app (schema) you'd like to support. I think I agree with @nodkz that we don't want the sub-app configs within schemaPath, and env variables could just become a GraphQLAppConfig that overrides the "default" GraphQL app configuration for each environment option.

If we're to support env variables, I think we should make sure they also contain inputDirs/excludeDirs/other configuration options that would apply to one app.

For this reason I think the previous suggestion + some additions make a good format candidate:

export type GraphQLConfigurations = {
  [appName: string]: GraphQLAppConfig
} | GraphQLAppConfig;

export type GraphQLAppConfig = {
  // Treat below config as a "default" set for each app
  schemaPath: URL | FilePath | {
    endPoint?: URL,
    // If `.js` is supplied below, will try to use `graphql-js` to generate schema
    localSchemaPath?: FilePath
  },
  inputDirs?: Array<DirPath>,
  excludeDirs?: Array<DirPath>,
  customValidationRules?: FilePath | Array<FilePath>,
  // If env is specified, use one of the app configs in here
  env: EnvironmentVariable
};

export type EnvironmentVariable = {
  production: GraphQLAppConfig,
  development: GraphQLAppConfig,
  // but really just the below
  [envVarName: string]: GraphQLAppConfig
};

Keep in mind that for the most use cases we'd only have one schema + one config - then we'd just have a very small object:

{
  "schemaPath": "uri/to/endpoint/or/path",
  "env": { "production": "prodUri", "dev": "devUri" }
}

The above would be kind of a protocol/specification of what we'd expect if use cases become more complex over time.

I love these suggestions - let's keep this up, agree on a draft spec, and then get moving fast!

@nodkz
Copy link
Contributor

nodkz commented Feb 3, 2017

@asiandrummer can we split schemaPath on separate vars for localPath and url?
Some tools will work only with path, other may support urls. So will be better just provide two vars (eg. schemaPath: FilePath, schemaUrl: URL) instead of union.

Also "env": { "production": "prodUri", "dev": "devUri" } is not good simplification. Tools maintainers curse you 🤗. Let keep it ready for simple merge function call and futher format extension. So it should be:

{
  "schemaPath": "path",
  "env": { 
    "production": { "schemaPath": "prodPath" }, 
    "dev": { "schemaPath": "devPath" }
  }
}

So in JS config under some environment can be fastly assembled in such way without deep merging:

opts = { ...config, ...config[GRAPHQL_ENV || NODE_ENV || 'dev']};

Dot config files intended mostly for programs/tools and less for humans.

@nodkz
Copy link
Contributor

nodkz commented Feb 3, 2017

Also, I have addition about clarifying what exactly is provided under path and url. I think should be a set of following names:

  • schemaPathJson - path to generated JSON file for linters, services and tools, eg for eslint-plugin-graphql, graphql-language-service, my babel-plugin-transform-relay-hot which watch changes on local file
  • schemaPathEntry - path to root schema file for generators, eg for my webpack-plugin-graphql-schema-hot
  • schemaUrlJson - the same purpose like in schemaPathJson, but plugins should support loading schema by URL (so for my babel-plugin I'll need add other option how often to download schema json file)
  • schemaUrlServer - for clients, eg relay, apollo-client and others. For tests. Also for in IDE plugins which can run a query and provide results. And again for linters and tools which can call schema introspection if JSON is not provided.

@akre54
Copy link
Contributor

akre54 commented Feb 3, 2017

@asiandrummer not to get too off-topic, but that link only refers to permissioning specific resources, not accessing the GraphQL endpoint. Our API is behind a Passport user login scheme, and all requests have to be authenticated and tied to a user. This makes it difficult to work with the babel plugin (at least the version that came out 7 months ago).

@wincent
Copy link

wincent commented Feb 3, 2017

A question: might we want to allow arrays here, with a first-round-wins resolution algorithm.

input-dirs and exclude-dirs are already arrays - do you mean to allow arrays for schema paths?

Yeah, that's what I meant. (I meant to write "first-found-wins", too, BTW.) My thinking here is that you might want to try an introspection URL first, and if that fails for whatever reason (for example, you are still in the process of setting up the endpoint), you can fall back to a default schema that you've stashed on the filesystem.

{
  "schemaPath":  ["http://localhost:9000/graphql", "config/default-schema"]
}

@wincent
Copy link

wincent commented Feb 3, 2017

I think it's important to preserve some flexibility here with respect to how people structure there apps. Here are some use cases that I'd like to support:

Single app, single schema

This is a simple repo containing the files for exactly one app and one schema. It will be most natural for the .graphqlrc to reside in the repo root, and the developer can structure the app files as they see fit.

In this scenario, it will be useful to have all the config at the top-level so that people can get started easily. But it's also going to be handy to permit switching based on GRAPHQL_ENV, which implies (optional) nesting within an env object.

Multiple apps, in well-isolated directories

Here we have more than one app in a single repo, and we might want a structure like this:

  • repo/.graphqlrc.
  • repo/app1/.graphqlrc.
  • repo/app1/* (other app1 files).
  • repo/app2/.graphqlrc.
  • repo/app2/* (other app2 files).
  • repo/app3/.graphqlrc.
  • repo/app3/* (other app2 files).

Here we have application-specific config in .graphqlrc files in each app dir. Note that we don't need to add any app-centric nesting into the .graphqlrc format to support this; each app can just have its own file.

Also note another idea here: that we can set common defaults in .graphqlrc higher up in the hierarchy (at the repo root) and have app-specific overrides in the app-local dirs.

Here again, we could optionally make use of an "env" object, directed by GRAPHQL_ENV.

Multiple apps, overlapping files

Here we have more than one app in a single repo, but the files are jumbled together in some way:

  • repo/.graphqlrc.
  • repo/app1/* (other app1 files).
  • repo/app2/* (other app2 files).
  • repo/common/* (files common to both).

Because app1 and app2's files are co-mingled, we have a use case now for putting app-specific sections into a single, shared .graphqlrc. Again, "env"-switching could be optional but useful. The question here, then, is should the "env" object be nested inside the app objects, or the other way round? My intuition suggests that the former is likely to be better.

@nodkz
Copy link
Contributor

nodkz commented Feb 3, 2017

@wincent i think your last case is some exotic, looks more like a poorly structured code. I think that .graphqlrc should not support such case. Do .flowconfig, .babelrc, .eslintrc and other tools support such case?

BTW As solution for such case, i can suggest to use symlinks to common inside app1 and app2 and put two separate .graphqlrc files to every app folder.

@wincent
Copy link

wincent commented Feb 3, 2017

i think your last case is some exotic, looks more like a poorly structured code

Maybe, but I fully expect that kind of thing to exist in the real world.

Not sure what .flowconfig etc do, but I believe that .eslintrc at least has the notion of "inheritance" of sorts, where you can have multiple .eslintrc files at different levels influencing the result of the lint. (Or at least, I think that's how to works.) This means you can have some pretty complicated/arbitrary layouts there too.

@asiandrummer
Copy link
Collaborator Author

Multiple apps, overlapping files

I would also think this is possible if an organization with pre-existing directory structure like that wants to use GraphQL and set up GraphQL configuration.

can we split schemaPath on separate vars for localPath and url?

@nodkz actually your example is a correct application of my loosely-flowtyped config format ;) Sorry about the confusion.

@asiandrummer
Copy link
Collaborator Author

Okay, so in trying to create more momentum, let me propose a draft "protocol" for GraphQL configuration. DISCLAIMER: THIS COMMENT WILL BE LONG - for tl;dr; look at the first part of each paragraph.

But first, a little note about using a "protocol" word - I'd like to think of GraphQL configuration standard as "suggestions", not "capabilities" or "requirements. I understand by saying "protocol" or "specification" it sounds like something we'd have to adhere to, but as we all understand, it's incredibly difficult to catch all the configuration use cases possible and have one single document that lists out requirements for those use cases.

I think our goal should be to create a minimal set of shared GraphQL configuration standard that all GraphQL applications/tools can understand. As we come across different use cases that add high value to this, we can consider them and update the standard upon agreement. A more informal way to think of this would be starting out with configuring to get GraphQL schema (a minimal config), and add env variables to specify which environment the application/tool should target to (a high-value addition to the standard).

Also, for the ease of understanding what each configuration option means, I'll go through the most common use cases @wincent provided above.

(If you have a better suggestion for calling this other than "protocol", "standard", or "specification", please let us know!)

GraphQL Configuration protocol

In a nutshell, the simplest format is:

export type GraphQLConfiguration = {
  schemaPath?: FilePath,  // may be .json or .graphql
  schemaUrl?: URL,
};

As we come across more complex use cases, this may evolve into:

export type GraphQLConfigurations = {
  [appName: string]: GraphQLAppConfig
} | GraphQLAppConfig;

export type GraphQLAppConfig = {
  // Treat below config as a "default" set for each app
  // If below path extname is .js, try parsing JS schema definitions.
  schemaPath?: FilePath,
  schemaUrl?: URL,

  // If env is specified, use one of the app configs in here
  env?: EnvironmentVariable

  // For multiple applications with overlapping files, bottom configuration options may be helpful
  inputDirs?: Array<DirPath>,
  excludeDirs?: Array<DirPath>,

  // If you have customized validation rules to run
  customValidationRules?: FilePath | Array<FilePath>,

  // If you'd like to specify any other configurations, we provide a reserved namespace for it
  optional?: Any
};

export type EnvironmentVariable = {
  production: GraphQLAppConfig,
  development: GraphQLAppConfig,
  // but really just the below
  [envVarName: string]: GraphQLAppConfig
};

Note that I'd like to discourage the recursive pattern in using the EnvironmentalVariable - for example:

{
  "app1": {
    "schemaPath": "...",
    "env": {
      "production": {
        "appProd": {
          // BEWARE OF THE BELOW PATTERN!
          "env": { ... }
        }
      }
    }
  }
}

Now the reasoning for it: let's start with a most simple use case - one app/one schema.

Single-app/single-schema

Usually a single app requires a schema path/url and an env variable to facilitate the build and deployment:

export type GraphQLAppConfig = {
  schemaPath: FilePath,
  schemaUrl: URL,
  env: EnvironmentVariable
};

export type EnvironmentVariable = {
  [envVarName: string]: GraphQLAppConfig
};

Multiple-apps/isolated directories

For multiple apps with isolated directories, I like @wincent's idea about a common-default GraphQL configurations, but in spirit of keeping things simple at first, I'd like to propose an one-config-per-directory approach as our first draft. I think there's an edge case where we'd have multiple parent GraphQL configurations to consider:

./repo/.graphqlrc
./repo/appFamily/.graphqlrc
./repo/appFamily/app1/.graphqlrc

This can become a discussion of its own, which we can have separately ;)

Multiple apps/shared files

For multiple apps with shared files, we can have one GraphQL config on the top directory like @wincent suggested. This would create a GraphQL configuration such as:

export type GraphQLConfigurations = {
  [appName: string]: GraphQLAppConfig
};

export type GraphQLAppConfig = {
  // Treat below config as a "default" set for each app
  // If below path extname is .js, try parsing JS schema definitions.
  schemaPath: FilePath,
  schemaUrl: URL,

  // If env is specified, use one of the app configs in here
  env: EnvironmentVariable

  // For multiple applications with overlapping files, bottom configuration options may be helpful
  // to specify where (not) to look for GraphQL artifacts.
  inputDirs?: Array<DirPath>,
  excludeDirs?: Array<DirPath>,
};

As @nodkz suggested, if you'd like to solve this use case using symlink + the one-config-per-directory, that should be completely possible. Personally, as symlinks won't solve a more complex case of having commonly shared code within each application code (it's gnarly, but something completely possible to happen in a huge codebase), I feel more strongly about a GraphQL configuration with multiple application settings.

Each approach has pros/cons I believe: for example, for tools that require generating GraphQL schema on the fly (such as a language server/service), treating one GraphQL config as a source of truth is useful, whereas for webpack config + building and deploying separate GraphQL config per apps/directories makes more sense.

Custom validation rules

You may want to run custom validation rules before building GraphQL apps/codegen/etc - customValidationRules is useful for that.

Reserved namespaces

@wincent made a suggestion to provide a way to include any other configurations if you desire. This namespace will contain an app/prod/organization/team-specific GraphQL configurations, and it will be treated as optional settings you'd like to use for your purpose.

I've proposed this namespace to be called optional - please feel free to make other suggestions as well ;)

What is this repo, then?

I think this repo should serve two roles in a broader sense:

1. Maintain this protocol and become a town hall for discussions/improvements for this protocol.
2. Provide reference implementations of helper methods to lubricate adoption for this protocol.

An example of the reference implementation of the helper method: we mentioned a way to find this GraphQL configuration is to walk up a directory until it finds one. This basically can be implemented as such:

/**
 * (From `graphql-language-service` repo:
 * Finds a .graphqlrc configuration file, and returns null if not found.
 * If the file isn't present in the provided directory path, walk up the
 * directory tree until the file is found or it reaches the root directory.
 */
export function findGraphQLConfigDir(dirPath: string): ?string {
  let currentPath = path.resolve(dirPath);
  while (true) {
    const filePath = path.join(currentPath, '.graphqlrc');
    if (fs.existsSync(filePath)) {
      break;
    }
    if (isRootDir(currentPath)) {
      break;
    }
    currentPath = path.dirname(currentPath);
  }
  return !isRootDir(currentPath) ? currentPath : null;
}

function isRootDIr(path: string): boolean {
  return path.dirname(path) === path;
}

As there are several use cases, we can tackle each of them and present a way to use this configuration programmatically. I can begin by identifying/suggesting some of them below:

  • A generalized module bundler configurations, using GraphQL configurations
    • webpack, yeoman, babel, and etc...
  • How to include custom validation rules in your application
  • How to generate schema with provided schema path(s)
    • Sending introspection query to the endpoint
    • Parsing/building the schema using a local file path
    • Compiling GraphQLSchema object using schema definitions in .js
    • How to cache the schema
      • local files, indexedDB, and etc...

Action items

There are many things to do! But when we successfully deliver this, I believe we can get to the point where we have a shared GraphQL configuration that everyone agrees on, with commonly-used techniques and best practices to configure your GraphQL environment.

  • Write a draft for this protocol
  • Populate this repository with those helper methods
    • Discuss what the superset of those helper methods would be
    • Prioritize by the most impactful and common ones and implement them
    • Generalize existing implementations and move them here
  • Update other remaining documentations, including this repo's README
  • Suggest GraphQL configuration as a best-practice at graphql.org

Lastly, although we've begun this proposal and made a bunch of suggestions, I'd love to have a collaborative effort in pushing this forward. It'll be amazing to see us working together to improve and deliver this proposal to the GraphQL community.

Please post your thoughts/concerns/questions! Also if you'd like to be responsible for one of the actions items above, don't be hesitate to do so :D

@nodkz
Copy link
Contributor

nodkz commented Feb 6, 2017

@asiandrummer @wincent can you explain how eg. some atom plugin will determine which app needs to use from .graphqlrc (if this file contains several apps configs in it)?
Another dot-file, or some drop-down in settings? It will be too complicated and wired. If it some CLI tool, then it will require some arg for determining which app needs to take.

For me .graphqlrc is the complete description of one exact application. In this case some atom plugin will take dev env { ...configFromUpperDir, ...config, ...config['dev']} and starts to work. So I'll never forgot to choose correct app from config.

I'm afraid that if we somehow introduce several

export type GraphQLConfigurations = {
  [appName: string]: GraphQLAppConfig
};

... we kill simplicity of consuming this config by tools. I'm sure that tools will not support multiapp configs. At least my plugins definitely will not support multiapps.

I such disputable things I like to follow YAGNI principle. Populated by @leebyron in this great talk.


PS. @schickling @asiandrummer our comments become tooooo long. I think that needs to create a new branch and publish draft there. For keep changes in track. And splitting our discussion to several issues.

@nodkz
Copy link
Contributor

nodkz commented Feb 6, 2017

Sorry, for offtop but I feel that must share it with you ;)
screen shot 2017-02-06 at 11 52 11

@wincent
Copy link

wincent commented Feb 6, 2017

@nodkz: YAGNI is a very useful perspective, but what may not be clear in the preceding discussion is that WADNI (We Actually Do Need It) already; at FB, we have a large immense monorepo containing multiple applications (some of them quite large), and with a variety of degrees of code sharing, or the inverse, code isolation. The code we currently have for dealing with multiple applications in a single .graphqlrc file with is not as simple as a basic one-config-file-per-app approach would require, but it's not dramatically complicated either.

how eg. some atom plugin will determine which app needs to use from .graphqlrc

This is a great question, and one that whose answer we should try to define rigorously. @asiandrummer may be in the best position to describe how disambiguation proceeds in this case, because he wrote the code, but he's out this week, so we could also just look at the code.

@nodkz
Copy link
Contributor

nodkz commented Feb 7, 2017

Thanks, @wincent you provide some clarification for me: pointing to your code and inform me that @asiandrummer also FB employee. Now I understand that for now only graphql-language-service requires multiapp .graphqlrc.

So I should convince you to not use multi-apps there. 😉
Opened issue with current problems which I see with graphql-language-service graphql/graphql-language-service#15

@nodkz
Copy link
Contributor

nodkz commented Feb 7, 2017

What about .graphqlrc.js for solving env and multiapp problem?

screenshot_2017-02-07-22-09-02-547

@stubailo
Copy link

stubailo commented Feb 7, 2017

I believe we can get to the point where we have a shared GraphQL configuration that everyone agrees on, with commonly-used techniques and best practices to configure your GraphQL environment.

I think it might be best to have a separate repository outside of the actual code of the language service to keep the specification for the config file - is this that repository?

@wincent
Copy link

wincent commented Feb 7, 2017

I think it might be best to have a separate repository outside of the actual code of the language service to keep the specification for the config file - is this that repository?

Agreed. The language service is just a single instance of an implementation. I am not sure if it is this repository or another one, but even if it is not this repository, this seems like a good place to start trying to sync up.

One thing that I don't think has been mentioned yet explicitly (although maybe it has in this long thread) which I think will be important is the following:

If we want a common "core" set of configuration values that works everywhere, we need to very clearly delineate the boundary between the "core" part and the "extension" part. There should effectively be a "namespace" of properties that are part of the core, and which tools wishing to implement support for should do so in a consistent way. And there should be a "namespace" where tools can place tool-specific config and know that it won't collide with other tool-specific extensions or core config.

For example, there might be a "graphql-language-service" section, an "apollo" section, a "relay" section, a "foo" section etc where these tools could place an arbitrary set of useful properties that may not be generally useful but which still make sense to be placed in the same .graphqlrc file for convenience.

I am not sure what the best way to demarcate these different sections are. My sense is that something like this would work:

{
  "topLevelProperty": "This is a core property",
  "note": "Only \"core\" properties should go here",
  "extensions": {
    "my-tool": {
      "here": "This is a tool-specific extension"
    }
  }
}

Basically, anything under "extensions", grouped by tool name, would be free-for-all. If we're worried about collisions on tool names, we could do something more heavy-handed like reverse FQDN keys ("com.facebook.relay" etc) or we could simply have an informal "registry" here or elsewhere where people "claimed" a property name by adding it to a list on a wiki page (I think I prefer this latter solution).

Anything outside "extensions" should be considered "core", which means that, if a tool accesses that property, it should do so in a way that is consistent with the meaning defined in the "spec" here.

@stubailo
Copy link

stubailo commented Feb 7, 2017

I think that's a great suggestion. Keeping the core small would be ideal and then individual tools can iterate quickly on extensions.

One more thing is that JSON is quite limited in stuff like splitting configuration across multiple files, etc. But the problem with .graphqlrc.js is that people using Ruby or Swift might find it weird to have config files in JS - is there any solution to this?

@schickling
Copy link
Contributor

What are your thoughts on using YAML instead? Many tools like Serverless are using it and it can be quite expressive.

In general I really like the idea of extensions to keep the core to a minimum. Maybe even the schema resolving from a URL could/should be factored out into such an extension?

@wincent
Copy link

wincent commented Feb 7, 2017

I think YAML is pretty readable, and in some ways easier to write, compared to JSON. There may be some precedent for tools being agnostic about whether stuff is stored in YAML or JSON, although I can't remember specific examples right now.

I do like the idea of starting with a declarative format that can be consumed from any language (not .js, which can only be consumed by evaluating it, and that's not so easy to do from outside JavaScript), and both YAML and JSON satisfy that. One question would be, should we:

  • Auto-detect .graphqlrc format type by sniffing the file contents, and then parsing using the appropriate tool (YAML or JSON)? This would unfortunately mean that all tools would have to implement support for both if they wanted to consume an arbitrary .graphqlrc file.
  • Assume JSON by default, but fall back to .graphqlrc.yml (explicit extension). In this case, how to resolve the ambiguity if the filesystem contains more than one of .graphqlrc, .graphqlrc.yaml and .graphqlrc.json?

At least to begin with, I think it's probably safest to start narrow and assume/require JSON, but consider extending to include YAML support once some of the above questions have been resolved.

@mac-
Copy link

mac- commented Feb 9, 2017

So obviously this is a lengthy thread, but it wasn't clear to me if the above proposals are suggesting that we do away with custom headers on requests to fetch a schema from an endpoint. Could someone clarify what the thoughts are on that?

@wincent
Copy link

wincent commented Feb 9, 2017

@mac-: My read of the situation with headers is this:

  • We should keep the core specification small.
  • It is easier to add things to the specification later, than to remove things (because that would be a breaking change).
  • If we store header configuration inside .graphqlrc, there may be some concern with sensitive headers (auth credentials) residing in a configuration file, which would suggest that a proper implementation should also support alternate means of setting headers, such as via environment variables. This would entail some additional complexity, albeit only a modicum.

So I am open to the idea of adding them, but think we should proceed with caution.

@mac-
Copy link

mac- commented Feb 9, 2017

Thanks @wincent that helps. I agree that the spec should be kept small and that storing auth headers in a .graphqlrc would be bad. However, I do think it is a common use case for endpoints that serve up a graphql schema to require some form of authentication. It would make a lot of sense to use env vars in that case. I'm just not sure what the best way to support that is at this point.

@asiandrummer
Copy link
Collaborator Author

Sorry for the late response! As @wincent mentioned I was on a break, but I'm back in business ;)

how eg. some atom plugin will determine which app needs to use from .graphqlrc

There's an util function to find a correct app config by looking at the file path and determine if one of the input directories specified contains that file. I commented on graphql/graphql-language-service#15 (cc @nodkz), but I've put in a hard assumption that all inputDirs and excludeDirs are mutually exclusive - that is, each app contains a distinct set of directory paths to each other, and this notion directly contradicts the first case.

So I should convince you to not use multi-apps there. 😉

Although I'm still convinced that we would have to consider a monolithic repository with multi-apps configuration, I agree that it's a bit premature to push towards a generalized approach for it at this point, especially without having seen how others would use this proposal.

I did want to reiterate that the multi-app use case is sometimes inevitable (i.e. an organization with pre-existing monorepo structure wants to try out GraphQL without major refactoring), but again we can come back to this later I think.

I've opened #20 to track the proposal draft progress - let's continue this discussion over there!

@asiandrummer
Copy link
Collaborator Author

@mac- @akre54 Obviously I'm lacking an exact context to this as well as how certain babel plugins work, and the linked resource + this additional post about authentication target specific use cases - I still think/recommend we solve the authentication problem outside of the configuration level.

If you absolutely cannot move away from having custom headers/tokens in the config file, I think env and extension property in the configuration proposal can be of a help. Maybe a said babel plugin could be extended to give users a way to authenticate without having to supply customized headers/tokens manually.

@asiandrummer
Copy link
Collaborator Author

ok this thread is super long now - I'm going to close this as the proposal discussion has moved to #20 :D

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

No branches or pull requests

8 participants