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

@ryanrutkin/autological

v1.0.7

Published

A lightweight querying library for local data processing.

Downloads

3

Readme

autological

Autological is a lightweight JSON condition system, designed to be super lightweight and super fast at query very large datasets. Currently, Autological only returns a boolean value stating that the data ingested does or does not pass the query defined. In future versions, we will return the result, either being all matches or even the result of cascading arithmetic operations. For now, it's just bools.

IMPORTANT NOTE

For easy reading, our examples use Array.prototype.filter. If your goal is to perform fast filtering on a large dataset, don't use this. A nostalgic for(let i = 0; i < data.length; i++) is still your fastest option by a longshot.

Basic Example

Now, how do we explain this?.. Maybe it would be best to lead by example.

Here, we have a set of data:

const data = [
    {
        location: "ABC Motors",
        address: {
            city: "Springfield",
            state: "IL",
            zipcode: "55555"
        },
        agents: [ "Bob", "Amy", "Joe" ]
    },
    {
        location: "Mr. Conar Tist",
        address: {
            city: "Winter River",
            state: "CT",
            zipcode: "33333"
        },
        agents: [ "Mike", "Michael", "Michelle" ]
    },
    {
        location: "ABC Motors",
        address: {
            city: "Hartford",
            state: "CT",
            zipcode: "22222"
        }
        agents: [ "Mike", "Jonny", "Ashley" ]
    }
];

Let's say we wanted to filter this data to show dealerships named "ABC Motors." We could build an Autological filter as such:

const filter = {
    "operator": "and",
    "checks": [
        {
            "path": "location",
            "operator": "=",
            "value": "ABC Motors"
        }
    ]
};

..and loop over our data calling checkCondition per entry

const matches = data.filter(entry => checkCondition(filter, entry));

// matches = [
//    {
//        location: "ABC Motors",
//        address: {
//            city: "Springfield",
//            state: "IL",
//            zipcode: "55555"
//        },
//        agents: [ "Bob", "Amy", "Joe" ]
//    },
//    {
//        location: "ABC Motors",
//        address: {
//            city: "Winter River",
//            state: "CT",
//            zipcode: "33333"
//        }
//        agents: [ "Mike", "Jonny", "Ashley" ]
//    }
// ]

That filter looked a little weird. Couldn't we specify this as:

["location","=","ABC Motors"]

That's the goal, and we'll get there.

The initial goal was a working POC, and it worked! And it's fast!

The syntax currently supported is our "verbose" syntax, and will always be the syntax accepted by our checkCondition method.

The idea is that you can always call checkCondition in a streamlined fashion, passing each entry item individually.

When the "sexy syntax" is supported, you will be passing the entire payload (the full data object we defined) and receiving the result (filtered set, aggregate, etc.).

This is because we will first need to interpret the "sexy syntax" and transform it into the "verbose syntax" for faster operations.

You wouldn't want us to type check every condition for every data entry now would you?

What makes Autological so fast?

Autological takes advantage of caching each path resolution, so it doesn't need to be resolved again per data object.

Currently, this is done on a per-data-entry basis (per each index of our data object we defined in the "Basic Example").

This comes in handy when dealing with much more complex queries, which could reference the same data point multiple times.

Yes, we've seen these kinds of queries in production. It helps A LOT.

A more complex example

This test has been scaled and benchmarked. As of 1.0.6, this complex filter preforms at 1 microsend per array entry.

Let's set up our data object for a more complex example:

const data = [
    {
        location: "ABC Motors",
        points: 10,
        address: {
            city: "Springfield",
            state: "IL",
            zipcode: "55555"
        },
        agents: [
            {
                name: "Bob"
            }
        ]
    },
    {
        location: "Mr. Conar Tist",
        points: 2,
        address: {
            city: "Winter River",
            state: "CT",
            zipcode: "33333"
        },
        agents: [
            {
                name: "Mike"
            },
            {
                name: "Michael"
            },
            {
                name: "Michelle"
            }
        ]
    },
    {
        location: "ABC Motors",
        points: 9,
        address: {
            city: "Hartford",
            state: "CT",
            zipcode: "55555"
        }
        agents: [
            {
                name: "Mike"
            },
            {
                name: "Jonny"
            },
            {
                name: "Ashley"
            }
        ]
    }
];

Note how we have a nested address.state and an array of agents.

Let's try finding all of the locations that are in Connecticut AND do not have an agent named Jonny OR have a point total over 5.

const matches = data.filter(entry => checkCondition({
    "operator": "or",
    "checks": [
        {
            "operator": "and",
            "checks": [
                {
                    "path": "address.state",
                    "operator": "=",
                    "value": "CT"
                },
                {
                    "operator": "not",
                    "target": {
                        "path": "agents",
                        "operator": "contains",
                        "condition": {
                            "operator": "and",
                            "checks": [
                                {
                                    "path": "name",
                                    "operator": "=",
                                    "value": "Jonny"
                                }
                            ]
                        }
                    }
                }
            ]
        },
        {
            "path": "points",
            "operator": ">",
            "value": 5
        }
    ]
}, data));

// matches = [
//     {
//         location: "ABC Motors",
//         points: 10,
//         address: {
//             city: "Springfield",
//             state: "IL",
//             zipcode: "55555"
//         },
//         agents: [
//             {
//                 name: "Bob"
//             }
//         ]
//     },
//     {
//        location: "Mr. Conar Tist",
//        points: 2,
//        address: {
//            city: "Winter River",
//            state: "CT",
//            zipcode: "33333"
//        },
//        agents: [
//            {
//                name: "Mike"
//            },
//            {
//                name: "Michael"
//            },
//            {
//                name: "Michelle"
//            }
//        ]
//    }
// ]

When we release version two, our target "sexy syntax" for this style of filter will be:

[
    "or",
    [
        ["and", [
            ["address.state", "=", "CT"],
            ["agents", "!contains", [
                ["name", "=", "Jonny"]
            ]]
        ]],
        ["points", ">", 5]
    ]
]

Again, this has the same result as the verbose syntax, but this is easier to read.

What config options are available?

autoCast has been deprecated as of 1.0.6

Currently only one special config option exists, and that is the autoCast option.

This can be passed to checkCondition to allow sloppy type coersion to take affect.

For example, if you had:

const data = [
    {
        name: "Dave",
        age: 64
    },
    {
        name: "Ryan",
        age: 33
    },
    {
        name: "Addie",
        age: 29
    }
];

and wanted to find someone age "33" (note that this is a string):

const matches = data.filter(entry => checkCondition({
    "operator": "and",
    "checks": [
        {
            "path": "age",
            "operator": "=",
            "value": "33"
        }
    ]
}, entry));

// matches = []

The result is that there are no matches.

This is because the provided value is a string, and the field is an integer.

By providing autoCast in config, we can automatically convert these values and get the expected result.

const matches = data.filter(entry => checkCondition({
    "operator": "and",
    "checks": [
        {
            "path": "age",
            "operator": "=",
            "value": "33"
        }
    ]
}, entry, { autoCast: true }));

// matches = [
//     {
//         name: "Ryan",
//         age: 33
//     }
// ]

Why is autoCast facing deprecation?

The fact that autoCast exists as an option today requires an additional logical check for every operation within a Check.

I played around with the idea of making each logical check a function, and swapping the mapping when in autoCast mode, but making each logical check a function would add another layer of operation to each check as well, being even less efficient.

If Autological is to retain it's original purpose of filtering very large datasets very quickly, this needs to be removed.