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

@jsfsi-core/ts-crossplatform

v1.1.16

Published

Cross-platform TypeScript utilities for building robust applications with functional error handling, type-safe configuration, and common domain primitives.

Readme

@jsfsi-core/ts-crossplatform

Cross-platform TypeScript utilities for building robust applications with functional error handling, type-safe configuration, and common domain primitives.

📦 Installation

npm install @jsfsi-core/ts-crossplatform

🏗️ Architecture

This package provides the foundational building blocks for the hexagonal architecture pattern:

  • Result Type: Functional error handling without exceptions
  • Failure Classes: Domain-specific error representations
  • Configuration: Type-safe environment variable parsing
  • Domain Primitives: Common utilities (GUID, DateTime, etc.)

These utilities are framework-agnostic and can be used in any TypeScript project (Node.js, NestJS, React, etc.).

📋 Features

Result Type

Type-safe error handling using a tuple pattern:

import { Result, Ok, Fail } from '@jsfsi-core/ts-crossplatform';

type Result<T, E extends Failure> = [T, E | undefined];

Example:

import { Result, Ok, Fail, isFailure } from '@jsfsi-core/ts-crossplatform';
import { Failure } from '@jsfsi-core/ts-crossplatform';

class ValidationFailure extends Failure {
  constructor(public readonly message: string) {
    super();
  }
}

function validateEmail(email: string): Result<string, ValidationFailure> {
  if (!email.includes('@')) {
    return Fail(new ValidationFailure('Invalid email format'));
  }
  return Ok(email);
}

// Usage
const [email, failure] = validateEmail('[email protected]');

if (isFailure(ValidationFailure)(failure)) {
  console.error(failure.message);
} else {
  console.log('Valid email:', email);
}

Failure Classes

Base class for all domain failures:

import { Failure } from '@jsfsi-core/ts-crossplatform';

export class SignInFailure extends Failure {
  constructor(public readonly error: unknown) {
    super();
  }
}

Failure Matchers

Always use isFailure and notFailure matchers - never use instanceof directly:

import { isFailure, notFailure } from '@jsfsi-core/ts-crossplatform';

const [user, failure] = await signIn();

// ✅ Correct way
if (isFailure(SignInFailure)(failure)) {
  // TypeScript narrows type to SignInFailure
  console.error('Sign in failed:', failure.error);
}

if (notFailure(SignInFailure)(failure)) {
  // TypeScript knows it's not a SignInFailure
  // Could be another failure type or undefined
}

// ❌ Wrong way - Don't use instanceof
if (failure instanceof SignInFailure) {
  // Avoid this pattern
}

Configuration

Type-safe configuration parsing with Zod:

import { z } from 'zod';
import { parseConfig } from '@jsfsi-core/ts-crossplatform';

const ConfigSchema = z.object({
  PORT: z
    .string()
    .transform((val) => parseInt(val, 10))
    .refine((val) => !isNaN(val), { message: 'PORT must be a valid number' }),
  DATABASE_URL: z.string().url(),
  ENABLE_LOGGING: z
    .string()
    .default('false')
    .transform((val) => val.toLowerCase() === 'true'),
});

// Throws if validation fails (use in application bootstrap)
export const config = parseConfig(ConfigSchema);

GUID

Type-safe GUID generation:

import { Guid } from '@jsfsi-core/ts-crossplatform';

const guid = Guid.newGuid();
console.log(guid); // e.g., "550e8400-e29b-41d4-a716-446655440000"

DateTime

DateTime utilities for formatting dates and times, plus a promise-based sleep function:

import { formatDate, formatTime, formatDateTime, sleep } from '@jsfsi-core/ts-crossplatform';

Sleep

Promise-based sleep function for async delays:

import { sleep } from '@jsfsi-core/ts-crossplatform';

// Sleep for 1 second
await sleep(1000);

// Usage in async operations
async function processWithDelay() {
  await sleep(500); // Wait 500ms
  // Continue processing
}

