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

@dvirus-js/utils

v0.0.16

Published

Readme

Utils Library

Shared utility package for string transforms, async helpers, HTTP calls, typed accessors, lightweight signals, and branded types.

Install

npm install @dvirus-js/utils

Exports

  • normalizeString, convertCase: normalize text and convert between cases.
  • tryCatch, tryCatchAsync, Result: tuple-style and object-style error handling.
  • http: small fetch wrapper with get, post, put, patch, delete.
  • groupBy, toArray, getProp: data shaping helpers.
  • delay, clamp, debounce: async and timing utilities.
  • signal, computed, effect, linkedSignal, resource: minimal reactive primitives.
  • parseRichText: parse supported inline HTML tags into styled text segments.
  • createBrand, NumericString: nominal typing helpers.

Examples

Cases and collections

import { convertCase, groupBy, normalizeString, toArray } from '@dvirus-js/utils';

const value = 'HelloWorld_example-string';

normalizeString(value); // 'hello world example string'
convertCase(value, 'snake_case'); // 'hello_world_example_string'

groupBy(
  [
    { type: 'a', value: 1 },
    { type: 'b', value: 2 },
    { type: 'a', value: 3 },
  ],
  (item) => item.type,
);

toArray('item'); // ['item']

Errors and HTTP

import { Result, http, tryCatch, tryCatchAsync } from '@dvirus-js/utils';

const [parsed, parseError] = tryCatch(() => JSON.parse('{"ok":true}'));
const [todo, requestError] = await tryCatchAsync(http.get<{ id: number; title: string }>('/api/todo/1'));

const safeDivide = Result.func(
  (a: number, b: number) => {
    if (b === 0) throw new Error('Cannot divide by zero');
    return a / b;
  },
  10,
  2,
);

if (!parseError && !requestError && safeDivide.isOk()) {
  console.log(parsed, todo, safeDivide.value);
}

Paths, timing, and text parsing

import { clamp, debounce, delay, getProp, parseRichText } from '@dvirus-js/utils';

const user = { profile: { name: 'Ada' } };
const name = getProp(user, 'profile.name'); // 'Ada'

await delay(200);
const page = clamp(1, 12, 10); // 10
const save = debounce(() => console.log('saved'), 300);

const segments = parseRichText('Hello <b>world</b>');

Rich text parser (parseRichText)

parseRichText converts a string with a limited set of HTML-like tags into structured text segments.

Each segment includes:

  • text: plain text for the segment.
  • tagNames: comma-separated active tag names (for quick display/debug).
  • tagNamesList: active tag names as an array.
  • style: inline CSS string built from active tags.

Supported tags

Default rich tags:

  • b, strong
  • i, em
  • u
  • s
  • mark
  • small
  • h1, h2, h3, h4, h5, h6
  • div
  • p
  • code

Default self-closing tags:

  • br

Notes:

  • Tag matching is case-insensitive.
  • Nested tags are supported.
  • Unsupported tags are treated as normal text.
  • Tags with attributes are not parsed as tags (for example <b class="x">).

Basic example

import { parseRichText } from '@dvirus-js/utils';

const input = 'Hello <b>world <i>now</i></b>!<br/>Done';
const segments = parseRichText(input);

/*
[
  {
    text: 'Hello ',
    tagNames: '',
    tagNamesList: [],
    style: ''
  },
  {
    text: 'world ',
    tagNames: 'b',
    tagNamesList: ['b'],
    style: 'font-weight: bold'
  },
  {
    text: 'now',
    tagNames: 'b,i',
    tagNamesList: ['b', 'i'],
    style: 'font-weight: bold; font-style: italic'
  },
  {
    text: '!',
    tagNames: '',
    tagNamesList: [],
    style: ''
  },
  {
    text: '',
    tagNames: 'br',
    tagNamesList: ['br'],
    style: 'display: block'
  },
  {
    text: 'Done',
    tagNames: '',
    tagNamesList: [],
    style: ''
  }
]
*/

Add custom rich tags

You can register additional tags at runtime:

import { parseRichText } from '@dvirus-js/utils';

parseRichText.addTag({
  tag: 'newTag',
  style: 'border:1px solid black, border-radius:1rem',
});

const out = parseRichText('Use <newTag>npm run build</newTag>');

You can also register multiple tags in one call:

import { parseRichText } from '@dvirus-js/utils';

parseRichText.addTag([
  { tag: 'sub', style: 'vertical-align: sub; font-size: smaller' },
  { tag: 'sup', style: 'vertical-align: super; font-size: smaller' },
]);

Add custom self-closing tags

import { parseRichText } from '@dvirus-js/utils';

parseRichText.addSelfClosingTag({ tag: 'hr', style: 'display: block; border-top: 1px solid #ddd' });

const out = parseRichText('Line one<hr/>Line two');

Alias tags to the same style key

Use tagName when you want multiple tags to map to the same style entry:

import { addRichTextTag } from '@dvirus-js/utils';

addRichTextTag({ tag: 'strong', tagName: 'b' });

This behaves like the built-in strong -> b and em -> i aliases.

Signals and branded types

import { NumericString, computed, effect, signal } from '@dvirus-js/utils';

const count = signal(0);
const doubled = computed(() => count() * 2);

const ref = effect(() => {
  console.log(count(), doubled());
});

count.set(2);

const userId = NumericString('12345');
console.log(userId);

ref.destroy();