apollo-language-server
Version:
A language server for Apollo GraphQL projects
209 lines (180 loc) • 5.81 kB
text/typescript
import { dirname } from "path";
import merge from "lodash.merge";
import { ClientID, ServiceID, ServiceSpecifier } from "../engine";
import URI from "vscode-uri";
import { WithRequired } from "apollo-env";
import { getGraphIdFromConfig, parseServiceSpecifier } from "./utils";
import { ValidationRule } from "graphql/validation/ValidationContext";
export interface EngineStatsWindow {
to: number;
from: number;
}
export const DefaultEngineStatsWindow = {
to: -0,
from: -86400, // one day
};
export interface HistoricalEngineStatsWindow extends EngineStatsWindow {}
export type EndpointURI = string;
export interface RemoteServiceConfig {
name: ServiceID;
url: EndpointURI;
headers?: { [key: string]: string };
skipSSLValidation?: boolean;
}
export interface LocalServiceConfig {
name: ServiceID;
localSchemaFile: string | string[];
}
export interface EngineConfig {
endpoint?: EndpointURI;
readonly apiKey?: string;
}
export const DefaultEngineConfig = {
endpoint: "https://graphql.api.apollographql.com/api/graphql",
};
export const DefaultConfigBase = {
includes: ["src/**/*.{ts,tsx,js,jsx,graphql,gql}"],
excludes: ["**/node_modules", "**/__tests__"],
};
export interface ConfigBase {
includes: string[];
excludes: string[];
}
export type ClientServiceConfig = RemoteServiceConfig | LocalServiceConfig;
export interface ClientConfigFormat extends ConfigBase {
// service linking
service?: ServiceSpecifier | ClientServiceConfig;
// client identity
name?: ClientID;
referenceID?: string;
version?: string;
// client schemas
clientOnlyDirectives?: string[];
clientSchemaDirectives?: string[];
addTypename?: boolean;
tagName?: string;
// stats window config
statsWindow?: EngineStatsWindow;
/**
* Rules that will be applied when validating GraphQL documents.
*
* If you wish to modify the default list of validation rules, import them from the apollo package and
* assign your custom list:
*
* ```js
* const { defaultValidationRules } = require("apollo/lib/defaultValidationRules");
*
* module.exports = {
* // ...
* validationRules: [...defaultValidationRules, ...customRules]
* }
* ```
*
* Or, if you simply wish to filter out some rules from the default list, you can specify a filter function:
*
* ```js
* module.exports = {
* // ...
* validationRules: rule => rule.name !== "NoAnonymousQueries"
* }
* ```
*/
validationRules?: ValidationRule[] | ((rule: ValidationRule) => boolean);
}
export const DefaultClientConfig = {
...DefaultConfigBase,
tagName: "gql",
clientOnlyDirectives: ["connection", "type"],
clientSchemaDirectives: ["client", "rest"],
addTypename: true,
statsWindow: DefaultEngineStatsWindow,
};
export interface ServiceConfigFormat extends ConfigBase {
name?: string;
endpoint?: Exclude<RemoteServiceConfig, "name">;
localSchemaFile?: string | string[];
}
export const DefaultServiceConfig = {
...DefaultConfigBase,
endpoint: {
url: "http://localhost:4000/graphql",
},
};
export interface ConfigBaseFormat {
client?: ClientConfigFormat;
service?: ServiceConfigFormat;
engine?: EngineConfig;
}
export type ApolloConfigFormat =
| WithRequired<ConfigBaseFormat, "client">
| WithRequired<ConfigBaseFormat, "service">;
export class ApolloConfig {
public isClient: boolean;
public isService: boolean;
public engine: EngineConfig;
public service?: ServiceConfigFormat;
public client?: ClientConfigFormat;
private _variant?: string;
private _graphId?: string;
constructor(public rawConfig: ApolloConfigFormat, public configURI?: URI) {
this.isService = !!rawConfig.service;
this.isClient = !!rawConfig.client;
this.engine = rawConfig.engine!;
this._graphId = getGraphIdFromConfig(rawConfig);
this.client = rawConfig.client;
this.service = rawConfig.service;
}
get configDirURI() {
// if the filepath has a _file_ in it, then we get its dir
return this.configURI && this.configURI.fsPath.match(/\.(ts|js|json)$/i)
? URI.parse(dirname(this.configURI.fsPath))
: this.configURI;
}
get projects() {
const configs: (ClientConfig | ServiceConfig)[] = [];
const { client, service } = this.rawConfig;
if (client) {
configs.push(new ClientConfig(this.rawConfig, this.configURI));
}
if (service) {
configs.push(new ServiceConfig(this.rawConfig, this.configURI));
}
return configs;
}
set variant(variant: string) {
this._variant = variant;
}
get variant(): string {
if (this._variant) return this._variant;
let tag: string = "current";
if (this.client && typeof this.client.service === "string") {
const parsedVariant = parseServiceSpecifier(this.client.service)[1];
if (parsedVariant) tag = parsedVariant;
} else if (this.service && typeof this.service.name === "string") {
const parsedVariant = parseServiceSpecifier(this.service.name)[1];
if (parsedVariant) tag = parsedVariant;
}
return tag;
}
set graph(graphId: string | undefined) {
this._graphId = graphId;
}
get graph(): string | undefined {
if (this._graphId) return this._graphId;
return getGraphIdFromConfig(this.rawConfig);
}
// this type needs to be an "EveryKeyIsOptionalApolloConfig"
public setDefaults({ client, engine, service }: any): void {
const config = merge(this.rawConfig, { client, engine, service });
this.rawConfig = config;
this.client = config.client;
this.service = config.service;
if (engine) this.engine = config.engine;
}
}
export class ClientConfig extends ApolloConfig {
public client!: ClientConfigFormat;
}
export class ServiceConfig extends ApolloConfig {
public service!: ServiceConfigFormat;
}