fetch-sparql-endpoint
Version:
A simple, lightweight module to send queries to SPARQL endpoints and retrieve their results in a streaming fashion.
240 lines • 12.6 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SparqlEndpointFetcher = void 0;
const isStream = require("is-stream");
const n3_1 = require("n3");
const readable_from_web_1 = require("readable-from-web");
const sparqljs_1 = require("sparqljs");
const sparqljson_parse_1 = require("sparqljson-parse");
const sparqlxml_parse_1 = require("sparqlxml-parse");
const stringifyStream = require("stream-to-string");
/**
* A SparqlEndpointFetcher can send queries to SPARQL endpoints,
* and retrieve and parse the results.
*/
class SparqlEndpointFetcher {
constructor(args) {
var _a, _b, _c, _d;
this.method = (_a = args === null || args === void 0 ? void 0 : args.method) !== null && _a !== void 0 ? _a : 'POST';
this.timeout = args === null || args === void 0 ? void 0 : args.timeout;
this.forceGetIfUrlLengthBelow = (_b = args === null || args === void 0 ? void 0 : args.forceGetIfUrlLengthBelow) !== null && _b !== void 0 ? _b : 0;
this.additionalUrlParams = (_c = args === null || args === void 0 ? void 0 : args.additionalUrlParams) !== null && _c !== void 0 ? _c : new URLSearchParams();
this.defaultHeaders = (_d = args === null || args === void 0 ? void 0 : args.defaultHeaders) !== null && _d !== void 0 ? _d : new Headers();
this.fetchCb = args === null || args === void 0 ? void 0 : args.fetch;
this.sparqlJsonParser = new sparqljson_parse_1.SparqlJsonParser(args);
this.sparqlXmlParser = new sparqlxml_parse_1.SparqlXmlParser(args);
this.sparqlParsers = {
[SparqlEndpointFetcher.CONTENTTYPE_SPARQL_JSON]: {
parseBooleanStream: sparqlResponseStream => this.sparqlJsonParser.parseJsonBooleanStream(sparqlResponseStream),
parseResultsStream: sparqlResponseStream => this.sparqlJsonParser.parseJsonResultsStream(sparqlResponseStream),
},
[SparqlEndpointFetcher.CONTENTTYPE_SPARQL_XML]: {
parseBooleanStream: sparqlResponseStream => this.sparqlXmlParser.parseXmlBooleanStream(sparqlResponseStream),
parseResultsStream: sparqlResponseStream => this.sparqlXmlParser.parseXmlResultsStream(sparqlResponseStream),
},
};
}
/**
* Get the query type of the given query.
*
* This will parse the query and thrown an exception on syntax errors.
*
* @param {string} query A query.
* @return {'SELECT' | 'ASK' | 'CONSTRUCT' | 'UNKNOWN'} The query type.
*/
getQueryType(query) {
const parsedQuery = new sparqljs_1.Parser({ sparqlStar: true }).parse(query);
if (parsedQuery.type === 'query') {
return parsedQuery.queryType === 'DESCRIBE' ? 'CONSTRUCT' : parsedQuery.queryType;
}
return 'UNKNOWN';
}
/**
* Get the query type of the given update query.
*
* This will parse the update query and thrown an exception on syntax errors.
*
* @param {string} query An update query.
* @return {'UNKNOWN' | IUpdateTypes} The included update operations.
*/
getUpdateTypes(query) {
const parsedQuery = new sparqljs_1.Parser({ sparqlStar: true }).parse(query);
if (parsedQuery.type === 'update') {
const operations = {};
for (const update of parsedQuery.updates) {
if ('type' in update) {
operations[update.type] = true;
}
else {
operations[update.updateType] = true;
}
}
return operations;
}
return 'UNKNOWN';
}
/**
* Send a SELECT query to the given endpoint URL and return the resulting bindings stream.
* @see IBindings
* @param {string} endpoint A SPARQL endpoint URL. (without the `?query=` suffix).
* @param {string} query A SPARQL query string.
* @return {Promise<NodeJS.ReadableStream>} A stream of {@link IBindings}.
*/
fetchBindings(endpoint, query) {
return __awaiter(this, void 0, void 0, function* () {
const [contentType, responseStream] = yield this.fetchRawStream(endpoint, query, SparqlEndpointFetcher.CONTENTTYPE_SPARQL);
const parser = this.sparqlParsers[contentType];
if (!parser) {
throw new Error(`Unknown SPARQL results content type: ${contentType}`);
}
return parser.parseResultsStream(responseStream);
});
}
/**
* Send an ASK query to the given endpoint URL and return a promise resolving to the boolean answer.
* @param {string} endpoint A SPARQL endpoint URL. (without the `?query=` suffix).
* @param {string} query A SPARQL query string.
* @return {Promise<boolean>} A boolean resolving to the answer.
*/
fetchAsk(endpoint, query) {
return __awaiter(this, void 0, void 0, function* () {
const [contentType, responseStream] = yield this.fetchRawStream(endpoint, query, SparqlEndpointFetcher.CONTENTTYPE_SPARQL);
const parser = this.sparqlParsers[contentType];
if (!parser) {
throw new Error(`Unknown SPARQL results content type: ${contentType}`);
}
return parser.parseBooleanStream(responseStream);
});
}
/**
* Send a CONSTRUCT/DESCRIBE query to the given endpoint URL and return the resulting triple stream.
* @param {string} endpoint A SPARQL endpoint URL. (without the `?query=` suffix).
* @param {string} query A SPARQL query string.
* @return {Promise<Stream>} A stream of triples.
*/
fetchTriples(endpoint, query) {
return __awaiter(this, void 0, void 0, function* () {
const [contentType, responseStream] = yield this.fetchRawStream(endpoint, query, SparqlEndpointFetcher.CONTENTTYPE_TURTLE);
return responseStream.pipe(new n3_1.StreamParser({ format: contentType }));
});
}
/**
* Send an update query to the given endpoint URL using POST.
*
* @param {string} endpoint A SPARQL endpoint URL. (without the `?query=` suffix).
* @param {string} query A SPARQL query string.
*/
fetchUpdate(endpoint, query) {
return __awaiter(this, void 0, void 0, function* () {
const abortController = new AbortController();
const defaultHeadersRaw = {};
// Headers object does not have other means to iterate it according to the typings
// eslint-disable-next-line unicorn/no-array-for-each
this.defaultHeaders.forEach((value, key) => {
defaultHeadersRaw[key] = value;
});
const init = {
method: 'POST',
headers: Object.assign(Object.assign({}, defaultHeadersRaw), { 'content-type': 'application/sparql-update' }),
body: query,
signal: abortController.signal,
};
yield this.handleFetchCall(endpoint, init, { ignoreBody: true });
abortController.abort();
});
}
/**
* Send a query to the given endpoint URL and return the resulting stream.
*
* This will only accept responses with the application/sparql-results+json content type.
*
* @param {string} endpoint A SPARQL endpoint URL. (without the `?query=` suffix).
* @param {string} query A SPARQL query string.
* @param {string} acceptHeader The HTTP accept to use.
* @return {Promise<[string, NodeJS.ReadableStream]>} The content type and SPARQL endpoint response stream.
*/
fetchRawStream(endpoint, query, acceptHeader) {
return __awaiter(this, void 0, void 0, function* () {
let method;
let url;
if (this.method === 'POST' && this.forceGetIfUrlLengthBelow <= endpoint.length) {
method = this.method;
url = endpoint;
}
else {
const getEndpoint = `${endpoint}?query=${encodeURIComponent(query)}`;
method = this.method === 'GET' || getEndpoint.length < this.forceGetIfUrlLengthBelow ? 'GET' : 'POST';
url = method === 'POST' ? endpoint : getEndpoint;
}
// Initiate request
let body;
const headers = new Headers(this.defaultHeaders);
headers.append('Accept', acceptHeader);
if (method === 'POST') {
headers.append('Content-Type', 'application/x-www-form-urlencoded');
body = new URLSearchParams();
body.set('query', query);
for (const [key, value] of this.additionalUrlParams.entries()) {
body.set(key, value);
}
headers.append('Content-Length', body.toString().length.toString());
}
else if (this.additionalUrlParams.toString().length > 0) {
url += `&${this.additionalUrlParams.toString()}`;
}
return this.handleFetchCall(url, { headers, method, body });
});
}
/**
* Helper function to generalize internal fetch calls.
*
* @param {string} url The URL to call.
* @param {RequestInit} init Options to pass along to the fetch call.
* @param {any} options Other specific fetch options.
* @return {Promise<[string, NodeJS.ReadableStream]>} The content type and SPARQL endpoint response stream.
*/
handleFetchCall(url, init, options) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c;
let timeout;
let responseStream;
if (this.timeout) {
const controller = new AbortController();
init.signal = controller.signal;
timeout = setTimeout(() => controller.abort(), this.timeout);
}
const httpResponse = yield ((_a = this.fetchCb) !== null && _a !== void 0 ? _a : fetch)(url, init);
clearTimeout(timeout);
// Handle response body
if (!(options === null || options === void 0 ? void 0 : options.ignoreBody) && httpResponse.body) {
// Wrap WhatWG readable stream into a Node.js readable stream
// If the body already is a Node.js stream (in the case of node-fetch), don't do explicit conversion.
responseStream = (isStream(httpResponse.body) ? httpResponse.body : (0, readable_from_web_1.readableFromWeb)(httpResponse.body));
}
// Emit an error if the server returned an invalid response
if (!httpResponse.ok || (!responseStream && !(options === null || options === void 0 ? void 0 : options.ignoreBody))) {
const simpleUrl = url.split('?').at(0);
const bodyString = responseStream ? yield stringifyStream(responseStream) : 'empty response';
throw new Error(`Invalid SPARQL endpoint response from ${simpleUrl} (HTTP status ${httpResponse.status}):\n${bodyString}`);
}
// Determine the content type
const contentType = (_c = (_b = httpResponse.headers.get('Content-Type')) === null || _b === void 0 ? void 0 : _b.split(';').at(0)) !== null && _c !== void 0 ? _c : '';
return [contentType, responseStream];
});
}
}
exports.SparqlEndpointFetcher = SparqlEndpointFetcher;
SparqlEndpointFetcher.CONTENTTYPE_SPARQL_JSON = 'application/sparql-results+json';
SparqlEndpointFetcher.CONTENTTYPE_SPARQL_XML = 'application/sparql-results+xml';
SparqlEndpointFetcher.CONTENTTYPE_TURTLE = 'text/turtle';
SparqlEndpointFetcher.CONTENTTYPE_SPARQL = `${SparqlEndpointFetcher.CONTENTTYPE_SPARQL_JSON};q=1.0,${SparqlEndpointFetcher.CONTENTTYPE_SPARQL_XML};q=0.7`;
//# sourceMappingURL=SparqlEndpointFetcher.js.map