@gospime/apollo-client
Version:
Apollo HTTP client with response caching
191 lines (147 loc) • 5.05 kB
JavaScript
const https = require('https');
const fetch = require('node-fetch');
// const ApolloClient = require('@apollo/client').ApolloClient; // @todo: use it in future
const ApolloClient = require('@apollo/client').ApolloClient;
const ApolloLink = require('apollo-link').ApolloLink;
const HttpLink = require('apollo-link-http').HttpLink;
const onError = require('apollo-link-error').onError;
const InMemoryCache = require('apollo-cache-inmemory').InMemoryCache;
//const createPersistedQueryLink = require('apollo-link-persisted-queries');
const gql = require('graphql-tag');
const introspectionQuery = require('graphql').introspectionQuery;
const getErrorHandler = logger => {
if (!logger || typeof logger !== 'object' || typeof logger.error !== 'function') {
logger = console;
};
return ({ /*operation, response, */graphQLErrors, networkError }) => {
if (graphQLErrors) {
const handler = ({ message, locations, path }) =>
logger.error(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
);
graphQLErrors.forEach(handler);
}
if (networkError) {
logger.error(`[Network error]: ${networkError}`);
}
}
};
const createAgent = options => new https.Agent(options);
const getFetchFn = agent =>
(fetchUri, fetchOptions) => fetch(fetchUri, { ...fetchOptions, agent });
module.exports = class Facade {
constructor(client) {
this.client = client;
}
/**
* Facade to create instance of Apollo client
*/
static create({ uri, token, agentOptions, defaultOptions, logger }) {
const agent = createAgent(agentOptions);
const fetchFn = getFetchFn(agent);
// https://github.com/github/fetch#sending-cookies
const credentials = 'same-origin';
const headers = token ? { authorization: `Bearer ${token}` } : undefined;
const httpLink = new HttpLink({ fetch: fetchFn, uri, credentials, headers });
const onErrorFn = onError(getErrorHandler(logger));
//const persistedQuery = createPersistedQueryLink({ useGETForHashedQueries: true });
const link = ApolloLink.from([ onErrorFn/*, persistedQuery*/, httpLink ]);
const cache = new InMemoryCache({ addTypename: false });
const ssrMode = true;
const client = new ApolloClient(
{ ssrMode, link, cache, defaultOptions }
);
return new Facade(client);
}
introspect() {
const query = gql`${introspectionQuery}`;
return this.client.query({ query });
}
query({ plan, args, fields }) {
const body = this.build(plan, args, fields);
const query = gql`query { ${body} }`;
return this.client.query({ query });
}
mutate({ plan, args, fields }) {
const body = this.build(plan, args, fields);
const mutation = gql`mutation { ${body} }`;
return this.client.mutate({ mutation });
}
build(plan, args, fields) {
const _args = this.parseArguments(args);
const _fields = this.parseFields(fields);
let query = plan;
if (_args) query += `( ${_args} )`;
if (_fields) query += `{ ${_fields} }`;
return query;
}
escapeValue(value) {
return typeof value === 'string'
? value.replace(/\\/g, '\\\\').replace(/\"/g, '\\"')
: value;
}
parseArguments(data = {}) {
if (!data || typeof data !=='object') {
return '';
}
const mapped = [];
const mapper = ([key, value]) => {
if (!value) return;
let _value = '';
if (Array.isArray(value)) {
const formatted = value
.map(item => `"${this.escapeValue(item)}"`)
.join(', ');
_value = `[${formatted}]`;
} else {
switch (typeof value) {
case 'object':
_value = value instanceof Date
? `"${value.toISOString()}"`
: `{ ${this.parseArguments(value)} }`;
break;
case 'symbol':
const v = value.description || null;
_value = v === v.toUpperCase()
? `${this.escapeValue(v)}` // as a constant
: `"${this.escapeValue(v)}"`; // simple string
break;
case 'string':
_value = `"${this.escapeValue(value)}"`;
break;
case 'number':
case 'boolean':
_value = value;
break;
}
}
if (_value) {
mapped.push(`${key}: ${_value}`);
}
};
Object.entries(data).map(mapper);
return mapped.join(', ');
}
parseFields(fields = []) {
if (!fields || !Array.isArray(fields)) {
return '';
}
const mapped = [];
const mapperSub = ([key, subfields]) => {
return mapped.push(`${key} { ${this.parseFields(subfields)} }`);
}
const mapperRoot = field => {
if (!field) return;
switch (typeof field) {
case 'object':
Object.entries(field).map(mapperSub);
break;
case 'string':
mapped.push(field);
break;
}
};
Object.values(fields).map(mapperRoot);
return mapped.join(' ');
}
};