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 🙏

© 2026 – Pkg Stats / Ryan Hefner

aqe-light

v1.0.2

Published

AQE Light — Atomic Quantum Engine (Free Edition). A lightweight, bitmask-based CSS selector engine.

Downloads

447

Readme

⚡ AQE — Atomic Quantum Engine

A high-performance CSS selector engine built on BigInt bitmasks, SharedArrayBuffer, dual Bloom bucket indexing, and off-thread Web Worker matching.


Table of Contents


Overview

AQE replaces the traditional DOM traversal model with a flat, memory-mapped approach. Every node in the document is projected into a compact binary representation stored in a typed array. CSS tokens — tag names, class names, IDs, attributes, pseudo-classes — are each assigned a unique bit in a 64-bit BigInt mask. Matching a selector becomes a bitwise AND operation rather than a recursive tree walk.

The result is a selector engine that scales sub-linearly on large, complex DOMs and stays responsive under high query frequency by offloading scanning to a background thread.


How It Works

Bitmask System

When a node is registered, AQE computes its CSS bitmask: a 64-bit integer where each bit corresponds to a unique CSS token.

div#main.active[data-ready]  →  bit_0 (div) | bit_1 (#main) | bit_2 (.active) | bit_3 ([data-ready])

Querying div.active produces a target mask with the same bits. Matching is then:

(nodeMask & targetMask) === targetMask

This reduces selector matching to a single integer comparison per node — no string parsing, no tree climbing, no attribute iteration at query time.

Masks are split into two 32-bit integers (mLow, mHigh) for storage in Int32Array. Beyond 63 unique tokens, bits alias (Bloom-style false positives), which are harmless: aliased candidates are validated by el.matches() on the main thread.

Bloom Bucket Index

AQE Pro maintains a dual Bloom bucket index alongside the main buffer. The node pool is divided into buckets of 32 nodes each. For every bucket, two 32-bit summary integers (bloomLow, bloomHigh) are kept: the OR of every node mask in that bucket.

Before scanning a bucket, the Worker checks:

(bloomLow[b]  & targetLow)  === targetLow
(bloomHigh[b] & targetHigh) === targetHigh

If either check fails, the entire bucket of 32 nodes is skipped in a single operation. On a typical page where most selectors are class or ID-specific, the majority of buckets are eliminated before any node-level comparison occurs.

The Bloom index is updated incrementally: syncNode uses Atomics.or to set bits, and unsyncNode recomputes the bucket by OR-ing the remaining 31 nodes — O(32) fixed cost.

SharedArrayBuffer & Atomics

AQE Pro allocates three SharedArrayBuffer regions:

| Buffer | Contents | Size | |---|---|---| | buffer | Node masks + spatial coordinates | maxNodes × 32 bytes | | bloomLowBuffer | Bucket OR summary (low 32 bits) | ⌈maxNodes/32⌉ × 4 bytes | | bloomHighBuffer | Bucket OR summary (high 32 bits) | ⌈maxNodes/32⌉ × 4 bytes |

All reads and writes use Atomics.store / Atomics.load / Atomics.or, guaranteeing sequential consistency between the main thread and the Worker without locks or copying.

Web Worker

The scanning loop runs entirely off the main thread. The Worker receives the shared buffer references (zero-copy transfer) and a serialised representation of the query steps, then:

  1. Iterates over Bloom buckets, skipping mismatches instantly.
  2. For each surviving bucket, scans up to 32 nodes with bitwise AND.
  3. Optionally applies a spatial AABB filter (geometric pruning).
  4. Returns an array of matching node indices via postMessage.

Each query carries a unique queryId. Responses are routed back to the originating Promise via a Map of pending callbacks, making concurrent queries fully safe.

MutationObserver

AQE watches the live DOM for structural and attribute changes:

  • childList mutations call syncNode on added nodes and unsyncNode on removed nodes — freeing their buffer slots into a freeSlots pool for reuse.
  • attributes mutations re-sync the affected node, recomputing its mask to reflect class, id, or attribute changes.

This keeps the buffer a real-time mirror of the DOM with no polling.

Query Pipeline

query(selector)
    │
    ├─ _parse()         → branch[] of { mask, relation } steps
    │
    ├─ selector list?   → fan out _querySingle() in Promise.all, deduplicate
    │
    └─ _querySingle()
            │
            ├─ compute mLow / mHigh for each step
            ├─ postMessage to Worker (zero-copy SharedArrayBuffer)
            │
            └─ Worker response
                    │
                    ├─ map indices → DOM nodes
                    └─ complex selector? → filter with el.matches()

Editions

AQE Light (free)

AQE Light is a dependency-free, single-class implementation designed for moderate-scale projects and environments where SharedArrayBuffer is unavailable (e.g. cross-origin contexts without COOP/COEP headers).

How it works:

  • Maintains a plain Map<Element, bigint> registry on the main thread.
  • Query is synchronous: iterates the registry, applies bitmask pre-filter, then el.matches() for complex selectors.
  • No Worker, no Bloom index, no shared memory.
  • Manual syncNode / syncAll — no automatic MutationObserver.

