UNPKG

origintrail-node

Version:

OriginTrail Node - Decentralized Knowledge Graph Node Library

777 lines (679 loc) 30.6 kB
/* eslint-disable no-await-in-loop */ import { setTimeout } from 'timers/promises'; import { kcTools } from 'assertion-tools'; import { BASE_NAMED_GRAPHS, TRIPLE_STORE_REPOSITORY, TRIPLES_VISIBILITY, PRIVATE_HASH_SUBJECT_PREFIX, DKG_PREDICATE, HAS_KNOWLEDGE_ASSET_SUFFIX, HAS_NAMED_GRAPH_SUFFIX, DKG_METADATA_PREDICATES, SCHEMA_CONTEXT, MAX_TOKEN_ID_PER_GET_PAGE, } from '../constants/constants.js'; class TripleStoreService { constructor(ctx) { this.config = ctx.config; this.logger = ctx.config.logging.enableExperimentalScopes ? ctx.logger.child({ scope: 'TripleStoreService', }) : ctx.logger; this.tripleStoreModuleManager = ctx.tripleStoreModuleManager; this.operationIdService = ctx.operationIdService; this.ualService = ctx.ualService; this.dataService = ctx.dataService; this.paranetService = ctx.paranetService; this.cryptoService = ctx.cryptoService; } initializeRepositories() { this.repositoryImplementations = {}; for (const implementationName of this.tripleStoreModuleManager.getImplementationNames()) { for (const repository in this.tripleStoreModuleManager.getImplementation( implementationName, ).module.repositories) { this.repositoryImplementations[repository] = implementationName; } } } async insertKnowledgeCollection( repository, knowledgeCollectionUAL, triples, metadata, retries = 5, retryDelay = 50, paranetUAL = '', contentType = '', ) { this.logger.info( `Inserting Knowledge Collection with the UAL: ${knowledgeCollectionUAL} ` + `to the Triple Store's ${repository} repository.`, ); const publicAssertion = triples.public ?? triples; const filteredPublic = []; const privateHashTriples = []; const tripleSet = new Set(); let totalNumberOfTriplesInserted = triples?.public ? triples.public.length + (triples.private?.length ?? 0) : triples?.length ?? 0; publicAssertion.forEach((triple) => { if (triple.startsWith(`<${PRIVATE_HASH_SUBJECT_PREFIX}`)) { privateHashTriples.push(triple); } else { filteredPublic.push(triple); } }); const publicKnowledgeAssetsTriplesGrouped = kcTools.groupNquadsBySubject( filteredPublic, true, ); publicKnowledgeAssetsTriplesGrouped.push( ...kcTools.groupNquadsBySubject(privateHashTriples, true), ); const publicKnowledgeAssetsUALs = publicKnowledgeAssetsTriplesGrouped.map( (_, index) => `${knowledgeCollectionUAL}/${index + 1}`, ); const allPossibleNamedGraphs = []; let privateGraphsInsert = ''; let currentPrivateMetadataTriples = ''; let connectionPrivateMetadataTriples = ''; const publicGraphsInsert = publicKnowledgeAssetsUALs .map( (ual, index) => ` GRAPH <${ual}/${TRIPLES_VISIBILITY.PUBLIC}> { ${publicKnowledgeAssetsTriplesGrouped[index].join('\n')} } `, ) .join('\n'); const currentPublicMetadataTriples = publicKnowledgeAssetsUALs .map( (ual) => `<current:graph> <${DKG_PREDICATE}${HAS_NAMED_GRAPH_SUFFIX}> <${ual}/${TRIPLES_VISIBILITY.PUBLIC}> .`, ) .join('\n'); const connectionPublicMetadataTriples = publicKnowledgeAssetsUALs .map((ual) => { const graphWithVisibility = `${ual}/${TRIPLES_VISIBILITY.PUBLIC}`; return [ `<${knowledgeCollectionUAL}> <${DKG_PREDICATE}${HAS_KNOWLEDGE_ASSET_SUFFIX}> <${ual}> .`, `<${knowledgeCollectionUAL}> <${DKG_PREDICATE}${HAS_NAMED_GRAPH_SUFFIX}> <${graphWithVisibility}> .`, ].join('\n'); }) .join('\n'); // current metadata triple relates to which named graph that represents Knowledge Asset hold the lates(current) data // so for each Knowledge Asset there will be one current metadata triple // in this case there are publicKnowledgeAssetsUALs.length number of named graphs created so for each there will be one current metadata triple totalNumberOfTriplesInserted += publicKnowledgeAssetsUALs.length; publicKnowledgeAssetsUALs.forEach((ual) => { const graphWithVisibility = `${ual}/public`; tripleSet.add( `<${knowledgeCollectionUAL}> <${DKG_PREDICATE}${HAS_KNOWLEDGE_ASSET_SUFFIX}> <${ual}> .`, ); tripleSet.add( `<${knowledgeCollectionUAL}> <${DKG_PREDICATE}${HAS_NAMED_GRAPH_SUFFIX}> <${graphWithVisibility}> .`, ); }); this.logger.info( `Adding metadata triples for public asets for Knowledge Collection: ${knowledgeCollectionUAL}`, ); allPossibleNamedGraphs.push(...publicKnowledgeAssetsUALs.map((ual) => `${ual}/public`)); if (triples.private?.length) { const privateKnowledgeAssetsTriplesGrouped = kcTools.groupNquadsBySubject( triples.private, true, ); const privateKnowledgeAssetsUALs = []; const publicSubjectMap = publicKnowledgeAssetsTriplesGrouped.reduce( (map, group, index) => { const [publicSubject] = group[0].split(' '); map.set(publicSubject, index); return map; }, new Map(), ); for (const privateTriple of privateKnowledgeAssetsTriplesGrouped) { const [privateSubject] = privateTriple[0].split(' '); if (publicSubjectMap.has(privateSubject)) { const ualIndex = publicSubjectMap.get(privateSubject); privateKnowledgeAssetsUALs.push(publicKnowledgeAssetsUALs[ualIndex]); } else { const privateSubjectHashed = `<${PRIVATE_HASH_SUBJECT_PREFIX}${this.cryptoService.sha256( privateSubject.slice(1, -1), )}>`; if (publicSubjectMap.has(privateSubjectHashed)) { const ualIndex = publicSubjectMap.get(privateSubjectHashed); privateKnowledgeAssetsUALs.push(publicKnowledgeAssetsUALs[ualIndex]); } } } privateGraphsInsert = privateKnowledgeAssetsUALs .map( (ual, index) => ` GRAPH <${ual}/${TRIPLES_VISIBILITY.PRIVATE}> { ${privateKnowledgeAssetsTriplesGrouped[index].join('\n')} } `, ) .join('\n'); currentPrivateMetadataTriples = privateKnowledgeAssetsUALs .map( (ual) => `<current:graph> <${DKG_PREDICATE}${HAS_NAMED_GRAPH_SUFFIX}> <${ual}/${TRIPLES_VISIBILITY.PRIVATE}> .`, ) .join('\n'); connectionPrivateMetadataTriples = privateKnowledgeAssetsUALs .map((ual) => { const graphWithVisibility = `${ual}/${TRIPLES_VISIBILITY.PRIVATE}`; return [ `<${knowledgeCollectionUAL}> <${DKG_PREDICATE}${HAS_KNOWLEDGE_ASSET_SUFFIX}> <${ual}> .`, `<${knowledgeCollectionUAL}> <${DKG_PREDICATE}${HAS_NAMED_GRAPH_SUFFIX}> <${graphWithVisibility}> .`, ].join('\n'); }) .join('\n'); // current metadata triple relates to which named graph that represents Knowledge Asset hold the lates(current) data // so for each Knowledge Asset there will be one current metadata triple // in this case there are privateKnowledgeAssetsUALs.length number of named graphs created so for each there will be one current metadata triple totalNumberOfTriplesInserted += privateKnowledgeAssetsUALs.length; privateKnowledgeAssetsUALs.forEach((ual) => { const graphWithVisibility = `${ual}/private`; tripleSet.add( `<${knowledgeCollectionUAL}> <${DKG_PREDICATE}${HAS_KNOWLEDGE_ASSET_SUFFIX}> <${ual}> .`, ); tripleSet.add( `<${knowledgeCollectionUAL}> <${DKG_PREDICATE}${HAS_NAMED_GRAPH_SUFFIX}> <${graphWithVisibility}> .`, ); }); this.logger.info( `Adding metadata triples for private asets for Knowledge Collection: ${knowledgeCollectionUAL}`, ); allPossibleNamedGraphs.push( ...privateKnowledgeAssetsUALs.map((ual) => `${ual}/private`), ); } // TODO: add new metadata triples and move to function insertMetadataTriples let metadataTriples = publicKnowledgeAssetsUALs .map( (publicKnowledgeAssetUAL) => `<${publicKnowledgeAssetUAL}> <http://schema.org/states> "${publicKnowledgeAssetUAL}:0" .`, ) .join('\n'); metadataTriples += `\n<${knowledgeCollectionUAL}> <${DKG_METADATA_PREDICATES.PUBLISHED_BY}> <did:dkg:publisherKey/${metadata.publisherKey}> .` + `\n<${knowledgeCollectionUAL}> <${DKG_METADATA_PREDICATES.PUBLISHED_AT_BLOCK}> "${metadata.blockNumber}" .` + `\n<${knowledgeCollectionUAL}> <${DKG_METADATA_PREDICATES.PUBLISH_TX}> "${metadata.txHash}" .` + `\n<${knowledgeCollectionUAL}> <${ DKG_METADATA_PREDICATES.PUBLISH_TIME }> "${new Date().toISOString()}"^^<http://www.w3.org/2001/XMLSchema#dateTime> .` + `\n<${knowledgeCollectionUAL}> <${DKG_METADATA_PREDICATES.BLOCK_TIME}> "${new Date( metadata.blockTimestamp * 1000, ).toISOString()}"^^<http://www.w3.org/2001/XMLSchema#dateTime> .`; // totalNumberOfTriplesInserted += publicKnowledgeAssetsUALs.length + 5; // one metadata triple for each public KA const insertQuery = ` PREFIX schema: <${SCHEMA_CONTEXT}> INSERT DATA { ${publicGraphsInsert} ${privateGraphsInsert} GRAPH <${BASE_NAMED_GRAPHS.CURRENT}> { ${currentPublicMetadataTriples} ${currentPrivateMetadataTriples} } GRAPH <${BASE_NAMED_GRAPHS.METADATA}> { ${connectionPublicMetadataTriples} ${connectionPrivateMetadataTriples} ${metadataTriples} } } `; const uniqueTripleCount = tripleSet.size; totalNumberOfTriplesInserted += uniqueTripleCount; let attempts = 0; let success = false; while (attempts < retries && !success) { try { await this.tripleStoreModuleManager.queryVoid( this.repositoryImplementations[repository], repository, insertQuery, this.config.modules.tripleStore.timeout.insert, ); if (paranetUAL) { await this.tripleStoreModuleManager.createParanetKnoledgeCollectionConnection( this.repositoryImplementations[repository], repository, knowledgeCollectionUAL, paranetUAL, contentType, this.config.modules.tripleStore.timeout.insert, ); totalNumberOfTriplesInserted += allPossibleNamedGraphs.length; // one triple will be created for each Knowledge Asset inserted into paranet this.logger.info(`Adding connection triples for paranet: ${paranetUAL}`); } success = true; this.logger.info( `Knowledge Collection with the UAL: ${knowledgeCollectionUAL} ` + `has been successfully inserted to the Triple Store's ${repository} repository.`, ); } catch (error) { this.logger.error( `Error during insertion of the Knowledge Collection to the Triple Store's ${repository} repository. ` + `UAL: ${knowledgeCollectionUAL}. Error: ${error.message}`, ); attempts += 1; if (attempts < retries) { this.logger.info( `Retrying insertion of the Knowledge Collection with the UAL: ${knowledgeCollectionUAL} ` + `to the Triple Store's ${repository} repository. Attempt ${ attempts + 1 } of ${retries} after delay of ${retryDelay} ms.`, ); await setTimeout(retryDelay); } else { this.logger.error( `Max retries reached for the insertion of the Knowledge Collection with the UAL: ${knowledgeCollectionUAL} ` + `to the Triple Store's ${repository} repository. Rolling back data.`, ); this.logger.info( `Rolling back Knowledge Collection with the UAL: ${knowledgeCollectionUAL} ` + `from the Triple Store's ${repository} repository Named Graphs.`, ); await Promise.all([ this.tripleStoreModuleManager.deleteKnowledgeCollectionNamedGraphs( this.repositoryImplementations[repository], repository, allPossibleNamedGraphs, ), this.tripleStoreModuleManager.deleteKnowledgeCollectionMetadata( this.repositoryImplementations[repository], repository, allPossibleNamedGraphs, ), ]); throw new Error( `Failed to store Knowledge Collection with the UAL: ${knowledgeCollectionUAL} ` + `to the Triple Store's ${repository} repository after maximum retries. Error ${error}`, ); } } } return totalNumberOfTriplesInserted; } async insertKnowledgeCollectionBatch(repository, KCs) { // this.logger.info( // `Inserting Knowledge Collection with the UAL: ${knowledgeCollectionUAL} ` + // `to the Triple Store's ${repository} repository.`, // ); // This metadata is not validated const { remote, metadata } = KCs; const insert = {}; const createdMetadata = []; const currentNamedGraphTriples = []; // remote { ual: { public: [triples], private: [triples] } } for (const ual of Object.keys(remote)) { const triples = remote[ual].public; const filteredPublic = []; const privateHashTriples = []; triples.forEach((triple) => { if (triple.startsWith(`<${PRIVATE_HASH_SUBJECT_PREFIX}`)) { privateHashTriples.push(triple); } else { filteredPublic.push(triple); } }); const publicKnowledgeAssetsTriplesGrouped = kcTools.groupNquadsBySubject( filteredPublic, true, ); publicKnowledgeAssetsTriplesGrouped.push( ...kcTools.groupNquadsBySubject(privateHashTriples, true), ); const publicKnowledgeAssetsUALs = publicKnowledgeAssetsTriplesGrouped.map( (_, index) => `${ual}/${index + 1}`, ); for (const [index, kaUAL] of publicKnowledgeAssetsUALs.entries()) { insert[`${kaUAL}/public`] = publicKnowledgeAssetsTriplesGrouped[index]; createdMetadata.push(`<${kaUAL}> <http://schema.org/states> "${kaUAL}:0" .`); currentNamedGraphTriples.push( `<current:graph> <https://ontology.origintrail.io/dkg/1.0#hasNamedGraph> <${kaUAL}/public> .`, ); createdMetadata.push( `<${ual}> <https://ontology.origintrail.io/dkg/1.0#hasKnowledgeAsset> <${kaUAL}> .`, ); } } await this.tripleStoreModuleManager.insertAssertionBatch( TRIPLE_STORE_REPOSITORY.DKG, repository, insert, metadata, createdMetadata, currentNamedGraphTriples, this.config.modules.tripleStore.timeout.insert, ); } async deletePublishTimestampMetadata(repository, ual) { await this.tripleStoreModuleManager.deletePublishTimestampMetadata( this.repositoryImplementations[repository], repository, ual, ); } async checkIfKnowledgeCollectionExistsInUnifiedGraph( ual, repository = TRIPLE_STORE_REPOSITORY.DKG, ) { const knowledgeCollectionExists = await this.tripleStoreModuleManager.knowledgeCollectionExistsInUnifiedGraph( this.repositoryImplementations[repository], repository, BASE_NAMED_GRAPHS.UNIFIED, ual, ); return knowledgeCollectionExists; } async getAssertion( blockchain, contract, knowledgeCollectionId, knowledgeAssetId, tokenIds, migrationFlag, visibility = TRIPLES_VISIBILITY.PUBLIC, repository = TRIPLE_STORE_REPOSITORY.DKG, operationId = undefined, ) { // TODO: Use stateId let ual = `did:dkg:${blockchain}/${contract}/${knowledgeCollectionId}`; // Performance instrumentation (enable only if operationId is supplied) const logTime = operationId !== undefined; const startTimer = (label) => { if (logTime) this.logger.startTimer(label); }; const endTimer = (label) => { if (logTime) this.logger.endTimer(label); }; const totalLabel = `[TripleStoreService.getAssertion TOTAL] ${operationId} ${ual}`; startTimer(totalLabel); let nquads = {}; if (typeof knowledgeAssetId === 'number') { ual = `${ual}/${knowledgeAssetId}`; const singleLabel = `[TripleStoreService.getAssertion SINGLE] ${operationId} ${ual}`; startTimer(singleLabel); this.logger.debug(`Getting Assertion with the UAL: ${ual}.`); nquads = await this.tripleStoreModuleManager.getKnowledgeAssetNamedGraph( this.repositoryImplementations[repository], repository, // TODO: Add state with implemented update `${ual}`, visibility, this.config.modules.tripleStore.timeout.get, ); endTimer(singleLabel); } else { this.logger.debug(`Getting Assertion with the UAL: ${ual}.`); const existsLabel = `[TripleStoreService.getAssertion EXISTS_CHECK] ${operationId} ${ual}`; startTimer(existsLabel); // first check if the knowledge collection exists in triple store using ASK const firstKAInCollection = `${ual}/${tokenIds.startTokenId}/${TRIPLES_VISIBILITY.PUBLIC}`; const lastKAInCollection = `${ual}/${tokenIds.endTokenId}/${TRIPLES_VISIBILITY.PUBLIC}`; const firstKAExists = this.tripleStoreModuleManager.checkIfKnowledgeAssetExists( this.repositoryImplementations[repository], repository, firstKAInCollection, this.config.modules.tripleStore.timeout.ask, ); const lastKAExists = this.tripleStoreModuleManager.checkIfKnowledgeAssetExists( this.repositoryImplementations[repository], repository, lastKAInCollection, this.config.modules.tripleStore.timeout.ask, ); const [firstKAResult, lastKAResult] = await Promise.all([firstKAExists, lastKAExists]); endTimer(existsLabel); if (!(firstKAResult && lastKAResult)) { this.logger.warn( `Knowledge Collection with the UAL: ${ual} does not exist in the Triple Store's ${repository} repository.`, ); endTimer(totalLabel); return { public: [], private: [] }; } // tokenIds are used to construct named graphs // do pagination through tokenIds const collectionLabel = `[TripleStoreService.getAssertion COLLECTION] ${operationId} ${ual}`; startTimer(collectionLabel); if (visibility === TRIPLES_VISIBILITY.PUBLIC || visibility === TRIPLES_VISIBILITY.ALL) { nquads.public = []; } if ( visibility === TRIPLES_VISIBILITY.PRIVATE || visibility === TRIPLES_VISIBILITY.ALL ) { nquads.private = []; } const maxTokenId = tokenIds.endTokenId; for (let i = 0; i <= tokenIds.endTokenId; i += MAX_TOKEN_ID_PER_GET_PAGE) { const paginationNquads = await this.tripleStoreModuleManager.getKnowledgeCollectionNamedGraphsOld( this.repositoryImplementations[repository], repository, ual, { startTokenId: i + 1, endTokenId: Math.min(i + MAX_TOKEN_ID_PER_GET_PAGE, maxTokenId), burned: tokenIds.burned, }, visibility, this.config.modules.tripleStore.timeout.get, ); if (paginationNquads?.public) { nquads.public.push( ...paginationNquads.public.split('\n').filter((line) => line !== ''), ); } if (paginationNquads?.private) { nquads.private.push( ...paginationNquads.private.split('\n').filter((line) => line !== ''), ); } } endTimer(collectionLabel); } const numberOfnquads = (nquads?.public?.length ?? 0) + (nquads?.private?.length ?? 0); this.logger.debug( `Assertion: ${ual} ${ numberOfnquads ? '' : 'is not' } found in the Triple Store's ${repository} repository.`, ); if (nquads.length) { this.logger.debug( `Number of n-quads retrieved from the Triple Store's ${repository} repository: ${numberOfnquads}.`, ); } endTimer(totalLabel); return nquads; } async getAssertionsInBatch( repository, uals, ualTokenIds, visibility = 'public', operationId = undefined, ) { // Conditional performance logging const logTime = operationId !== undefined; const startTimer = (label) => { if (logTime) this.logger.startTimer(label); }; const endTimer = (label) => { if (logTime) this.logger.endTimer(label); }; const totalLabel = `[TripleStoreService.getAssertionsInBatch TOTAL] ${operationId} ${uals.length}`; startTimer(totalLabel); const results = await Promise.all( uals.map(async (ual) => { const { blockchain, contract, knowledgeCollectionId } = this.ualService.resolveUAL(ual); return this.getAssertion( blockchain, contract, knowledgeCollectionId, null, ualTokenIds[ual], false, visibility, repository, operationId, ); }), ); const result = {}; for (const [index, ual] of uals.entries()) { result[ual] = results[index]; } endTimer(totalLabel); return result; } async getV6Assertion(repository, assertionId) { this.logger.debug( `Getting Assertion with the ID: ${assertionId} from the Triple Store's ${repository} repository.`, ); const nquads = await this.tripleStoreModuleManager.getV6Assertion( this.repositoryImplementations[repository], repository, assertionId, ); this.logger.debug( `Assertion: ${assertionId} ${ nquads.length ? '' : 'is not' } found in the Triple Store's ${repository} repository.`, ); if (nquads.length) { this.logger.debug( `Number of n-quads retrieved from the Triple Store's ${repository} repository: ${nquads.length}.`, ); } return nquads; } async checkIfKnowledgeAssetExists(repository, kaUAL) { const knowledgeAssetExists = await this.tripleStoreModuleManager.checkIfKnowledgeAssetExists( this.repositoryImplementations[repository], repository, kaUAL, ); return knowledgeAssetExists; } async getAssertionMetadata( blockchain, contract, knowledgeCollectionId, knowledgeAssetId, repository = TRIPLE_STORE_REPOSITORY.DKG, ) { const ual = `did:dkg:${blockchain}/${contract}/${knowledgeCollectionId}${ Number.isInteger(knowledgeAssetId) ? `/${knowledgeAssetId}` : '' }`; this.logger.debug(`Getting Assertion Metadata with the UAL: ${ual}.`); let nquads; if (Number.isInteger(knowledgeAssetId)) { nquads = await this.tripleStoreModuleManager.getKnowledgeAssetMetadata( this.repositoryImplementations[repository], repository, ual, this.config.modules.tripleStore.timeout.get, ); } else { nquads = await this.tripleStoreModuleManager.getKnowledgeCollectionMetadata( this.repositoryImplementations[repository], repository, ual, this.config.modules.tripleStore.timeout.get, ); } nquads = nquads.split('\n').filter((line) => line !== ''); this.logger.debug( `Knowledge Asset Metadata: ${ual} ${ nquads.length ? '' : 'is not' } found in the Triple Store's ${repository} repository.`, ); if (nquads.length) { this.logger.debug( `Number of n-quads retrieved from the Triple Store's ${repository} repository: ${nquads.length}.`, ); } return nquads; } async getAssertionMetadataBatch(uals) { const metadataTriples = await this.tripleStoreModuleManager.getMetadataInBatch( this.repositoryImplementations[TRIPLE_STORE_REPOSITORY.DKG], TRIPLE_STORE_REPOSITORY.DKG, uals, ); const metadata = {}; for (const line of metadataTriples.split('\n').filter((result) => result !== '')) { const splitLine = line.split(' '); const ual = splitLine[0].replace(/[<>]/g, ''); if (!metadata[ual]) { metadata[ual] = [line]; } else { metadata[ual].push(line); } } return metadata; } async getLatestAssertionId(repository, ual) { const nquads = await this.tripleStoreModuleManager.getLatestAssertionId( this.repositoryImplementations[repository], repository, ual, ); return nquads; } async construct(query, repository = TRIPLE_STORE_REPOSITORY.DKG, timeout = 60000) { return this.tripleStoreModuleManager.construct( this.repositoryImplementations[repository] ?? this.repositoryImplementations[TRIPLE_STORE_REPOSITORY.DKG], repository, query, timeout, ); } async getKnowledgeAssetNamedGraph(repository, ual, visibility, timeout) { return this.tripleStoreModuleManager.getKnowledgeAssetNamedGraph( this.repositoryImplementations[repository], repository, ual, visibility, timeout, ); } async select(query, repository = TRIPLE_STORE_REPOSITORY.DKG, timeout = 60000) { return this.tripleStoreModuleManager.select( this.repositoryImplementations[repository] ?? this.repositoryImplementations[TRIPLE_STORE_REPOSITORY.DKG], repository, query, timeout, ); } async ask(query, repository = TRIPLE_STORE_REPOSITORY.DKG) { return this.tripleStoreModuleManager.ask( this.repositoryImplementations[repository] ?? this.repositoryImplementations[TRIPLE_STORE_REPOSITORY.DKG], repository, query, ); } getRepositorySparqlEndpoint(repository) { const implementationName = this.repositoryImplementations[repository]; const endpoint = this.tripleStoreModuleManager.getImplementation(implementationName).module.repositories[ repository ].sparqlEndpoint; return endpoint; } } export default TripleStoreService;