quadstore
Version:
Quadstore is a LevelDB-backed RDF graph database / triplestore for JavaScript runtimes (browsers, Node.js, Deno, Bun, ...) that implements the RDF/JS interfaces and supports SPARQL queries and querying across named graphs.
250 lines • 8.72 kB
JavaScript
'use strict';
import { ResultType } from './types/index.js';
import { EventEmitter } from 'events';
import { EmptyIterator, wrap, } from 'asynciterator';
import { streamToArray, ensureAbstractLevel, } from './utils/stuff.js';
import { emptyObject, defaultIndexes, separator, levelPutOpts, levelDelOpts, emptyValue, } from './utils/constants.js';
import { consumeOneByOne } from './utils/consumeonebyone.js';
import { consumeInBatches } from './utils/consumeinbatches.js';
import { uid } from './utils/uid.js';
import { getApproximateSize, getStream } from './get/index.js';
import { Scope } from './scope/index.js';
import { twoStepsQuadWriter } from './serialization/quads.js';
export class Quadstore {
db;
indexes;
id;
prefixes;
dataFactory;
constructor(opts) {
ensureAbstractLevel(opts.backend, '"opts.backend"');
this.dataFactory = opts.dataFactory;
this.db = opts.backend;
this.indexes = [];
this.id = uid();
(opts.indexes || defaultIndexes)
.forEach((index) => this._addIndex(index));
this.prefixes = opts.prefixes || {
expandTerm: term => term,
compactIri: iri => iri,
};
}
ensureReady() {
if (this.db.status !== 'open') {
throw new Error(`Store is not ready (status: "${this.db.status}"). Did you call store.open()?`);
}
}
async open() {
if (this.db.status !== 'open') {
await this.db.open();
}
}
async close() {
if (this.db.status !== 'closed') {
await this.db.close();
}
}
toString() {
return this.toJSON();
}
toJSON() {
return `[object ${this.constructor.name}::${this.id}]`;
}
_addIndex(terms) {
const name = terms.map(t => t.charAt(0).toUpperCase()).join('');
this.indexes.push({
terms,
prefix: name + separator,
});
}
async clear() {
await this.db.clear();
}
match(subject, predicate, object, graph, opts = emptyObject) {
if (subject && subject.termType === 'Literal') {
return new EmptyIterator();
}
const pattern = { subject, predicate, object, graph };
return wrap(this.getStream(pattern, opts).then(results => results.iterator));
}
async countQuads(subject, predicate, object, graph, opts = emptyObject) {
if (subject && subject.termType === 'Literal') {
return 0;
}
const pattern = { subject, predicate, object, graph };
const results = await this.getApproximateSize(pattern, opts);
return results.approximateSize;
}
import(source) {
const emitter = new EventEmitter();
this.putStream(source, {})
.then(() => { emitter.emit('end'); })
.catch((err) => { emitter.emit('error', err); });
return emitter;
}
remove(source) {
const emitter = new EventEmitter();
this.delStream(source, {})
.then(() => emitter.emit('end'))
.catch((err) => emitter.emit('error', err));
return emitter;
}
removeMatches(subject, predicate, object, graph, opts = emptyObject) {
const source = this.match(subject, predicate, object, graph, opts);
return this.remove(source);
}
deleteGraph(graph) {
return this.removeMatches(undefined, undefined, undefined, graph);
}
async getApproximateSize(pattern, opts = emptyObject) {
this.ensureReady();
return await getApproximateSize(this, pattern, opts);
}
_batchPut(quad, batch) {
const { indexes, prefixes } = this;
twoStepsQuadWriter.ingest(quad, prefixes);
for (let i = 0, il = indexes.length, index; i < il; i += 1) {
index = indexes[i];
const key = twoStepsQuadWriter.write(index.prefix, index.terms);
batch = batch.put(key, emptyValue, levelPutOpts);
}
return batch;
}
async put(quad, opts = emptyObject) {
this.ensureReady();
const { indexes, db } = this;
let batch = db.batch();
if (opts.scope) {
quad = opts.scope.parseQuad(quad, batch);
}
this._batchPut(quad, batch);
await this.writeBatch(batch, opts);
return { type: ResultType.VOID };
}
async multiPut(quads, opts = emptyObject) {
this.ensureReady();
const { indexes, db } = this;
let batch = db.batch();
for (let q = 0, ql = quads.length, quad; q < ql; q += 1) {
quad = quads[q];
if (opts.scope) {
quad = opts.scope.parseQuad(quad, batch);
}
this._batchPut(quad, batch);
}
await this.writeBatch(batch, opts);
return { type: ResultType.VOID };
}
_batchDel(quad, batch) {
const { indexes, prefixes } = this;
twoStepsQuadWriter.ingest(quad, prefixes);
for (let i = 0, il = indexes.length, index; i < il; i += 1) {
index = indexes[i];
const key = twoStepsQuadWriter.write(index.prefix, index.terms);
batch = batch.del(key, levelDelOpts);
}
return batch;
}
async del(quad, opts = emptyObject) {
this.ensureReady();
const batch = this.db.batch();
this._batchDel(quad, batch);
await this.writeBatch(batch, opts);
return { type: ResultType.VOID };
}
async multiDel(quads, opts = emptyObject) {
this.ensureReady();
const batch = this.db.batch();
for (let q = 0, ql = quads.length, quad; q < ql; q += 1) {
quad = quads[q];
this._batchDel(quad, batch);
}
await this.writeBatch(batch, opts);
return { type: ResultType.VOID };
}
async patch(oldQuad, newQuad, opts = emptyObject) {
this.ensureReady();
const { indexes, db } = this;
const batch = db.batch();
this._batchDel(oldQuad, batch);
this._batchPut(newQuad, batch);
await this.writeBatch(batch, opts);
return { type: ResultType.VOID };
}
async multiPatch(oldQuads, newQuads, opts = emptyObject) {
this.ensureReady();
const { indexes, db } = this;
let batch = db.batch();
for (let oq = 0, oql = oldQuads.length, oldQuad; oq < oql; oq += 1) {
oldQuad = oldQuads[oq];
this._batchDel(oldQuad, batch);
}
for (let nq = 0, nql = newQuads.length, newQuad; nq < nql; nq += 1) {
newQuad = newQuads[nq];
this._batchPut(newQuad, batch);
}
await this.writeBatch(batch, opts);
return { type: ResultType.VOID };
}
async writeBatch(batch, opts) {
if (opts.preWrite) {
await opts.preWrite(batch);
}
await batch.write();
}
async get(pattern, opts = emptyObject) {
this.ensureReady();
const results = await this.getStream(pattern, opts);
const items = await streamToArray(results.iterator);
return {
items,
type: results.type,
order: results.order,
index: results.index,
resorted: results.resorted,
};
}
async getStream(pattern, opts = emptyObject) {
this.ensureReady();
return await getStream(this, pattern, opts);
}
async putStream(source, opts = emptyObject) {
this.ensureReady();
const batchSize = opts.batchSize || 1;
if (batchSize === 1) {
await consumeOneByOne(source, quad => this.put(quad, opts));
}
else {
await consumeInBatches(source, batchSize, quads => this.multiPut(quads, opts));
}
return { type: ResultType.VOID };
}
async delStream(source, opts = emptyObject) {
this.ensureReady();
const batchSize = opts.batchSize || 1;
if (batchSize === 1) {
await consumeOneByOne(source, quad => this.del(quad));
}
else {
await consumeInBatches(source, batchSize, quads => this.multiDel(quads));
}
return { type: ResultType.VOID };
}
async initScope() {
await this.ensureReady();
return await Scope.init(this);
}
async loadScope(scopeId) {
await this.ensureReady();
return await Scope.load(this, scopeId);
}
async deleteScope(scopeId) {
await this.ensureReady();
await Scope.delete(this, scopeId);
}
async deleteAllScopes() {
await this.ensureReady();
await Scope.delete(this);
}
}
//# sourceMappingURL=quadstore.js.map