11cb0ef41Sopenharmony_ciconst util = require('util')
21cb0ef41Sopenharmony_ciconst _delete = Symbol('delete')
31cb0ef41Sopenharmony_ciconst _append = Symbol('append')
41cb0ef41Sopenharmony_ci
51cb0ef41Sopenharmony_ciconst sqBracketsMatcher = str => str.match(/(.+)\[([^\]]+)\]\.?(.*)$/)
61cb0ef41Sopenharmony_ci
71cb0ef41Sopenharmony_ci// replaces any occurrence of an empty-brackets (e.g: []) with a special
81cb0ef41Sopenharmony_ci// Symbol(append) to represent it, this is going to be useful for the setter
91cb0ef41Sopenharmony_ci// method that will push values to the end of the array when finding these
101cb0ef41Sopenharmony_ciconst replaceAppendSymbols = str => {
111cb0ef41Sopenharmony_ci  const matchEmptyBracket = str.match(/^(.*)\[\]\.?(.*)$/)
121cb0ef41Sopenharmony_ci
131cb0ef41Sopenharmony_ci  if (matchEmptyBracket) {
141cb0ef41Sopenharmony_ci    const [, pre, post] = matchEmptyBracket
151cb0ef41Sopenharmony_ci    return [...replaceAppendSymbols(pre), _append, post].filter(Boolean)
161cb0ef41Sopenharmony_ci  }
171cb0ef41Sopenharmony_ci
181cb0ef41Sopenharmony_ci  return [str]
191cb0ef41Sopenharmony_ci}
201cb0ef41Sopenharmony_ci
211cb0ef41Sopenharmony_ciconst parseKeys = key => {
221cb0ef41Sopenharmony_ci  const sqBracketItems = new Set()
231cb0ef41Sopenharmony_ci  sqBracketItems.add(_append)
241cb0ef41Sopenharmony_ci  const parseSqBrackets = str => {
251cb0ef41Sopenharmony_ci    const index = sqBracketsMatcher(str)
261cb0ef41Sopenharmony_ci
271cb0ef41Sopenharmony_ci    // once we find square brackets, we recursively parse all these
281cb0ef41Sopenharmony_ci    if (index) {
291cb0ef41Sopenharmony_ci      const preSqBracketPortion = index[1]
301cb0ef41Sopenharmony_ci
311cb0ef41Sopenharmony_ci      // we want to have a `new String` wrapper here in order to differentiate
321cb0ef41Sopenharmony_ci      // between multiple occurrences of the same string, e.g:
331cb0ef41Sopenharmony_ci      // foo.bar[foo.bar] should split into { foo: { bar: { 'foo.bar': {} } }
341cb0ef41Sopenharmony_ci      /* eslint-disable-next-line no-new-wrappers */
351cb0ef41Sopenharmony_ci      const foundKey = new String(index[2])
361cb0ef41Sopenharmony_ci      const postSqBracketPortion = index[3]
371cb0ef41Sopenharmony_ci
381cb0ef41Sopenharmony_ci      // we keep track of items found during this step to make sure
391cb0ef41Sopenharmony_ci      // we don't try to split-separate keys that were defined within
401cb0ef41Sopenharmony_ci      // square brackets, since the key name itself might contain dots
411cb0ef41Sopenharmony_ci      sqBracketItems.add(foundKey)
421cb0ef41Sopenharmony_ci
431cb0ef41Sopenharmony_ci      // returns an array that contains either dot-separate items (that will
441cb0ef41Sopenharmony_ci      // be split apart during the next step OR the fully parsed keys
451cb0ef41Sopenharmony_ci      // read from square brackets, e.g:
461cb0ef41Sopenharmony_ci      // foo.bar[1.0.0].a.b -> ['foo.bar', '1.0.0', 'a.b']
471cb0ef41Sopenharmony_ci      return [
481cb0ef41Sopenharmony_ci        ...parseSqBrackets(preSqBracketPortion),
491cb0ef41Sopenharmony_ci        foundKey,
501cb0ef41Sopenharmony_ci        ...(postSqBracketPortion ? parseSqBrackets(postSqBracketPortion) : []),
511cb0ef41Sopenharmony_ci      ]
521cb0ef41Sopenharmony_ci    }
531cb0ef41Sopenharmony_ci
541cb0ef41Sopenharmony_ci    // at the end of parsing, any usage of the special empty-bracket syntax
551cb0ef41Sopenharmony_ci    // (e.g: foo.array[]) has  not yet been parsed, here we'll take care
561cb0ef41Sopenharmony_ci    // of parsing it and adding a special symbol to represent it in
571cb0ef41Sopenharmony_ci    // the resulting list of keys
581cb0ef41Sopenharmony_ci    return replaceAppendSymbols(str)
591cb0ef41Sopenharmony_ci  }
601cb0ef41Sopenharmony_ci
611cb0ef41Sopenharmony_ci  const res = []
621cb0ef41Sopenharmony_ci  // starts by parsing items defined as square brackets, those might be
631cb0ef41Sopenharmony_ci  // representing properties that have a dot in the name or just array
641cb0ef41Sopenharmony_ci  // indexes, e.g: foo[1.0.0] or list[0]
651cb0ef41Sopenharmony_ci  const sqBracketKeys = parseSqBrackets(key.trim())
661cb0ef41Sopenharmony_ci
671cb0ef41Sopenharmony_ci  for (const k of sqBracketKeys) {
681cb0ef41Sopenharmony_ci    // keys parsed from square brackets should just be added to list of
691cb0ef41Sopenharmony_ci    // resulting keys as they might have dots as part of the key
701cb0ef41Sopenharmony_ci    if (sqBracketItems.has(k)) {
711cb0ef41Sopenharmony_ci      res.push(k)
721cb0ef41Sopenharmony_ci    } else {
731cb0ef41Sopenharmony_ci      // splits the dot-sep property names and add them to the list of keys
741cb0ef41Sopenharmony_ci      /* eslint-disable-next-line no-new-wrappers */
751cb0ef41Sopenharmony_ci      for (const splitKey of k.split('.')) {
761cb0ef41Sopenharmony_ci        res.push(String(splitKey))
771cb0ef41Sopenharmony_ci      }
781cb0ef41Sopenharmony_ci    }
791cb0ef41Sopenharmony_ci  }
801cb0ef41Sopenharmony_ci
811cb0ef41Sopenharmony_ci  // returns an ordered list of strings in which each entry
821cb0ef41Sopenharmony_ci  // represents a key in an object defined by the previous entry
831cb0ef41Sopenharmony_ci  return res
841cb0ef41Sopenharmony_ci}
851cb0ef41Sopenharmony_ci
861cb0ef41Sopenharmony_ciconst getter = ({ data, key }) => {
871cb0ef41Sopenharmony_ci  // keys are a list in which each entry represents the name of
881cb0ef41Sopenharmony_ci  // a property that should be walked through the object in order to
891cb0ef41Sopenharmony_ci  // return the final found value
901cb0ef41Sopenharmony_ci  const keys = parseKeys(key)
911cb0ef41Sopenharmony_ci  let _data = data
921cb0ef41Sopenharmony_ci  let label = ''
931cb0ef41Sopenharmony_ci
941cb0ef41Sopenharmony_ci  for (const k of keys) {
951cb0ef41Sopenharmony_ci    // empty-bracket-shortcut-syntax is not supported on getter
961cb0ef41Sopenharmony_ci    if (k === _append) {
971cb0ef41Sopenharmony_ci      throw Object.assign(new Error('Empty brackets are not valid syntax for retrieving values.'), {
981cb0ef41Sopenharmony_ci        code: 'EINVALIDSYNTAX',
991cb0ef41Sopenharmony_ci      })
1001cb0ef41Sopenharmony_ci    }
1011cb0ef41Sopenharmony_ci
1021cb0ef41Sopenharmony_ci    // extra logic to take into account printing array, along with its
1031cb0ef41Sopenharmony_ci    // special syntax in which using a dot-sep property name after an
1041cb0ef41Sopenharmony_ci    // arry will expand it's results, e.g:
1051cb0ef41Sopenharmony_ci    // arr.name -> arr[0].name=value, arr[1].name=value, ...
1061cb0ef41Sopenharmony_ci    const maybeIndex = Number(k)
1071cb0ef41Sopenharmony_ci    if (Array.isArray(_data) && !Number.isInteger(maybeIndex)) {
1081cb0ef41Sopenharmony_ci      _data = _data.reduce((acc, i, index) => {
1091cb0ef41Sopenharmony_ci        acc[`${label}[${index}].${k}`] = i[k]
1101cb0ef41Sopenharmony_ci        return acc
1111cb0ef41Sopenharmony_ci      }, {})
1121cb0ef41Sopenharmony_ci      return _data
1131cb0ef41Sopenharmony_ci    } else {
1141cb0ef41Sopenharmony_ci      if (!Object.hasOwn(_data, k)) {
1151cb0ef41Sopenharmony_ci        return undefined
1161cb0ef41Sopenharmony_ci      }
1171cb0ef41Sopenharmony_ci      _data = _data[k]
1181cb0ef41Sopenharmony_ci    }
1191cb0ef41Sopenharmony_ci
1201cb0ef41Sopenharmony_ci    label += k
1211cb0ef41Sopenharmony_ci  }
1221cb0ef41Sopenharmony_ci
1231cb0ef41Sopenharmony_ci  // these are some legacy expectations from
1241cb0ef41Sopenharmony_ci  // the old API consumed by lib/view.js
1251cb0ef41Sopenharmony_ci  if (Array.isArray(_data) && _data.length <= 1) {
1261cb0ef41Sopenharmony_ci    _data = _data[0]
1271cb0ef41Sopenharmony_ci  }
1281cb0ef41Sopenharmony_ci
1291cb0ef41Sopenharmony_ci  return {
1301cb0ef41Sopenharmony_ci    [key]: _data,
1311cb0ef41Sopenharmony_ci  }
1321cb0ef41Sopenharmony_ci}
1331cb0ef41Sopenharmony_ci
1341cb0ef41Sopenharmony_ciconst setter = ({ data, key, value, force }) => {
1351cb0ef41Sopenharmony_ci  // setter goes to recursively transform the provided data obj,
1361cb0ef41Sopenharmony_ci  // setting properties from the list of parsed keys, e.g:
1371cb0ef41Sopenharmony_ci  // ['foo', 'bar', 'baz'] -> { foo: { bar: { baz:  {} } }
1381cb0ef41Sopenharmony_ci  const keys = parseKeys(key)
1391cb0ef41Sopenharmony_ci  const setKeys = (_data, _key) => {
1401cb0ef41Sopenharmony_ci    // handles array indexes, converting valid integers to numbers,
1411cb0ef41Sopenharmony_ci    // note that occurrences of Symbol(append) will throw,
1421cb0ef41Sopenharmony_ci    // so we just ignore these for now
1431cb0ef41Sopenharmony_ci    let maybeIndex = Number.NaN
1441cb0ef41Sopenharmony_ci    try {
1451cb0ef41Sopenharmony_ci      maybeIndex = Number(_key)
1461cb0ef41Sopenharmony_ci    } catch {
1471cb0ef41Sopenharmony_ci      // leave it NaN
1481cb0ef41Sopenharmony_ci    }
1491cb0ef41Sopenharmony_ci    if (!Number.isNaN(maybeIndex)) {
1501cb0ef41Sopenharmony_ci      _key = maybeIndex
1511cb0ef41Sopenharmony_ci    }
1521cb0ef41Sopenharmony_ci
1531cb0ef41Sopenharmony_ci    // creates new array in case key is an index
1541cb0ef41Sopenharmony_ci    // and the array obj is not yet defined
1551cb0ef41Sopenharmony_ci    const keyIsAnArrayIndex = _key === maybeIndex || _key === _append
1561cb0ef41Sopenharmony_ci    const dataHasNoItems = !Object.keys(_data).length
1571cb0ef41Sopenharmony_ci    if (keyIsAnArrayIndex && dataHasNoItems && !Array.isArray(_data)) {
1581cb0ef41Sopenharmony_ci      _data = []
1591cb0ef41Sopenharmony_ci    }
1601cb0ef41Sopenharmony_ci
1611cb0ef41Sopenharmony_ci    // converting from array to an object is also possible, in case the
1621cb0ef41Sopenharmony_ci    // user is using force mode, we should also convert existing arrays
1631cb0ef41Sopenharmony_ci    // to an empty object if the current _data is an array
1641cb0ef41Sopenharmony_ci    if (force && Array.isArray(_data) && !keyIsAnArrayIndex) {
1651cb0ef41Sopenharmony_ci      _data = { ..._data }
1661cb0ef41Sopenharmony_ci    }
1671cb0ef41Sopenharmony_ci
1681cb0ef41Sopenharmony_ci    // the _append key is a special key that is used to represent
1691cb0ef41Sopenharmony_ci    // the empty-bracket notation, e.g: arr[] -> arr[arr.length]
1701cb0ef41Sopenharmony_ci    if (_key === _append) {
1711cb0ef41Sopenharmony_ci      if (!Array.isArray(_data)) {
1721cb0ef41Sopenharmony_ci        throw Object.assign(new Error(`Can't use append syntax in non-Array element`), {
1731cb0ef41Sopenharmony_ci          code: 'ENOAPPEND',
1741cb0ef41Sopenharmony_ci        })
1751cb0ef41Sopenharmony_ci      }
1761cb0ef41Sopenharmony_ci      _key = _data.length
1771cb0ef41Sopenharmony_ci    }
1781cb0ef41Sopenharmony_ci
1791cb0ef41Sopenharmony_ci    // retrieves the next data object to recursively iterate on,
1801cb0ef41Sopenharmony_ci    // throws if trying to override a literal value or add props to an array
1811cb0ef41Sopenharmony_ci    const next = () => {
1821cb0ef41Sopenharmony_ci      const haveContents = !force && _data[_key] != null && value !== _delete
1831cb0ef41Sopenharmony_ci      const shouldNotOverrideLiteralValue = !(typeof _data[_key] === 'object')
1841cb0ef41Sopenharmony_ci      // if the next obj to recurse is an array and the next key to be
1851cb0ef41Sopenharmony_ci      // appended to the resulting obj is not an array index, then it
1861cb0ef41Sopenharmony_ci      // should throw since we can't append arbitrary props to arrays
1871cb0ef41Sopenharmony_ci      const shouldNotAddPropsToArrays =
1881cb0ef41Sopenharmony_ci        typeof keys[0] !== 'symbol' && Array.isArray(_data[_key]) && Number.isNaN(Number(keys[0]))
1891cb0ef41Sopenharmony_ci
1901cb0ef41Sopenharmony_ci      const overrideError = haveContents && shouldNotOverrideLiteralValue
1911cb0ef41Sopenharmony_ci      if (overrideError) {
1921cb0ef41Sopenharmony_ci        throw Object.assign(
1931cb0ef41Sopenharmony_ci          new Error(`Property ${_key} already exists and is not an Array or Object.`),
1941cb0ef41Sopenharmony_ci          { code: 'EOVERRIDEVALUE' }
1951cb0ef41Sopenharmony_ci        )
1961cb0ef41Sopenharmony_ci      }
1971cb0ef41Sopenharmony_ci
1981cb0ef41Sopenharmony_ci      const addPropsToArrayError = haveContents && shouldNotAddPropsToArrays
1991cb0ef41Sopenharmony_ci      if (addPropsToArrayError) {
2001cb0ef41Sopenharmony_ci        throw Object.assign(new Error(`Can't add property ${key} to an Array.`), {
2011cb0ef41Sopenharmony_ci          code: 'ENOADDPROP',
2021cb0ef41Sopenharmony_ci        })
2031cb0ef41Sopenharmony_ci      }
2041cb0ef41Sopenharmony_ci
2051cb0ef41Sopenharmony_ci      return typeof _data[_key] === 'object' ? _data[_key] || {} : {}
2061cb0ef41Sopenharmony_ci    }
2071cb0ef41Sopenharmony_ci
2081cb0ef41Sopenharmony_ci    // sets items from the parsed array of keys as objects, recurses to
2091cb0ef41Sopenharmony_ci    // setKeys in case there are still items to be handled, otherwise it
2101cb0ef41Sopenharmony_ci    // just sets the original value set by the user
2111cb0ef41Sopenharmony_ci    if (keys.length) {
2121cb0ef41Sopenharmony_ci      _data[_key] = setKeys(next(), keys.shift())
2131cb0ef41Sopenharmony_ci    } else {
2141cb0ef41Sopenharmony_ci      // handles special deletion cases for obj props / array items
2151cb0ef41Sopenharmony_ci      if (value === _delete) {
2161cb0ef41Sopenharmony_ci        if (Array.isArray(_data)) {
2171cb0ef41Sopenharmony_ci          _data.splice(_key, 1)
2181cb0ef41Sopenharmony_ci        } else {
2191cb0ef41Sopenharmony_ci          delete _data[_key]
2201cb0ef41Sopenharmony_ci        }
2211cb0ef41Sopenharmony_ci      } else {
2221cb0ef41Sopenharmony_ci        // finally, sets the value in its right place
2231cb0ef41Sopenharmony_ci        _data[_key] = value
2241cb0ef41Sopenharmony_ci      }
2251cb0ef41Sopenharmony_ci    }
2261cb0ef41Sopenharmony_ci
2271cb0ef41Sopenharmony_ci    return _data
2281cb0ef41Sopenharmony_ci  }
2291cb0ef41Sopenharmony_ci
2301cb0ef41Sopenharmony_ci  setKeys(data, keys.shift())
2311cb0ef41Sopenharmony_ci}
2321cb0ef41Sopenharmony_ci
2331cb0ef41Sopenharmony_ciclass Queryable {
2341cb0ef41Sopenharmony_ci  #data = null
2351cb0ef41Sopenharmony_ci
2361cb0ef41Sopenharmony_ci  constructor (obj) {
2371cb0ef41Sopenharmony_ci    if (!obj || typeof obj !== 'object') {
2381cb0ef41Sopenharmony_ci      throw Object.assign(new Error('Queryable needs an object to query properties from.'), {
2391cb0ef41Sopenharmony_ci        code: 'ENOQUERYABLEOBJ',
2401cb0ef41Sopenharmony_ci      })
2411cb0ef41Sopenharmony_ci    }
2421cb0ef41Sopenharmony_ci
2431cb0ef41Sopenharmony_ci    this.#data = obj
2441cb0ef41Sopenharmony_ci  }
2451cb0ef41Sopenharmony_ci
2461cb0ef41Sopenharmony_ci  query (queries) {
2471cb0ef41Sopenharmony_ci    // this ugly interface here is meant to be a compatibility layer
2481cb0ef41Sopenharmony_ci    // with the legacy API lib/view.js is consuming, if at some point
2491cb0ef41Sopenharmony_ci    // we refactor that command then we can revisit making this nicer
2501cb0ef41Sopenharmony_ci    if (queries === '') {
2511cb0ef41Sopenharmony_ci      return { '': this.#data }
2521cb0ef41Sopenharmony_ci    }
2531cb0ef41Sopenharmony_ci
2541cb0ef41Sopenharmony_ci    const q = query =>
2551cb0ef41Sopenharmony_ci      getter({
2561cb0ef41Sopenharmony_ci        data: this.#data,
2571cb0ef41Sopenharmony_ci        key: query,
2581cb0ef41Sopenharmony_ci      })
2591cb0ef41Sopenharmony_ci
2601cb0ef41Sopenharmony_ci    if (Array.isArray(queries)) {
2611cb0ef41Sopenharmony_ci      let res = {}
2621cb0ef41Sopenharmony_ci      for (const query of queries) {
2631cb0ef41Sopenharmony_ci        res = { ...res, ...q(query) }
2641cb0ef41Sopenharmony_ci      }
2651cb0ef41Sopenharmony_ci      return res
2661cb0ef41Sopenharmony_ci    } else {
2671cb0ef41Sopenharmony_ci      return q(queries)
2681cb0ef41Sopenharmony_ci    }
2691cb0ef41Sopenharmony_ci  }
2701cb0ef41Sopenharmony_ci
2711cb0ef41Sopenharmony_ci  // return the value for a single query if found, otherwise returns undefined
2721cb0ef41Sopenharmony_ci  get (query) {
2731cb0ef41Sopenharmony_ci    const obj = this.query(query)
2741cb0ef41Sopenharmony_ci    if (obj) {
2751cb0ef41Sopenharmony_ci      return obj[query]
2761cb0ef41Sopenharmony_ci    }
2771cb0ef41Sopenharmony_ci  }
2781cb0ef41Sopenharmony_ci
2791cb0ef41Sopenharmony_ci  // creates objects along the way for the provided `query` parameter
2801cb0ef41Sopenharmony_ci  // and assigns `value` to the last property of the query chain
2811cb0ef41Sopenharmony_ci  set (query, value, { force } = {}) {
2821cb0ef41Sopenharmony_ci    setter({
2831cb0ef41Sopenharmony_ci      data: this.#data,
2841cb0ef41Sopenharmony_ci      key: query,
2851cb0ef41Sopenharmony_ci      value,
2861cb0ef41Sopenharmony_ci      force,
2871cb0ef41Sopenharmony_ci    })
2881cb0ef41Sopenharmony_ci  }
2891cb0ef41Sopenharmony_ci
2901cb0ef41Sopenharmony_ci  // deletes the value of the property found at `query`
2911cb0ef41Sopenharmony_ci  delete (query) {
2921cb0ef41Sopenharmony_ci    setter({
2931cb0ef41Sopenharmony_ci      data: this.#data,
2941cb0ef41Sopenharmony_ci      key: query,
2951cb0ef41Sopenharmony_ci      value: _delete,
2961cb0ef41Sopenharmony_ci    })
2971cb0ef41Sopenharmony_ci  }
2981cb0ef41Sopenharmony_ci
2991cb0ef41Sopenharmony_ci  toJSON () {
3001cb0ef41Sopenharmony_ci    return this.#data
3011cb0ef41Sopenharmony_ci  }
3021cb0ef41Sopenharmony_ci
3031cb0ef41Sopenharmony_ci  [util.inspect.custom] () {
3041cb0ef41Sopenharmony_ci    return this.toJSON()
3051cb0ef41Sopenharmony_ci  }
3061cb0ef41Sopenharmony_ci}
3071cb0ef41Sopenharmony_ci
3081cb0ef41Sopenharmony_cimodule.exports = Queryable
309