Best suited for: projects with up to ~5,000 nodes, infrequent queries, or environments with strict security headers.

AQE Pro (paid)

AQE Pro is the full engine: off-thread scanning, Bloom bucket skipping, atomic shared memory, live DOM observation, concurrent query support, and full CSS selector coverage.

Best suited for: large SPAs, design tools, virtual DOMs, dashboards, or any context with 5,000+ nodes and high query throughput.


Performance

Benchmark Estimates

The following estimates are based on a DOM of 20,000 nodes with an average of 3 classes and 2 attributes per node, querying a moderately selective compound selector (.active[data-ready]).

| Scenario | querySelectorAll (native) | AQE Light | AQE Pro | |---|---|---|---| | Cold query, 20k nodes | ~4–8 ms | ~3–6 ms | ~0.3–0.8 ms | | Warm query (cached tokens) | ~4–8 ms | ~1–3 ms | ~0.1–0.4 ms | | 100 concurrent queries | ~400–800 ms | ~150–300 ms | ~5–15 ms | | Spatial filter (radius 200px) | not supported | not supported | ~0.05–0.2 ms | | DOM of 50,000 nodes | ~10–20 ms | ~8–15 ms | ~0.5–1.5 ms |

Estimates assume a modern desktop browser (V8/SpiderMonkey). Mobile devices may show 1.5–3× slower absolute times across all columns, with AQE Pro's relative advantage maintained.

Why AQE Pro Is Faster

1. Off-thread execution. The Worker runs the scan loop concurrently with the main thread. On multi-core hardware the query resolves while the UI thread continues rendering — zero jank contribution.

2. Bloom bucket skipping. With 32 nodes per bucket and a selective target mask, typically 60–90% of buckets are eliminated in two integer comparisons. At 20,000 nodes (625 buckets), a selective query may inspect fewer than 100 actual nodes.

3. Zero-copy memory access. SharedArrayBuffer is passed by reference to the Worker — no serialisation, no structuredClone, no memory allocation on the transfer. The three buffers for a 50,000-node pool consume ~1.6 MB total.

4. Atomic consistency without locking. Atomics.store / Atomics.load / Atomics.or provide sequentially consistent access across threads without mutexes or Transferable ownership transfers, eliminating copy overhead on every query.

5. Incremental sync. freeSlots reuse means removed nodes immediately free their buffer position for the next inserted node — no compaction, no reallocation, constant-time insert and remove.

Rough speed ratio between editions:

| Query type | AQE Light vs AQE Pro | |---|---| | Simple selector, 5k nodes | ~3–5× faster (Pro) | | Compound selector, 20k nodes | ~8–15× faster (Pro) | | High-frequency (100 req/s), 20k nodes | ~20–40× faster (Pro) | | Spatial filter query | ∞ (feature not available in Light) |


Supported Selector Syntax

| Feature | AQE Light | AQE Pro | |---|---|---| | Tag div | ✅ | ✅ | | Class .active | ✅ | ✅ | | ID #main | ✅ | ✅ | | Attribute [type] [type=text] | ✅ | ✅ | | Attribute operators ^= $= *= ~= \|= | ✅ | ✅ | | Compound div.active#main | ✅ | ✅ | | Selector list h1, h2, h3 | ✅ | ✅ | | Descendant div .child | ✅ (via el.matches) | ✅ (via el.matches) | | Child div > .child | ✅ (via el.matches) | ✅ (via el.matches) | | Adjacent h1 + p | ✅ (via el.matches) | ✅ (via el.matches) | | Sibling h1 ~ p | ✅ (via el.matches) | ✅ (via el.matches) | | Pseudo :checked :disabled :focus | ✅ | ✅ | | Pseudo :not() :is() :where() :has() | ✅ (via el.matches) | ✅ (via el.matches) | | Pseudo-element ::before | partial | ✅ | | Spatial filter {x, y, radius} | ❌ | ✅ | | Live DOM observation | ❌ | ✅ | | Concurrent async queries | ❌ | ✅ | | Off-thread execution | ❌ | ✅ |


Installation

AQE Light

No build step required. Drop the file into your project and import it.

Via script tag:

<script src="AQE-Light.js"></script>

Via ES module:

import AQELight from './AQE-Light.js';

Via npm (if published):

npm install aqe-light

AQE Pro

AQE Pro requires SharedArrayBuffer, which is available only in secure contexts with cross-origin isolation. Add the following headers to your server:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Via script tag:

<script src="AQE-Pro.js"></script>

Via ES module:

import AQE from './AQE-Pro.js';

AQE Pro is available for purchase at williammartin.lemonsqueezy.com or by contacting the developer directly (see Contact).


Usage

Usage — AQE Light

const engine = new AQELight();

// Sync the full DOM once on init
engine.syncAll();

// Simple query
const buttons = engine.query('button.primary');

