npm package: @jsxx/bytes
Description: A lightweight, type-safe utility for converting between bytes and human-readable strings
Author: Aashish Panchal
Latest version: 1.0.1

# @jsxx/bytes

[![NPM Version](https://img.shields.io/npm/v/@jsxx/bytes.svg)](https://www.npmjs.com/package/@jsxx/bytes)
[![License](https://img.shields.io/npm/l/@jsxx/bytes.svg)](https://github.com/vajra-labs/jsxx/blob/main/LICENSE)

A lightweight, type-safe utility library for converting between bytes and human-readable strings.

## Features

- **Zero dependencies** - Minimal footprint
- **Type-safe** - Full TypeScript support with template literal types
- **Dual API** - Single function for both formatting and parsing
- **Smart formatting** - Auto-hides unnecessary decimals
- **Customizable** - Control precision and formatting
- **Fast** - Optimized for performance
- **Well-tested** - Comprehensive test coverage

## Installation

```bash
npm add @jsxx/bytes
# or
pnpm add @jsxx/bytes
# or
yarn add @jsxx/bytes
# or
bun add @jsxx/bytes
```

## Quick Start

```typescript
import bytes from '@jsxx/bytes';

// Format bytes to human-readable string
bytes(1024); // "1KB"
bytes(1536); // "1.5KB"
bytes(1048576); // "1MB"

// Parse human-readable string to bytes
bytes('1KB'); // 1024
bytes('1.5MB'); // 1572864
bytes('2GB'); // 2147483648
```

## API Reference

### `bytes(value, options?)`

Main function with overloaded signatures for bidirectional conversion.

#### Signature 1: Format bytes to string

```typescript
function bytes(value: number, options?: FormatOptions): string;
```

Converts a numeric byte count into a human-readable string using binary units (1024-based).

**Parameters:**

- `value` (number): The number of bytes to format
- `options` (FormatOptions): Optional formatting options

**Returns:** `string` - Formatted string like `"1.5KB"` or `"2MB"`

**Examples:**

```typescript
bytes(0); // "0B"
bytes(500); // "500B"
bytes(1024); // "1KB"
bytes(1536); // "1.5KB"
bytes(1048576); // "1MB"
bytes(1073741824); // "1GB"
bytes(-1024); // "-1KB"

// With custom decimals
bytes(1536, {decimals: 0}); // "2KB"
bytes(1536, {decimals: 2}); // "1.5KB"
bytes(1075, {decimals: 2}); // "1.05KB"

// With separator
bytes(1024, {separator: ' '}); // "1 KB"
bytes(1536, {separator: ' '}); // "1.5 KB"

// Combined options
bytes(1536, {decimals: 2, separator: ' '}); // "1.5 KB"
```

**Edge Cases:**

- Returns `"0B"` for `0`, `Infinity`, `-Infinity`, or `NaN`
- Supports negative numbers: `-1024` → `"-1KB"`
- Automatically hides unnecessary decimals: `1024` → `"1KB"` (not "1.0KB")
- Maximum supported unit: `PB` (Petabyte)

---

#### Signature 2: Parse string to bytes

```typescript
function bytes(value: ByteString | string): number;
```

Parses a human-readable byte string into a numeric byte count using binary units (1024-based).

**Parameters:**

- `value` (ByteString | string): A formatted byte string (e.g., `"10MB"`, `"512KB"`)

**Returns:** `number` - The number of bytes

**Examples:**

```typescript
bytes('1B'); // 1
bytes('1KB'); // 1024
bytes('1.5KB'); // 1536
bytes('1MB'); // 1048576
bytes('1GB'); // 1073741824
bytes('1TB'); // 1099511627776
bytes('1PB'); // 1125899906842624
```

**Case Handling:**

```typescript
bytes('1kb'); // 1024 (lowercase works)
bytes('1KB'); // 1024 (uppercase works)
bytes('1Kb'); // 1024 (mixed case works)
```

**Throws:**

- Invalid format (e.g., `"ABC"`, `"100"` without unit, `"KB"` without number)

**Returns NaN:**

- Invalid decimal format: `"1.2.3KB"`, `"..5MB"` (matches regex but invalid number)

---

### `formatBytes(bytes, options?)`

Explicit function to format bytes to a human-readable string.

```typescript
function formatBytes(bytes: number, options?: FormatOptions): string;
```

**Parameters:**

- `bytes` (number): The number of bytes
- `options` (FormatOptions): Optional formatting options

**Returns:** `string`

**Example:**

```typescript
import {formatBytes} from '@jsxx/bytes';

formatBytes(1024); // "1KB"
formatBytes(1536); // "1.5KB"
formatBytes(1048576); // "1MB"
formatBytes(1536, {decimals: 0}); // "2KB"
formatBytes(1024, {separator: ' '}); // "1 KB"
```

---

### `parseBytes(value)`

Explicit function to parse a byte string into a number.

```typescript
function parseBytes(value: ByteString | string): number;
```

**Parameters:**

- `value` (ByteString | string): A formatted byte string

**Returns:** `number`

**Throws:** Error if the string format is invalid

**Example:**

```typescript
import {parseBytes} from '@jsxx/bytes';

parseBytes('1KB'); // 1024
parseBytes('1.5MB'); // 1572864
parseBytes('2GB'); // 2147483648
```

---

### Types

#### `ByteUnit`

```typescript
type ByteUnit = 'B' | 'KB' | 'MB' | 'GB' | 'TB' | 'PB';
```

Supported byte units (binary, 1024-based).

#### `ByteString`

```typescript
type ByteString = `${number}${ByteUnit}` | `${number}${Lowercase<ByteUnit>}`;
```

Template literal type for type-safe byte strings. Ensures compile-time checking for valid formats.

**Valid formats:**

- `"1KB"`, `"1.5MB"`, `"2GB"` (uppercase units)
- `"1kb"`, `"1.5mb"`, `"2gb"` (lowercase units)

**Invalid formats** (caught at compile-time):

- `"1 KB"` (space not allowed in type, but works at runtime)
- `"KB"` (number required)
- `"100"` (unit required)

#### `FormatOptions`

```typescript
interface FormatOptions {
  /**
   * Number of decimal places to display.
   * @default 1
   */
  decimals?: number;
  /**
   * Separator between number and unit.
   * @default ''
   */
  separator?: string;
}
```

Options for customizing byte formatting.

## Supported Units

All units use **binary** (base-1024) calculation:

| Unit | Name     | Bytes                 | Calculation |
| ---- | -------- | --------------------- | ----------- |
| B    | Byte     | 1                     | 1           |
| KB   | Kilobyte | 1,024                 | 1024¹       |
| MB   | Megabyte | 1,048,576             | 1024²       |
| GB   | Gigabyte | 1,073,741,824         | 1024³       |
| TB   | Terabyte | 1,099,511,627,776     | 1024⁴       |
| PB   | Petabyte | 1,125,899,906,842,624 | 1024⁵       |

## Usage Examples

### Basic Formatting

```typescript
import bytes from '@jsxx/bytes';

console.log(bytes(1024)); // "1KB"
console.log(bytes(1536)); // "1.5KB"
console.log(bytes(1048576)); // "1MB"
console.log(bytes(5242880)); // "5MB"
console.log(bytes(1073741824)); // "1GB"
```

### Basic Parsing

```typescript
import bytes from '@jsxx/bytes';

console.log(bytes('1KB')); // 1024
console.log(bytes('1.5KB')); // 1536
console.log(bytes('5MB')); // 5242880
console.log(bytes('1GB')); // 1073741824
```

### File Size Display

```typescript
import bytes from '@jsxx/bytes';

function displayFileSize(fileSizeInBytes: number) {
  return `File size: ${bytes(fileSizeInBytes)}`;
}

displayFileSize(2048); // "File size: 2KB"
displayFileSize(5242880); // "File size: 5MB"
```

### Parsing User Input

```typescript
import bytes from '@jsxx/bytes';

function validateUploadSize(input: string, maxSize: string) {
  const uploadBytes = bytes(input as ByteString);
  const maxBytes = bytes(maxSize as ByteString);

  return uploadBytes <= maxBytes;
}

validateUploadSize('5MB', '10MB'); // true
validateUploadSize('15MB', '10MB'); // false
```

### Custom Precision

```typescript
import bytes from '@jsxx/bytes';

// Default: 1 decimal place (auto-hidden if zero)
bytes(1024); // "1KB"
bytes(1536); // "1.5KB"

// No decimals (rounded)
bytes(1536, {decimals: 0}); // "2KB"
bytes(1700, {decimals: 0}); // "2KB"

// 2 decimal places
bytes(1075, {decimals: 2}); // "1.05KB"
bytes(1100, {decimals: 2}); // "1.07KB"

// 3 decimal places
bytes(1075, {decimals: 3}); // "1.05KB"
bytes(1100, {decimals: 3}); // "1.074KB"
```

### With Separators

```typescript
import bytes from '@jsxx/bytes';

// Space separator
bytes(1024, {separator: ' '}); // "1 KB"
bytes(1536, {separator: ' '}); // "1.5 KB"

// Custom separators
bytes(1024, {separator: '-'}); // "1-KB"
bytes(1536, {separator: '_'}); // "1.5_KB"

// Combined with decimals
bytes(1536, {decimals: 2, separator: ' '}); // "1.5 KB"
bytes(1024, {decimals: 0, separator: ' '}); // "1 KB"
```

### Memory Usage Monitoring

```typescript
import bytes from '@jsxx/bytes';

const memoryUsage = process.memoryUsage();

console.log('RSS:', bytes(memoryUsage.rss));
console.log('Heap Total:', bytes(memoryUsage.heapTotal));
console.log('Heap Used:', bytes(memoryUsage.heapUsed));
```

### Network Transfer Display

```typescript
import bytes from '@jsxx/bytes';

function showDownloadProgress(downloaded: number, total: number) {
  return `${bytes(downloaded)} / ${bytes(total)}`;
}

showDownloadProgress(524288, 1048576); // "512KB / 1MB"
```

### Configuration Parsing

```typescript
import bytes from '@jsxx/bytes';

interface CacheConfig {
  maxSize: string;
}

function getCacheMaxBytes(config: CacheConfig): number {
  return bytes(config.maxSize as ByteString);
}

getCacheMaxBytes({maxSize: '100MB'}); // 104857600
```

## Edge Cases & Behavior

### Zero and Special Values

```typescript
bytes(0); // "0B"
bytes(Infinity); // "0B"
bytes(-Infinity); // "0B"
bytes(NaN); // "0B"
```

### Negative Numbers

```typescript
bytes(-1024); // "-1KB"
bytes(-1048576); // "-1MB"
```

### Smart Decimal Hiding

The library automatically hides unnecessary decimal places:

```typescript
bytes(1024); // "1KB" (not "1.0KB")
bytes(2048); // "2KB" (not "2.0KB")
bytes(1536); // "1.5KB" (keeps non-zero decimal)
bytes(1075); // "1.05KB" (keeps necessary decimals)
```

You can control this with the `decimals` option:

```typescript
bytes(1024, {decimals: 2}); // "1KB" (still hides if zero)
bytes(1075, {decimals: 2}); // "1.05KB"
bytes(1100, {decimals: 3}); // "1.074KB"
```

### Precision

Parsing uses `Math.floor()` for decimal results:

```typescript
bytes('1.5KB'); // 1536 (exactly 1.5 * 1024)
bytes('1.999KB'); // 2046 (floor of 1.999 * 1024)
bytes('0.5MB'); // 524288 (floor of 0.5 * 1048576)
```

### Invalid Input

```typescript
// Throws error
parseBytes('invalid'); // Error: Invalid byte string: invalid
parseBytes(''); // Error: Invalid byte string:
parseBytes('100'); // Error: Invalid byte string: 100
parseBytes('KB'); // Error: Invalid byte string: KB

// Returns NaN (matches regex but invalid number)
parseBytes('1.2.3KB'); // NaN
parseBytes('..5MB'); // NaN
```

### Whitespace Handling

Runtime whitespace is trimmed (though TypeScript type doesn't allow it):

```typescript
parseBytes(' 1KB ' as ByteString); // 1024 (works at runtime)
```

## TypeScript Usage

### Type Safety

```typescript
import bytes, {ByteString} from '@jsxx/bytes';

// Valid at compile-time
const size: ByteString = '1.5MB';
bytes(size); // ✓

// Invalid at compile-time
const invalid: ByteString = '1 MB'; // ✗ Type error (but works at runtime)
const invalid2: ByteString = '100'; // ✗ Type error
```

### Function Overloads

TypeScript correctly infers return types:

```typescript
const str = bytes(1024); // type: string
const num = bytes('1KB'); // type: number
```

### Generic Usage

```typescript
function formatSize<T extends number | ByteString>(
  value: T,
): T extends number ? string : number {
  return bytes(value as any);
}

formatSize(1024); // Returns string
formatSize('1KB'); // Returns number
```

## Performance

- **Lightweight**: ~1KB minified + gzipped
- **Fast**: O(1) time complexity for both formatting and parsing
- **Efficient**: Pre-computed unit mappings, minimal allocations
- **No regex overhead**: Only one regex match per parse operation

## Browser Support

Works in all modern browsers and Node.js environments that support:

- ES2015+ features
- `Number.isFinite()`
- `Math.log()`, `Math.floor()`, `Math.pow()`
- `String.prototype.trim()`, `String.prototype.match()`

## Why @jsxx/bytes?

### vs `bytes` (popular npm package)

| Feature                | @jsxx/bytes | bytes   |
| ---------------------- | ----------- | ------- |
| Bundle size            | ~1KB        | ~2.5KB  |
| TypeScript types       | ✓ Built-in  | ✓       |
| Template literal types | ✓           | ✗       |
| Auto-hide decimals     | ✓           | ✗       |
| Custom decimals        | ✓           | ✓       |
| Custom separator       | ✓           | ✗       |
| Tree-shakeable         | ✓           | Limited |
| ESM-first              | ✓           | ✗       |

### Key Advantages

- **Smaller bundle size** - 60% smaller than popular alternatives
- **Better TypeScript support** - Template literal types for compile-time safety
- **Smarter formatting** - Auto-hides unnecessary decimals
- **Modern** - Written with modern JavaScript, ESM-first
- **Well-tested** - Comprehensive test coverage including edge cases
- **Part of @jsxx ecosystem** - Consistent API across @jsxx packages

## Contributing

Contributions are welcome! Please ensure:

1. All tests pass: `pnpm test`
2. Code follows existing style: `pnpm lint`
3. Types are correct: `pnpm check-types`
4. Add tests for new features
5. Update documentation

## License

MIT