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

@hydrogen-ui/utils

v0.1.0

Published

Utility functions for Hydrogen UI - formatters, validators, and helpers for Shopify storefronts

Readme

@hydrogen-ui/utils

A comprehensive utility library for Shopify Hydrogen storefronts. Provides formatting, validation, helper functions, and API utilities optimized for e-commerce applications.

Installation

npm install @hydrogen-ui/utils
# or
yarn add @hydrogen-ui/utils
# or
pnpm add @hydrogen-ui/utils

Features

  • 💰 Money Formatting - Multi-currency support with proper formatting
  • 📅 Date Formatting - Locale-aware date and time formatting
  • Validation - Email, phone, postal code, and password validation
  • 🛍️ Product Helpers - Variant selection, availability, and URL generation
  • 🖼️ Image Optimization - Shopify CDN transformations and responsive images
  • 🔤 String Utilities - Text manipulation and formatting
  • 🌐 URL Helpers - URL building and manipulation
  • 🚀 API Utilities - Rate limiting, batching, and Storefront API client
  • 🌍 International - Multi-locale and multi-currency support
  • 🔒 Type Safe - Full TypeScript support

Quick Start

import { 
  formatMoney, 
  validateEmail, 
  getVariantByOptions,
  transformImageUrl 
} from '@hydrogen-ui/utils';

// Format money
const price = formatMoney(29.99, 'USD'); // $29.99

// Validate email
const isValid = validateEmail('[email protected]'); // true

// Get product variant
const variant = getVariantByOptions(product, {
  Color: 'Blue',
  Size: 'Medium'
});

// Optimize image
const imageUrl = transformImageUrl(product.image, {
  width: 800,
  height: 600,
  crop: 'center'
});

API Reference

Formatters

Money Formatting

import { formatMoney, formatPriceRange, Money } from '@hydrogen-ui/utils/formatters';

// Format single price
formatMoney(29.99, 'USD'); // $29.99
formatMoney(29.99, 'EUR', 'de-DE'); // 29,99 €
formatMoney(2999, 'JPY', 'ja-JP'); // ¥2,999

// Format price range
formatPriceRange(
  { amount: 19.99, currencyCode: 'USD' },
  { amount: 49.99, currencyCode: 'USD' }
); // $19.99 - $49.99

// Money arithmetic
const price = new Money(2999, 'USD');
const tax = price.multiply(0.08);
const total = price.add(tax);
console.log(total.format()); // $32.39

// Calculate discount
const discount = calculateDiscountPercentage(
  { amount: 100, currencyCode: 'USD' },
  { amount: 75, currencyCode: 'USD' }
); // 25

Date Formatting

import { 
  formatDate, 
  formatRelativeTime, 
  formatOrderDate,
  formatDateRange 
} from '@hydrogen-ui/utils/formatters';

// Format dates
formatDate(new Date(), 'en-US'); // October 15, 2023
formatDate(new Date(), 'en-US', { format: 'short' }); // 10/15/23

// Relative time
formatRelativeTime(new Date(Date.now() - 3600000)); // 1 hour ago
formatRelativeTime(new Date(Date.now() + 86400000)); // in 1 day

// Order dates
formatOrderDate('2023-10-15T10:30:00Z'); // October 15, 2023 at 10:30 AM

// Date ranges
formatDateRange(
  new Date('2023-10-15'),
  new Date('2023-10-20'),
  'en-US'
); // Oct 15 - 20, 2023

Tax Formatting

import { calculateTax, formatTax, calculateTotal } from '@hydrogen-ui/utils/formatters';

// Calculate tax
const subtotal = { amount: 100, currencyCode: 'USD' };
const taxRate = 0.08; // 8%
const tax = calculateTax(subtotal, taxRate); // { amount: 8, currencyCode: 'USD' }

// Format tax display
formatTax(tax); // $8.00 (8%)

// Calculate total
const shipping = { amount: 10, currencyCode: 'USD' };
const total = calculateTotal(subtotal, tax, shipping); // { amount: 118, currencyCode: 'USD' }

