Your IP : 216.73.216.86


Current Path : /home/emeraadmin/public_html/4d695/
Upload File :
Current File : /home/emeraadmin/public_html/4d695/minipass-json-stream.tar

package.json000064400000002273151701446040007040 0ustar00{
  "_id": "minipass-json-stream@1.0.1",
  "_inBundle": true,
  "_location": "/npm/minipass-json-stream",
  "_phantomChildren": {
    "yallist": "4.0.0"
  },
  "_requiredBy": [
    "/npm/npm-registry-fetch"
  ],
  "author": {
    "name": "Isaac Z. Schlueter",
    "email": "i@izs.me",
    "url": "https://izs.me"
  },
  "bugs": {
    "url": "https://github.com/npm/minipass-json-stream/issues"
  },
  "dependencies": {
    "jsonparse": "^1.3.1",
    "minipass": "^3.0.0"
  },
  "description": "Like JSONStream, but using Minipass streams",
  "devDependencies": {
    "JSONStream": "^1.3.5",
    "tap": "^14.6.9"
  },
  "files": [
    "index.js"
  ],
  "homepage": "https://github.com/npm/minipass-json-stream#readme",
  "keywords": [
    "stream",
    "json",
    "parse",
    "minipass",
    "JSONStream"
  ],
  "license": "MIT",
  "name": "minipass-json-stream",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/npm/minipass-json-stream.git"
  },
  "scripts": {
    "postpublish": "git push origin --follow-tags",
    "postversion": "npm publish",
    "preversion": "npm test",
    "snap": "tap",
    "test": "tap"
  },
  "tap": {
    "check-coverage": true
  },
  "version": "1.0.1"
}
node_modules/minipass/package.json000064400000003011151701446040013327 0ustar00{
  "_id": "minipass@3.3.6",
  "_inBundle": true,
  "_location": "/npm/minipass-json-stream/minipass",
  "_phantomChildren": {},
  "_requiredBy": [
    "/npm/minipass-json-stream"
  ],
  "author": {
    "name": "Isaac Z. Schlueter",
    "email": "i@izs.me",
    "url": "http://blog.izs.me/"
  },
  "bugs": {
    "url": "https://github.com/isaacs/minipass/issues"
  },
  "dependencies": {
    "yallist": "^4.0.0"
  },
  "description": "minimal implementation of a PassThrough stream",
  "devDependencies": {
    "@types/node": "^17.0.41",
    "end-of-stream": "^1.4.0",
    "prettier": "^2.6.2",
    "tap": "^16.2.0",
    "through2": "^2.0.3",
    "ts-node": "^10.8.1",
    "typescript": "^4.7.3"
  },
  "engines": {
    "node": ">=8"
  },
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "homepage": "https://github.com/isaacs/minipass#readme",
  "keywords": [
    "passthrough",
    "stream"
  ],
  "license": "ISC",
  "main": "index.js",
  "name": "minipass",
  "prettier": {
    "semi": false,
    "printWidth": 80,
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "jsxSingleQuote": false,
    "bracketSameLine": true,
    "arrowParens": "avoid",
    "endOfLine": "lf"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/isaacs/minipass.git"
  },
  "scripts": {
    "postpublish": "git push origin --follow-tags",
    "postversion": "npm publish",
    "preversion": "npm test",
    "test": "tap"
  },
  "tap": {
    "check-coverage": true
  },
  "types": "index.d.ts",
  "version": "3.3.6"
}
node_modules/minipass/LICENSE000064400000001423151701446040012053 0ustar00The ISC License

Copyright (c) 2017-2022 npm, Inc., Isaac Z. Schlueter, and Contributors

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
node_modules/minipass/index.js000064400000040367151701446040012525 0ustar00'use strict'
const proc = typeof process === 'object' && process ? process : {
  stdout: null,
  stderr: null,
}
const EE = require('events')
const Stream = require('stream')
const SD = require('string_decoder').StringDecoder

