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

@hidayetcanozcan/custom-fetch

v1.0.0

Published

Production-ready, 100% type-safe HTTP client for Node.js and Bun server-side environments. Zero dependencies, discriminated union responses, automatic retries, interceptors, caching, runtime validation, and more.

Downloads

132

Readme

🚀 CustomFetch

Production-Ready, 100% Type-Safe HTTP Client

npm version TypeScript Zero Dependencies License: MIT Node.js Bun

A lightweight, zero-dependency HTTP client built for server-side TypeScript applications.

Enterprise-grade features • Discriminated union responses • Never throws • Full type inference

InstallationQuick StartAPI ReferenceExamples


📋 Table of Contents


🤔 Why CustomFetch?

Modern applications require robust HTTP clients that go beyond simple fetch calls. CustomFetch addresses common pain points:

| Problem | CustomFetch Solution | |---------|---------------------| | Inconsistent error handling | Discriminated union responses (isSuccess: true/false) - never throws, always returns structured data | | No built-in retries | Automatic retries with exponential backoff for 5xx errors and rate limits (429) | | No request timeouts | Built-in timeout support with AbortController | | Repetitive boilerplate | Client factory with shared configuration (baseUrl, headers, interceptors) | | Runtime type safety | Optional type guards for validating response shapes at runtime | | No caching | In-memory response caching with configurable TTL | | Hard to debug | Request IDs, duration tracking, and configurable debug logging | | Complex interceptor patterns | Simple onRequest and onResponse hooks | | No type assertions needed | 100% type-safe with zero any, unknown, or as casts | | Inconsistent binary handling | Automatic detection and base64 encoding of binary responses |

The Problem with Traditional Fetch

// ❌ Traditional fetch - error prone, verbose, no type safety
try {
  const response = await fetch("https://api.example.com/users/1");
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }
  const user = await response.json(); // any type!
  // No idea if user actually has the shape you expect
} catch (error) {
  // Could be network error, JSON parse error, or HTTP error
  // Good luck distinguishing them!
}

// ✅ CustomFetch - clean, type-safe, predictable
const result = await api.get<User>("/users/1");
if (result.isSuccess) {
  console.log(result.response.name); // Fully typed!
} else {
  console.log(result.errors.message); // Structured error
  console.log(result.code); // HTTP status or null for network errors
}

✨ Features

Core Features

| Feature | Description | |---------|-------------| | 🎯 Zero Dependencies | Built entirely on native fetch API - no bloat | | 📦 Tiny Bundle | < 5KB minified + gzipped | | 🔒 100% Type-Safe | No any, no unknown, no type assertions | | 🏷️ Discriminated Unions | isSuccess flag enables perfect type narrowing | | 🚫 Never Throws | All errors returned in structured ErrorResponse | | 🔄 Automatic Retries | Exponential backoff for 5xx and 429 errors | | ⏱️ Request Timeouts | Built-in AbortController-based timeouts | | 💾 Response Caching | In-memory TTL-based caching | | 🪝 Interceptors | onRequest and onResponse hooks | | 🔍 Runtime Validation | Type guards for response shape validation | | 📊 Request Metrics | Unique request IDs and duration tracking | | 🔧 Client Factory | Pre-configured reusable instances |

HTTP Features

| Feature | Description | |---------|-------------| | 📤 All HTTP Methods | GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS | | 🔗 Query Parameters | Automatic serialization of objects, arrays, primitives | | 📎 Custom Headers | Static headers or async header factories | | 🍪 Credentials | Optional cookie inclusion | | 🔐 Bearer Auth | Built-in customToken option | | 📁 Binary Support | Automatic base64 encoding for binary responses |

Developer Experience

| Feature | Description | |---------|-------------| | 🐛 Debug Mode | Environment-aware logging | | 🔄 Transformers | Response and error transformation hooks | | ⚡ Async Headers | Dynamic header resolution (e.g., refresh tokens) | | 🆔 Request IDs | Unique identifiers for tracing | | ⏰ Duration Metrics | Built-in request timing |


📦 Installation

# npm
npm install @hco/custom-fetch

# yarn
yarn add @hco/custom-fetch

# pnpm
pnpm add @hco/custom-fetch

# bun
bun add @hco/custom-fetch

Requirements

  • Node.js >= 18.0.0 (native fetch support)
  • Bun >= 1.0.0

Note: This package is designed for server-side usage only. It uses Node.js/Bun globals like process.env and Buffer.


🚀 Quick Start

Basic Usage

import { CustomFetch } from "@hco/custom-fetch";

interface User {
  id: string;
  name: string;
  email: string;
}