Validators

Email Validation

import { validateEmail, normalizeEmail } from '@hydrogen-ui/utils/validators';

// Validate email
validateEmail('[email protected]'); // true
validateEmail('invalid.email'); // false

// Advanced validation
validateEmail('[email protected]', {
  allowDisplayName: true,
  requireDisplayName: false,
  allowUnicode: true,
  requireTld: true,
  allowIpDomain: false,
  domainSpecificValidation: true,
  blacklistedDomains: ['tempmail.com']
});

// Normalize email
normalizeEmail('[email protected]'); // [email protected]

Phone Validation

import { validatePhone, formatPhone, parsePhone } from '@hydrogen-ui/utils/validators';

// Validate phone
validatePhone('+1 (555) 123-4567', 'US'); // true
validatePhone('07700 900123', 'GB'); // true

// Format phone
formatPhone('5551234567', 'US'); // (555) 123-4567
formatPhone('7700900123', 'GB'); // 07700 900123

// Parse phone
const parsed = parsePhone('+1 (555) 123-4567');
// { countryCode: '1', areaCode: '555', number: '1234567' }

Postal Code Validation

import { validatePostalCode, formatPostalCode } from '@hydrogen-ui/utils/validators';

// Validate postal codes
validatePostalCode('12345', 'US'); // true
validatePostalCode('K1A 0B1', 'CA'); // true
validatePostalCode('SW1A 1AA', 'GB'); // true

// Format postal codes
formatPostalCode('12345-6789', 'US'); // 12345-6789
formatPostalCode('k1a0b1', 'CA'); // K1A 0B1

Password Validation

import { validatePassword, getPasswordStrength } from '@hydrogen-ui/utils/validators';

// Basic validation
validatePassword('SecurePass123!'); // true

// Custom requirements
const result = validatePassword('MyP@ssw0rd', {
  minLength: 8,
  maxLength: 128,
  requireUppercase: true,
  requireLowercase: true,
  requireNumbers: true,
  requireSpecialChars: true,
  disallowCommonPasswords: true,
  customBlacklist: ['password', 'shopify']
});

if (!result.isValid) {
  console.log(result.errors);
  // ['Password must be at least 12 characters long']
}

// Password strength
const strength = getPasswordStrength('MySecureP@ssw0rd123');
// { score: 4, level: 'strong', feedback: [] }

Helpers

Product Helpers

import { 
  getVariantByOptions,
  isProductAvailable,
  isOnSale,
  getProductUrl,
  getLowestPrice,
  getHighestPrice 
} from '@hydrogen-ui/utils/helpers';

// Get variant by options
const variant = getVariantByOptions(product, {
  Size: 'Medium',
  Color: 'Blue'
});

// Check availability
isProductAvailable(product); // true
isProductAvailable(variant); // true

// Check if on sale
isOnSale(product); // true

// Generate product URL
getProductUrl(product); // /products/t-shirt
getProductUrl(product, { 
  includeVariant: true,
  variant: variant.id 
}); // /products/t-shirt?variant=123

// Get price range
const lowest = getLowestPrice(product); // { amount: 19.99, currencyCode: 'USD' }
const highest = getHighestPrice(product); // { amount: 39.99, currencyCode: 'USD' }

Image Helpers

import { 
  transformImageUrl,
  generateSrcSet,
  getImageAspectRatio,
  preloadImage 
} from '@hydrogen-ui/utils/helpers';

// Transform Shopify CDN images
const optimized = transformImageUrl(
  'https://cdn.shopify.com/image.jpg',
  { 
    width: 800, 
    height: 600, 
    crop: 'center',
    scale: 2,
    format: 'webp'
  }
); // https://cdn.shopify.com/[email protected]

// Generate responsive srcset
const srcset = generateSrcSet(imageUrl, {
  widths: [400, 800, 1200],
  format: 'webp'
});
// image_400w.webp 400w, image_800w.webp 800w, image_1200w.webp 1200w