const EOF = Symbol('EOF')
const MAYBE_EMIT_END = Symbol('maybeEmitEnd')
const EMITTED_END = Symbol('emittedEnd')
const EMITTING_END = Symbol('emittingEnd')
const EMITTED_ERROR = Symbol('emittedError')
const CLOSED = Symbol('closed')
const READ = Symbol('read')
const FLUSH = Symbol('flush')
const FLUSHCHUNK = Symbol('flushChunk')
const ENCODING = Symbol('encoding')
const DECODER = Symbol('decoder')
const FLOWING = Symbol('flowing')
const PAUSED = Symbol('paused')
const RESUME = Symbol('resume')
const BUFFERLENGTH = Symbol('bufferLength')
const BUFFERPUSH = Symbol('bufferPush')
const BUFFERSHIFT = Symbol('bufferShift')
const OBJECTMODE = Symbol('objectMode')
const DESTROYED = Symbol('destroyed')
const EMITDATA = Symbol('emitData')
const EMITEND = Symbol('emitEnd')
const EMITEND2 = Symbol('emitEnd2')
const ASYNC = Symbol('async')

const defer = fn => Promise.resolve().then(fn)

// TODO remove when Node v8 support drops
const doIter = global._MP_NO_ITERATOR_SYMBOLS_  !== '1'
const ASYNCITERATOR = doIter && Symbol.asyncIterator
  || Symbol('asyncIterator not implemented')
const ITERATOR = doIter && Symbol.iterator
  || Symbol('iterator not implemented')

// events that mean 'the stream is over'
// these are treated specially, and re-emitted
// if they are listened for after emitting.
const isEndish = ev =>
  ev === 'end' ||
  ev === 'finish' ||
  ev === 'prefinish'

const isArrayBuffer = b => b instanceof ArrayBuffer ||
  typeof b === 'object' &&
  b.constructor &&
  b.constructor.name === 'ArrayBuffer' &&
  b.byteLength >= 0

const isArrayBufferView = b => !Buffer.isBuffer(b) && ArrayBuffer.isView(b)

class Pipe {
  constructor (src, dest, opts) {
    this.src = src
    this.dest = dest
    this.opts = opts
    this.ondrain = () => src[RESUME]()
    dest.on('drain', this.ondrain)
  }
  unpipe () {
    this.dest.removeListener('drain', this.ondrain)
  }
  // istanbul ignore next - only here for the prototype
  proxyErrors () {}
  end () {
    this.unpipe()
    if (this.opts.end)
      this.dest.end()
  }
}

class PipeProxyErrors extends Pipe {
  unpipe () {
    this.src.removeListener('error', this.proxyErrors)
    super.unpipe()
  }
  constructor (src, dest, opts) {
    super(src, dest, opts)
    this.proxyErrors = er => dest.emit('error', er)
    src.on('error', this.proxyErrors)
  }
}