const result = await CustomFetch<User>({
  options: {
    url: "https://api.example.com/users/1",
  },
});

if (result.isSuccess) {
  console.log(result.response); // User object
  console.log(result.code);     // 200
  console.log(result.requestId); // "m5x2k-a3b4c5"
  console.log(result.durationMs); // 142
} else {
  console.error(result.errors.message);
  console.error(result.code); // null or HTTP status code
}

Using the Client Factory

import { createClient } from "@hco/custom-fetch";

const api = createClient({
  baseUrl: "https://api.example.com",
  defaultHeaders: { "Authorization": "Bearer token123" },
  defaultTimeout: 10000,
  defaultRetries: 3,
});

// GET request
const users = await api.get<User[]>("/users");

// POST request
const newUser = await api.post<User>("/users", {
  name: "John Doe",
  email: "[email protected]",
});

// PUT request
const updated = await api.put<User>("/users/1", { name: "Jane Doe" });

// PATCH request
const patched = await api.patch<User>("/users/1", { email: "[email protected]" });

// DELETE request
const deleted = await api.delete<void>("/users/1");

📖 API Reference

CustomFetch Function

The core function for making HTTP requests.

async function CustomFetch<T, E extends BaseError = BaseError>(
  props: CustomFetchProps<T, E>
): Promise<CustomFetchResult<T, E>>

Type Parameters:

  • T - Expected response data type
  • E - Error type (must extend BaseError, defaults to BaseError)

Returns: Promise<CustomFetchResult<T, E>> - A discriminated union of success or error response


createClient Factory

Creates a configured HTTP client instance with shared defaults.

function createClient(config?: ClientConfig): {
  get<T, E>(path: string, options?): Promise<CustomFetchResult<T, E>>;
  post<T, E>(path: string, body?, options?): Promise<CustomFetchResult<T, E>>;
  put<T, E>(path: string, body?, options?): Promise<CustomFetchResult<T, E>>;
  patch<T, E>(path: string, body?, options?): Promise<CustomFetchResult<T, E>>;
  delete<T, E>(path: string, options?): Promise<CustomFetchResult<T, E>>;
  request<T, E>(method: HttpMethod, path: string, options?): Promise<CustomFetchResult<T, E>>;
}

ClientConfig Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | baseUrl | string | "" | Base URL prepended to all requests | | defaultHeaders | HeadersInit \| () => HeadersInit \| Promise<HeadersInit> | {} | Default headers (static or async function) | | defaultTimeout | number | 30000 | Default request timeout in milliseconds | | defaultRetries | number | 0 | Default retry count for failed requests | | defaultRetryDelay | number | 1000 | Default base delay between retries | | debug | boolean | process.env.NODE_ENV === "development" | Enable debug logging | | onRequest | RequestInterceptor | - | Global request interceptor | | onResponse | ResponseInterceptor | - | Global response interceptor |


Cache Utilities

// Clear all cached responses
function clearFetchCache(): void;

// Clear a specific cache entry
function clearSpecificCache(
  url: string,
  method?: HttpMethod,  // default: "GET"
  body?: string | null  // default: null
): void;

// Get cache statistics
function getCacheStats(): { size: number; keys: string[] };

Type Guard Helpers

// Create a type guard that checks for required fields
function createObjectGuard<T extends Record<string, unknown>>(
  requiredFields: (keyof T)[]
): (data: unknown) => data is T;

// Create a type guard with custom validation logic
function createCustomGuard<T>(
  validator: (data: unknown) => boolean
): (data: unknown) => data is T;

⚙️ Configuration Options

Full list of options available in FetchOptions<T, E>:

Request Configuration

| Option | Type | Default | Description | |--------|------|---------|-------------| | url | string | required | Request URL (absolute or relative to baseUrl) | | method | HttpMethod | "GET" | HTTP method: "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS" | | headers | HeadersInit | {} | Request headers | | body | BodyInit \| null | null | Request body | | params | QueryParams | - | URL query parameters (automatically serialized) |

Authentication

| Option | Type | Default | Description | |--------|------|---------|-------------| | customToken | string | - | Bearer token (automatically adds Authorization: Bearer <token> header) |

Timing & Retry

| Option | Type | Default | Description | |--------|------|---------|-------------| | timeout | number | 30000 | Request timeout in milliseconds | | retries | number | 0 | Number of retry attempts for failed requests | | retryDelay | number | 1000 | Base delay between retries (uses exponential backoff) |

Status Handling

| Option | Type | Default | Description | |--------|------|---------|-------------| | validateStatus | (status: number) => boolean | (status) => status >= 200 && status < 300 | Custom status validation function |