Format Date

Format a timestamp as a date string (MM/DD/YYYY format):

import { formatDate } from '@jsfsi-core/ts-crossplatform';

const timestamp = new Date('2025-01-15').getTime();
const formatted = formatDate(timestamp);
console.log(formatted); // "01/15/2025"

// With locale support
const formattedDE = formatDate(timestamp, 'de-DE');
console.log(formattedDE); // "15.01.2025" (German format)

Format Time

Format a timestamp as a time string (24-hour format HH:MM:SS):

import { formatTime } from '@jsfsi-core/ts-crossplatform';

const timestamp = new Date('2025-01-15T14:30:45').getTime();
const formatted = formatTime(timestamp);
console.log(formatted); // "14:30:45"

// With locale support
const formattedDE = formatTime(timestamp, 'de-DE');
console.log(formattedDE); // "14:30:45"

Format Date and Time

Format a timestamp as both date and time:

import { formatDateTime } from '@jsfsi-core/ts-crossplatform';

const timestamp = new Date('2025-01-15T14:30:45').getTime();
const formatted = formatDateTime(timestamp);
console.log(formatted); // "01/15/2025 14:30:45"

// With locale support
const formattedDE = formatDateTime(timestamp, 'de-DE');
console.log(formattedDE); // "15.01.2025 14:30:45" (German format)

Complete Example

import { formatDate, formatTime, formatDateTime, sleep } from '@jsfsi-core/ts-crossplatform';

// Format current date/time
const now = Date.now();
console.log(formatDate(now)); // "01/15/2025"
console.log(formatTime(now)); // "14:30:45"
console.log(formatDateTime(now)); // "01/15/2025 14:30:45"

// Sleep in async function
async function delayedProcessing() {
  console.log('Starting...');
  await sleep(1000);
  console.log('Done after 1 second');
}

Notes:

  • All formatting functions accept a timestamp (number) as the first parameter
  • Optional locales parameter for internationalization (follows Intl.LocalesArgument)
  • Date format: MM/DD/YYYY (US format by default)
  • Time format: HH:MM:SS (24-hour format, always)
  • Sleep uses milliseconds (1 second = 1000ms)

Partial Types

Recursive partial types for deep optional properties:

import { RecursivePartial } from '@jsfsi-core/ts-crossplatform';

type User = {
  id: string;
  profile: {
    name: string;
    address: {
      city: string;
    };
  };
};

type PartialUser = RecursivePartial<User>;
// All properties are optional, recursively

Mock Utility

Type-safe mock utility for testing that works with RecursivePartial types:

import { mock } from '@jsfsi-core/ts-crossplatform';

Basic Usage

Create mock objects with only the properties you need for testing:

import { mock } from '@jsfsi-core/ts-crossplatform';

type User = {
  id: string;
  email: string;
  name: string;
  profile: {
    bio: string;
    avatar: string;
  };
};

// Mock with no properties
const emptyUser = mock<User>();
// emptyUser is typed as User but with all properties undefined

// Mock with partial properties
const partialUser = mock<User>({
  id: '123',
  email: '[email protected]',
});

// Mock with nested properties
const fullUser = mock<User>({
  id: '123',
  email: '[email protected]',
  name: 'John Doe',
  profile: {
    bio: 'Software developer',
    // avatar can be omitted - it's optional via RecursivePartial
  },
});

Testing Examples

Use mocks in your tests to create test data:

import { describe, it, expect } from 'vitest';
import { mock } from '@jsfsi-core/ts-crossplatform';

describe('UserService', () => {
  it('creates user with minimal data', async () => {
    const userData = mock<User>({
      email: '[email protected]',
      name: 'Test User',
    });

    const [user, failure] = await userService.createUser(userData);

    expect(user).toBeDefined();
    expect(failure).toBeUndefined();
  });

  it('handles user with nested profile', async () => {
    const userData = mock<User>({
      id: '123',
      email: '[email protected]',
      profile: {
        bio: 'Test bio',
        // avatar omitted - RecursivePartial makes it optional
      },
    });

    const [user, failure] = await userService.updateUser(userData);

    expect(user?.profile.bio).toBe('Test bio');
  });
});

