@graphql-tools/url-loader
Version:
A set of utils for faster development of GraphQL tools
283 lines (282 loc) • 11.8 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.UrlLoader = exports.SubscriptionProtocol = void 0;
const tslib_1 = require("tslib");
const graphql_1 = require("graphql");
const isomorphic_ws_1 = tslib_1.__importDefault(require("isomorphic-ws"));
const value_or_promise_1 = require("value-or-promise");
const executor_graphql_ws_1 = require("@graphql-tools/executor-graphql-ws");
const executor_http_1 = require("@graphql-tools/executor-http");
const executor_legacy_ws_1 = require("@graphql-tools/executor-legacy-ws");
const utils_1 = require("@graphql-tools/utils");
const wrap_1 = require("@graphql-tools/wrap");
const defaultAsyncFetch_js_1 = require("./defaultAsyncFetch.js");
const defaultSyncFetch_js_1 = require("./defaultSyncFetch.js");
const asyncImport = (moduleName) => Promise.resolve(`${`${moduleName}`}`).then(s => tslib_1.__importStar(require(s)));
// eslint-disable-next-line @typescript-eslint/no-require-imports
const syncImport = (moduleName) => require(`${moduleName}`);
var SubscriptionProtocol;
(function (SubscriptionProtocol) {
SubscriptionProtocol["WS"] = "WS";
/**
* Use legacy web socket protocol `graphql-ws` instead of the more current standard `graphql-transport-ws`
*/
SubscriptionProtocol["LEGACY_WS"] = "LEGACY_WS";
/**
* Use SSE for subscription instead of WebSocket
*/
SubscriptionProtocol["SSE"] = "SSE";
/**
* Use `graphql-sse` for subscriptions
*/
SubscriptionProtocol["GRAPHQL_SSE"] = "GRAPHQL_SSE";
})(SubscriptionProtocol || (exports.SubscriptionProtocol = SubscriptionProtocol = {}));
const acceptableProtocols = ['http:', 'https:', 'ws:', 'wss:'];
function isCompatibleUri(uri) {
try {
const url = new URL(uri);
return acceptableProtocols.includes(url.protocol);
}
catch {
return false;
}
}
/**
* This loader loads a schema from a URL. The loaded schema is a fully-executable,
* remote schema since it's created using [@graphql-tools/wrap](/docs/remote-schemas).
*
* ```
* const schema = await loadSchema('http://localhost:3000/graphql', {
* loaders: [
* new UrlLoader(),
* ]
* });
* ```
*/
class UrlLoader {
buildHTTPExecutor(initialEndpoint, fetchFn, options) {
const HTTP_URL = switchProtocols(initialEndpoint, {
wss: 'https',
ws: 'http',
});
return (0, executor_http_1.buildHTTPExecutor)({
endpoint: HTTP_URL,
fetch: fetchFn,
...options,
});
}
buildWSExecutor(subscriptionsEndpoint, webSocketImpl, connectionParams) {
const WS_URL = switchProtocols(subscriptionsEndpoint, {
https: 'wss',
http: 'ws',
});
return (0, executor_graphql_ws_1.buildGraphQLWSExecutor)({
url: WS_URL,
webSocketImpl,
connectionParams,
});
}
buildWSLegacyExecutor(subscriptionsEndpoint, WebSocketImpl, options) {
const WS_URL = switchProtocols(subscriptionsEndpoint, {
https: 'wss',
http: 'ws',
});
return (0, executor_legacy_ws_1.buildWSLegacyExecutor)(WS_URL, WebSocketImpl, options);
}
getFetch(customFetch, importFn) {
if (customFetch) {
if (typeof customFetch === 'string') {
const [moduleName, fetchFnName] = customFetch.split('#');
return new value_or_promise_1.ValueOrPromise(() => importFn(moduleName))
.then(module => (fetchFnName ? module[fetchFnName] : module))
.resolve();
}
else if (typeof customFetch === 'function') {
return customFetch;
}
}
if (importFn === asyncImport) {
return defaultAsyncFetch_js_1.defaultAsyncFetch;
}
else {
return defaultSyncFetch_js_1.defaultSyncFetch;
}
}
getDefaultMethodFromOptions(method, defaultMethod) {
if (method) {
defaultMethod = method;
}
return defaultMethod;
}
getWebSocketImpl(importFn, options) {
if (typeof options?.webSocketImpl === 'string') {
const [moduleName, webSocketImplName] = options.webSocketImpl.split('#');
return new value_or_promise_1.ValueOrPromise(() => importFn(moduleName))
.then(importedModule => webSocketImplName ? importedModule[webSocketImplName] : importedModule)
.resolve();
}
else {
const websocketImpl = options?.webSocketImpl || isomorphic_ws_1.default;
return websocketImpl;
}
}
buildSubscriptionExecutor(subscriptionsEndpoint, fetch, importFn, options) {
if (options?.subscriptionsProtocol === SubscriptionProtocol.SSE) {
return this.buildHTTPExecutor(subscriptionsEndpoint, fetch, options);
}
else if (options?.subscriptionsProtocol === SubscriptionProtocol.GRAPHQL_SSE) {
if (!options?.subscriptionsEndpoint) {
// when no custom subscriptions endpoint is specified,
// graphql-sse is recommended to be used on `/graphql/stream`
subscriptionsEndpoint += '/stream';
}
return this.buildHTTPExecutor(subscriptionsEndpoint, fetch, options);
}
else {
const webSocketImpl$ = new value_or_promise_1.ValueOrPromise(() => this.getWebSocketImpl(importFn, options));
const executor$ = webSocketImpl$.then(webSocketImpl => {
if (options?.subscriptionsProtocol === SubscriptionProtocol.LEGACY_WS) {
return this.buildWSLegacyExecutor(subscriptionsEndpoint, webSocketImpl, options);
}
else {
return this.buildWSExecutor(subscriptionsEndpoint, webSocketImpl, options?.connectionParams);
}
});
return request => executor$.then(executor => executor(request)).resolve();
}
}
getExecutor(endpoint, importFn, options) {
const fetch$ = new value_or_promise_1.ValueOrPromise(() => this.getFetch(options?.customFetch, importFn));
const httpExecutor$ = fetch$.then(fetch => {
return this.buildHTTPExecutor(endpoint, fetch, options);
});
if (options?.subscriptionsEndpoint != null ||
options?.subscriptionsProtocol !== SubscriptionProtocol.SSE) {
const subscriptionExecutor$ = fetch$.then(fetch => {
const subscriptionsEndpoint = options?.subscriptionsEndpoint || endpoint;
return this.buildSubscriptionExecutor(subscriptionsEndpoint, fetch, importFn, options);
});
function getExecutorByRequest(request) {
request.operationType =
request.operationType || (0, utils_1.getOperationASTFromRequest)(request)?.operation;
if (request.operationType === 'subscription' &&
(0, executor_http_1.isLiveQueryOperationDefinitionNode)((0, utils_1.getOperationASTFromRequest)(request))) {
request.operationType = 'subscription';
}
if (request.operationType === 'subscription') {
return subscriptionExecutor$;
}
else {
return httpExecutor$;
}
}
return request => getExecutorByRequest(request)
.then(executor => executor(request))
.resolve();
}
else {
return request => httpExecutor$.then(executor => executor(request)).resolve();
}
}
getExecutorAsync(endpoint, options) {
return this.getExecutor(endpoint, asyncImport, options);
}
getExecutorSync(endpoint, options) {
return this.getExecutor(endpoint, syncImport, options);
}
handleSDL(pointer, fetch, options) {
const defaultMethod = this.getDefaultMethodFromOptions(options?.method, 'GET');
return new value_or_promise_1.ValueOrPromise(() => fetch(pointer, {
method: defaultMethod,
headers: typeof options?.headers === 'function' ? options.headers() : options?.headers,
}))
.then(response => response.text())
.then(schemaString => (0, utils_1.parseGraphQLSDL)(pointer, schemaString, options))
.resolve();
}
async load(pointer, options) {
if (!isCompatibleUri(pointer)) {
return [];
}
let source = {
location: pointer,
};
let executor;
if (options?.handleAsSDL || pointer.endsWith('.graphql') || pointer.endsWith('.graphqls')) {
const fetch = await this.getFetch(options?.customFetch, asyncImport);
source = await this.handleSDL(pointer, fetch, options);
if (!source.schema && !source.document && !source.rawSDL) {
throw new Error(`Invalid SDL response`);
}
source.schema =
source.schema ||
(source.document
? (0, graphql_1.buildASTSchema)(source.document, options)
: source.rawSDL
? (0, graphql_1.buildSchema)(source.rawSDL, options)
: undefined);
}
else {
executor = this.getExecutorAsync(pointer, options);
source.schema = await (0, wrap_1.schemaFromExecutor)(executor, {}, options);
}
if (!source.schema) {
throw new Error(`Invalid introspected schema`);
}
if (options?.endpoint) {
executor = this.getExecutorAsync(options.endpoint, options);
}
if (executor) {
source.schema = (0, wrap_1.wrapSchema)({
schema: source.schema,
executor,
batch: options?.batch,
});
}
return [source];
}
loadSync(pointer, options) {
if (!isCompatibleUri(pointer)) {
return [];
}
let source = {
location: pointer,
};
let executor;
if (options?.handleAsSDL || pointer.endsWith('.graphql') || pointer.endsWith('.graphqls')) {
const fetch = this.getFetch(options?.customFetch, syncImport);
source = this.handleSDL(pointer, fetch, options);
if (!source.schema && !source.document && !source.rawSDL) {
throw new Error(`Invalid SDL response`);
}
source.schema =
source.schema ||
(source.document
? (0, graphql_1.buildASTSchema)(source.document, options)
: source.rawSDL
? (0, graphql_1.buildSchema)(source.rawSDL, options)
: undefined);
}
else {
executor = this.getExecutorSync(pointer, options);
source.schema = (0, wrap_1.schemaFromExecutor)(executor, {}, options);
}
if (!source.schema) {
throw new Error(`Invalid introspected schema`);
}
if (options?.endpoint) {
executor = this.getExecutorSync(options.endpoint, options);
}
if (executor) {
source.schema = (0, wrap_1.wrapSchema)({
schema: source.schema,
executor,
});
}
return [source];
}
}
exports.UrlLoader = UrlLoader;
function switchProtocols(pointer, protocolMap) {
return Object.entries(protocolMap).reduce((prev, [source, target]) => prev.replace(`${source}://`, `${target}://`).replace(`${source}:\\`, `${target}:\\`), pointer);
}
;