UNPKG

atuin

Version:

An RDF and SPARQL editor with graph visualization

147 lines (127 loc) 5.71 kB
/** * @file SparqlService.js * Service for executing SPARQL queries against a given endpoint. */ import { eventBus, EVENTS } from 'evb'; export class SparqlService { /** * Creates a new SparqlService. * @param {LoggerService} logger - The logger service instance. */ constructor(logger) { this.logger = logger; } /** * Executes a SPARQL query against the specified endpoint. * @param {string} query - The SPARQL query string. * @param {string} endpoint - The URL of the SPARQL endpoint. * @returns {Promise<object>} A promise that resolves with the response from the endpoint or rejects with an error. */ async executeQuery(query, endpoint) { this.logger.info(`Executing query on endpoint: ${endpoint}`); this.logger.debug(`Query:\n${query}`); if (!endpoint) { this.logger.error('Endpoint URL is missing.'); return Promise.reject(new Error('SPARQL endpoint URL is required.')); } if (!query || query.trim() === '') { this.logger.error('SPARQL query is empty.'); return Promise.reject(new Error('SPARQL query cannot be empty.')); } // Determine query type to set appropriate Accept header const queryType = this._detectQueryType(query); const acceptHeader = queryType === 'CONSTRUCT' || queryType === 'DESCRIBE' ? 'text/turtle, application/rdf+xml, application/n-triples, text/n3, application/trig, application/rdf+json, application/sparql-results+json' : 'application/sparql-results+json'; try { const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', // Standard for SPARQL POST requests 'Accept': acceptHeader }, // SPARQL query is typically sent as a URL-encoded parameter named 'query' body: `query=${encodeURIComponent(query)}` }); if (!response.ok) { let errorDetails = `HTTP error ${response.status}`; try { const errorText = await response.text(); errorDetails += ` - ${errorText}`; } catch (e) { // Ignore if can't read error text } this.logger.error(`Failed to execute SPARQL query: ${errorDetails}`); throw new Error(`Failed to execute SPARQL query: ${errorDetails}`); } // Handle different response types based on content-type const contentType = response.headers.get('content-type') || ''; // Enhanced logging to debug content-type detection this.logger.info(`Response content-type: "${contentType}"`); this.logger.info(`Query type detected: ${queryType}`); this.logger.info(`Accept header sent: ${acceptHeader}`); if (this._isRdfContentType(contentType)) { // Handle RDF response - route to turtle editor and graph visualization const rdfContent = await response.text(); this.logger.info(`SPARQL CONSTRUCT/DESCRIBE query executed successfully - routing RDF (${contentType}) to editor/graph`); this.logger.debug('RDF content:', rdfContent); // Emit MODEL_SYNCED event to update turtle editor and graph visualization eventBus.emit(EVENTS.MODEL_SYNCED, rdfContent); return { type: 'rdf', content: rdfContent, contentType: contentType, message: `CONSTRUCT/DESCRIBE results (${contentType}) loaded into turtle editor and graph visualization` }; } else { // Handle JSON response (SELECT, ASK queries) const results = await response.json(); this.logger.info('SPARQL query executed successfully.'); this.logger.debug('Results:', results); return results; } } catch (error) { this.logger.error('Error during SPARQL query execution:', error); // Re-throw the error so the caller can handle it throw error; } } /** * Detects the type of SPARQL query * @private * @param {string} query - The SPARQL query string * @returns {string} Query type ('SELECT', 'ASK', 'CONSTRUCT', 'DESCRIBE', 'INSERT', 'DELETE') */ _detectQueryType(query) { const trimmedQuery = query.trim().toUpperCase(); if (trimmedQuery.startsWith('SELECT')) return 'SELECT'; if (trimmedQuery.startsWith('ASK')) return 'ASK'; if (trimmedQuery.startsWith('CONSTRUCT')) return 'CONSTRUCT'; if (trimmedQuery.startsWith('DESCRIBE')) return 'DESCRIBE'; if (trimmedQuery.startsWith('INSERT')) return 'INSERT'; if (trimmedQuery.startsWith('DELETE')) return 'DELETE'; // Default to SELECT if cannot detect return 'SELECT'; } /** * Checks if a content-type indicates RDF data that should be routed to the turtle editor * @private * @param {string} contentType - The content-type header value * @returns {boolean} True if the content-type indicates RDF data */ _isRdfContentType(contentType) { const lowercaseType = contentType.toLowerCase(); // Common RDF media types returned by SPARQL endpoints for CONSTRUCT/DESCRIBE queries const rdfMediaTypes = [ 'text/turtle', // Most common turtle format 'application/turtle', // Alternative turtle media type 'application/rdf+xml', // RDF/XML format (often default) 'application/n-triples', // N-Triples format 'text/n3', // Notation3 format 'application/trig', // TriG format (Turtle for named graphs) 'application/rdf+json', // RDF/JSON format 'text/plain' // Sometimes used for N-Triples ]; return rdfMediaTypes.some(mediaType => lowercaseType.includes(mediaType)); } }