Complex Types

Works with complex nested types:

import { mock } from '@jsfsi-core/ts-crossplatform';

type Order = {
  id: string;
  customer: {
    id: string;
    email: string;
    address: {
      street: string;
      city: string;
      zipCode: string;
    };
  };
  items: Array<{
    productId: string;
    quantity: number;
    price: number;
  }>;
};

// Mock with only what you need for the test
const orderMock = mock<Order>({
  id: 'order-123',
  customer: {
    id: 'customer-456',
    email: '[email protected]',
    // address can be omitted - RecursivePartial makes it optional
  },
  items: [
    {
      productId: 'product-789',
      quantity: 2,
      // price can be omitted for this test
    },
  ],
});

Benefits

  • Type Safety: Mock objects are fully typed, catching errors at compile time
  • Flexibility: Only specify the properties you need for each test
  • Recursive: Works with deeply nested objects
  • Simple: No complex setup or configuration required

📝 Naming Conventions

  • Result type: Use Result<T, E> where T is success type, E extends Failure
  • Failure classes: Suffix with Failure (e.g., SignInFailure, ValidationFailure)
  • Helper functions: Use descriptive names (Ok, Fail, isFailure, notFailure)

🧪 Testing Principles

Testing Result Types

import { describe, it, expect } from 'vitest';
import { Ok, Fail, Result } from './result';
import { Failure, isFailure } from '../failures';

describe('validateEmail', () => {
  it('returns Ok with email on valid input', () => {
    const [email, failure] = validateEmail('[email protected]');

    expect(email).toBe('[email protected]');
    expect(failure).toBeUndefined();
  });

  it('returns ValidationFailure on invalid input', () => {
    const [email, failure] = validateEmail('invalid');

    expect(email).toBeUndefined();
    expect(isFailure(ValidationFailure)(failure)).toBe(true);
    if (isFailure(ValidationFailure)(failure)) {
      expect(failure.message).toBe('Invalid email format');
    }
  });
});

Testing Failure Matchers

import { describe, it, expect } from 'vitest';
import { isFailure, notFailure } from './matchers';
import { Failure } from './failure';

class CustomFailure extends Failure {
  constructor(public readonly message: string) {
    super();
  }
}

describe('isFailure', () => {
  it('matches when value is the failure type', () => {
    const failure = new CustomFailure('error');
    expect(isFailure(CustomFailure)(failure)).toBe(true);
  });

  it('does not match when value is different failure type', () => {
    const failure = new Failure();
    expect(isFailure(CustomFailure)(failure)).toBe(false);
  });
});

describe('notFailure', () => {
  it('matches when value is not the failure type', () => {
    const failure = new Failure();
    expect(notFailure(CustomFailure)(failure)).toBe(true);
  });
});

⚠️ Error Handling Principles

Result Pattern

Always use Result types for operations that can fail:

// ✅ Good
function parseNumber(input: string): Result<number, ParseFailure> {
  const num = Number(input);
  if (isNaN(num)) {
    return Fail(new ParseFailure(`Cannot parse "${input}" as number`));
  }
  return Ok(num);
}

// ❌ Bad - Throwing exceptions
function parseNumber(input: string): number {
  const num = Number(input);
  if (isNaN(num)) {
    throw new Error(`Cannot parse "${input}" as number`);
  }
  return num;
}

Failure Matchers

Always use isFailure and notFailure matchers:

// ✅ Good
const [value, failure] = await operation();
if (isFailure(CustomFailure)(failure)) {
  // Handle CustomFailure
}

// ❌ Bad
if (failure instanceof CustomFailure) {
  // Don't use instanceof directly
}

Chaining Results