Runtime Validation

| Option | Type | Default | Description | |--------|------|---------|-------------| | responseGuard | ResponseGuard<T> | - | Type guard to validate successful response shape | | errorGuard | ErrorGuard<E> | - | Type guard to validate error response shape |

Transformers

| Option | Type | Default | Description | |--------|------|---------|-------------| | responseTransformer | ResponseTransformer<T> | - | Transform raw response data to type T | | errorTransformer | ErrorTransformer<E> | - | Transform raw error data to type E |

Caching

| Option | Type | Default | Description | |--------|------|---------|-------------| | cacheResponse | boolean | false | Enable response caching | | cacheTime | number | 300000 (5 min) | Cache TTL in milliseconds |

Other Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | includeCookies | boolean | false | Include cookies in request (credentials: "include") | | debug | boolean | process.env.NODE_ENV === "development" | Enable debug console logging | | abortController | AbortController | - | External abort controller for request cancellation |

Interceptors

| Option | Type | Default | Description | |--------|------|---------|-------------| | onRequest | RequestInterceptor | - | Modify request before sending | | onResponse | ResponseInterceptor<T, E> | - | Process/modify response after receiving |


📋 Response Types

SuccessResponse

Returned when isSuccess: true:

interface SuccessResponse<T> {
  isSuccess: true;
  response: T;           // Parsed response data
  errors: undefined;
  code: number;          // HTTP status code (e.g., 200)
  createdAt: Date;       // Response timestamp
  headers: Record<string, string | string[]>;
  requestId: string;     // Unique request identifier
  durationMs: number;    // Request duration in milliseconds
}

ErrorResponse

Returned when isSuccess: false:

interface ErrorResponse<E> {
  isSuccess: false;
  response: undefined;
  errors: E;             // Error data (extends BaseError)
  code: number | null;   // HTTP status code or null for network errors
  createdAt: Date;       // Response timestamp
  requestId: string;     // Unique request identifier
  durationMs: number;    // Request duration in milliseconds
  stack?: string;        // Error stack trace (only in development)
}

BaseError

Minimum error shape:

interface BaseError {
  message: string;
  [key: string]: string | number | boolean | null | undefined;
}

🔧 Advanced Usage

Runtime Validation

Validate response shapes at runtime using type guards:

import { CustomFetch, createObjectGuard, createCustomGuard } from "@hco/custom-fetch";

interface User {
  id: string;
  name: string;
  email: string;
  age: number;
}

// Simple field presence check
const isUser = createObjectGuard<User>(["id", "name", "email", "age"]);

// Custom validation with type checking
const isValidUser = createCustomGuard<User>((data) => {
  const d = data as Partial<User>;
  return (
    typeof d.id === "string" &&
    typeof d.name === "string" &&
    typeof d.email === "string" &&
    d.email.includes("@") &&
    typeof d.age === "number" &&
    d.age > 0
  );
});

const result = await CustomFetch<User>({
  options: {
    url: "https://api.example.com/users/1",
    responseGuard: isValidUser,
  },
});

if (result.isSuccess) {
  // TypeScript knows result.response is User
  console.log(result.response.email);
} else {
  // Could be "Response validation failed" if guard returned false
  console.error(result.errors.message);
}

Request/Response Interceptors

Modify requests before sending and process responses after receiving:

import { createClient, type RequestContext, type ResponseContext } from "@hco/custom-fetch";

const api = createClient({
  baseUrl: "https://api.example.com",
  
  // Request interceptor - runs before every request
  onRequest: (context: RequestContext) => {
    // Add custom headers
    context.headers.set("X-Request-ID", context.requestId);
    context.headers.set("X-Timestamp", Date.now().toString());
    
    // Log outgoing requests
    console.log(`[${context.requestId}] ${context.method} ${context.url}`);
    
    return context;
  },
  
  // Response interceptor - runs after every response
  onResponse: (context) => {
    const { request, result, durationMs } = context;
    
    // Log response metrics
    console.log(
      `[${request.requestId}] ${request.method} ${request.url} -> ${result.code} (${durationMs}ms)`
    );
    
    // You can transform the result here
    return result;
  },
});

// Per-request interceptors (override global)
const result = await api.get<User>("/users/1", {
  onRequest: (ctx) => {
    ctx.headers.set("X-Custom-Header", "value");
    return ctx;
  },
});

Async Interceptors:

const api = createClient({
  baseUrl: "https://api.example.com",
  
  // Async header resolution (e.g., refresh token)
  defaultHeaders: async () => {
    const token = await getAccessToken(); // Your async token logic
    return {
      "Authorization": `Bearer ${token}`,
      "Content-Type": "application/json",
    };
  },
  
  onRequest: async (context) => {
    // Async operations in interceptor
    await logToAnalytics(context);
    return context;
  },
});

