import stardog from 'stardog'
import SparqlClient from 'sparql-http-client'
import { Store } from './store.js'
import { chunkBetween } from './utils.js'
const { Connection, db, query } = stardog
/**
* Class to interact with a Stardog store
*
* @extends Store
*/
export class Stardog extends Store {
/**
* @param {Object} [options={}] Connection params
* @param {string} [options.user=admin] Username
* @param {string} [options.password=admin] Password
* @param {string} [options.endpoint=http://localhost:5820] Endpoint
*/
constructor (options = {}) {
super()
this.endpointParams = Object.assign({ user: 'admin', password: 'admin', endpoint: 'http://localhost:5820' }, options)
this._conn = new Connection({
username: this.endpointParams.user,
password: this.endpointParams.password,
endpoint: this.endpointParams.endpoint
})
}
/**
* Creates a database
*
* <https://github.com/stardog-union/stardog.js/tree/065edf84d92f50dc6ad9a6548d0bc9b16325cd3d#dbcreateconn-database-databaseoptions-options-params>
*
* @async
* @param {string} dbname
* @param {Object} [options]
* @param {Object} [options.databaseOptions={}]
* @param {Object} [options.options={}]
* @param {Object} [options.params={}]
*/
async createDb (dbname, options = {}) {
options = Object.assign({ databaseOptions: {}, options: {}, params: {} }, options)
return this._handleResult(
db.create(this.conn, dbname, options.databaseOptions, options.options, options.params)
)
}
/**
* Deletes a database
*
* <https://github.com/stardog-union/stardog.js/tree/065edf84d92f50dc6ad9a6548d0bc9b16325cd3d#dbdropconn-database-params>
*
* @async
* @param {string} dbname
* @param {Object} [options]
* @param {Object} [options.params={}]
*/
async dropDb (dbname, options = {}) {
options = Object.assign({ params: {} }, options)
return this._handleResult(
db.drop(this.conn, dbname, options.params)
)
}
/**
* Empties a database
*
* <https://github.com/stardog-union/stardog.js/tree/065edf84d92f50dc6ad9a6548d0bc9b16325cd3d#dbdropconn-database-params>
*
* @async
* @param {string} dbname
* @param {Object} [options]
* @param {Object} [options.params={}]
*/
async clearDb (dbname, options = {}) {
options = Object.assign({ params: {} }, options)
const tx = new Transaction(this.conn, dbname, options.params)
await tx.add((transactionId) => db.clear(this.conn, dbname, transactionId, options.params))
return this._handleResult(tx.execute())
}
/**
* Brings an offline database back online so that it can accept connections.
*
* <https://github.com/stardog-union/stardog.js/tree/065edf84d92f50dc6ad9a6548d0bc9b16325cd3d#dbonlineconn-database-params>
*
* @async
* @param {string} dbname
* @param {Object} [options]
* @param {Object} [options.params={}]
*/
async online (dbname, options = {}) {
options = Object.assign({ params: {} }, options)
return this._handleResult(
db.online(this.conn, dbname, options.params)
)
}
/**
* Brings an online database offline.
*
* <https://github.com/stardog-union/stardog.js/tree/065edf84d92f50dc6ad9a6548d0bc9b16325cd3d#dbofflineconn-database-params>
*
* @async
* @param {string} dbname
* @param {Object} [options]
* @param {Object} [options.params={}]
*/
async offline (dbname, options = {}) {
options = Object.assign({ params: {} }, options)
return this._handleResult(
db.offline(this.conn, dbname, options.params)
)
}
/**
* Issues an ASK query
*
* @async
* @param {string} dbname
* @param {string} sparql query
* @param {Object} [options]
* @param {Object} [options.options]
* @param {string} [options.options.accept=application/json]
* @param {string} [options.params={}]
* @param {string} [options.additionalHandlers={}]
*/
async ask (dbname, sparql, options = {}) {
return this._readQuery(dbname, sparql, options)
}
/**
* Issues a CONSTRUCT query
*
* @async
* @param {string} dbname
* @param {string} sparql query
* @param {Object} [options]
* @param {Object} [options.options]
* @param {string} [options.options.accept=application/json]
* @param {string} [options.params={}]
* @param {string} [options.additionalHandlers={}]
*/
async construct (dbname, sparql, options = {}) {
return this._readQuery(dbname, sparql, options)
}
/**
* Issues a DESCRIBE query
*
* @async
* @param {string} dbname
* @param {string} sparql query
* @param {Object} [options]
* @param {Object} [options.options]
* @param {string} [options.options.accept=application/json]
* @param {string} [options.params={}]
* @param {string} [options.additionalHandlers={}]
*/
async describe (dbname, sparql, options = {}) {
return this._readQuery(dbname, sparql, options)
}
/**
* Issues a SELECT query
*
* @async
* @param {string} dbname
* @param {string} sparql query
* @param {Object} [options]
* @param {Object} [options.options]
* @param {string} [options.options.accept=application/json]
* @param {string} [options.params={}]
* @param {string} [options.additionalHandlers={}]
*/
async select (dbname, sparql, options = {}) {
return this._readQuery(dbname, sparql, options)
}
/**
* Issues a SPARQL UPDATE query
*
* <https://github.com/stardog-union/stardog.js/#queryexecuteconn-database-query-accept-params-additionalhandlers>
* @async
* @param {string} dbname
* @param {string} sparql query
* @param {Object} [options]
* @param {Object} [options.options]
* @param {string} [options.options.accept=application/json]
* @param {string} [options.params={}]
* @param {string} [options.additionalHandlers={}]
*/
async update (dbname, sparql, options = {}) {
return this._query(dbname, sparql, options)
}
/**
* Issues many SPARQL queries in a transaction
*
* <https://github.com/stardog-union/stardog.js/#queryexecuteconn-database-query-accept-params-additionalhandlers>
* @async
* @param {string} dbname
* @param {string[]} sparqlQueries queries
* @param {Object} [options]
* @param {Object} [options.options]
* @param {string} [options.options.accept=application/json]
* @param {string} [options.params={}]
* @param {string} [options.additionalHandlers={}]
*/
async queries (dbname, sparqlQueries, options = {}) {
options = Object.assign({ options: { accept: 'application/json' }, params: {}, additionalHandlers: {} }, options)
const tx = new Transaction(this.conn, dbname, options.params)
for (let i = 0; i < sparqlQueries.length; i++) {
const chunk = sparqlQueries[i]
const result = await tx.add((transactionId) => query.executeInTransaction(this.conn, dbname, transactionId, chunk, options.options, options.params))
if (!result.ok) {
this.log(result.body)
throw new Error(result.body.message)
}
}
return this._handleResult(tx.execute())
}
/**
* Loads triples or quads into a database
*
* @async
* @param {string} dbname
* @param {string|Buffer} ntriples triples or quads
* @param {string=} graph named graph to insert to, defaults to default graph
*/
async import (dbname, ntriples, graph) {
const mapper = graph
? (chunk) => `INSERT DATA { GRAPH <${graph}> { ${chunk} } }`
: (chunk) => `INSERT DATA { ${chunk} }`
const queries = chunkBetween(ntriples, mapper, 1_000)
return this.queries(dbname, queries)
}
/**
* Creates a [SPARQL HTTP Client](https://zazuko.github.io/sparql-http-client/) for a database
*
* @param {string} dbname
* @param {Object} options
* @param {HeadersInit} [options.headers] HTTP headers to send with every endpoint request
* @param {string} [options.user=username set when instantiating the store] user used for basic authentication
* @param {string} [options.password=password set when instantiating the store] password used for basic authentication
* @param {string} [options.endpointUrl=URL generated based on the store endpoint] SPARQL Query endpoint URL
* @param {string} [options.updateUrl=URL generated based on the store endpoint] SPARQL Update endpoint URL
* @param {string} [options.storeUrl] Graph Store URL
* @param {fetch} [options.fetch=nodeify-fetch] fetch implementation
* @param {factory} [options.factory=uses @rdfjs/data-model by default] RDF/JS DataFactory
* @returns {SparqlHttpClient}
*/
sparqlClientFor (dbname, options) {
options = Object.assign({
user: this.endpointParams.user,
password: this.endpointParams.password,
endpointUrl: `${this.endpointParams.endpoint}/${dbname}/query`,
updateUrl: `${this.endpointParams.endpoint}/${dbname}/update`,
storeUrl: `${this.endpointParams.endpoint}/${dbname}`
}, options)
return new SparqlClient(options)
}
// https://github.com/stardog-union/stardog.js/#queryexecuteconn-database-query-accept-params-additionalhandlers
async _readQuery (dbname, sparql, options = {}) {
return this._query(dbname, sparql, options)
}
async _query (dbname, sparql, options = {}) {
options = Object.assign({ options: { accept: 'application/json' }, params: {}, additionalHandlers: {} }, options)
return this._handleResult(
query.execute(this.conn, dbname, sparql, options.accept, options.params, options.additionalHandlers)
)
}
async _handleResult (response) {
const result = await response
if (result.ok) {
if (result.body?.message) {
return result.body.message
}
if (typeof result.body !== 'undefined' && result.body !== null) {
return result.body
}
return true
}
else {
console.error(result)
}
return null
}
}
class Transaction {
constructor (conn, dbname, params) {
this.conn = conn
this.dbname = dbname
this.params = params
}
async add (fn) {
if (!this.tx) {
await this.begin()
}
return fn(this.tx)
}
async execute () {
const result = await db.transaction.commit(this.conn, this.dbname, this.tx, this.params)
if (!result.ok) {
console.warn('rolling back')
await db.transaction.rollback(this.conn, this.dbname, this.tx, this.params)
}
return result
}
async begin () {
const response = await db.transaction.begin(this.conn, this.dbname, this.params)
this.tx = response.transactionId
}
}