UNPKG

datum-focus

Version:

Data shape, model, metadata, JSON, JSON Schema, GraphQL, MongoDB query and aggregations, iterator generators

328 lines (287 loc) 8.42 kB
import { Parser } from './parse/stream'; import { Through } from '../through'; import { Stream, Transform, TransformOptions } from 'stream'; import { basename } from 'path/posix'; class Parser2 extends Parser { stream: Through; root?: any; header?: any; footer?: any; count = 0; setHeaderFooter(key: string, value: string) { // header has not been emitted yet if (this.header !== false) { this.header = this.header || {} this.header[key] = value } // footer has not been emitted yet but header has if (this.footer !== false && this.header === false) { this.footer = this.footer || {} this.footer[key] = value } } constructor(public path: string[], public map: any) { super(); this.stream = new Through(this.transform, this.flush); } transform(chunk: any, encoding: BufferEncoding, callback: (error?: Error, data?: any) => void): void { if (typeof chunk === 'string') chunk = Buffer.from(chunk) this.write(chunk); callback(); } flush(callback: (error?: Error, data?: any) => void): void { if (this.header) { this.stream.emit('header', this.header); } if (this.footer) { this.stream.emit('footer', this.footer); } if (this.tState != Parser.C.START || this.stack.length > 0) { callback(new Error('Incomplete JSON')) return } callback() } onValue(value: any) { if (!this.root) { this.stream.root = value } if (!this.path) { return; } let i = 0 // iterates on path let j = 0 // iterates on stack let emitKey = false let emitPath = false while (i < this.path.length) { let key: any = this.path[i] let c j++ if (key && !key.recurse) { c = j === this.stack.length ? this : this.stack[j] if (!c) return if (!check(key, c.key)) { this.setHeaderFooter(c.key, value) return } emitKey = !!key.emitKey emitPath = !!key.emitPath i++ } else { i++ let nextKey = this.path[i] if (!nextKey) return while (true) { c = j === this.stack.length ? this : this.stack[j] if (!c) return if (check(nextKey, c.key)) { i++ if (!Object.isFrozen(this.stack[j])) this.stack[j].value = null break } else { this.setHeaderFooter(c.key, value) } j++ } } } // emit header if (this.header) { this.stream.emit('header', this.header) this.header = false } if (j !== this.stack.length) return this.count++ let actualPath = this.stack .slice(1) .map(function (element: any) { return element.key }) .concat([this.key]) let data = value if (null != data) if (null != (data = this.map ? this.map(data, actualPath) : data)) { if (emitKey || emitPath) { data = { value: data } if (emitKey) data['key'] = this.key if (emitPath) data['path'] = actualPath } this.stream.push(data) } if (this.value) delete this.value[this.key] for (let k in this.stack) if (!Object.isFrozen(this.stack[k])) this.stack[k].value = null } _onToken = super.onToken onToken(token: any, value: any) { super.onToken(token, value) if (this.stack.length === 0) { if (this.stream.root) { if (!this.path) { this.stream.push(this.stream.root) } this.count = 0 this.stream.root = null } } } onError(err: any) { if (err.message.indexOf('at position') > -1) err.message = 'Invalid JSON (' + err.message + ')' this.stream.destroy(err) } } export function parse(path: any, map: any): any { if ('string' === typeof path) path = path.split('.').map(function (e) { if (e === '$*') return { emitKey: true } else if (e === '*') return true else if (e === '') // '..'.split('.') returns an empty string return { recurse: true } else return e }); if (!path || !path.length) { path = null } if (!path || !path.length) path = null const parser = new Parser2(path, map); return parser.stream; } function check(x: any, y: any): any { if ('string' === typeof x) return y == x else if (x && 'function' === typeof x.exec) return x.exec(y) else if ('boolean' === typeof x || 'object' === typeof x) return x else if ('function' === typeof x) return x(y) return false } class JsonStream extends Through implements ReadableStream { constructor( transform: (chunk: any, encoding: BufferEncoding, callback: (error?: Error, data?: any) => void) => void, flush: (callback: (error?: Error, data?: any) => void) => void, options?: TransformOptions) { super(transform, flush, options); this.locked = false; } locked: boolean; cancel(reason?: any): Promise<void> { throw new Error('Method not implemented.'); } getReader(): ReadableStreamDefaultReader<any> { throw new Error('Method not implemented.'); } pipeThrough<T>(transform: ReadableWritablePair<T, any>, options?: StreamPipeOptions): ReadableStream<T> { throw new Error('Method not implemented.'); } pipeTo(destination: WritableStream<any>, options?: StreamPipeOptions): Promise<void> { throw new Error('Method not implemented.'); } tee(): [ReadableStream<any>, ReadableStream<any>] { throw new Error('Method not implemented.'); } forEach(callbackfn: (value: any, key: number, parent: ReadableStream<any>) => void, thisArg?: any): void { throw new Error('Method not implemented.'); } } class Serializer { stream: JsonStream; first = true; anyData = false; constructor(public op?: any, public sep?: any, public cl?: any, public indent?: number) { this.indent = indent || 0 if (op === false) { this.op = '' this.sep = '\n' this.cl = '' } else if (op == null) { this.op = '[\n' this.sep = '\n,\n' this.cl = '\n]\n' } this.stream = new JsonStream(this.transform, this.flush); } transform(data: any, _: any, cb: any): void { this.anyData = true let json try { json = JSON.stringify(data, null, this.indent) } catch (err) { return cb(err) } if (this.first) { this.first = false cb(null, (typeof this.op === 'function' ? this.op() : this.op) + json) } else { cb(null, this.sep + json) } } flush(cb: any) { if (!this.anyData) this.stream.push(this.op) if (typeof this.cl === 'function') { this.cl((err: any, res: any) => { if (err) { return cb(err) } this.stream.push(res); cb(); }) return; } this.stream.push(this.cl); cb(); } } export function stringify(op?: any, sep?: any, cl?: any, indent?: number): ReadableStream { const serializer = new Serializer(op, sep, cl, indent); return serializer.stream; } class ObjectSerializer { stream: Through; first = true; anyData = false; constructor(public op?: any, public sep?: any, public cl?: any, public indent?: number) { this.indent = indent || 0 if (op === false) { this.op = ''; this.sep = '\n'; this.cl = ''; } else if (op == null) { this.op = '{\n'; this.sep = '\n,\n'; this.cl = '\n}\n'; } this.stream = new Through(this.transform, this.flush); } transform(data: any, enc: any, cb: any): void { this.anyData = true let json try { json = JSON.stringify(data[0]) + ':' + JSON.stringify(data[1], null, this.indent) } catch (err) { return cb(err) } if (this.first) { this.first = false cb(null, (typeof this.op === 'function' ? this.op() : this.op) + json) } else { cb(null, this.sep + json) } } flush(cb: any) { if (!this.anyData) this.stream.push(this.op) if (typeof this.cl === 'function') { this.cl((err: any, res: any) => { if (err) { return cb(err); } this.stream.push(res); cb(); }) return; } this.stream.push(this.cl); cb() } } export function stringifyObject(op?: any, sep?: any, cl?: any, indent?: number) { const serializer = new ObjectSerializer(op, sep, cl, indent); return serializer.stream; }