npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@ique/jql

v0.1.6

Published

JavaScript Object Query Language

Downloads

14

Readme

jql

JavaScript Query Language

TO DO

Project Stuff

  • Get Babel working
  • Write more tests
  • Write some documentation
  • Remove dependence on lodash
  • OPTIMIZE

Handle Basic Top Level Select

This doesn't work...

const data = {
  first: 'One',
  second: 'Two',
  third: 'Three',
  fourth: 'Four',
}

const query = `
  first,
  third
`

Current implementation requires a top level key. Should be able to handle this case with another if statement looking for end of string or comma to parseBlock.

Elevate Keys

Move the selected key up a level with ^ character. Think:

const query = `
  1: {
    first,
    second: {
      two,
      four: {
        ^val
      }
    }
  },
  2: {
    third
  }
`;

would produce

const filteredData = {
  1: {
    first: 'One',
    second: {
      two: 'Two',
      val: 'Four'
    }
  },
  2: {
    third: 'Tre'
  }
};

May need to be done in another operation.

Alias Keys

Alias the selected keys in a similar fashion to a destructure ': newName'. Think:

const query = `
  1: {
    first,
    second: {
      two,
      four: {
        ^val : four
      }
    }
  },
  2: {
    third
  }
`;

would produce

const filteredData = {
  1: {
    first: 'One',
    second: {
      two: 'Two',
      four: 'Four'
    }
  },
  2: {
    third: 'Tre'
  }
};

This could be tricky because of our dependence on the position of the :.

Idea

"Query" or "Pick" from an object/map using a string template literal in the style of GraphQL.

Example:

Start with:

const data = {
  1: {
    first: 'One',
    second: {
      two: 'Two',
      three: 'Three',
      four: {
        val: 'Four'
      }
    }
  },
  2: {
    first: 'Uno',
    second: 'Due',
    third: 'Tre'
  }
};

Query:

const query = `
  1: {
    first,
    second: {
      two,
      four: {
        val
      }
    }
  },
  2: {
    third
  }
`;

Call:

const result = jql(data, query);

Result:

const filteredData = {
  1: {
    first: 'One',
    second: {
      two: 'Two',
      four: {
        val: 'Four'
      }
    }
  },
  2: {
    third: 'Tre'
  }
};

Approach

Current approach is to convert the query into an array of selectors similar to the ones used in a lodash 'pick'.

const converted = [
  '1.first',
  '1.second',
  '1.second.two',
  '1.second.four.val',
  '2.third'
]

This array would be filtered to remove duplicate higher level paths like:

const converted = [
  '1.first',
  '1.second.two',
  '1.second.four.val',
  '2.third'
]

And then be fed to lodash to select the keys and return the desired object.

Future

I'd like to not be dependent on lodash, so re-implementing the pick logic in this codebase and with extension in mind.

Thoughts About the Parsing Algorithm

The basic logic is something like this:

Input: a String Output: an array of strings that are object properties, with duplicates removed

  • Initialize an empty array to hold the properties
  • We start with the first property, since we know the input will always start with a property (see example data), and iterate through characters.
  • While the character is not a ',' or a :, we keep adding characters to this property.
  • If we reach a ',' we push the current working string (property) onto our properties array, and start a new property.
  • If we reach a ':', then we know we have reached a property that itself is an object. So we call a function that takes the current property string as an argument (appending a '.' to the end since we know we are now inside a nested object), and the current character index + 1 (to skip the curly brace itself) as another argument, and then iterate through characters again, starting from the current character.
  • If we reach a ',' we start a new property, meaning we go back to the one passed in to this instance of the function (which already has some value like '1.first.two', and start appending it again.
  • If we reach a ':' again, the function calls itself, again passing in the current character index + 1 as the starting index, the current string we're building, etc.

Examples / working ideas (that will probably break your call stack)

const query = `
  1: {
    first,
    second: {
      two,
      four: {
        val
      }
    }
  },
  2: {
    third
  }
`;

// Maybe something like this?

const normalizedQuery = query.replace(/(\n|\s)/g, '');
const converted = [];

createPropertyString(normalizedQuery, '', 0);

const createPropertyString = (query, currentPropString, charPosition) => {
  while (query[charPosition] !== ',' && query[charPosition] !== ':') {
    currentPropString += query[charPosition];
    charPosition++;
  }

  if (query[charPosition] === ',') {
    converted.push(currentPropString);
    createPropertyString(query.slice(charPosition + 1), '', 0);
  } else if (query[charPosition] === ':') {
    createPropertyString(query.slice(charPosition + 1), (currentPropString + '.'), 0);
  }

  return currentPropString;
};

// Or maybe something like this for a single section... the question is how to properly nest these and the iterate over 
// the query as a whole? Do we go through and count the brackets for example, in order to split the string up at the 
// commas in the correct place, then feed each section obtained that way into a recursive function like this or the above?
// (this doesn't work, btw, but seems like it could...)

const createPropertyString = (query, currentPropString, charPosition) => {
  for (let i = charPosition; i < query.length; i++) {
    if (query[charPosition] === ',' ) {
      return currentPropString;
    } else if (query[charPosition] === ':') {
      createPropertyString(query.slice(charPosition + 1), (currentPropString += '.'), i);
    } else {
      currentPropString += query[charPosition];
    }
}