module.exports = class Minipass extends Stream {
  constructor (options) {
    super()
    this[FLOWING] = false
    // whether we're explicitly paused
    this[PAUSED] = false
    this.pipes = []
    this.buffer = []
    this[OBJECTMODE] = options && options.objectMode || false
    if (this[OBJECTMODE])
      this[ENCODING] = null
    else
      this[ENCODING] = options && options.encoding || null
    if (this[ENCODING] === 'buffer')
      this[ENCODING] = null
    this[ASYNC] = options && !!options.async || false
    this[DECODER] = this[ENCODING] ? new SD(this[ENCODING]) : null
    this[EOF] = false
    this[EMITTED_END] = false
    this[EMITTING_END] = false
    this[CLOSED] = false
    this[EMITTED_ERROR] = null
    this.writable = true
    this.readable = true
    this[BUFFERLENGTH] = 0
    this[DESTROYED] = false
  }

  get bufferLength () { return this[BUFFERLENGTH] }

  get encoding () { return this[ENCODING] }
  set encoding (enc) {
    if (this[OBJECTMODE])
      throw new Error('cannot set encoding in objectMode')

    if (this[ENCODING] && enc !== this[ENCODING] &&
        (this[DECODER] && this[DECODER].lastNeed || this[BUFFERLENGTH]))
      throw new Error('cannot change encoding')

    if (this[ENCODING] !== enc) {
      this[DECODER] = enc ? new SD(enc) : null
      if (this.buffer.length)
        this.buffer = this.buffer.map(chunk => this[DECODER].write(chunk))
    }

    this[ENCODING] = enc
  }

  setEncoding (enc) {
    this.encoding = enc
  }

  get objectMode () { return this[OBJECTMODE] }
  set objectMode (om) { this[OBJECTMODE] = this[OBJECTMODE] || !!om }

  get ['async'] () { return this[ASYNC] }
  set ['async'] (a) { this[ASYNC] = this[ASYNC] || !!a }

  write (chunk, encoding, cb) {
    if (this[EOF])
      throw new Error('write after end')

    if (this[DESTROYED]) {
      this.emit('error', Object.assign(
        new Error('Cannot call write after a stream was destroyed'),
        { code: 'ERR_STREAM_DESTROYED' }
      ))
      return true
    }

    if (typeof encoding === 'function')
      cb = encoding, encoding = 'utf8'

    if (!encoding)
      encoding = 'utf8'

    const fn = this[ASYNC] ? defer : f => f()

    // convert array buffers and typed array views into buffers
    // at some point in the future, we may want to do the opposite!
    // leave strings and buffers as-is
    // anything else switches us into object mode
    if (!this[OBJECTMODE] && !Buffer.isBuffer(chunk)) {
      if (isArrayBufferView(chunk))
        chunk = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength)
      else if (isArrayBuffer(chunk))
        chunk = Buffer.from(chunk)
      else if (typeof chunk !== 'string')
        // use the setter so we throw if we have encoding set
        this.objectMode = true
    }

    // handle object mode up front, since it's simpler
    // this yields better performance, fewer checks later.
    if (this[OBJECTMODE]) {
      /* istanbul ignore if - maybe impossible? */
      if (this.flowing && this[BUFFERLENGTH] !== 0)
        this[FLUSH](true)

      if (this.flowing)
        this.emit('data', chunk)
      else
        this[BUFFERPUSH](chunk)

      if (this[BUFFERLENGTH] !== 0)
        this.emit('readable')

      if (cb)
        fn(cb)

      return this.flowing
    }

    // at this point the chunk is a buffer or string
    // don't buffer it up or send it to the decoder
    if (!chunk.length) {
      if (this[BUFFERLENGTH] !== 0)
        this.emit('readable')
      if (cb)
        fn(cb)
      return this.flowing
    }

    // fast-path writing strings of same encoding to a stream with
    // an empty buffer, skipping the buffer/decoder dance
    if (typeof chunk === 'string' &&
        // unless it is a string already ready for us to use
        !(encoding === this[ENCODING] && !this[DECODER].lastNeed)) {
      chunk = Buffer.from(chunk, encoding)
    }

    if (Buffer.isBuffer(chunk) && this[ENCODING])
      chunk = this[DECODER].write(chunk)

    // Note: flushing CAN potentially switch us into not-flowing mode
    if (this.flowing && this[BUFFERLENGTH] !== 0)
      this[FLUSH](true)

    if (this.flowing)
      this.emit('data', chunk)
    else
      this[BUFFERPUSH](chunk)

    if (this[BUFFERLENGTH] !== 0)
      this.emit('readable')

    if (cb)
      fn(cb)

    return this.flowing
  }

  read (n) {
    if (this[DESTROYED])
      return null

    if (this[BUFFERLENGTH] === 0 || n === 0 || n > this[BUFFERLENGTH]) {
      this[MAYBE_EMIT_END]()
      return null
    }

    if (this[OBJECTMODE])
      n = null

    if (this.buffer.length > 1 && !this[OBJECTMODE]) {
      if (this.encoding)
        this.buffer = [this.buffer.join('')]
      else
        this.buffer = [Buffer.concat(this.buffer, this[BUFFERLENGTH])]
    }

    const ret = this[READ](n || null, this.buffer[0])
    this[MAYBE_EMIT_END]()
    return ret
  }

  [READ] (n, chunk) {
    if (n === chunk.length || n === null)
      this[BUFFERSHIFT]()
    else {
      this.buffer[0] = chunk.slice(n)
      chunk = chunk.slice(0, n)
      this[BUFFERLENGTH] -= n
    }

    this.emit('data', chunk)

    if (!this.buffer.length && !this[EOF])
      this.emit('drain')

    return chunk
  }

  end (chunk, encoding, cb) {
    if (typeof chunk === 'function')
      cb = chunk, chunk = null
    if (typeof encoding === 'function')
      cb = encoding, encoding = 'utf8'
    if (chunk)
      this.write(chunk, encoding)
    if (cb)
      this.once('end', cb)
    this[EOF] = true
    this.writable = false

    // if we haven't written anything, then go ahead and emit,
    // even if we're not reading.
    // we'll re-emit if a new 'end' listener is added anyway.
    // This makes MP more suitable to write-only use cases.
    if (this.flowing || !this[PAUSED])
      this[MAYBE_EMIT_END]()
    return this
  }

  // don't let the internal resume be overwritten
  [RESUME] () {
    if (this[DESTROYED])
      return

    this[PAUSED] = false
    this[FLOWING] = true
    this.emit('resume')
    if (this.buffer.length)
      this[FLUSH]()
    else if (this[EOF])
      this[MAYBE_EMIT_END]()
    else
      this.emit('drain')
  }

  resume () {
    return this[RESUME]()
  }

  pause () {
    this[FLOWING] = false
    this[PAUSED] = true
  }

  get destroyed () {
    return this[DESTROYED]
  }

  get flowing () {
    return this[FLOWING]
  }

  get paused () {
    return this[PAUSED]
  }

  [BUFFERPUSH] (chunk) {
    if (this[OBJECTMODE])
      this[BUFFERLENGTH] += 1
    else
      this[BUFFERLENGTH] += chunk.length
    this.buffer.push(chunk)
  }

  [BUFFERSHIFT] () {
    if (this.buffer.length) {
      if (this[OBJECTMODE])
        this[BUFFERLENGTH] -= 1
      else
        this[BUFFERLENGTH] -= this.buffer[0].length
    }
    return this.buffer.shift()
  }

  [FLUSH] (noDrain) {
    do {} while (this[FLUSHCHUNK](this[BUFFERSHIFT]()))

    if (!noDrain && !this.buffer.length && !this[EOF])
      this.emit('drain')
  }

  [FLUSHCHUNK] (chunk) {
    return chunk ? (this.emit('data', chunk), this.flowing) : false
  }

  pipe (dest, opts) {
    if (this[DESTROYED])
      return

    const ended = this[EMITTED_END]
    opts = opts || {}
    if (dest === proc.stdout || dest === proc.stderr)
      opts.end = false
    else
      opts.end = opts.end !== false
    opts.proxyErrors = !!opts.proxyErrors

    // piping an ended stream ends immediately
    if (ended) {
      if (opts.end)
        dest.end()
    } else {
      this.pipes.push(!opts.proxyErrors ? new Pipe(this, dest, opts)
        : new PipeProxyErrors(this, dest, opts))
      if (this[ASYNC])
        defer(() => this[RESUME]())
      else
        this[RESUME]()
    }

    return dest
  }

  unpipe (dest) {
    const p = this.pipes.find(p => p.dest === dest)
    if (p) {
      this.pipes.splice(this.pipes.indexOf(p), 1)
      p.unpipe()
    }
  }

  addListener (ev, fn) {
    return this.on(ev, fn)
  }

  on (ev, fn) {
    const ret = super.on(ev, fn)
    if (ev === 'data' && !this.pipes.length && !this.flowing)
      this[RESUME]()
    else if (ev === 'readable' && this[BUFFERLENGTH] !== 0)
      super.emit('readable')
    else if (isEndish(ev) && this[EMITTED_END]) {
      super.emit(ev)
      this.removeAllListeners(ev)
    } else if (ev === 'error' && this[EMITTED_ERROR]) {
      if (this[ASYNC])
        defer(() => fn.call(this, this[EMITTED_ERROR]))
      else
        fn.call(this, this[EMITTED_ERROR])
    }
    return ret
  }

  get emittedEnd () {
    return this[EMITTED_END]
  }

  [MAYBE_EMIT_END] () {
    if (!this[EMITTING_END] &&
        !this[EMITTED_END] &&
        !this[DESTROYED] &&
        this.buffer.length === 0 &&
        this[EOF]) {
      this[EMITTING_END] = true
      this.emit('end')
      this.emit('prefinish')
      this.emit('finish')
      if (this[CLOSED])
        this.emit('close')
      this[EMITTING_END] = false
    }
  }

  emit (ev, data, ...extra) {
    // error and close are only events allowed after calling destroy()
    if (ev !== 'error' && ev !== 'close' && ev !== DESTROYED && this[DESTROYED])
      return
    else if (ev === 'data') {
      return !data ? false
        : this[ASYNC] ? defer(() => this[EMITDATA](data))
        : this[EMITDATA](data)
    } else if (ev === 'end') {
      return this[EMITEND]()
    } else if (ev === 'close') {
      this[CLOSED] = true
      // don't emit close before 'end' and 'finish'
      if (!this[EMITTED_END] && !this[DESTROYED])
        return
      const ret = super.emit('close')
      this.removeAllListeners('close')
      return ret
    } else if (ev === 'error') {
      this[EMITTED_ERROR] = data
      const ret = super.emit('error', data)
      this[MAYBE_EMIT_END]()
      return ret
    } else if (ev === 'resume') {
      const ret = super.emit('resume')
      this[MAYBE_EMIT_END]()
      return ret
    } else if (ev === 'finish' || ev === 'prefinish') {
      const ret = super.emit(ev)
      this.removeAllListeners(ev)
      return ret
    }

    // Some other unknown event
    const ret = super.emit(ev, data, ...extra)
    this[MAYBE_EMIT_END]()
    return ret
  }

  [EMITDATA] (data) {
    for (const p of this.pipes) {
      if (p.dest.write(data) === false)
        this.pause()
    }
    const ret = super.emit('data', data)
    this[MAYBE_EMIT_END]()
    return ret
  }

  [EMITEND] () {
    if (this[EMITTED_END])
      return

    this[EMITTED_END] = true
    this.readable = false
    if (this[ASYNC])
      defer(() => this[EMITEND2]())
    else
      this[EMITEND2]()
  }

  [EMITEND2] () {
    if (this[DECODER]) {
      const data = this[DECODER].end()
      if (data) {
        for (const p of this.pipes) {
          p.dest.write(data)
        }
        super.emit('data', data)
      }
    }

    for (const p of this.pipes) {
      p.end()
    }
    const ret = super.emit('end')
    this.removeAllListeners('end')
    return ret
  }

  // const all = await stream.collect()
  collect () {
    const buf = []
    if (!this[OBJECTMODE])
      buf.dataLength = 0
    // set the promise first, in case an error is raised
    // by triggering the flow here.
    const p = this.promise()
    this.on('data', c => {
      buf.push(c)
      if (!this[OBJECTMODE])
        buf.dataLength += c.length
    })
    return p.then(() => buf)
  }

  // const data = await stream.concat()
  concat () {
    return this[OBJECTMODE]
      ? Promise.reject(new Error('cannot concat in objectMode'))
      : this.collect().then(buf =>
          this[OBJECTMODE]
            ? Promise.reject(new Error('cannot concat in objectMode'))
            : this[ENCODING] ? buf.join('') : Buffer.concat(buf, buf.dataLength))
  }

  // stream.promise().then(() => done, er => emitted error)
  promise () {
    return new Promise((resolve, reject) => {
      this.on(DESTROYED, () => reject(new Error('stream destroyed')))
      this.on('error', er => reject(er))
      this.on('end', () => resolve())
    })
  }

  // for await (let chunk of stream)
  [ASYNCITERATOR] () {
    const next = () => {
      const res = this.read()
      if (res !== null)
        return Promise.resolve({ done: false, value: res })

      if (this[EOF])
        return Promise.resolve({ done: true })

      let resolve = null
      let reject = null
      const onerr = er => {
        this.removeListener('data', ondata)
        this.removeListener('end', onend)
        reject(er)
      }
      const ondata = value => {
        this.removeListener('error', onerr)
        this.removeListener('end', onend)
        this.pause()
        resolve({ value: value, done: !!this[EOF] })
      }
      const onend = () => {
        this.removeListener('error', onerr)
        this.removeListener('data', ondata)
        resolve({ done: true })
      }
      const ondestroy = () => onerr(new Error('stream destroyed'))
      return new Promise((res, rej) => {
        reject = rej
        resolve = res
        this.once(DESTROYED, ondestroy)
        this.once('error', onerr)
        this.once('end', onend)
        this.once('data', ondata)
      })
    }

    return { next }
  }

  // for (let chunk of stream)
  [ITERATOR] () {
    const next = () => {
      const value = this.read()
      const done = value === null
      return { value, done }
    }
    return { next }
  }

  destroy (er) {
    if (this[DESTROYED]) {
      if (er)
        this.emit('error', er)
      else
        this.emit(DESTROYED)
      return this
    }

    this[DESTROYED] = true

    // throw away all buffered data, it's never coming out
    this.buffer.length = 0
    this[BUFFERLENGTH] = 0

    if (typeof this.close === 'function' && !this[CLOSED])
      this.close()

    if (er)
      this.emit('error', er)
    else // if no error to emit, still reject pending promises
      this.emit(DESTROYED)

    return this
  }

  static isStream (s) {
    return !!s && (s instanceof Minipass || s instanceof Stream ||
      s instanceof EE && (
        typeof s.pipe === 'function' || // readable
        (typeof s.write === 'function' && typeof s.end === 'function') // writable
      ))
  }
}
LICENSE000064400000002437151701446040005561 0ustar00The MIT License

