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

codec-url

v0.2.6

Published

Framework-agnostic URL codec: structured state ⟷ tiny URL-safe string

Readme

codec-url — URL Codec Library

Try the Demo →

codec-url is a framework-agnostic URL codec (Rust → WASM core) that plugs into existing URL/state libraries instead of replacing them.

Single responsibility: Structured state ⇄ tiny URL-safe string

Why codec-url?

Every URL library eventually reads/writes:

string | null  ←→  URLSearchParams

codec-url provides the encoding/decoding layer between your application state and the URL string. It composes with — not competes against — React Router, Vue Router, TanStack Router, nuqs, use-query-params, and SvelteKit.

Features

  • Deterministic — Same state always produces the same encoded string
  • Reversible — Lossless encode/decode cycle
  • URL-safe — base64url output (no special characters)
  • Versioned — Schema version embedded in every payload
  • Small — 6-20x smaller than raw query strings
  • Fast — <1ms encode/decode (schema mode: ~10x faster than schema-less)
  • Framework agnostic — Zero framework imports in core
  • First-class adapters — Native integrations for popular routing libraries

Installation

npm install codec-url

Quick Start (Schema-less Mode)

No schema required — works with any serializable object immediately:

import { initCodecUrl, createCodec } from 'codec-url'

await initCodecUrl()

const codec = createCodec()

const state = {
  brands: ['nike', 'puma'],
  priceMin: 1000,
  priceMax: 5000,
  inStock: true,
  sort: 'price',
  page: 3,
}

const encoded = codec.encode(state)
// => "eyJicmFuZCI6WzEsMl0sInByaWNlTWluI..."

const decoded = codec.decode(encoded)
// => { brands: [1, 2], priceMin: 1000, priceMax: 5000, ... }

Schema Mode (Maximum Compression)

For maximum compression and type safety, define a schema:

import { initCodecUrl, createCodec, defineSchema, u8, u16, array, enumType, bool } from 'codec-url'

await initCodecUrl()

const schema = defineSchema({
  brands: array(u8, 10),    // brand IDs (max 10 items)
  priceMin: u16,
  priceMax: u16,
  inStock: bool,
  sort: enumType(['price', 'rating', 'newest']),
  page: u8,
})

const codec = createCodec(schema, {
  defaults: {
    brands: [],
    priceMin: 0,
    priceMax: 0,
    inStock: false,
    sort: 'price',
    page: 1,
  },
})

const state = {
  brands: [1, 2],  // brand IDs, not names
  priceMin: 1000,
  priceMax: 5000,
  inStock: true,
  sort: 'price',
  page: 3,
}

const encoded = codec.encode(state)
// => "AQ..."  (12 bytes vs 77 bytes raw query string)

const decoded = codec.decode(encoded)
// => { brands: [1, 2], priceMin: 1000, priceMax: 5000, inStock: true, sort: 'price', page: 3 }

API

Core

import { initCodecUrl, createCodec, defineSchema, u8, u16, u32, bool, f32, array, tuple, enumType, string } from 'codec-url'

// Initialize WASM (required for schema-less mode)
await initCodecUrl()

// Schema-less mode (any serializable object)
const codec = createCodec()

// Schema mode (bit-packed, max compression)
const codec = createCodec(schema, options)

// Schema DSL
const schema = defineSchema({
  fieldName: u8 | u16 | u32 | bool | f32,
  fieldName: array(u8, maxLength),
  fieldName: tuple(u16, u16),
  fieldName: enumType(['value1', 'value2', ...]),
  fieldName: string(maxBytes),
})

Options

const codec = createCodec(schema, {
  paramName: 'd',      // URL param name (default: 'd')
  version: 1,          // payload version (default: 1)
  defaults: {...},     // default state for decode failures
  migrate: {           // version migration functions
    1: (old) => newState,
  },
})

Framework Adapters

React Router

import { useSearchParams } from 'react-router-dom'
import { createCodec } from 'codec-url'
import { bindToReactRouter } from 'codec-url/react-router'

const codec = createCodec()
const bound = bindToReactRouter(codec)

function App() {
  const [params, setParams] = useSearchParams()
  const state = bound.read(params)

  const updateState = (newState) => {
    setParams(bound.write(newState))
  }

  // Use state...
}

nuqs

import { useQueryState } from 'nuqs'
import { createCodec } from 'codec-url'
import { urlCodecParser } from 'codec-url/nuqs'

const codec = createCodec()
const parser = urlCodecParser(codec)

function App() {
  const [state, setState] = useQueryState('d', parser)

  // Use state...
}

use-query-params

import { useQueryParam, UrlCodecParam } from 'use-query-params'
import { createCodec } from 'codec-url'

const codec = createCodec()

function App() {
  const [state, setState] = useQueryParam('d', UrlCodecParam(codec))

  // Use state...
}

Vue Router

import { useRoute, useRouter } from 'vue-router'
import { createCodec } from 'codec-url'
import { bindToVueRouter } from 'codec-url/vue-router'

const codec = createCodec()
const bound = bindToVueRouter(codec)

function App() {
  const route = useRoute()
  const router = useRouter()

  const state = bound.read(route.query)

  const updateState = (newState) => {
    router.replace({ query: bound.write(newState) })
  }

  // Use state...
}

SvelteKit

import { page } from '$app/stores'
import { goto } from '$app/navigation'
import { createCodec } from 'codec-url'
import { bindToSvelteKit } from 'codec-url/sveltekit'

const codec = createCodec()
const bound = bindToSvelteKit(codec)

$: state = bound.read($page.url.searchParams)

