rdf-streaming-store
Version:
A read-only RDF/JS store that allows parallel data lookup and insertion.
152 lines • 7.37 kB
JavaScript
;
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.StreamingStore = void 0;
const n3_1 = require("n3");
const readable_stream_1 = require("readable-stream");
const PendingStreamsIndex_1 = require("./PendingStreamsIndex");
/**
* A StreamingStore allows data lookup and insertion to happen in parallel.
* Concretely, this means that `match()` calls happening before `import()` calls, will still consider those triples that
* are inserted later, which is done by keeping the response streams of `match()` open.
* Only when the `end()` method is invoked, all response streams will close, and the StreamingStore will be considered
* immutable.
*
* WARNING: `end()` MUST be called at some point, otherwise all `match` streams will remain unended.
*/
class StreamingStore {
constructor(store = new n3_1.Store()) {
this.pendingStreams = new PendingStreamsIndex_1.PendingStreamsIndex();
this.ended = false;
this.listeners = [];
this.store = store;
}
addEndListener(listener) {
this.listeners.push(listener);
}
emitEndEvent() {
for (const listener of this.listeners) {
listener();
}
}
hasEnded() {
return this.ended;
}
/**
* Mark this store as ended.
*
* This will make sure that all running and future `match` calls will end,
* and all next `import` calls to this store will throw an error.
* It will run all the listeners added with `addEndListener`.
*/
end() {
this.ended = true;
// Mark all pendingStreams as ended.
for (const pendingStream of this.pendingStreams.allStreams) {
pendingStream.push(null);
}
this.emitEndEvent();
}
importToListeners(stream) {
stream.on('data', (quad) => {
if (!this.ended && !this.store.countQuads(quad.subject, quad.predicate, quad.object, quad.graph)) {
for (const pendingStream of this.pendingStreams.getPendingStreamsForQuad(quad)) {
/**
* The pendingStream emits 'quad' events even before it is initialized.
* This allows us to detect when matching quads are added to the store,
* without having to consume the stream returned by the `match` method.
*/
pendingStream.emit('quad', quad);
if (pendingStream.isInitialized) {
pendingStream.push(quad);
}
}
}
});
}
static concatStreams(readables) {
return __asyncGenerator(this, arguments, function* concatStreams_1() {
var _a, e_1, _b, _c;
for (const readable of readables) {
try {
for (var _d = true, readable_1 = (e_1 = void 0, __asyncValues(readable)), readable_1_1; readable_1_1 = yield __await(readable_1.next()), _a = readable_1_1.done, !_a;) {
_c = readable_1_1.value;
_d = false;
try {
const chunk = _c;
yield yield __await(chunk);
}
finally {
_d = true;
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_d && !_a && (_b = readable_1.return)) yield __await(_b.call(readable_1));
}
finally { if (e_1) throw e_1.error; }
}
}
});
}
import(stream) {
if (this.ended) {
throw new Error('Attempted to import into an ended StreamingStore');
}
this.importToListeners(stream);
return this.store.import(stream);
}
match(subject, predicate, object, graph) {
const storeResult = this.store.match(subject, predicate, object, graph);
let stream = storeResult;
// If the store hasn't ended yet, also create a new pendingStream
if (!this.ended) {
// The new pendingStream remains open, until the store is ended.
const pendingStream = new readable_stream_1.PassThrough({ objectMode: true });
this.pendingStreams.addPatternListener(pendingStream, subject, predicate, object, graph);
stream = readable_stream_1.Readable.from(StreamingStore.concatStreams([storeResult, pendingStream]));
pendingStream.on('quad', quad => {
stream.emit('quad', quad);
});
stream._pipeSource = storeResult;
// This is an ugly hack to annotate pendingStream with the isInitialized once the store stream started being read.
// This is necessary to avoid duplicate quads cases where match() is called but not yet read, an import is done,
// and only then the match() stream is read.
// eslint-disable-next-line @typescript-eslint/unbound-method
const readOld = storeResult._read;
storeResult._read = (size) => {
pendingStream.isInitialized = true;
readOld.call(storeResult, size);
};
}
return stream;
}
/**
* The internal store with all imported quads.
*/
getStore() {
return this.store;
}
}
exports.StreamingStore = StreamingStore;
//# sourceMappingURL=StreamingStore.js.map