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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@goodbyenjn/utils

v26.6.1

Published

GoodbyeNJN's utils for typescript and javascript

Readme

@goodbyenjn/utils

npm version License: MIT

A modern TypeScript/JavaScript utility library providing a comprehensive collection of type-safe utility functions, functional error handling with the Result and Option patterns, filesystem operations, and shell command execution.

Features

  • 🚀 Modern: Built with TypeScript, targeting ES modules and modern JavaScript
  • 🔒 Type-safe: Full TypeScript support with comprehensive type definitions and type inference
  • 📦 Modular: Import only what you need with tree-shakable exports and multiple entry points
  • 🛡️ Result & Option Pattern: Functional error handling and optional values without exceptions, based on Rust-style Result and Option types
  • 📁 VFile & FS: Type-safe file system operations and a powerful virtual file object
  • 🐚 Exec: Powerful and flexible command execution with safe and unsafe variants
  • 🧰 Common Utilities: String manipulation, math operations, promise utilities, and JSON handling
  • 📊 FP Utilities: Functional programming utilities built on top of Remeda and Rotery, including async iteration helpers

Installation

npm install @goodbyenjn/utils
# or
pnpm add @goodbyenjn/utils
# or
yarn add @goodbyenjn/utils

Usage

Quick Start

// Import what you need from the main module
import { sleep, template } from "@goodbyenjn/utils";
import { exec } from "@goodbyenjn/utils/exec";
import { exec as safeExec } from "@goodbyenjn/utils/exec/safe";
import { BaseVFile } from "@goodbyenjn/utils/fs";
import { readFile as safeReadFile } from "@goodbyenjn/utils/fs/safe";
import { Ok, Result } from "@goodbyenjn/utils/result";
import { parse } from "@goodbyenjn/utils/json";
import { parse as safeParse } from "@goodbyenjn/utils/json/safe";
import { Some, None } from "@goodbyenjn/utils/option";
import type { Nullable, SetOptional } from "@goodbyenjn/utils/types";

// Enable global type augmentations (better Object.keys, Map.has, Array.filter, etc.)
import "@goodbyenjn/utils/global-types";

Common Utilities

String Operations

import { template, unindent, addPrefix, removeSuffix, joinWith, splitBy } from "@goodbyenjn/utils";

// String templating
const greeting = template("Hello, {name}! You are {age} years old.", {
    name: "Alice",
    age: 30,
});
console.log(greeting); // "Hello, Alice! You are 30 years old."

// Remove common indentation from template strings (default: trim start and end)
const code = unindent`
    function example() {
        return 'formatted';
    }
`;
console.log(code);
/*
function example() {
    return 'formatted';
}
*/

// Custom trim behavior with factory function
const codeNoTrim = unindent(false, false)`
    function example() {
        return 'formatted';
    }
`;
console.log(codeNoTrim);
/*

function example() {
    return 'formatted';
}

*/

// Only trim start, keep end
const onlyTrimStart = unindent(true, false)("  hello\n  world\n");
console.log(onlyTrimStart); // "hello\nworld\n"

// Add indentation to strings
const indented = indent(2)`
    if (a) {
        b()
    }
`;
console.log(indented);
/*
      if (a) {
          b()
      }
*/

// With custom string
const arrowIndented = indent(">>")("line1\nline2");
console.log(arrowIndented); // ">>line1\n>>line2"

// Only trim start, keep end
const onlyTrimStart = indent(2, true, false)("hello\nworld\n");
console.log(onlyTrimStart); // "  hello\n  world\n"

// Prefix and suffix operations
const withPrefix = addPrefix("@", "myfile"); // "@myfile"
const cleaned = removeSuffix(".js", "script.js"); // "script"

// String joining and splitting
const path = joinWith("/", "home", "user", "docs"); // "/home/user/docs"
const parts = splitBy("-", "hello-world-js"); // ["hello", "world", "js"]

// Split string by line breaks (handles both \n and \r\n)
const lines = splitByLineBreak("line1\nline2\r\nline3");
console.log(lines); // ["line1", "line2", "line3"]

// Parse boolean value with custom default
const isEnabled = parseValueToBoolean("yes", false); // true
const debugMode = parseValueToBoolean("invalid", "auto"); // "auto"

Promise Utilities

import { sleep, createLock, createSingleton, createPromiseWithResolvers } from "@goodbyenjn/utils";

// Sleep/delay execution
await sleep(1000); // Wait 1 second