// Compound selector
const inputs = engine.query('input[type=text].visible');

// Selector list
const headings = engine.query('h1, h2, h3');

// Complex selector (descendant)
const items = engine.query('.sidebar > ul li.active');

// Re-sync a node after a manual class change
el.classList.add('highlighted');
engine.syncNode(el);

// Clean up
engine.destroy();

Usage — AQE Pro

const engine = new AQE(50_000); // maxNodes (default 50,000)

// Sync the full DOM once on init.
// After this, MutationObserver keeps the buffer live automatically.
engine.syncAll();

// Simple async query
const buttons = await engine.query('button.primary');

// Compound selector
const inputs = await engine.query('input[type=text].visible:not(:disabled)');

// Selector list — branches run in parallel
const headings = await engine.query('h1, h2, h3');

// Complex selector
const items = await engine.query('.sidebar > ul li.active');

// Spatial query — only nodes within a 300px radius of (500, 400)
const nearby = await engine.query('.tooltip', { x: 500, y: 400, radius: 300 });

// Concurrent queries — fully safe, each resolves independently
const [navItems, cards, badges] = await Promise.all([
    engine.query('nav a.active'),
    engine.query('.card[data-loaded]'),
    engine.query('span.badge:not(.hidden)'),
]);

// Clean up Worker and Observer when done
engine.destroy();

API Reference

AQELight

| Method | Signature | Description | |---|---|---| | syncNode | (el: Element) → void | Registers or updates a single node | | unsyncNode | (el: Element) → void | Removes a node from the registry | | syncAll | () → void | Syncs all nodes currently in the document | | query | (selector: string) → Element[] | Synchronous selector query | | destroy | () → void | Clears registry and token map |

AQE (Pro)

| Method | Signature | Description | |---|---|---| | syncNode | (el: Element) → void | Registers or updates a single node in the shared buffer | | unsyncNode | (el: Element) → void | Removes a node and frees its buffer slot | | syncAll | () → void | Full DOM sync — call once on init | | query | (selector: string, spatial?: {x, y, radius}) → Promise<Element[]> | Async query, off-thread | | destroy | () → void | Terminates the Worker and disconnects the Observer |


Architecture Diagram

┌─────────────────────────────────────────────────────────┐
│                        Main Thread                       │
│                                                         │
│  DOM ──MutationObserver──▶ syncNode()                   │
│                                │                        │
│                         BigInt bitmask                  │
│                                │                        │
│              ┌─────────────────▼──────────────────┐     │
│              │         SharedArrayBuffer           │     │
│              │  [mask_low | mask_high | x|y|w|h]  │     │
│              └─────────────────┬──────────────────┘     │
│                                │   (zero-copy ref)      │
│  query() ──────────────────────▼                        │
│                         postMessage()                   │
└─────────────────────────────────┬───────────────────────┘
                                  │
                    ┌─────────────▼──────────────┐
                    │         Web Worker          │
                    │                            │
                    │  Bloom bucket check ──▶ skip 32 nodes
                    │         │                  │
                    │  bitmask AND per node       │
                    │         │                  │
                    │  spatial AABB filter        │
                    │         │                  │
                    │  postMessage(matches)       │
                    └─────────┬──────────────────┘
                              │
                    ┌─────────▼──────────────────┐
                    │       Main Thread           │
                    │                            │
                    │  idx[] → Element[]         │
                    │  complex? → el.matches()   │
                    │  resolve(Promise)          │
                    └────────────────────────────┘

Limitations & Trade-offs

Bit aliasing beyond 63 tokens. AQE uses 64-bit masks split into two 32-bit integers. Once more than 63 unique CSS tokens are registered, new tokens alias onto existing bit positions. This produces Bloom-style false positives (candidates that pass the bitmask check but fail el.matches()), not false negatives. Correctness is preserved; only a negligible number of extra el.matches() calls are added.

SharedArrayBuffer requires cross-origin isolation (Pro). The headers Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp must be set. This is a browser security requirement, not an AQE constraint.

Combinator validation on the main thread. Complex selectors (div > .child, .parent ~ .sibling) are pre-filtered by the Worker using the subject (last compound part) bitmask, then validated by el.matches() on the main thread. For selectors where the subject is very broad (e.g. * > .item), the pre-filter is less effective and more el.matches() calls occur.

Spatial coordinates are viewport-relative. getBoundingClientRect() returns coordinates relative to the current viewport. Spatial queries assume a stable scroll position at sync time. Call syncAll() after large scroll events if spatial accuracy is critical.


Get AQE Pro

AQE Pro is available through the official store or directly from the developer.

Licences are available for individual developers, teams, and enterprise use. Reach out by email for volume pricing or custom integration support.


Contact

For bug reports, feature requests, or general questions, open an issue on this repository or write to [email protected].


License

AQE Light is released under the MIT License.

AQE Pro is a commercial product. Redistribution, reverse engineering, and sublicensing are prohibited without explicit written permission from the author. See the licence file included with your purchase for full terms.