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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@openreply/aql

v2.1.2

Published

API Query Language - parser and filter

Downloads

257

Readme

AQL Manual

Description

API Query Language (AQL) is a filtering language designed for use in URIs with JSON data structures.

Usage

The strings used for filtering, e.g. 'eq(age,25)' must be URI-encoded since this is designed for usage in the query string of a URI. Paths are defined as JSONPath to look inside JSON objects.

const { filter, Operator } = require('@openreply/aql');

const customOperators = ({
  // adding a new operator
  hasKey: new Operator((evaluateSubexpression, collection, options, key) =>
    collection.filter(item => Object.keys(item).includes(key))),
  // overriding a default operator
  eq: new Operator((evaluateSubexpression, collection, options, path, value) =>
    collection.filter(item =>
      item[path] === value))
});

const collection = [
  {
    name: 'Isom Hamill',
    age: 25,
    phones: [
      { type: 'work', number: '012345' },
      { type: 'home', number: '012345' }
    ],
    address: {
      street: '7264 Kade Alley'
    }
  },
  {
    name: 'Lonzo Dooley',
    age: 30,
    phones: [
      { type: 'mobile', number: '032345' },
      { type: 'home', number: '042346' }
    ]
  },
  {
    name: 'Bertrand Mertz',
    age: 35
  }
]

// Basic filtering
// filter by name
const result1 = filter('eq(name,Isom Hamill)')(collection);
// => [{"name":"Isom Hamill","age":25,"phones":[{"type":"work","number":"012345"},{"type":"home","number":"012345"}],"address":{"street":"7264 Kade Alley"}}]


// Using JSONPath
// filter by street: street must include 'Kade'
const result2 = filter('includes(address.street,Kade)')(collection);
// => [{"name":"Isom Hamill","age":25,"phones":[{"type":"work","number":"012345"},{"type":"home","number":"012345"}],"address":{"street":"7264 Kade Alley"}}]


// Using custom operators
// filter all items that have the key ('phones')
const result3 = filter('hasKey(phones)', customOperators)(collection);
// => [{"name":"Isom Hamill","age":25,"phones":[{"type":"work","number":"012345"},{"type":"home","number":"012345"}],"address":{"street":"7264 Kade Alley"}},
//     {"name":"Lonzo Dooley","age":30,"phones":[{"type":"mobile","number":"032345"},{"type":"home","number":"042346"}]}]


// quantitative operators
const result4 = filter('all(phones.*,phone,startsWith(phone.number,01))')(collection)
// => [{"name":"Isom Hamill","age":25,"phones":[{"type":"work","number":"012345"},{"type":"home","number":"012345"}],"address":{"street":"7264 Kade Alley"}}]

const result5 = filter('any(phones.*,phone,eq(phone.type,home))')(collection)
// => [{"name":"Isom Hamill","age":25,"phones":[{"type":"work","number":"012345"},{"type":"home","number":"012345"}],"address":{"street":"7264 Kade Alley"}},
//     {"name":"Lonzo Dooley","age":30,"phones":[{"type":"mobile","number":"032345"},{"type":"home","number":"042346"}]}]

Operators

These operators are defined by default:

  • eq(path, value): The item at path must have the value value.
  • ne(path, value): The item at path must have a value different from value.
  • in(path, value1, value2, ...): The item at path must have one of the specified values.
  • out(path, value1, value2, ...): The item at path must not equal to any the specified values.
  • gt(path, value): The item at path must be greater than value.
  • ge(path, value): The item at path must be greater than or equal to value.
  • lt(path, value): The item at path must be less than value.
  • le(path, value): The item at path must be less than or equal to value.
  • startsWith(path, prefix): The item at path must have the prefix prefix.
  • endsWith(path, suffix): The item at path must have the suffix suffix.
  • includes(path, value): The string/number item at path must include the infix value OR the array at path must contain the value value.
  • and(condition1, condition2, ...): All the conditions must hold. Example: and(includes(name,o),ge(age,30)) filters items that include an o in the name and with age greater than or equal to 30.
  • or(condition1, condition2, ...): At least one of the conditions must hold. Example: or(eq(phones.0.type,work),eq(phones.0.type,mobile)) filters items that either have a work phone or a mobile phone as the first phones entry.
  • all(path, binding, condition): For all of the items binding that match path, the condition must hold and there must be at least one item that matches path. Example: all(phones.*,phone,startsWith(phone.number,01)) filters items that have at least one phone number and all of the existing phone numbers of the item must start with 01.
  • any(path, binding, condition): For at least one of the items binding that match path, the condition must hold. Example: any(phones.*,phone,eq(phone.type,home)) filters items that have at least one phone that has the type home.

If the actual value at path is a number, value (or value*) is coerced to a number.

Extensions

New AQL operators can be added by providing a customOperators function to the filter function:

const { filter, Operator } = require('@openreply/aql');

const customOperators = collection => ({
  // adding a new operator
  hasKey: new Operator((evaluateSubexpression, collection, options, key) =>
    collection.filter((item) => Object.keys(item).includes(key))),
});

const filteredCollection = filter('hasKey(phones)', customOperators)(fullCollection);

The customOperators function takes the collection as its only argument and returns an object with the operators. The keys this object define the name of the operator. If a key is used that is already defined in the default set of operators, the default will be overridden. Each operator takes an options object as the first argument. It contains information on how the operator should be modified. The other arguments of the operator are free and can be defined and used by the operator itself. The return value of an operator is expected to be an Array that has the filter condition applied.

Acknowledgement

Special thanks to persvr for creating the RQL. This package is heavily insprired by it. The main difference is that AQL is using JSONPath instead of a custom path language and AQL does not sort nor aggregate.

License

This project is licensed under the MIT license.