// Create a reusable mutex lock
const lock = createLock();
await lock.acquire();
try {
    // Critical section
    console.log("Executing exclusively");
} finally {
    lock.release();
}

// Singleton pattern factory
const getDatabase = createSingleton(() => {
    console.log("Initializing database...");
    return new Database();
});
const db1 = await getDatabase(); // Initializes once
const db2 = await getDatabase(); // Returns same instance

// Promise with external resolvers
const { promise, resolve, reject } = createPromiseWithResolvers<string>();
setTimeout(() => resolve("done!"), 1000);
const result = await promise;

Command Execution

import { exec } from "@goodbyenjn/utils/exec";
import { exec as safeExec } from "@goodbyenjn/utils/exec/safe";

// 1. Unsafe Execution (throws on failure)
const output = await exec`npm install`;
console.log(output.stdout);

// String command with args
const lsOutput = await exec("ls", ["-la"]);
console.log(lsOutput.stdout);

// 2. Safe Execution (returns Result)
const safeOutput = await safeExec`npm install`;
if (safeOutput.isOk()) {
    console.log("Success:", safeOutput.unwrap().stdout);
} else {
    // Result contains error information (e.g., NonZeroExitError)
    console.error("Failed:", safeOutput.unwrapErr().message);
}

// 3. Pipe Output
const piped = await exec`echo "hello"`.pipe`cat`;
console.log(piped.stdout);

// 4. Iterate Output
for await (const line of exec`cat large-file.txt`) {
    console.log(line);
}

// 5. Configuration Factory
const withCwd = exec({ cwd: "/path/to/project" });
const result3 = await withCwd`npm install`;

Math Utilities

import { linear, scale } from "@goodbyenjn/utils";

// Linear interpolation between values
const mid = linear(0.5, [0, 100]); // 50

// Scale a value from one range to another
const scaledValue = scale(75, [0, 100], [0, 1]); // 0.75
const percentage = scale(200, [0, 255], [0, 100]); // 78.43...

Error Handling

import { normalizeError, getErrorMessage } from "@goodbyenjn/utils";

// Normalize any value to an Error object
const error = normalizeError("Something went wrong");
const typeError = normalizeError({ code: 500 });

// Safely extract error message
const message1 = getErrorMessage(new Error("Oops"));
const message2 = getErrorMessage("Plain string error");
const message3 = getErrorMessage(null); // "Unknown error"

Throttling and Debouncing

import { debounce, throttle } from "@goodbyenjn/utils/fp";

// Debounce - wait for inactivity before executing
const debouncedSearch = debounce((query: string) => {
    console.log("Searching for:", query);
}, 300);

// Call multiple times, executes only after 300ms of inactivity
input.addEventListener("input", e => {
    debouncedSearch((e.target as HTMLInputElement).value);
});

// Throttle - execute at most once per interval
const throttledScroll = throttle(() => {
    console.log("Scroll position:", window.scrollY);
}, 100);

window.addEventListener("scroll", throttledScroll);

JSON Handling

import { parse, stringify } from "@goodbyenjn/utils/json";
// Or use the safe-only entry point:
import { parse as safeParse, stringify as safeStringify } from "@goodbyenjn/utils/json/safe";

// Standard JSON parsing (returns value or nil)
const data = parse('{"a": 1}'); // Some({ a: 1 })
const invalid = parse("bad"); // None

// Safe JSON parsing (returns Result)
const result = safeParse('{"a": 1}');
if (result.isOk()) {
    console.log(result.unwrap().a);
}

// Safe stringify
const json = safeStringify({ a: 1 }); // Result<string, Error>

File System Operations

import {
    BaseVFile,
    exists as safeExists,
    mkdir as safeMkdir,
    readFile as safeReadFile,
    readFileByLine as safeReadFileByLine,
    readJson as safeReadJson,
    rm as safeRm,
    writeFile as safeWriteFile,
    writeJson as safeWriteJson,
} from "@goodbyenjn/utils/fs/safe";

// BaseVFile - Unified file handling
const vfile = new BaseVFile("example.json");

// Fluid path manipulation
vfile.filename("data").extname("ts");
console.log(vfile.basename()); // "data.ts"

// Cross-platform path handling
const relative = vfile.pathname.relative(); // "data.ts" (relative to cwd)
const absolute = vfile.pathname(); // "/full/path/to/data.ts"

