supacommerce

Utils

Full API reference for @supacommerce/utils.

@supacommerce/utils

Shared utilities for supacommerce. Intentionally public — use these directly in your own application code.

Installation

pnpm add @supacommerce/utils

Currency

All monetary values in supacommerce are stored as integers in the smallest currency unit — cents for USD, pence for GBP. These helpers handle conversion and formatting.

Zero-decimal currencies (JPY, KRW, VND, and others) are handled automatically.

import {
  toMinorUnit,
  fromMinorUnit,
  formatCurrency,
  addMoney,
  subtractMoney,
} from "@supacommerce/utils";

// Convert decimal to storage integer
toMinorUnit(29.99, "USD");  // 2999
toMinorUnit(100, "JPY");    // 100 — JPY is zero-decimal

// Convert storage integer to decimal
fromMinorUnit(2999, "USD"); // 29.99
fromMinorUnit(100, "JPY");  // 100

// Format for display
formatCurrency(2999, "USD");          // "$29.99"
formatCurrency(2999, "USD", "de-DE"); // "29,99 $"
formatCurrency(2999, "GBP");          // "£29.99"
formatCurrency(300, "JPY");           // "¥300"

// Safe integer arithmetic
addMoney(1999, 500);      // 2499
subtractMoney(1999, 500); // 1499

Result type

A typed alternative to try/catch for operations that can fail predictably.

import { ok, err, isOk, isErr, unwrap, type Result } from "@supacommerce/utils";

function divide(a: number, b: number): Result<number, Error> {
  if (b === 0) return err(new Error("Division by zero"));
  return ok(a / b);
}

const result = divide(10, 2);

if (isOk(result)) {
  console.log(result.value); // 5
}

if (isErr(result)) {
  console.error(result.error.message);
}

// Unwrap — returns the value or throws the error
const value = unwrap(divide(10, 2)); // 5

Error types

All @supacommerce/client methods throw these typed errors. Import them in your catch blocks for precise handling.

import {
  SupacommerceError,
  NotFoundError,
  ValidationError,
  UnauthorizedError,
  ForbiddenError,
  ConflictError,
  InventoryError,
  PaymentError,
} from "@supacommerce/utils";
ClassStatusCode
NotFoundError404NOT_FOUND
ValidationError400VALIDATION_ERROR
UnauthorizedError401UNAUTHORIZED
ForbiddenError403FORBIDDEN
ConflictError409CONFLICT
InventoryError422INVENTORY_ERROR
PaymentError402PAYMENT_ERROR

Every error has statusCode, code, and message properties inherited from SupacommerceError.

const e = new NotFoundError("Product", "abc-123");
e.statusCode; // 404
e.code;       // "NOT_FOUND"
e.message;    // "Product with id 'abc-123' not found"

// ValidationError also has a fields property
const ve = new ValidationError("Invalid input", {
  email: "Must be a valid email address",
});
ve.fields; // { email: "Must be a valid email address" }

Pagination

import {
  buildPaginatedResult,
  type PaginationParams,
  type PaginatedResult,
} from "@supacommerce/utils";

const result = buildPaginatedResult(data, totalCount, { limit: 20, offset: 0 });

// result.data     — the items
// result.count    — total count across all pages
// result.limit    — limit used
// result.offset   — offset used
// result.hasMore  — true if more pages exist

ID generation

Generates Stripe/Medusa-style prefixed IDs.

import { generateId } from "@supacommerce/utils";

generateId("cart");  // "cart_a1b2c3d4e5f6g7h8"
generateId("order"); // "order_x9y8z7w6v5u4t3s2"

Date helpers

import { nowISO, isPast, isFuture } from "@supacommerce/utils";

nowISO();                           // "2024-06-15T12:34:56.789Z"
isPast("2020-01-01T00:00:00Z");    // true
isFuture("2099-01-01T00:00:00Z"); // true

Type utilities

import type {
  RequireKeys,
  PartialExcept,
  DeepPartial,
} from "@supacommerce/utils";

// Make specific keys required
type T = RequireKeys<{ a?: string; b?: string }, "a">;
// { a: string; b?: string }

// Make all keys optional except specified ones
type T = PartialExcept<{ a: string; b: string; c: string }, "a">;
// { a: string; b?: string; c?: string }

// Deep partial — all nested objects also become partial
type T = DeepPartial<{ a: { b: string } }>;
// { a?: { b?: string } }

On this page