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

@jpahd/qparams

v0.1.0

Published

Schema-driven URL query parameter builder with support for forbidden, repeatable, optional, and static params.

Downloads

44

Readme

qparams

Schema-driven URL query parameter handling. Define rules for how your params behave — forbidden, repeatable, optional, static — and let qparams enforce them.

Zero runtime dependencies. Pure functions. Returns standard URLSearchParams.

Install

pnpm add @jpahd/qparams

Quick start

import { createSchema, constrain, merge } from "@jpahd/qparams";

const schema = createSchema({
  forbidden: ["token"],       // silently stripped
  repeatable: ["tag"],        // may appear multiple times
  optional: ["filter"],       // dropped on merge when not re-supplied
  static: ["_page"],          // set once, never overwritten
});

// Constrain raw user input against the schema
const params = constrain(schema, "token=secret&tag=js&tag=ts&name=test");
params.toString(); // "tag=js&tag=ts&name=test"

// Merge new params into existing ones
const next = merge(schema, params, "tag=ts&tag=go&name=updated");
next.getAll("tag"); // ["ts", "go"]  — "js" removed, "go" added
next.get("name");   // "updated"

Any param not listed in the schema is treated as a regular param (last-write-wins).

API

createSchema(options?): Schema

Create a schema that classifies param keys into categories.

interface SchemaOptions {
  forbidden?: string[];   // Dropped on constrain/merge
  repeatable?: string[];  // Appended, not overwritten; diffed on merge
  optional?: string[];    // Dropped from current state on merge unless re-supplied
  static?: string[];      // Set once, subsequent writes are ignored
  hooks?: Hooks;          // Optional callbacks fired after operations
  validate?: (schema: Schema) => void;  // BYO validation callback
}

Returns a Schema (an object of ReadonlySet<string>s + hooks).

constrain(schema, incoming, base?): URLSearchParams

Apply schema rules to raw params. Useful for sanitizing user/URL input before use.

  • Forbidden params are dropped.
  • Repeatable params are appended.
  • Static params are set only if absent in base.
  • Regular params use last-write-wins.
const schema = createSchema({ forbidden: ["debug"], static: ["v"] });

// With a base — static param "v" already exists, so incoming "v=2" is ignored
constrain(schema, "v=2&debug=1&q=hello", "v=1");
// => "v=1&q=hello"

incoming and base accept URLSearchParams | string | Record<string, string>.

merge(schema, current, incoming): URLSearchParams

Merge incoming params into current params, respecting all schema rules.

| Category | Behavior on merge | |------------|---------------------------------------------------------------------| | forbidden | Silently dropped from incoming | | repeatable | Diffed: shared values kept, new values appended, missing values removed | | optional | Dropped from current before merge; survives only if re-supplied | | static | Preserved in current; incoming writes ignored | | regular | Last-write-wins |

const schema = createSchema({
  repeatable: ["tag"],
  optional: ["filter"],
});

const current = new URLSearchParams("tag=a&tag=b&filter=active&sort=name");
const next = merge(schema, current, "tag=b&tag=c&sort=date");

next.getAll("tag");    // ["b", "c"]      — "a" removed, "c" added
next.has("filter");    // false            — optional, not re-supplied
next.get("sort");      // "date"           — regular, overwritten

without(schema, params, options?): URLSearchParams

Return a copy with certain schema categories removed.

const schema = createSchema({ static: ["_page", "_limit"] });
const clean = without(schema, "_page=1&_limit=20&q=hello", { static: true });
clean.toString(); // "q=hello"

Options: { static?: boolean; forbidden?: boolean }.

fromURL(url): URLSearchParams

Extract search params from a full URL string or URL object.

import { fromURL } from "@jpahd/qparams";

const params = fromURL("https://example.com/search?q=hello&tag=js");
params.get("q"); // "hello"

get(params, key): string | null

Convenience wrapper around URLSearchParams.get().

getAll(params, key): string[]

Convenience wrapper around URLSearchParams.getAll(). Useful for repeatable params.

toObject(params): Record<string, string | string[]>

Convert URLSearchParams to a plain object. Keys that appear multiple times become arrays; singular keys become strings.

const params = new URLSearchParams("tag=a&tag=b&name=test");
toObject(params); // { tag: ["a", "b"], name: "test" }

Hooks

Opt-in callbacks fired after operations. Useful for logging, analytics, or debugging.

import { createSchema } from "@jpahd/qparams";

const schema = createSchema({
  repeatable: ["tag"],
  hooks: {
    onConstrain(incoming, result) {
      console.log("constrain:", result.toString());
    },
    onMerge(current, incoming, result) {
      console.log("merge:", result.toString());
    },
    onWithout(input, result) {
      console.log("without:", result.toString());
    },
  },
});

Schema validation

createSchema accepts a validate callback for BYO validation. It receives the built schema and can throw if something is wrong.

import { createSchema } from "@jpahd/qparams";

createSchema({
  forbidden: ["tag"],
  repeatable: ["tag"], // oops — same key in two categories
  validate(schema) {
    const seen = new Set<string>();
    for (const category of [schema.forbidden, schema.repeatable, schema.optional, schema.static]) {
      for (const key of category) {
        if (seen.has(key)) throw new Error(`Key "${key}" in multiple categories`);
        seen.add(key);
      }
    }
  },
});
// throws: Key "tag" in multiple categories

See examples/zod-validator.ts for a zod-based validator.

Framework bindings

Companion packages that sync qparams with framework-specific reactivity. All share the same return shape: params, update, strip, toObject.

| Package | Install | API | |---|---|---| | React | pnpm add @jpahd/qparams-react | useQParams(schema) | | Vue | pnpm add @jpahd/qparams-vue | useQParams(schema) | | Lit | pnpm add @jpahd/qparams-lit | new QParamsController(host, schema) | | Svelte | pnpm add @jpahd/qparams-svelte | createQParams(schema) | | Solid | pnpm add @jpahd/qparams-solid | createQParams(schema) |

// React example
import { createSchema } from "@jpahd/qparams";
import { useQParams } from "@jpahd/qparams-react";

const schema = createSchema({ repeatable: ["tag"], optional: ["q"] });

function SearchPage() {
  const { params, update, strip, toObject } = useQParams(schema);

  return (
    <input
      value={params.get("q") ?? ""}
      onChange={(e) => update({ q: e.target.value })}
    />
  );
}

Param categories explained

| Category | Use case | Example keys | |----------------|-------------------------------------------------|------------------------| | forbidden | Secrets, internal tokens — never leak to the URL | token, session_id | | repeatable | Multi-select filters, tags | tag, category | | optional | Temporary filters that shouldn't persist | filter, highlight | | static | Pagination or versioning that initializes once | _page, _per_page | | (regular) | Everything else — normal key/value pairs | q, sort, name |

Design principles

  • Pure functions — no internal mutable state. Pass data in, get data out.
  • Standard types — operates on URLSearchParams throughout. No wrapper objects to unwrap.
  • Zero dependencies — only uses the web-standard URLSearchParams API.
  • Flexible input — all operations accept URLSearchParams, query strings, or plain objects.
  • Framework-agnostic — works in browsers, Node, Deno, Bun, or any runtime with URLSearchParams.

Development

pnpm install
pnpm test          # run tests once
pnpm test:watch    # run tests in watch mode
pnpm lint          # oxlint
pnpm fmt           # oxfmt (write)
pnpm fmt:check     # oxfmt (check, for CI)
pnpm build         # compile to dist/

License

MIT