// Get aspect ratio
const ratio = getImageAspectRatio(image); // 1.5

// Preload critical images
preloadImage(heroImage, { as: 'image', type: 'image/webp' });

String Helpers

import { 
  truncate,
  slugify,
  titleCase,
  pluralize,
  stripHtml,
  capitalize,
  camelCase,
  kebabCase 
} from '@hydrogen-ui/utils/helpers';

// Text truncation
truncate('Long text that needs truncating', 20); // "Long text that need..."
truncate('Long text', 20, { suffix: '…' }); // "Long text…"

// Generate slugs
slugify('Product Name! (2023)'); // "product-name-2023"
slugify('Café Rouge', { lower: false }); // "Cafe-Rouge"

// Case transformations
titleCase('hello world'); // "Hello World"
capitalize('hello'); // "Hello"
camelCase('hello-world'); // "helloWorld"
kebabCase('HelloWorld'); // "hello-world"

// Pluralization
pluralize(1, 'product'); // "product"
pluralize(5, 'product'); // "products"
pluralize(1, 'box', 'boxes'); // "box"
pluralize(2, 'box', 'boxes'); // "boxes"

// HTML stripping
stripHtml('<p>Hello <strong>world</strong></p>'); // "Hello world"

URL Helpers

import { 
  buildUrl,
  parseUrl,
  addQueryParams,
  removeQueryParams,
  getQueryParam 
} from '@hydrogen-ui/utils/helpers';

// Build URLs
buildUrl('/products', { sort: 'price', order: 'asc' });
// "/products?sort=price&order=asc"

// Parse URLs
const parsed = parseUrl('/products?sort=price#reviews');
// { pathname: '/products', search: '?sort=price', hash: '#reviews' }

// Query parameters
const url = '/products?sort=price&filter=sale';
addQueryParams(url, { page: 2 }); // "/products?sort=price&filter=sale&page=2"
removeQueryParams(url, ['filter']); // "/products?sort=price"
getQueryParam(url, 'sort'); // "price"

API Utilities

Storefront API Client

import { StorefrontClient } from '@hydrogen-ui/utils/api';

// Initialize client
const client = new StorefrontClient({
  domain: 'my-store.myshopify.com',
  storefrontAccessToken: 'token',
  apiVersion: '2023-10'
});

// Execute queries
const { data } = await client.query({
  query: `
    query GetProduct($handle: String!) {
      product(handle: $handle) {
        id
        title
        description
      }
    }
  `,
  variables: { handle: 'my-product' }
});

// With caching
const cachedData = await client.query({
  query: productQuery,
  variables: { handle: 'my-product' },
  cache: {
    maxAge: 300, // 5 minutes
    staleWhileRevalidate: 600 // 10 minutes
  }
});

Rate Limiter

import { RateLimiter, TokenBucketRateLimiter } from '@hydrogen-ui/utils/api';

// Simple rate limiter
const limiter = new RateLimiter({
  maxRequests: 10,
  windowMs: 60000 // 1 minute
});

// Execute with rate limiting
await limiter.execute(async () => {
  return fetch('/api/endpoint');
});

// Token bucket algorithm
const bucketLimiter = new TokenBucketRateLimiter({
  capacity: 100,
  refillRate: 10, // tokens per second
  initialTokens: 100
});

// Check if request allowed
if (await bucketLimiter.tryConsume(1)) {
  // Make request
}

Request Batcher

import { RequestBatcher } from '@hydrogen-ui/utils/api';

// Create batcher
const batcher = new RequestBatcher({
  maxBatchSize: 50,
  maxBatchWindow: 10, // ms
  fetch: (requests) => {
    // Batch multiple requests
    return fetch('/api/batch', {
      method: 'POST',
      body: JSON.stringify(requests)
    });
  }
});

// Requests are automatically batched
const [result1, result2] = await Promise.all([
  batcher.fetch('/api/product/1'),
  batcher.fetch('/api/product/2')
]);

