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

@avidly/utils

v1.0.2

Published

Pure utility functions for Avidly monorepo - zero dependencies

Downloads

1,050

Readme

@avidly/utils

Pure utility functions for the Avidly monorepo with zero runtime dependencies.

Philosophy

This package contains only pure utility functions that:

  • Have no external dependencies (zero deps!)
  • Work in both browser and Node.js environments (where applicable)
  • Are framework-agnostic
  • Can be tree-shaken for optimal bundle sizes

Installation

# Already installed in monorepo
bun add @avidly/utils

Usage

Category-Specific Imports (Recommended)

Import only what you need for better tree-shaking:

// String utilities
import { toCamelCase, sanitize, isString } from '@avidly/utils/string';

// Array utilities
import { sorter, groupItems, unique } from '@avidly/utils/array';

// Object utilities
import { get } from '@avidly/utils/object';

// Date utilities
import { formatDateSSR, formatDateFinnishSSR } from '@avidly/utils/date';

// DOM utilities (client-side only)
import { onClickOutside, scrollToAnchor } from '@avidly/utils/dom';

// ID utilities
import { getId, getAreaId } from '@avidly/utils/id';

// Server-only utilities (Node.js/Remix loaders)
import { isProduction, getCacheConfig, getCacheHeaders } from '@avidly/utils/server';

// Types
import type { SortKey, SortOrder, DateInput } from '@avidly/utils/types';

Main Barrel Import

// Import everything (less efficient for tree-shaking)
import { toCamelCase, sorter, formatDateSSR } from '@avidly/utils';

API Reference

String Utilities

toCamelCase(str: string): string

Convert kebab-case or snake_case to camelCase.

toCamelCase('hello-world'); // 'helloWorld'
toCamelCase('hello_world'); // 'helloWorld'

sanitize(text: string): string

Sanitize text for URL usage. Handles Finnish characters (ä, ö, å).

sanitize('Hello World!'); // 'hello-world'
sanitize('Mäkinen Ärkkölä'); // 'makinen-arkkola'

isString(value: any): boolean

Type guard to check if value is a string.

isString('hello'); // true
isString(123); // false

Array Utilities

sorter<T>(property, key, order?)

Generic array sorter function.

const items = [
  { name: 'banana', price: 2 },
  { name: 'apple', price: 1 },
];

// Sort by string
items.sort(sorter('name', 'string', 'asc'));

// Sort by number
items.sort(sorter('price', 'number', 'desc'));

// Sort by date
events.sort(sorter('date', 'date', 'asc'));

groupItems<T>(items, groupBy): Record<string, T[]>

Group array items by a property.

const items = [
  { category: 'fruit', name: 'apple' },
  { category: 'fruit', name: 'banana' },
  { category: 'vegetable', name: 'carrot' },
];

const grouped = groupItems(items, 'category');
// {
//   fruit: [{ category: 'fruit', name: 'apple' }, { category: 'fruit', name: 'banana' }],
//   vegetable: [{ category: 'vegetable', name: 'carrot' }]
// }

unique<T>(item, i, k?): string

Generate unique keys for list items.

unique({ id: 'abc' }, 0); // 'abc_0'
unique({ text: 'Hello' }, 0); // 'Hello_0'
unique({}, 0, 1); // 'item_0_1'

Object Utilities

get(object, path, defval?): any

