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

@pocket.software/address-kit

v0.1.0

Published

Typed address and URL registry for framework infrastructure

Readme

@pocket.software/address-kit

Typed address and URL registry for framework infrastructure.

This package is built around one idea:

addresses should be defined semantically, not assembled ad hoc at call sites.

That means you define stable infrastructure once:

  • cdn
  • api
  • auth
  • media
  • localApi

and then resolve addresses from those named presets with a small, composable API.


Why this pattern is the strongest one

From the patterns you were playing with, the cleanest architecture is:

  • pure functional core for normalization and rendering
  • typed registry for named infrastructure targets
  • route-template layer for ergonomic framework-facing calls

That is much stronger than:

  • punctuation-level primitives like DOT, SLASH, COLON
  • one giant positional function
  • a mutable class as the primary abstraction

The package separates responsibilities cleanly:

  • buildAddress renders a final address
  • createAddressRegistry resolves named presets
  • createAddressRoutes binds route templates onto that registry

Install

npm install @pocket.software/address-kit

Package surface

import {
  buildAddress,
  buildHost,
  buildQuery,
  buildHash,
  joinPath,
  createAddressRegistry,
  createAddressRoutes,
  defineRoute,
  defineStaticRoute,
} from '@pocket.software/address-kit';

1. Core builder

Use buildAddress when you want a small, pure address renderer without a registry yet.

import { buildAddress } from '@pocket.software/address-kit';

const value = buildAddress(
  {
    protocol: 'https',
    subdomain: 'public',
    domain: 'hostquarter',
    tld: 'com',
    basePath: ['assets'],
  },
  {
    path: ['images', 'logo.png'],
    query: { v: '7' },
    hash: 'preview',
  },
);

console.log(value);
// https://public.hostquarter.com/assets/images/logo.png?v=7#preview

Raw host support

If you need a host like localhost:3000, use host directly.

const local = buildAddress(
  {
    protocol: 'http',
    host: 'localhost:3000',
    basePath: ['api'],
  },
  {
    path: ['users'],
  },
);

console.log(local);
// http://localhost:3000/api/users

Domain + tld + port support

const withPort = buildAddress(
  {
    protocol: 'https',
    subdomain: 'api',
    domain: 'example',
    tld: 'com',
    port: 8443,
    basePath: ['v1'],
  },
  {
    path: ['health'],
  },
);

console.log(withPort);
// https://api.example.com:8443/v1/health

2. Normalization helpers

These are exported because sometimes framework code needs them independently.

import {
  joinPath,
  buildQuery,
  buildHash,
} from '@pocket.software/address-kit';

console.log(joinPath('/assets/', '/images/', 'logo.png'));
// assets/images/logo.png

console.log(buildQuery({ page: 2, tag: ['audio', 'lecture'] }));
// ?page=2&tag=audio&tag=lecture

console.log(buildHash('preview'));
// #preview

3. Registry-driven addressing

This is the main pattern for a framework.

You define your infrastructure once:

import { createAddressRegistry } from '@pocket.software/address-kit';

const ADDRESSES = createAddressRegistry({
  cdn: {
    protocol: 'https',
    subdomain: 'public',
    domain: 'hostquarter',
    tld: 'com',
    basePath: ['assets'],
  },

  api: {
    protocol: 'https',
    subdomain: 'api',
    domain: 'hostquarter',
    tld: 'com',
    basePath: ['v1'],
  },

  auth: {
    protocol: 'https',
    subdomain: 'auth',
    domain: 'hostquarter',
    tld: 'com',
  },

  media: {
    protocol: 'https',
    subdomain: 'media',
    domain: 'hostquarter',
    tld: 'com',
    basePath: ['content'],
  },

  localApi: {
    protocol: 'http',
    host: 'localhost:3000',
    basePath: ['api'],
  },
});

Then build from intent:

const logoUrl = ADDRESSES.build('cdn', {
  path: ['images', 'logo.png'],
  query: { v: '42' },
});

const usersUrl = ADDRESSES.build('api', {
  path: ['users'],
  query: { page: 1, limit: 20 },
});

const loginUrl = ADDRESSES.build('auth', {
  path: ['login'],
});

const mediaUrl = ADDRESSES.build('media', {
  path: ['lectures', 'alan-watts.mp3'],
});

const localUsers = ADDRESSES.build('localApi', {
  path: ['users'],
});

Outputs:

// https://public.hostquarter.com/assets/images/logo.png?v=42
// https://api.hostquarter.com/v1/users?page=1&limit=20
// https://auth.hostquarter.com/login
// https://media.hostquarter.com/content/lectures/alan-watts.mp3
// http://localhost:3000/api/users

4. Scoped registry access

If you want a pre-bound helper for a single preset:

const api = ADDRESSES.scope('api');

const usersUrl = api.build({
  path: ['users'],
  query: { page: 1 },
});

That is especially nice when wiring a specific service module.


5. Extending a registry

You can derive a new registry without mutating the old one.

const EXTENDED = ADDRESSES.extend('edgeApi', {
  protocol: 'https',
  host: 'edge.example.com',
  basePath: ['v2'],
});

const edgeHealth = EXTENDED.build('edgeApi', {
  path: ['health'],
});

6. Per-call preset overrides

Sometimes you want to keep the semantic preset but swap the actual host or protocol for a dev target.

const devHealth = ADDRESSES.build('api', {
  path: ['health'],
  overrides: {
    protocol: 'http',
    host: 'localhost:3001',
    basePath: [],
  },
});

console.log(devHealth);
// http://localhost:3001/health

This is cleaner than creating one-off string builders all over a codebase.


7. Route-template layer

This is the nicest framework-facing layer.

You define route shapes once and bind them to the registry.

Static route