function processUser(email: string): Result<User, ValidationFailure | SignInFailure> {
  const [validEmail, emailFailure] = validateEmail(email);

  if (isFailure(ValidationFailure)(emailFailure)) {
    return Fail(emailFailure);
  }

  const [user, signInFailure] = await signIn(validEmail);

  if (isFailure(SignInFailure)(signInFailure)) {
    return Fail(signInFailure);
  }

  return Ok(user);
}

🎯 Domain-Driven Design

Failure as Domain Concept

Failures are part of your domain model:

// Domain failures represent business errors
export class SignInFailure extends Failure {
  constructor(public readonly error: unknown) {
    super();
  }
}

export class InsufficientFundsFailure extends Failure {
  constructor(
    public readonly balance: number,
    public readonly required: number,
  ) {
    super();
  }
}

Value Objects

Use Result types when returning value objects:

function createEmail(value: string): Result<Email, InvalidEmailFailure> {
  if (!isValidEmail(value)) {
    return Fail(new InvalidEmailFailure(value));
  }
  return Ok({ value } as Email);
}

🔄 Result Class Usage

Basic Pattern

import { Result, Ok, Fail } from '@jsfsi-core/ts-crossplatform';

function divide(a: number, b: number): Result<number, DivisionByZeroFailure> {
  if (b === 0) {
    return Fail(new DivisionByZeroFailure());
  }
  return Ok(a / b);
}

Handling Multiple Failure Types

type AuthResult = Result<User, SignInFailure | NetworkFailure>;

async function authenticate(email: string, password: string): Promise<AuthResult> {
  const [networkCheck, networkFailure] = await checkNetwork();

  if (isFailure(NetworkFailure)(networkFailure)) {
    return Fail(networkFailure);
  }

  const [user, signInFailure] = await signIn(email, password);

  if (isFailure(SignInFailure)(signInFailure)) {
    return Fail(signInFailure);
  }

  return Ok(user);
}

// Usage with type narrowing
const [user, failure] = await authenticate(email, password);

if (isFailure(SignInFailure)(failure)) {
  // Handle sign-in failure
  console.error('Sign in failed:', failure.error);
} else if (isFailure(NetworkFailure)(failure)) {
  // Handle network failure
  console.error('Network error:', failure.message);
} else {
  // Success case
  console.log('Authenticated:', user.name);
}

Early Returns Pattern

function processOrder(order: Order): Result<OrderId, ValidationFailure | PaymentFailure> {
  // Early return on first failure
  const [, validationFailure] = validateOrder(order);
  if (isFailure(ValidationFailure)(validationFailure)) {
    return Fail(validationFailure);
  }

  const [, paymentFailure] = processPayment(order);
  if (isFailure(PaymentFailure)(paymentFailure)) {
    return Fail(paymentFailure);
  }

  return Ok(order.id);
}

📚 Best Practices

1. Type Safety

Always specify failure types explicitly:

// ✅ Good - Explicit failure types
function getUser(id: string): Result<User, UserNotFoundFailure | DatabaseFailure> {
  // ...
}

// ⚠️ Acceptable - Generic Failure
function getUser(id: string): Result<User, Failure> {
  // ...
}

2. Failure Messages

Include meaningful information in failures:

// ✅ Good
export class ValidationFailure extends Failure {
  constructor(
    public readonly field: string,
    public readonly message: string,
    public readonly value: unknown,
  ) {
    super();
  }
}

// ❌ Bad - No context
export class ValidationFailure extends Failure {
  constructor() {
    super();
  }
}

3. Avoid Throwing

Never throw exceptions in domain logic - use Result types:

// ✅ Good
function parseConfig<T>(schema: z.ZodSchema<T>): Result<T, ConfigParseFailure> {
  // ...
}

// ❌ Bad
function parseConfig<T>(schema: z.ZodSchema<T>): T {
  // Throws exception
}

🔗 Additional Resources

📄 License

ISC