UNPKG

rdf-streaming-store

Version:

A read-only RDF/JS store that allows parallel data lookup and insertion.

152 lines 7.37 kB
"use strict"; 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