import {
  createAddressRoutes,
  defineStaticRoute,
} from '@pocket.software/address-kit';

const ROUTES = createAddressRoutes(ADDRESSES, {
  api: {
    users: defineStaticRoute({
      path: ['users'],
    }),
  },
});

console.log(ROUTES.api.users());
// https://api.hostquarter.com/v1/users

Parameterized route

import {
  createAddressRoutes,
  defineRoute,
} from '@pocket.software/address-kit';

const ROUTES = createAddressRoutes(ADDRESSES, {
  api: {
    userById: defineRoute((userId: string) => ({
      path: ['users', userId],
    })),
  },
});

console.log(ROUTES.api.userById('42'));
// https://api.hostquarter.com/v1/users/42

Object-shaped route params

const ROUTES = createAddressRoutes(ADDRESSES, {
  api: {
    userSearch: defineRoute(
      (input: { query: string; page?: number; tags?: string[] }) => ({
        path: ['users', 'search'],
        query: {
          q: input.query,
          page: input.page ?? 1,
          tag: input.tags,
        },
      }),
    ),
  },
});

console.log(
  ROUTES.api.userSearch({
    query: 'alan watts',
    page: 2,
    tags: ['audio', 'lecture'],
  }),
);

// https://api.hostquarter.com/v1/users/search?q=alan+watts&page=2&tag=audio&tag=lecture

CDN asset route template

const ROUTES = createAddressRoutes(ADDRESSES, {
  cdn: {
    asset: defineRoute(
      (input: { folders?: string[]; file: string; version?: string }) => ({
        path: [...(input.folders ?? []), input.file],
        query: input.version ? { v: input.version } : undefined,
      }),
    ),
  },
});

console.log(
  ROUTES.cdn.asset({
    folders: ['images'],
    file: 'logo.png',
    version: '7',
  }),
);

// https://public.hostquarter.com/assets/images/logo.png?v=7

8. Route templates with extra per-call options

A bound route can still accept extra registry-level options.

const url = ROUTES.api.userById('42', {
  query: { expand: true },
  hash: 'profile',
});

console.log(url);
// https://api.hostquarter.com/v1/users/42?expand=true#profile

You can even redirect the same semantic route to a dev host:

const localUrl = ROUTES.api.userById('42', {
  overrides: {
    protocol: 'http',
    host: 'localhost:3001',
    basePath: [],
  },
});

console.log(localUrl);
// http://localhost:3001/users/42

9. The design philosophy

The package intentionally does not make a mutable class the main public API.

Why:

  • pure functions are easier to test
  • immutable preset objects are easier to reason about
  • registry-driven semantic naming scales better in a framework
  • object-based build calls are easier to extend later

The most important idea here is the move from:

'https://' + something + '/' + somethingElse

to:

ADDRESSES.build('api', {
  path: ['users', userId],
  query: { expand: true },
});

and then eventually:

ROUTES.api.userById(userId)

That is the real abstraction improvement.


10. Recommended file organization in a larger framework

A good shape is:

address/
  registry.ts
  routes.ts
  helpers.ts

Example:

address/registry.ts

import { createAddressRegistry } from '@pocket.software/address-kit';

export const ADDRESSES = createAddressRegistry({
  cdn: {
    protocol: 'https',
    subdomain: 'public',
    domain: 'hostquarter',
    tld: 'com',
    basePath: ['assets'],
  },

  api: {
    protocol: 'https',
    subdomain: 'api',
    domain: 'hostquarter',
    tld: 'com',
    basePath: ['v1'],
  },

  localApi: {
    protocol: 'http',
    host: 'localhost:3000',
    basePath: ['api'],
  },
});

address/routes.ts

import {
  createAddressRoutes,
  defineRoute,
  defineStaticRoute,
} from '@pocket.software/address-kit';
import { ADDRESSES } from './registry';

export const ROUTES = createAddressRoutes(ADDRESSES, {
  api: {
    users: defineStaticRoute({
      path: ['users'],
    }),

    userById: defineRoute((userId: string) => ({
      path: ['users', userId],
    })),
  },

  cdn: {
    asset: defineRoute(
      (input: { folders?: string[]; file: string; version?: string }) => ({
        path: [...(input.folders ?? []), input.file],
        query: input.version ? { v: input.version } : undefined,
      }),
    ),
  },
});

address/helpers.ts

import { ROUTES } from './routes';

export const assetUrl = (
  folders: string[],
  file: string,
  version?: string,
): string =>
  ROUTES.cdn.asset({
    folders,
    file,
    version,
  });

export const userUrl = (userId: string): string =>
  ROUTES.api.userById(userId);

11. API reference

buildAddress(preset, options?)

Build a URL from a preset and optional path, query, and hash.

buildHost(preset)

Render just the host portion.

joinPath(...parts)

Normalize path segments and join them with one slash.

buildQuery(query)

Render query params into a string.

buildHash(hash)

Render a hash fragment into a string.

createAddressRegistry(registry)

Create a typed registry of named presets.

Returns:

  • registry
  • get(name)
  • has(name)
  • build(name, options?)
  • scope(name)
  • extend(name, preset)

defineRoute(fn)

Define a parameterized route template.

defineStaticRoute(options)

Define a no-arg route template.

createAddressRoutes(registry, definitions)

Bind route templates to a registry and produce typed route functions.


12. Running locally

npm install
npm run typecheck
npm run test
npm run build

13. Included files in this zip

  • full TypeScript source
  • test suite
  • examples
  • package metadata
  • build configs
  • README

14. Final recommendation

Use the package in three layers:

  1. buildAddress for raw composition
  2. createAddressRegistry for semantic infrastructure resolution
  3. createAddressRoutes for ergonomic framework route templates

That is the cleanest long-term pattern from everything you listed.