Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
json-stream.js224 linesDownload Raw Back to lib
1const Parser = require('jsonparse')
2const { Minipass } = require('minipass')
3
4class JSONStreamError extends Error {
5  constructor (err, caller) {
6    super(err.message)
7    Error.captureStackTrace(this, caller || this.constructor)
8  }
9
10  get name () {
11    return 'JSONStreamError'
12  }
13}
14
15const check = (x, y) =>
16  typeof x === 'string' ? String(y) === x
17  : x && typeof x.test === 'function' ? x.test(y)
18  : typeof x === 'boolean' || typeof x === 'object' ? x
19  : typeof x === 'function' ? x(y)
20  : false
21
22class JSONStream extends Minipass {
23  #count = 0
24  #ending = false
25  #footer = null
26  #header = null
27  #map = null
28  #onTokenOriginal
29  #parser
30  #path = null
31  #root = null
32
33  constructor (opts) {
34    super({
35      ...opts,
36      objectMode: true,
37    })
38
39    const parser = this.#parser = new Parser()
40    parser.onValue = value => this.#onValue(value)
41    this.#onTokenOriginal = parser.onToken
42    parser.onToken = (token, value) => this.#onToken(token, value)
43    parser.onError = er => this.#onError(er)
44
45    this.#path = typeof opts.path === 'string'
46      ? opts.path.split('.').map(e =>
47        e === '$*' ? { emitKey: true }
48        : e === '*' ? true
49        : e === '' ? { recurse: true }
50        : e)
51      : Array.isArray(opts.path) && opts.path.length ? opts.path
52      : null
53
54    if (typeof opts.map === 'function') {
55      this.#map = opts.map
56    }
57  }
58
59  #setHeaderFooter (key, value) {
60    // header has not been emitted yet
61    if (this.#header !== false) {
62      this.#header = this.#header || {}
63      this.#header[key] = value
64    }
65
66    // footer has not been emitted yet but header has
67    if (this.#footer !== false && this.#header === false) {
68      this.#footer = this.#footer || {}
69      this.#footer[key] = value
70    }
71  }
72
73  #onError (er) {
74    // error will always happen during a write() call.
75    const caller = this.#ending ? this.end : this.write
76    this.#ending = false
77    return this.emit('error', new JSONStreamError(er, caller))
78  }
79
80  #onToken (token, value) {
81    const parser = this.#parser
82    this.#onTokenOriginal.call(this.#parser, token, value)
83    if (parser.stack.length === 0) {
84      if (this.#root) {
85        const root = this.#root
86        if (!this.#path) {
87          super.write(root)
88        }
89        this.#root = null
90        this.#count = 0
91      }
92    }
93  }
94
95  #onValue (value) {
96    const parser = this.#parser
97    // the LAST onValue encountered is the root object.
98    // just overwrite it each time.
99    this.#root = value
100
101    if (!this.#path) {
102      return
103    }
104
105    let i = 0 // iterates on path
106    let j = 0 // iterates on stack
107    let emitKey = false
108    while (i < this.#path.length) {
109      const key = this.#path[i]
110      j++
111
112      if (key && !key.recurse) {
113        const c = (j === parser.stack.length) ? parser : parser.stack[j]
114        if (!c) {
115          return
116        }
117        if (!check(key, c.key)) {
118          this.#setHeaderFooter(c.key, value)
119          return
120        }
121        emitKey = !!key.emitKey
122        i++
123      } else {
124        i++
125        if (i >= this.#path.length) {
126          return
127        }
128        const nextKey = this.#path[i]
129        if (!nextKey) {
130          return
131        }
132        while (true) {
133          const c = (j === parser.stack.length) ? parser : parser.stack[j]
134          if (!c) {
135            return
136          }
137          if (check(nextKey, c.key)) {
138            i++
139            if (!Object.isFrozen(parser.stack[j])) {
140              parser.stack[j].value = null
141            }
142            break
143          } else {
144            this.#setHeaderFooter(c.key, value)
145          }
146          j++
147        }
148      }
149    }
150
151    // emit header
152    if (this.#header) {
153      const header = this.#header
154      this.#header = false
155      this.emit('header', header)
156    }
157    if (j !== parser.stack.length) {
158      return
159    }
160
161    this.#count++
162    const actualPath = parser.stack.slice(1)
163      .map(e => e.key).concat([parser.key])
164    if (value !== null && value !== undefined) {
165      const data = this.#map ? this.#map(value, actualPath) : value
166      if (data !== null && data !== undefined) {
167        const emit = emitKey ? { value: data } : data
168        if (emitKey) {
169          emit.key = parser.key
170        }
171        super.write(emit)
172      }
173    }
174
175    if (parser.value) {
176      delete parser.value[parser.key]
177    }
178
179    for (const k of parser.stack) {
180      k.value = null
181    }
182  }
183
184  write (chunk, encoding) {
185    if (typeof chunk === 'string') {
186      chunk = Buffer.from(chunk, encoding)
187    } else if (!Buffer.isBuffer(chunk)) {
188      return this.emit('error', new TypeError(
189        'Can only parse JSON from string or buffer input'))
190    }
191    this.#parser.write(chunk)
192    return this.flowing
193  }
194
195  end (chunk, encoding) {
196    this.#ending = true
197    if (chunk) {
198      this.write(chunk, encoding)
199    }
200
201    const h = this.#header
202    this.#header = null
203    const f = this.#footer
204    this.#footer = null
205    if (h) {
206      this.emit('header', h)
207    }
208    if (f) {
209      this.emit('footer', f)
210    }
211    return super.end()
212  }
213
214  static get JSONStreamError () {
215    return JSONStreamError
216  }
217
218  static parse (path, map) {
219    return new JSONStream({ path, map })
220  }
221}
222
223module.exports = JSONStream
224 
codekingpro/portable-devtools · Team Ai