// Built-in operations (available in extended VFile implementations)
// await vfile.read(); // Get content
// await vfile.write(); // Write content

const textResult = await safeReadFile("example.txt");
if (textResult.isOk()) {
    console.log("File content:", textResult.unwrap());
} else {
    console.error("Failed to read file:", textResult.unwrapErr().message);
}

// Read and parse JSON safely
const jsonResult = await safeReadJson("package.json");
if (jsonResult.isOk()) {
    const pkg = jsonResult.unwrap();
    console.log("Package name:", pkg.name);
}

// Write JSON file
const writeResult = await safeWriteJson("data.json", { users: [] }, 2);
if (writeResult.isErr()) {
    console.error("Write failed:", writeResult.unwrapErr());
}

// Check if file exists
const exists = await safeExists("path/to/file.txt");
if (exists) {
    console.log("File exists!");
}

// Create directories (recursive)
const mkResult = await safeMkdir("src/components/ui", { recursive: true });

// Delete files or directories
const rmResult = await safeRm("build", { recursive: true, force: true });

// Read file line by line
const lineResult = await safeReadFileByLine("large-file.log");
if (lineResult.isOk()) {
    for await (const line of lineResult.unwrap()) {
        console.log(line);
    }
}

Glob Patterns

import { glob, globSync, convertPathToPattern } from "@goodbyenjn/utils/glob";

// Async glob pattern matching
const files = await glob("src/**/*.{ts,tsx}", { cwd: "." });
console.log("Found files:", files);

// Synchronous version
const syncFiles = globSync("**/*.test.ts", { cwd: "tests" });

// Convert file path to glob pattern
const pattern = convertPathToPattern("/home/user/project");

Result Pattern - Functional Error Handling

import { Err, Ok, Result } from "@goodbyenjn/utils/result";

// Create results explicitly
const success = Ok(42);
const failure = Err("Something went wrong");

// Handle results with chainable methods
const doubled = success
    .map(value => value * 2) // Supports async: .map(async v => v * 2) returns Promise<Result>
    .mapErr(err => `Error: ${err}`)
    .unwrapOr(0); // 84

// Transform error type
const result: Result<string, Error> = Ok("value");
const transformed = result.mapErr(() => new Error("Custom error"));

// Convert throwing functions or promises to Result
async function fetchUser(id: string) {
    // Result.try catches thrown errors
    const user = await Result.try(() => JSON.parse(userJson));
    // Or handle promise rejections
    const user = await Result.try(fetch(`/api/users/${id}`));

    return user.map(u => u.name).mapErr(err => new Error(`Failed to parse user: ${err.message}`));
}

// Wrap a function to always return a Result
const safeParse = Result.wrap(JSON.parse, Error);
const data = safeParse('{"valid": true}'); // Result<any, Error>

// Combine multiple Results
const results = [Ok(1), Ok(2), Err("oops"), Ok(4)];
const combined = Result.all(results); // Err("oops")

// Generator-based "do" notation for flattening Results
const finalResult = Result.gen(function* () {
    const a = yield* Ok(10);
    const b = yield* Ok(20);
    return a + b;
}); // Ok(30)

// Supports async generators
const asyncFinal = await Result.gen(async function* () {
    const user = yield* await fetchUser("1");
    return user.name;
});

Type Utilities

import type {
    Nullable,
    Optional,
    YieldType,
    OmitByKey,
    SetNullable,
    TemplateFn,
    // New function types with `this` binding
    FnWithThis,
    AsyncFnWithThis,
    SyncFnWithThis,
    // Re-exported from type-fest
    SetOptional,
    SetRequired,
    PartialDeep,
    Simplify,
    LiteralUnion,
    PackageJson,
} from "@goodbyenjn/utils/types";

// ... (other types)

// Template string function type
const myTag: TemplateFn<string> = (strings, ...values) => {
    return strings[0] + values[0];
};

// Nullable type for values that can be null or undefined
type User = {
id: string;
name: string;
email: Nullable<string>; // string | null | undefined
};

// Optional type (undefined but not null)
type Profile = {
bio: Optional<string>; // string | undefined
};

// Extract yield type from generators
function\* numberGenerator() {
yield 1;
yield 2;
yield 3;
}

type NumberType = YieldType<typeof numberGenerator>; // number

// Omit properties by their value type
type Config = {
name: string;
debug: boolean;
verbose: boolean;
timeout: number;
};
type WithoutBooleans = OmitByKey<Config, boolean>; // { name: string; timeout: number }

