UNPKG

@graphy/core.class.scribable

Version:
319 lines (250 loc) 7.19 kB
// queueMicrotask shim { // not defined or not a function if('function' !== typeof queueMicrotask) { // create resolved promise let dp_resolve = Promise.resolve(); // try to redefine try { // eslint-disable-next-line no-global-assign queueMicrotask = fk => dp_resolve.then(fk) .catch(e_callback => setTimeout(() => { throw e_callback; }, 0)); } // oh well, at least we tried catch(e_define) {} } } const factory = require('@graphy/core.data.factory'); const stream = require('@graphy/core.iso.stream'); // max string buffer size const N_DEFAULT_MAX_BUFFER = 1 << 15; // 32 KiB class Scribable extends stream.Transform { // flush buffer static _flush_buffer(k_self) { // no buffer; exit if(!k_self._s_push) return; // push buffer to stream k_self.push(k_self._s_push); // reset buffer k_self._s_push = ''; } constructor(gc_scribable={}) { super({ writableObjectMode: true, readableObjectMode: false, }); let { prefixes: h_prefixes={}, } = gc_scribable; // internal buffer this._s_push = ''; // max buffer length this._n_max_buffer = gc_scribable.max_buffer || gc_scribable.maxBuffer || N_DEFAULT_MAX_BUFFER; // prefixes this._h_prefixes = factory.cache_prefixes(h_prefixes || {}); // on new source(s) this.on('pipe', (ds_src) => { // listen for prefix events ds_src.on('prefix', (s_prefix_id, p_iri) => { this.write({ type: 'prefixes', value: { [s_prefix_id]: p_iri, }, }); }); // listen for comment events ds_src.on('comment', (s_comment) => { this.write({ type: 'comment', value: s_comment, }); }); }); // bind event listeners if(gc_scribable.close) this.once('close', gc_scribable.close); if(gc_scribable.drain) this.on('drain', gc_scribable.drain); if(gc_scribable.error) this.on('error', gc_scribable.error); if(gc_scribable.finish) this.once('finish', gc_scribable.finish); if(gc_scribable.data) this.on('data', gc_scribable.data); if(gc_scribable.end) this.once('end', gc_scribable.end); if(gc_scribable.warning) this.on('warning', gc_scribable.warning); } _serialize_hash_comment(s_comment) { return '# '+s_comment.replace(/\n/g, '\n# ')+'\n'; } _serialize_newlines(n_newlines=1) { return '\n'.repeat(n_newlines); } _serialize_c4r(hc4r_quads) { let h_prefixes = this._h_prefixes; let a_unions = []; let s_write = ''; // each graph in quads hash for(let sc1_graph in hc4r_quads) { // non-default graph; union from dataset if('*' !== sc1_graph) a_unions.push(sc1_graph); // add all quads from graph s_write += this._serialize_c3r(hc4r_quads[sc1_graph]); } // a union was performed if(a_unions.length) { // warn about implicit union let s_warning = `Destination format does not support quads; an implicit union into the default graph was performed on the quads contained in graphs: ${a_unions.map(sc1 => factory.c1(sc1, h_prefixes).verbose()).join(', ')}`; // emit warning, wasn't listened to; force thru warn/stderr channel if(!this.emit('warning', s_warning)) { console.warn(s_warning); } } return s_write; } _serialize_c3() { throw new Error(`Write event type 'c3' not supported by ${this.constructor.name}`); } _serialize_c4() { throw new Error(`Write event type 'c4' not supported by ${this.constructor.name}`); } _serialize_c3r() { throw new Error(`Write event type 'c3r' should have been implemented by subclass ${this.constructor.name}`); } _serialize_quad() { throw new Error(`Write event type 'quad' should have been implemented by subclass ${this.constructor.name}`); } // ignorable events _serialize_comment() {} // eslint-disable-line class-methods-use-this // update prefix mappings _update_prefixes(h_prefixes_in, b_terse=false) { // merge prefixes let h_prefixes = { ...this._h_prefixes, ...h_prefixes_in, }; // recache prefixes this._h_prefixes = factory.cachePrefixes(h_prefixes || {}, b_terse); } // implement stream.Transform _transform(g_event, s_encoding, fke_transform) { let w_write; // try to serialize input value try { w_write = this.serialize(g_event); } // serialization error catch(e_serialize) { // report error fke_transform(e_serialize); // bail on transform return e_serialize; } // data to push if(w_write) { // flush internal buffer Scribable._flush_buffer(this); // push data to stream this.push(w_write); } // nothing returned from serialization else { let nl_push = this._s_push.length; // internal buffer high water mark if(nl_push > this._n_max_buffer) { Scribable._flush_buffer(this); } // allow buffer to build else if(nl_push) { // do not worry about clearing timeouts queueMicrotask(() => Scribable._flush_buffer(this)); } } // callback fke_transform(); } // queue data to be pushed later _queue(s_push) { this._s_push += s_push; // internal buffer high water mark if(this._s_push.length > this._n_max_buffer) { Scribable._flush_buffer(this); } else { // do not worry about clearing timeouts queueMicrotask(() => Scribable._flush_buffer(this)); } } // route event object to serialization method serialize(g_event) { switch(g_event.type) { // rdfjs quad // eslint-disable-next-line no-undefined case undefined: return this._serialize_quad(g_event); // concise triple struct strict-mode case 'c3r': return this._serialize_c3r(g_event.value); // concise quad struct strict-mode case 'c4r': return this._serialize_c4r(g_event.value); // array of events case 'array': { // string building let s_write = ''; // each subevent for(let g_sub of g_event.value) { // build serialization string let s_push = this.serialize(g_sub); // something to push if(s_push) { s_write += s_push; } // push was queued else if(this._s_push) { // concat to write s_write += this._s_push; // reset push this._s_push = ''; } } // all done return s_write; } // quad case 'quad': return this._serialize_quad(g_event.value); // concise triple struct case 'c3': return this._serialize_c3(g_event.value); // concise quad struct case 'c4': return this._serialize_c4(g_event.value); // prefixes case 'prefixes': { return this._serialize_prefixes(g_event.value); } // comment case 'comment': { return this._serialize_comment(g_event.value); } // newline(s) case 'newline': case 'newlines': { return this._serialize_newlines(g_event.value); } // no such event type default: { throw new Error(`no such writable data event type for RDF stream: '${g_event.type}'`); } } } // rinse off buffer to writable rinse() { this._reset(); Scribable._flush_buffer(this); } _flush() { // flush buffer Scribable._flush_buffer(this); // eof this.push(null); } } Object.assign(Scribable.prototype, { isGraphyWritable: true, _serialize_prefixes: Scribable.prototype._update_prefixes, }); module.exports = Scribable;