Retry with Exponential Backoff

Automatic retries for transient failures (5xx errors, 429 rate limits, network errors):

const result = await CustomFetch<User>({
  options: {
    url: "https://api.example.com/users/1",
    retries: 3,          // Retry up to 3 times
    retryDelay: 1000,    // Base delay: 1 second
    debug: true,         // See retry logs
  },
});

// Retry behavior:
// - Attempt 1: immediate
// - Attempt 2: after 1000ms (1s)
// - Attempt 3: after 2000ms (2s) 
// - Attempt 4: after 4000ms (4s)
// Total max wait: 7 seconds

// Retries happen for:
// - HTTP 500-599 (server errors)
// - HTTP 429 (rate limited)
// - Network errors (TypeError with "NetworkError")

Response Caching

Cache successful responses in memory:

const api = createClient({
  baseUrl: "https://api.example.com",
});

// Enable caching for this request
const result = await api.get<User[]>("/users", {
  cacheResponse: true,
  cacheTime: 60000, // Cache for 1 minute
});

// Second call returns cached response instantly
const cachedResult = await api.get<User[]>("/users", {
  cacheResponse: true,
  cacheTime: 60000,
});

// Cache management
import { clearFetchCache, clearSpecificCache, getCacheStats } from "@hco/custom-fetch";

// Clear specific entry
clearSpecificCache("https://api.example.com/users", "GET");

// Clear all cache
clearFetchCache();

// Check cache status
const stats = getCacheStats();
console.log(`Cache size: ${stats.size}`);
console.log(`Cached keys:`, stats.keys);

Cache Key Format: {METHOD}:{URL}:{BODY}


Timeout & Abort Controller

Request timeouts and manual cancellation:

// Automatic timeout
const result = await CustomFetch<User>({
  options: {
    url: "https://api.example.com/slow-endpoint",
    timeout: 5000, // 5 second timeout
  },
});

if (!result.isSuccess && result.code === 408) {
  console.log("Request timed out");
}

// Manual cancellation with external AbortController
const controller = new AbortController();

// Cancel after 3 seconds
setTimeout(() => controller.abort(), 3000);

const result = await CustomFetch<User>({
  options: {
    url: "https://api.example.com/users/1",
    abortController: controller,
  },
});

// Or cancel on user action
document.getElementById("cancel-btn")?.addEventListener("click", () => {
  controller.abort();
});

Binary Response Handling

Binary responses (images, PDFs, etc.) are automatically encoded as base64:

// Fetch an image
const result = await CustomFetch<string>({
  options: {
    url: "https://api.example.com/avatar.png",
  },
});

if (result.isSuccess) {
  // result.response is base64-encoded string
  const base64Image = result.response;
  const imgSrc = `data:image/png;base64,${base64Image}`;
}

// Detected binary content types:
// - image/*
// - audio/*
// - video/*
// - font/*
// - application/octet-stream
// - application/pdf
// - application/zip, x-zip, x-rar, x-tar, x-bzip, x-gzip
// - application/java-archive
// - application/vnd.ms-*
// - application/vnd.openxmlformats-*

Error Transformation

Transform API error responses to a consistent format:

// API returns: { error: "Not found", errorCode: 404 }
// You want:    { message: "Not found", code: 404 }

interface ApiError extends BaseError {
  message: string;
  code: number;
}

const result = await CustomFetch<User, ApiError>({
  options: {
    url: "https://api.example.com/users/999",
    errorTransformer: (rawError, statusCode) => ({
      message: (rawError as any).error || rawError.message || "Unknown error",
      code: (rawError as any).errorCode || statusCode,
    }),
  },
});

if (!result.isSuccess) {
  console.log(result.errors.message); // "Not found"
  console.log(result.errors.code);    // 404
}

Query Parameters

Automatic serialization of query parameters:

const result = await api.get<User[]>("/users", {
  params: {
    page: 1,
    limit: 10,
    status: "active",
    tags: ["admin", "verified"],  // Arrays supported
    includeDeleted: false,
    search: null,                  // null/undefined are skipped
  },
});

// Request URL: /users?page=1&limit=10&status=active&tags=admin&tags=verified&includeDeleted=false

📝 TypeScript Types

All types are exported for your convenience:

import type {
  // Response types
  SuccessResponse,
  ErrorResponse,
  CustomFetchResult,
  
  // Base types
  BaseError,
  HttpMethod,
  QueryParams,
  
  // Validation
  ResponseGuard,
  ErrorGuard,
  
  // Transformers
  ErrorTransformer,
  ResponseTransformer,
  
  // Interceptors
  RequestContext,
  ResponseContext,
  RequestInterceptor,
  ResponseInterceptor,
  
  // Configuration
  FetchOptions,
  CustomFetchProps,
  ClientConfig,
  CacheItem,
  
  // Legacy aliases
  CustomFetchReturnType,
  OptionsType,
} from "@hco/custom-fetch";

✅ Best Practices

1. Always Use Discriminated Unions

// ✅ Good - Use isSuccess for type narrowing
const result = await api.get<User>("/users/1");
if (result.isSuccess) {
  // TypeScript knows: result.response is User
  console.log(result.response.name);
} else {
  // TypeScript knows: result.errors is BaseError
  console.error(result.errors.message);
}

// ❌ Bad - Don't assume success
const result = await api.get<User>("/users/1");
console.log(result.response.name); // Error: response could be undefined

2. Create Configured Client Instances

// ✅ Good - Reusable configured client
const api = createClient({
  baseUrl: process.env.API_URL,
  defaultHeaders: { "X-API-Key": process.env.API_KEY },
  defaultTimeout: 10000,
  defaultRetries: 2,
});

// ❌ Bad - Repeating configuration
await CustomFetch({ options: { url: "https://api.example.com/users", timeout: 10000 }});
await CustomFetch({ options: { url: "https://api.example.com/posts", timeout: 10000 }});

3. Use Runtime Validation for External APIs

// ✅ Good - Validate untrusted responses
const isUser = createObjectGuard<User>(["id", "name", "email"]);
const result = await api.get<User>("/users/1", { responseGuard: isUser });

// ❌ Bad - Trust external API blindly
const result = await api.get<User>("/users/1");
// API could return anything!

4. Handle Errors Gracefully

// ✅ Good - Comprehensive error handling
const result = await api.get<User>("/users/1");

if (!result.isSuccess) {
  if (result.code === 404) {
    return notFound();
  }
  if (result.code === 401) {
    return redirect("/login");
  }
  if (result.code === null) {
    // Network error
    return showNetworkError();
  }
  // Generic error
  return showError(result.errors.message);
}

return result.response;

5. Use Debug Mode During Development

// Automatically enabled when NODE_ENV === "development"
// Or enable explicitly:
const result = await api.get<User>("/users/1", { debug: true });

// Console output:
// [CustomFetch] GET https://api.example.com/users/1 -> 200 (142ms)
// [CustomFetch] Retry 1/3 after 1000ms
// [CustomFetch] Cache hit: https://api.example.com/users/1

🔄 Comparison with Alternatives

| Feature | CustomFetch | axios | ky | got | |---------|-------------|-------|-----|-----| | Bundle Size | ~5KB | ~13KB | ~8KB | ~48KB | | Zero Dependencies | ✅ | ❌ | ❌ | ❌ | | TypeScript First | ✅ | Partial | Partial | Partial | | Discriminated Unions | ✅ | ❌ | ❌ | ❌ | | Never Throws | ✅ | ❌ | ❌ | ❌ | | Runtime Validation | ✅ Built-in | ❌ | ❌ | ❌ | | Type Guards | ✅ Built-in | ❌ | ❌ | ❌ | | Request ID Tracking | ✅ Built-in | ❌ | ❌ | ❌ | | Duration Metrics | ✅ Built-in | ❌ | ❌ | ❌ | | Server-side Optimized | ✅ | ❌ | ✅ | ✅ | | Automatic Retries | ✅ | Via plugin | ✅ | ✅ | | Request/Response Interceptors | ✅ | ✅ | ✅ | ✅ | | Response Caching | ✅ Built-in | ❌ | ❌ | ✅ |

Why Not axios/ky/got?

  • axios: Large bundle, throws on HTTP errors, requires interceptors for structured error handling
  • ky: Throws on HTTP errors, limited TypeScript inference, no built-in validation
  • got: Node.js only, large bundle, complex API, throws on HTTP errors

CustomFetch is purpose-built for server-side TypeScript applications where type safety and predictable error handling are paramount.


👤 Author

Hidayet Can Özcan

GitHub Email

Contact


⚠️ Contributing

This project is not accepting external contributions.

CustomFetch is a personal project maintained solely by the author. Bug reports and feature requests can be submitted via GitHub Issues, but pull requests will not be reviewed or merged.


📄 License

MIT License © 2024 Hidayet Can Özcan

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Made with ❤️ for the TypeScript community

If you find this package useful, consider giving it a ⭐ on GitHub!