// Set specific properties to nullable
type APIResponse = {
id: number;
name: string;
email: string;
};
type PartialResponse = SetNullable<APIResponse, "email" | "name">; // email and name become nullable

FP Utilities

The library provides 100+ functional utilities via @goodbyenjn/utils/fp, built on top of Remeda and Rotery:

import {
    // Custom type-checking helpers
    hasOwnProperty,
    isFunction,
    isPromiseLike,
    isOption, // check if a value is an Option
    isResult, // check if a value is a Result

    // Throttle / debounce (moved from main module)
    debounce,
    throttle,

    // Array operations (from Remeda)
    chunk,
    filter,
    find,
    flatMap,
    flatten,
    map,
    partition,
    reverse,
    take,
    drop,
    unique,

    // Object operations (from Remeda)
    pick,
    omit,
    merge,
    keys,
    values,
    entries,

    // Functional composition (from Remeda)
    pipe,
    compose,

    // Aggregations (from Remeda)
    groupBy,
    countBy,
    sumBy,

    // Async iteration (from Rotery, aliased with P suffix)
    filterP, // async filter
    mapP, // async map
    flatMapP, // async flatMap
    forEachP, // async forEach
    every, // sync every
    everyP, // async every
    some, // sync some
    someP, // async some
    toArray, // collect iterator to array
    toArrayP, // async collect
    flatten as flattenSync,
    flattenP, // async flatten
    reduceP, // async reduce
    concurrency, // limit concurrency
    buffer, // buffer items
} from "@goodbyenjn/utils/fp";

// Type-safe property checking
const obj = { name: "John", age: 30, active: true };
if (hasOwnProperty(obj, "name")) {
    console.log(obj.name); // TypeScript type narrowing
}

// Function type checking
const maybeCallback: unknown = (x: number) => x * 2;
if (isFunction(maybeCallback)) {
    maybeCallback(5);
}

// Promise detection
async function handleValue(value: any) {
    if (isPromiseLike(value)) {
        const result = await value;
        console.log("Async result:", result);
    }
}

// Functional data transformations
const users = [
    { id: 1, name: "Alice", role: "admin", active: true },
    { id: 2, name: "Bob", role: "user", active: false },
    { id: 3, name: "Charlie", role: "user", active: true },
];

// Chain operations with pipe
const adminNames = pipe(
    users,
    filter(u => u.role === "admin"),
    map(u => u.name),
); // ["Alice"]

// Group users by role
const byRole = groupBy(users, u => u.role);
// { admin: [...], user: [...] }

// Sum ages of active users
const totalAge = sumBy(
    filter(users, u => u.active),
    u => u.age ?? 0,
);

// Chunk array into groups
const chunked = chunk(users, 2);
// [[user1, user2], [user3]]

// Async iteration with Rotery helpers
const results = await pipe(
    [1, 2, 3],
    toIterator,
    mapP(async n => fetchUser(n)),
    filterP(async u => u.active),
    toArrayP,
);

Requirements

  • Node.js: >= 20.0.0
  • TypeScript: >= 6.0 (for development/type checking)

Modern browsers are supported through ES module imports.

Versioning

Note: This project does not follow Semantic Versioning (semver). Instead, it uses a calendar-based versioning scheme:

Version Format: v<YY>.<M>.<PATCH>

  • <YY> - Release year (e.g., 26 for 2026)
  • <M> - Release month (1-12)
  • <PATCH> - Patch/revision number within the same month (starting from 0)

Example versions:

  • v26.1.0 - First release in January 2026
  • v26.1.1 - Second release in January 2026
  • v26.2.0 - First release in February 2026

This scheme provides clarity on when features were released while allowing multiple updates within the same month.

Development

# Install dependencies
pnpm install

# Development mode with watch
pnpm run dev

# Build the library
pnpm run build

# Clean build artifacts
pnpm run clean

# Run tests (if configured)
pnpm run test

Performance Considerations

  • Tree-shaking: All modules are properly configured for tree-shaking. Import only what you need.
  • Result Pattern: The Result type has minimal overhead compared to exceptions and enables better error handling.
  • Functional Composition: Use Remeda utilities with pipe for efficient data transformations.
  • Shell Execution: The $ function safely escapes arguments and is suitable for production use.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request at GitHub Repository.

License

MIT © GoodbyeNJN

Links


Maintained with ❤️ by GoodbyeNJN