TypeScript Support

The package includes comprehensive TypeScript definitions:

import type {
  MoneyV2,
  Product,
  ProductVariant,
  ValidationResult,
  ImageTransformOptions,
  StorefrontClientConfig
} from '@hydrogen-ui/utils';

// Use types in your code
function formatProductPrice(product: Product): string {
  const money: MoneyV2 = product.priceRange.minVariantPrice;
  return formatMoney(money.amount, money.currencyCode);
}

// Validation with types
function validateUserInput(email: string): ValidationResult {
  return validateEmail(email);
}

Advanced Usage

Custom Validators

import { createValidator, ValidationRule } from '@hydrogen-ui/utils/validators';

// Create custom validator
const validateUsername = createValidator<string>([
  {
    test: (value) => value.length >= 3,
    message: 'Username must be at least 3 characters'
  },
  {
    test: (value) => /^[a-zA-Z0-9_]+$/.test(value),
    message: 'Username can only contain letters, numbers, and underscores'
  },
  {
    test: async (value) => {
      const exists = await checkUsernameExists(value);
      return !exists;
    },
    message: 'Username already taken'
  }
]);

// Use validator
const result = await validateUsername('john_doe');

Custom Formatters

import { createFormatter } from '@hydrogen-ui/utils/formatters';

// Create custom formatter
const formatOrderNumber = createFormatter({
  prefix: 'ORD-',
  padLength: 6,
  padChar: '0'
});

formatOrderNumber(123); // "ORD-000123"

Internationalization

import { 
  formatMoney, 
  formatDate, 
  validatePhone 
} from '@hydrogen-ui/utils';

// Automatic locale detection
const locale = navigator.language; // 'fr-FR'

// Format for French users
formatMoney(29.99, 'EUR', locale); // "29,99 €"
formatDate(new Date(), locale); // "15 octobre 2023"
validatePhone('06 12 34 56 78', 'FR'); // true

Performance Optimization

import { memoize } from '@hydrogen-ui/utils/helpers';

// Memoize expensive operations
const getExpensiveData = memoize(async (productId: string) => {
  const response = await fetch(`/api/products/${productId}/details`);
  return response.json();
});

// Subsequent calls with same ID return cached result
const data1 = await getExpensiveData('123'); // API call
const data2 = await getExpensiveData('123'); // From cache

Error Handling

All validators return consistent error objects:

const result = validateEmail('invalid');

if (!result.isValid) {
  console.error(result.errors);
  // ['Please enter a valid email address']
  
  // With error codes
  console.error(result.errorCodes);
  // ['INVALID_EMAIL_FORMAT']
}

Best Practices

  1. Import only what you need for optimal bundle size:

    // ✅ Good
    import { formatMoney } from '@hydrogen-ui/utils/formatters';
       
    // ❌ Avoid
    import * as utils from '@hydrogen-ui/utils';
  2. Use TypeScript for better development experience:

    // Type-safe options
    const options: ImageTransformOptions = {
      width: 800,
      height: 600,
      crop: 'center'
    };
  3. Handle errors gracefully:

    try {
      const formatted = formatMoney(amount, currency);
    } catch (error) {
      console.error('Invalid currency:', error);
      return 'Price unavailable';
    }
  4. Leverage caching for expensive operations:

    const client = new StorefrontClient({
      cache: {
        ttl: 300, // 5 minutes
        storage: 'memory'
      }
    });

Browser Support

  • Modern browsers (Chrome, Firefox, Safari, Edge)
  • Node.js 14+
  • React Native (with polyfills)

Contributing

Adding New Utilities

  1. Create utility in appropriate directory (formatters/, validators/, etc.)
  2. Add comprehensive tests
  3. Export from category index
  4. Add TypeScript definitions
  5. Update documentation

Testing

# Run all tests
npm test

# Run specific category
npm test validators

# Watch mode
npm test -- --watch

License

MIT