Get nested property value (like Lodash's _.get()).

const obj = { user: { name: 'John', address: { city: 'NYC' } } };

get(obj, 'user.name'); // 'John'
get(obj, 'user.address.city'); // 'NYC'
get(obj, ['user', 'name']); // 'John'
get(obj, 'user.age', 25); // 25 (default value)

Date Utilities

All date utilities are SSR-safe and provide consistent formatting across server and client.

formatDateSSR(date: DateInput): string

Format date as YYYY-MM-DD.

formatDateSSR(new Date('2024-01-15')); // '2024-01-15'
formatDateSSR('2024-01-15T10:30:00Z'); // '2024-01-15'

formatTimeSSR(date: DateInput): string

Format time as HH:MM (24-hour format).

formatTimeSSR(new Date('2024-01-15T10:30:00')); // '10:30'

formatDateTimeSSR(date: DateInput): string

Format date and time as YYYY-MM-DD HH:MM.

formatDateTimeSSR(new Date('2024-01-15T10:30:00')); // '2024-01-15 10:30'

formatDateFinnishSSR(date: DateInput): string

Format date as DD.MM.YYYY (Finnish format).

formatDateFinnishSSR(new Date('2024-01-15')); // '15.01.2024'

getRelativeDateLabelSSR(date: DateInput): string

Get relative date label.

getRelativeDateLabelSSR(new Date()); // 'Today'
getRelativeDateLabelSSR(new Date(Date.now() - 86400000)); // 'Yesterday'
getRelativeDateLabelSSR(new Date(Date.now() - 259200000)); // '3 days ago'

DOM Utilities

Client-side utilities (require browser environment).

onClickOutside(element: string, callback: () => void)

Set up click outside event listener.

const cleanup = onClickOutside('dropdown-menu', () => {
  console.log('Clicked outside!');
  closeDropdown();
});

// Later: cleanup();

elementInViewport(el: HTMLElement): boolean

Check if element is in viewport.

const button = document.getElementById('my-button');
if (elementInViewport(button)) {
  // Element is visible
}

scrollToAnchor(url?: string, linkText?: string): boolean

Smooth scroll to anchor element.

scrollToAnchor('#section-title'); // Scroll to #section-title
scrollToAnchor('/page#contact', 'Contact'); // Try #contact or generated ID

assetIdIsEmpty(src: any): boolean

Check if asset ID is empty or invalid.

assetIdIsEmpty(''); // true
assetIdIsEmpty('asset-123'); // false
assetIdIsEmpty({ desktop: 'img.jpg', mobile: 'img-m.jpg' }); // false
assetIdIsEmpty({ desktop: '', mobile: '' }); // true

ID Utilities

getId(label: string): string

Generate camelCase ID from label.

getId('hello-world'); // 'helloWorld'
getId('my_component'); // 'myComponent'

getAreaId(id: string): string

Generate sanitized area ID (takes first 1-2 words).

getAreaId('Contact Us Now'); // 'contact-us'
getAreaId('Features'); // 'features'
getAreaId('Möbel & Wöhne'); // 'mobel-wohne'

Server Utilities (Node.js/SSR Only)

⚠️ Server-only utilities - require Node.js environment. Do NOT import in client components!

isVercelProduction(): boolean

Check if running in Vercel production environment (VERCEL_ENV === 'production').

import { isVercelProduction } from '@avidly/utils/server';

export const loader = async () => {
  const isProdEnv = isVercelProduction();
  // true only if VERCEL_ENV === 'production'
};

isProductionDomain(request: Request): boolean

Check if on the actual production domain (not .vercel.app).

import { isProductionDomain } from '@avidly/utils/server';

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const isProdDomain = isProductionDomain(request);
  // true if hostname is avidly.fi or www.avidly.fi
  // false for *.vercel.app domains
};

isProduction(request?: Request): boolean

Check if this is true production (BOTH environment AND domain). Use this for SEO-related decisions (robots meta tags, etc.).

import { isProduction } from '@avidly/utils/server';

export const loader = async ({ request }: LoaderFunctionArgs) => {
  // Full check: environment + domain
  const isProd = isProduction(request);
  
  // Legacy: just environment check (no request needed)
  const isProdEnv = isProduction();
  
  return { isProduction: isProd };
};

getCacheConfig(): CacheConfig

Get Builder.io cache configuration based on environment. Production gets longer cache times and excludes unpublished content.

import { getCacheConfig } from '@avidly/utils/server';
import { fetchOneEntry } from '@builder.io/sdk-react';

const content = await fetchOneEntry({
  model: 'page',
  apiKey,
  options: {
    includeRefs: true,
    ...getCacheConfig(), // Injects: includeUnpublished, staleCacheSeconds, cachebust
  },
});

getCacheHeaders(): HeadersInit

Get HTTP cache headers for responses. Production gets aggressive caching (1 hour), others get no-cache.

import { getCacheHeaders } from '@avidly/utils/server';
import { json } from '@remix-run/node';

export const loader = async () => {
  const data = await fetchData();
  
  // Automatically sets correct Cache-Control header
  const headers = new Headers(getCacheHeaders());
  
  return json(data, { headers });
};

Types

All utility types are available for import:

import type {
  // Array types
  SortKey,
  SortOrder,
  UniqueItem,

  // Object types
  PropertyPath,

  // Date types
  DateInput,
  DateFormat,
  RelativeDateLabel,

  // DOM types
  ElementId,
  OutsideClickCallback,
  AssetId,
} from '@avidly/utils/types';

Development

# Build package
cd packages/utils
bun run build

# Watch mode
bun run dev

# Type check
bun run typecheck

Design Decisions

Why Zero Dependencies?

  • Smaller bundle sizes - No external dependencies means less code
  • Faster builds - Fewer packages to process
  • Better reliability - No dependency updates to track
  • Framework agnostic - Works anywhere JavaScript runs

Why Category Files?

  • Better tree-shaking - Import only what you need
  • Easier navigation - Find utilities by category
  • Clear organization - Related utilities stay together
  • Type safety - Category-specific types co-located with utilities

Why Extract from UI Package?

  • Separation of concerns - Pure utilities vs UI-specific utilities
  • Reusability - Can be used by any package or app
  • Single source of truth - No duplication across packages

What's NOT Included

This package intentionally does NOT include:

  • Validation utilities (use @avidly/ui/validation - depends on i18n)
  • Theme utilities (use @avidly/public-theme/utils - theme-specific)
  • Builder.io utilities (use @avidly/ui/utils/builder - UI-specific)
  • API utilities (use @avidly/ui/utils/api - UI-specific)
  • Error utilities (use @avidly/ui/utils/error - UI-specific)
  • Any utilities with package dependencies

Contributing

When adding new utilities:

  1. Ensure they are pure functions with no dependencies
  2. Add appropriate types in src/types/[category].ts
  3. Implement utility in src/[category].ts
  4. Export from src/index.ts barrel
  5. Update this README with examples
  6. Add JSDoc comments with examples

License

Private - Avidly monorepo only