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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@query-state/core

v1.0.3

Published

Core query state management

Readme

@query-state/core

@query-state/core is the core package of the Query State library, a lightweight and reactive query manager for URL state.
It leverages observer patterns and a pub/sub system to allow fine-grained control over query parameters in your application.

npm version License Package Size


📦 Installation

npm i @query-state/core
# or
pnpm i @query-state/core

💡 Overview

This package provides a Query Manager that:

  • Observes the browser URL and query parameters

  • Notifies subscribers only for the keys that changed

  • Applies updates in order using a microtask queue to avoid race conditions

  • Patches history.pushState and history.replaceState for controlled updates

  • Supports silent updates without triggering notifications

  • Handles rate-limiting for high-frequency updates


⚙️ How the Query Manager Works

The core of @query-state/core is the Query Manager, implemented via the ImpQueryManager class.

It:

  1. Listens to changes in the URL via:
  • popstate events (browser back/forward)

  • Custom qschange events dispatched on query updates

  1. Maintains a subscriber list per query key for granular notifications

  2. Uses a microtask queue to apply multiple updates in order

  3. Rate-limits updates with configurable options

  4. Patches the browser history API (pushState / replaceState)

  5. Allows silent updates that modify the URL without notifying subscribers


Internal Flow

sequenceDiagram participant Browser as Browser URL participant Manager as QueryManager participant Queue as QueryQueue participant Consumer as Subscriber

Browser->>Manager: URL changes (popstate or __qschange__)
Manager->>Queue: Enqueue updates in microtask
Queue->>Manager: Apply queued updates
Manager->>Consumer: Notify subscribers of changed keys

Key points:

  • Granular Updates: Only affected subscribers are notified.

  • Microtask Queue: Ensures updates are applied in order.

  • Silent Updates: Modify URL without notifications (silentUpdate).


📦 Public API

@query-state/core exposes convenient accessors for interacting with the query state:

| Function | Description | | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | readParam(key: string) | Reads the current value of a query key from the URL. | | writeParam(key: string, value: string \| null, mode?: 'push' \| 'replace', options?: FullOptions) | Updates a query key in the URL and notifies subscribers. | | subscribeQS(callback: () => void, key: string) | Subscribes to changes of a specific query key. Returns an unsubscribe function. |


Example: Subscribing and Updating Query Keys

import { subscribeQS, writeParam, readParam } from '@query-state/core';

// Subscribe to a query key
const unsubscribePage = subscribeQS(() => {
  const page = readParam('page');
  console.log('Page changed:', page);
}, 'page');

// Update a query key
writeParam('page', '2'); // triggers __qschange__ event

// Later, if you want to stop listening
unsubscribePage();

Example: Using Accessors for Multiple Keys

import { readParam, writeParam, subscribeQS } from '@query-state/core';

// Read a key
console.log(readParam('limit')); // e.g. "10"

// Subscribe to changes
const unsubscribeLimit = subscribeQS(() => {
  console.log('Limit changed:', readParam('limit'));
}, 'limit');

// Update a key
writeParam('limit', '20'); // triggers subscriber

// Stop listening when done
unsubscribeLimit();

📖 Features

  • Reactive URL state: Automatically observes changes in query parameters.

  • Granular updates: Only notifies subscribers of keys that changed.

  • Safe history API patching: Avoids breaking browser back/forward navigation.

  • Queue system: Applies multiple updates in order to avoid conflicts.

  • Rate-limiting support: Prevents excessive updates in high-frequency scenarios.

  • Silent updates: Modify URL without triggering subscribers.

  • TypeScript support: Full typings for queries, options, and events.


🔹 Query State Parsers (qsParser)

@query-state/core provides a set of type-safe parsers to convert query string values from the URL into strongly-typed data and back.
These parsers are built using the createParser builder, allowing customization, default values, and equality checks.

⚙️ How it works

Each parser implements a parse/serialize interface:

interface ParserConfig<T> {
  parse?: (value: string) => T | null;
  serialize?: (value: T) => string;
  equals?: (a: T, b: T) => boolean;
}
  • parse: Converts a string from the URL to a typed value.

  • serialize: Converts a typed value back to a string for the URL.

  • equals (optional): Compares two values for equality.

You can also extend parsers with options like defaultValue, history mode, rateLimit, and more.


🔹 Built-in Parsers

(Boolean):

import { qsParserBoolean } from '@query-state/core';

const parsed = qsParserBoolean.parse('true'); // true
const serialized = qsParserBoolean.serialize(false); // "false"

(String):

import { qsParserString } from '@query-state/core';

qsParserString.parse('hello'); // "hello"
qsParserString.serialize('world'); // "world"

(Integer & Float):

import { qsParserInteger, qsParserFloat } from '@query-state/core';

qsParserInteger.parse('42'); // 42
qsParserFloat.parse('3.14'); // 3.14

(Date & Timestamp):

import {
  qsParserTimestamp,
  qsParserDateTime,
  qsParserISODate,
} from '@query-state/core';

qsParserTimestamp.parse('1697685600000'); // Date object from milliseconds
qsParserDateTime.parse('2025-10-18T23:00:00.000Z'); // Date object from ISO string
qsParserISODate.parse('2025-10-18'); // Date at midnight UTC

(Literal Types):

import {
  qsParserStringLiteral,
  qsParserNumberLiteral,
} from '@query-state/core';

const colorParser = qsParserStringLiteral(['red', 'green', 'blue']);
colorParser.parse('red'); // "red"
colorParser.parse('yellow'); // null

const numberParser = qsParserNumberLiteral([1, 2, 3]);
numberParser.parse('2'); // 2
numberParser.parse('5'); // null

(JSON):

import { qsParserJson } from '@query-state/core';

const userParser = qsParserJson<{ id: number; name: string }>();
userParser.parse('{"id":1,"name":"Pedro"}'); // { id: 1, name: "Pedro" }
userParser.serialize({ id: 2, name: 'Alice' }); // '{"id":2,"name":"Alice"}'

(Arrays):

import { qsParserArray, qsParserInteger } from '@query-state/core';

const arrayParser = qsParserArray(qsParserInteger, ',');
arrayParser.parse('1,2,3'); // [1, 2, 3]
arrayParser.serialize([4, 5, 6]); // "4,5,6"

💡 Customization & Defaults

All parsers can be extended with defaults and custom options:

const booleanWithDefault = qsParserBoolean.setDefault(true);
const stringLimited = qsParserString.defineOptions({ history: 'push' });

🔹 Use Case

When reading or writing query parameters, you can parse safely into the expected type and serialize back to the URL, ensuring type safety and avoiding invalid values:

import { readParam, writeParam } from '@query-state/core';

const page = qsParserInteger.parse(readParam('page')); // safely parse page number
writeParam('page', qsParserInteger.serialize(2)); // update URL

🛠️ Advanced Usage

You can integrate @query-state/core with any frontend framework (React, Vue, etc.) to manage URL query state reactively, ensuring your UI is always in sync with the browser URL.