Copyright (c) Isaac Z. Schlueter and Contributors
Copyright (c) 2011 Dominic Tarr

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

----
This is a derivative work based on JSONStream by Dominic Tarr, modified and
redistributed according to the terms of the MIT license above.
https://github.com/dominictarr/JSONStream
index.js000064400000013640151701446040006217 0ustar00// put javascript in here
'use strict'

const Parser = require('jsonparse')
const Minipass = require('minipass')

class JSONStreamError extends Error {
  constructor (err, caller) {
    super(err.message)
    Error.captureStackTrace(this, caller || this.constructor)
  }
  get name () {
    return 'JSONStreamError'
  }
  set name (n) {}
}

const check = (x, y) =>
  typeof x === 'string' ? String(y) === x
  : x && typeof x.test === 'function' ? x.test(y)
  : typeof x === 'boolean' || typeof x === 'object' ? x
  : typeof x === 'function' ? x(y)
  : false

const _parser = Symbol('_parser')
const _onValue = Symbol('_onValue')
const _onTokenOriginal = Symbol('_onTokenOriginal')
const _onToken = Symbol('_onToken')
const _onError = Symbol('_onError')
const _count = Symbol('_count')
const _path = Symbol('_path')
const _map = Symbol('_map')
const _root = Symbol('_root')
const _header = Symbol('_header')
const _footer = Symbol('_footer')
const _setHeaderFooter = Symbol('_setHeaderFooter')
const _ending = Symbol('_ending')

