RepoFold

Architecture

This page describes the high-level architecture of the ms library, including its module structure, internal parsing and formatting mechanisms, and the key data flows that transform time strings to milliseconds and back.

System Overview

ms is a single-purpose utility library with no runtime processes, network services, or persistent state. It consists of two modules:

  • src – the core library implementing all public functions (parse, format, ms, parseStrict)
  • Project Root ("") – development tooling configuration (Jest, lint-staged, tsdown)

The library has no dependencies and runs entirely within the host process (Node.js, browser, Deno, Bun, or Edge Runtime).

Rendering diagram…

Module Layering

src Module (Core Library)

The src module is the sole runtime layer. It contains a single source file (src/index.ts) that exports all public functions and types, plus four test files that validate behavior. There are no internal submodules, services, or intermediate layers.

Public API surface:

SymbolSignaturePurpose
ms(value: string | number, options?: MsOptions) => string | number | undefinedMain entry point: parses strings to milliseconds or formats numbers to strings
parse(value: string) => number | undefinedParses a time string to milliseconds; returns undefined on invalid input
parseStrict(value: StringValue) => numberType-safe parsing that accepts only valid template literal patterns at compile time; throws on invalid input
format(value: number, options?: MsOptions) => stringFormats milliseconds to a human-readable string (short or long form)
StringValueTypeTemplate literal type representing all valid time string patterns (e.g., "2 hours", "1d")
MsOptionsInterfaceOptions object with optional long: boolean property

Project Root Module (Configuration)

The root module contains three configuration files that are never loaded at runtime:

  • jest.config.ts – test runner configuration
  • lint-staged.config.ts – pre-commit linting setup
  • tsdown.config.ts – bundler configuration for producing the npm package

These files govern the development pipeline only. They do not affect the library's behavior in production.

Key Flows

Flow 1: Parsing a Time String to Milliseconds

When a consumer calls ms("2 hours") or parse("2 days"), the library performs the following steps:

  1. Input normalization – The string is trimmed and converted to lowercase. The numeric portion and unit portion are extracted using a regular expression.

  2. Unit resolution – The unit string (e.g., "hours", "h", "hour") is matched against a lookup table of supported units. Each unit maps to a millisecond multiplier:

UnitMultiplier (ms)
second / seconds / s1,000
minute / minutes / m60,000
hour / hours / h3,600,000
day / days / d86,400,000
week / weeks / w604,800,000
month / months / mo2,592,000,000 (30 days)
year / years / y31,536,000,000 (365 days)
  1. Multiplication – The numeric value is multiplied by the unit's millisecond constant. Fractional values (e.g., "1.5 hours") are supported.

  2. Return – The result is returned as a number. If the input string is invalid (unrecognized unit, non-numeric prefix, empty string), parse() returns undefined and ms() returns undefined. parseStrict() throws a RangeError instead.

The parseStrict() function adds a compile-time constraint: its parameter type StringValue is a TypeScript template literal type that only accepts strings matching the allowed patterns. This prevents passing arbitrary strings at compile time.

Flow 2: Formatting Milliseconds to a String

When a consumer calls ms(60000) or format(86400000, { long: true }), the library:

  1. Determines the sign – If the value is negative, the output is prefixed with -.

  2. Finds the best unit – The absolute value is compared against unit thresholds in descending order (years, months, weeks, days, hours, minutes, seconds). The largest unit that produces a whole number (or a value >= 1) is selected. For example, 60000 matches minutes (1 minute), while 90000 matches minutes (1.5 minutes).

  3. Formats the output – The value is divided by the unit's millisecond constant and rounded to one decimal place if fractional. The unit abbreviation (e.g., "m") or full name (e.g., " minute") is appended based on the long option.

  4. Pluralization – In long format, the unit name is pluralized when the value is not exactly 1 (e.g., "2 minutes" vs "1 minute").

Flow 3: Combined Parse and Format via ms()

The ms() function acts as a dispatcher:

  • If the input is a string, it delegates to the parsing logic (same as parse()).
  • If the input is a number, it delegates to the formatting logic (same as format()).

This allows consumers to use a single import for both directions.

Runtime Topology

The library has no runtime topology. It is a pure function library that executes entirely within the caller's process. There are no:

  • Network services or APIs
  • Background processes or workers
  • Caches or state stores
  • Configuration files loaded at runtime

The only "runtime" is the host environment (Node.js, browser, Deno, Bun, Edge Runtime) that imports and invokes the exported functions.

Edge Runtime Compatibility

Because ms has no dependencies and uses no Node.js-specific APIs (no fs, net, process), it runs in any JavaScript environment including Vercel Edge Functions, Cloudflare Workers, and Deno. The package's package.json exports both CommonJS and ESM builds to support all module systems.

Generated from commit 4ff48ce