function updateState(newState) {
  const params = new URLSearchParams(bound.write(newState))
  goto(`?${params}`)
}

TanStack Router

import { createRoute, useSearch, useNavigate } from '@tanstack/react-router'
import { createCodec } from 'codec-url'
import { tanstackCodecSearch, bindToTanStackRouter } from 'codec-url/tanstack-router'

const codec = createCodec()
const bound = bindToTanStackRouter(codec)

// Define route with codec as validateSearch
const productRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: 'products',
  validateSearch: tanstackCodecSearch(codec),
})

function Products() {
  const search = useSearch({ from: productRoute.id })
  const navigate = useNavigate({ from: productRoute.id })

  // search.d is already decoded!

  const updateState = (newState) => {
    navigate({
      search: (prev) => ({ ...prev, ...bound.write(newState) }),
    })
  }

  // Use search.d...
}

Zod Integration

import { z } from 'zod'
import { createCodec } from 'codec-url'
import { wrapParserWithCodec } from 'codec-url/zod'

const schema = z.object({
  brands: z.array(z.number()),
  priceMin: z.number(),
  priceMax: z.number(),
  inStock: z.boolean(),
  sort: z.enum(['price', 'rating', 'newest']),
  page: z.number().int().positive(),
})

const codec = createCodec()
const binding = wrapParserWithCodec(schema, codec)

// Decode: URL → codec.decode → zod.parse
const state = binding.decode(urlParam)

// Encode: state → codec.encode → URL
const urlParam = binding.encode(state)

Performance Benchmarks

Compression Ratio (vs. raw query string)

| Mode | Raw Query String | Encoded | Compression | |------|-----------------|---------|-------------| | Raw | 77 bytes | 77 bytes | 1x (baseline) | | Schema-less | 77 bytes | 115 bytes | 0.67x | | Schema | 77 bytes | 12 bytes | 6.42x smaller |

Note: Schema-less mode adds overhead due to the deflate compression algorithm, which doesn't compress well on small payloads. It shines with larger state objects.

Throughput (Node.js)

| Mode | Encode (ops/sec) | Decode (ops/sec) | p99 Encode | p99 Decode | |------|------------------|------------------|------------|------------| | Schema-less | ~72,000 | ~45,000 | ~0.03ms | ~0.05ms | | Schema | ~1,120,000 | ~421,000 | ~0.001ms | ~0.007ms |

Schema mode is ~15x faster for encoding and ~9x faster for decoding than schema-less mode.

WASM Size

| Metric | Size | |--------|------| | WASM (gzipped) | ~22 KB | | Target | <80 KB ✓ |

Demo

Example: https://codec-url.netlify.app

Configuration

Vite

For Vite-based projects, install the required plugins for WASM support:

npm install vite-plugin-wasm vite-plugin-top-level-await
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import wasm from 'vite-plugin-wasm'
import topLevelAwait from 'vite-plugin-top-level-await'

export default defineConfig({
  plugins: [react(), wasm(), topLevelAwait()],
})

Vite (Development Server)

For vite dev, if you encounter ESM integration errors, enable the WASM plugins:

npm install vite-plugin-wasm vite-plugin-top-level-await
// vite.config.ts
import { defineConfig } from 'vite'
import wasm from 'vite-plugin-wasm'
import topLevelAwait from 'vite-plugin-top-level-await'

export default defineConfig({
  plugins: [wasm(), topLevelAwait()],
})

Next.js

For Next.js App Router projects, ensure the WASM file is properly served. Add to next.config.js:

/** @type {import('next').NextConfig} */
const nextConfig = {
  webpack: (config) => {
    config.experiments = config.experiments || {}
    config.experiments.asyncWebAssembly = true
    return config
  },
}

module.exports = nextConfig

For Pages Router, use next.config.js:

/** @type {import('next').NextConfig} */
const nextConfig = {
  webpack: (config, { isServer }) => {
    if (!isServer) {
      config.resolve.fallback = config.resolve.fallback || {}
      config.resolve.fallback.fs = false
    }
    return config
  },
}

module.exports = nextConfig

Important: When using codec-url with Next.js, initialize the WASM module outside of React rendering:

// lib/codec-url.ts (singleton, imported once)
import { initCodecUrl, createCodec } from 'codec-url'

let codec: ReturnType<typeof createCodec> | null = null

export async function getCodec() {
  if (!codec) {
    await initCodecUrl()
    codec = createCodec()
  }
  return codec
}

Error Handling

  • Invalid payload → returns default state (never throws during render)
  • Dev mode warnings via console.warn in development
  • Version mismatch → attempts migration, falls back to defaults

Versioning & Migration

Payload format: [version][data]

const codec = createCodec(schema, {
  version: 2,
  migrate: {
    1: (old) => ({
      ...old,
      sort: old.sort === 'alpha' ? 'rating' : old.sort,  // migration logic
    }),
  },
})

Project Structure

codec-url/
��── src/
│   ├── core/           # WASM + JS wrapper (zero framework knowledge)
│   │   ├── codec.ts    # Main codec implementation
│   │   ├── schema.ts   # Schema DSL
│   │   ├── bits.ts     # Bit packing utilities
│   │   ├── base64url.ts
│   │   └── wasm-loader.ts
│   ├── adapters/       # Framework bindings
│   │   ├── react-router.ts
│   │   ├── nuqs.ts
│   │   ├── use-query-params.ts
│   │   ├── vue-router.ts
│   │   ├── sveltekit.ts
│   │   ├── tanstack-router.ts
│   │   └── zod.ts
├── bench/              # Vitest benchmark suite
└── docs/

License

MIT