class JSONStream extends Minipass {
  constructor (opts = {}) {
    super({
      ...opts,
      objectMode: true,
    })

    this[_ending] = false
    const parser = this[_parser] = new Parser()
    parser.onValue = value => this[_onValue](value)
    this[_onTokenOriginal] = parser.onToken
    parser.onToken = (token, value) => this[_onToken](token, value)
    parser.onError = er => this[_onError](er)

    this[_count] = 0
    this[_path] = typeof opts.path === 'string'
      ? opts.path.split('.').map(e =>
          e === '$*' ? { emitKey: true }
          : e === '*' ? true
          : e === '' ? { recurse: true }
          : e)
      : Array.isArray(opts.path) && opts.path.length ? opts.path
      : null

    this[_map] = typeof opts.map === 'function' ? opts.map : null
    this[_root] = null
    this[_header] = null
    this[_footer] = null
    this[_count] = 0
  }

  [_setHeaderFooter] (key, value) {
    // 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
    }
  }

  [_onError] (er) {
    // error will always happen during a write() call.
    const caller = this[_ending] ? this.end : this.write
    this[_ending] = false
    return this.emit('error', new JSONStreamError(er, caller))
  }

  [_onToken] (token, value) {
    const parser = this[_parser]
    this[_onTokenOriginal].call(parser, token, value)
    if (parser.stack.length === 0) {
      if (this[_root]) {
        const root = this[_root]
        if (!this[_path])
          super.write(root)
        this[_root] = null
        this[_count] = 0
      }
    }
  }

  [_onValue] (value) {
    const parser = this[_parser]
    // the LAST onValue encountered is the root object.
    // just overwrite it each time.
    this[_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) {
      const key = this[_path][i]
      j++

      if (key && !key.recurse) {
        const c = (j === parser.stack.length) ? parser : parser.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++
        if (i >= this[_path].length)
          return
        const nextKey = this[_path][i]
        if (!nextKey)
          return
        while (true) {
          const c = (j === parser.stack.length) ? parser : parser.stack[j]
          if (!c) return
          if (check(nextKey, c.key)) {
            i++
            if (!Object.isFrozen(parser.stack[j]))
              parser.stack[j].value = null
            break
          } else {
            this[_setHeaderFooter](c.key, value)
          }
          j++
        }
      }
    }

    // emit header
    if (this[_header]) {
      const header = this[_header]
      this[_header] = false
      this.emit('header', header)
    }
    if (j !== parser.stack.length) return

    this[_count] ++
    const actualPath = parser.stack.slice(1)
      .map(e => e.key).concat([parser.key])
    if (value !== null && value !== undefined) {
      const data = this[_map] ? this[_map](value, actualPath) : value
      if (data !== null && data !== undefined) {
        const emit = emitKey || emitPath ? { value: data } : data
        if (emitKey)
          emit.key = parser.key
        if (emitPath)
          emit.path = actualPath
        super.write(emit)
      }
    }

    if (parser.value)
      delete parser.value[parser.key]

    for (const k of parser.stack) {
      k.value = null
    }
  }

  write (chunk, encoding, cb) {
    if (typeof encoding === 'function')
      cb = encoding, encoding = null
    if (typeof chunk === 'string')
      chunk = Buffer.from(chunk, encoding)
    else if (!Buffer.isBuffer(chunk))
      return this.emit('error', new TypeError(
        'Can only parse JSON from string or buffer input'))
    this[_parser].write(chunk)
    if (cb)
      cb()
    return this.flowing
  }

  end (chunk, encoding, cb) {
    this[_ending] = true
    if (typeof encoding === 'function')
      cb = encoding, encoding = null
    if (typeof chunk === 'function')
      cb = chunk, chunk = null
    if (chunk)
      this.write(chunk, encoding)
    if (cb)
      this.once('end', cb)

    const h = this[_header]
    this[_header] = null
    const f = this[_footer]
    this[_footer] = null
    if (h)
      this.emit('header', h)
    if (f)
      this.emit('footer', f)
    return super.end()
  }

  static get JSONStreamError () { return JSONStreamError }
  static parse (path, map) {
    return new JSONStream({path, map})
  }
}

module.exports = JSONStream