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

@uniqu/url

v0.0.5

Published

URL query string parser producing the Uniqu canonical query format

Readme

@uniqu/url

URL query string parser that produces the Uniqu canonical query format. Human-readable URL syntax with full filter expressions, sorting, pagination, and projection.

Install

pnpm add @uniqu/url

Usage

import { parseUrl } from '@uniqu/url'

const { filter, controls, insights } = parseUrl(
  'age>=18&status!=DELETED&name~=/^Jo/i&$select=name,email&$limit=20'
)

Result:

filter = {
  age: { $gte: 18 },
  status: { $ne: 'DELETED' },
  name: { $regex: '/^Jo/i' },
}

controls = {
  $select: ['name', 'email'],
  $limit: 20,
}

insights = Map {
  'age'    => Set { '$gte' },
  'status' => Set { '$ne' },
  'name'   => Set { '$regex', '$select' },
  'email'  => Set { '$select' },
}

Query Syntax

Comparison Operators

| Syntax | Operator | Example | Result | |--------|----------|---------|--------| | = | $eq | status=ACTIVE | { status: 'ACTIVE' } | | != | $ne | status!=DELETED | { status: { $ne: 'DELETED' } } | | > | $gt | age>25 | { age: { $gt: 25 } } | | >= | $gte | age>=18 | { age: { $gte: 18 } } | | < | $lt | price<100 | { price: { $lt: 100 } } | | <= | $lte | price<=99.99 | { price: { $lte: 99.99 } } | | ~= | $regex | name~=/^Jo/i | { name: { $regex: '/^Jo/i' } } |

Lists (IN / NOT IN)

role{Admin,Editor}        → { role: { $in: ['Admin', 'Editor'] } }
status!{Draft,Deleted}    → { status: { $nin: ['Draft', 'Deleted'] } }

Between

25<age<35     → { age: { $gt: 25, $lt: 35 } }
25<=age<=35   → { age: { $gte: 25, $lte: 35 } }

Exists

$exists=phone,email       → { phone: { $exists: true }, email: { $exists: true } }
$!exists=deletedAt        → { deletedAt: { $exists: false } }

Negation (NOT)

!(...) negates a grouped expression:

!(status=DELETED)
→ { $not: { status: 'DELETED' } }

!(age>18&status=active)
→ { $not: { age: { $gt: 18 }, status: 'active' } }

!(status=DELETED^status=ARCHIVED)
→ { $not: { $or: [{ status: 'DELETED' }, { status: 'ARCHIVED' }] } }

$not can be combined with other operators via & and ^:

!(role{Guest,Anonymous})&age>=18
→ { $and: [{ $not: { role: { $in: ['Guest', 'Anonymous'] } } }, { age: { $gte: 18 } }] }

Logical Operators

& is AND (higher precedence), ^ is OR (lower precedence). Parentheses override precedence:

age>25^score>550&status=VIP
→ { $or: [{ age: { $gt: 25 } }, { score: { $gt: 550 }, status: 'VIP' }] }

(age>25^score>550)&status=VIP
→ { $and: [{ $or: [{ age: { $gt: 25 } }, { score: { $gt: 550 } }] }, { status: 'VIP' }] }

Adjacent AND conditions on the same field are merged when safe:

age>=18&age<=30  → { age: { $gte: 18, $lte: 30 } }

Literal Types

| Syntax | Type | Examples | |--------|------|---------| | Bare number | number | 42, -3.14, 0 | | Leading zero | string | 007, 00, 01 | | true / false | boolean | flag=true | | null | null | deleted=null | | 'quoted' | string | name='John Doe' | | Bare word | string | status=ACTIVE | | /pattern/flags | string | name~=/^Jo/i |

Percent Encoding

All parts are decoded with decodeURIComponent() before parsing. Encode special characters in URLs:

name=%27John%20Doe%27  → name: 'John Doe'
name~=%2F%5EJo%2Fi     → name: { $regex: '/^Jo/i' }

Control Keywords

Control keywords start with $ and are separated from filter expressions:

| Keyword | Aliases | Example | Result | |---------|---------|---------|--------| | $select | — | $select=name,email | { $select: ['name', 'email'] } | | $order | $sort | $order=-createdAt,score | { $sort: { createdAt: -1, score: 1 } } | | $limit | $top | $limit=20 | { $limit: 20 } | | $skip | — | $skip=40 | { $skip: 40 } | | $count | — | $count | { $count: true } | | $with | — | $with=posts,author | { $with: [{ name: 'posts', filter: {}, controls: {} }, ...] } | | $<custom> | — | $search=term | { $search: 'term' } |

Prefix a field with - in $select to exclude it. When any exclusion is present, $select produces an object ({ name: 1, password: 0 }); otherwise it produces an array (['name', 'email']). Prefix with - in $order for descending sort.

Relation Loading ($with)

$with declares which relations to populate alongside the primary query. Relations are comma-separated:

$with=posts,comments,author

Per-Relation Sub-Queries

Each relation can include an inline sub-query in parentheses. Inside the parens, the full query syntax applies — filters, controls, and nested $with:

$with=posts($sort=-createdAt&$limit=5&status=published)

This parses to:

controls.$with = [
  {
    name: 'posts',
    filter: { status: 'published' },
    controls: { $sort: { createdAt: -1 }, $limit: 5 },
  },
]

Each relation is a full Uniquery sub-query with its own filter, controls, and insights. All controls are supported inside parens: $sort, $limit, $skip, $select, $count, and nested $with.

Nested Relations

$with is recursive — relations can load their own sub-relations to any depth:

$with=posts($sort=-createdAt&$limit=5&$with=comments($limit=10&$with=author),tags)

This produces a tree:

controls.$with = [
  {
    name: 'posts',
    filter: {},
    controls: {
      $sort: { createdAt: -1 },
      $limit: 5,
      $with: [
        {
          name: 'comments',
          filter: {},
          controls: {
            $limit: 10,
            $with: [{ name: 'author', filter: {}, controls: {} }],
          },
        },
        { name: 'tags', filter: {}, controls: {} },
      ],
    },
  },
]

Inside each level of parens, & separates parameters and , separates sibling relations within $with=. The parser handles balanced parentheses correctly across nesting levels.

Combined Example

status=active&$with=posts($sort=-createdAt&$limit=5&$select=title,body&status=published),author

Produces:

{
  filter: { status: 'active' },
  controls: {
    $with: [
      {
        name: 'posts',
        filter: { status: 'published' },
        controls: {
          $sort: { createdAt: -1 },
          $limit: 5,
          $select: ['title', 'body'],
        },
        insights: Map { 'status' => Set { '$eq' }, ... },
      },
      { name: 'author', filter: {}, controls: {} },
    ],
  },
  insights: Map {
    'status' => Set { '$eq' },
    'posts'  => Set { '$with' },
    'posts.status' => Set { '$eq' },
    'author' => Set { '$with' },
  },
}

Each $with relation carries its own scoped insights, and nested insights bubble up to the root with dot-notation prefixed field names.

Edge Cases

| Case | Behavior | |------|----------| | $with=posts,posts | Deduplicated — one entry | | $with= or $with | No relations (empty/omitted) | | $with=posts() | Empty parens — same as $with=posts | | Unknown relation names | Recorded as-is — consumer validates against its schema |

Consumer Responsibility

Uniqu parses and types the $with declaration. The consumer (e.g. a database adapter) is responsible for:

  • Execution strategy — JOINs, subqueries, or separate queries
  • Relation validation — checking that relation names exist on the entity
  • Circular reference detection — preventing infinite $with chains
  • Depth limits — restricting nesting depth for performance

Insights

Insights are computed eagerly during URL parsing — a Map<string, Set<InsightOp>> recording which fields are used and with which operators. This includes both filter operators and control usage ($select, $order).

For queries constructed as JSON objects (not parsed from URL), use computeInsights() from @uniqu/core for lazy computation.

Full Example

$select=firstName,-client.ssn
&$order=-createdAt,score
&$limit=50&$skip=10
&$count
&$with=posts($sort=-date&$limit=5&status=published),profile
&$exists=client.phone
&$!exists=deletedAt
&age>=18&age<=30
&status!=DELETED
&name~=/^Jo/i
&role{Admin,Editor}
&25<height<35
^score>550&price>50&price<100

Produces:

{
  filter: {
    $or: [
      {
        'client.phone': { $exists: true },
        deletedAt: { $exists: false },
        age: { $gte: 18, $lte: 30 },
        status: { $ne: 'DELETED' },
        name: { $regex: '/^Jo/i' },
        role: { $in: ['Admin', 'Editor'] },
        height: { $gt: 25, $lt: 35 },
      },
      {
        score: { $gt: 550 },
        price: { $gt: 50, $lt: 100 },
      },
    ],
  },
  controls: {
    $select: { firstName: 1, 'client.ssn': 0 },
    $sort: { createdAt: -1, score: 1 },
    $limit: 50,
    $skip: 10,
    $count: true,
    $with: [
      {
        name: 'posts',
        filter: { status: 'published' },
        controls: { $sort: { date: -1 }, $limit: 5 },
      },
      { name: 'profile', filter: {}, controls: {} },
    ],
  },
}

API Reference

parseUrl(raw: string): UrlQuery

Parse a URL query string (without the leading ?) into the uniqu format.

UrlQuery

interface UrlQuery extends Uniquery {
  insights: UniqueryInsights
}

Narrows the optional insights field from Uniquery to required — insights are eagerly computed during URL parsing. Use getInsights() from @uniqu/core to transparently handle both URL-parsed queries (pre-computed) and manually constructed queries (lazy).

License

MIT