datum-focus
Version:
Data shape, model, metadata, JSON, JSON Schema, GraphQL, MongoDB query and aggregations, iterator generators
328 lines (287 loc) • 8.42 kB
text/typescript
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;
}