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

adaptive-extender

v0.9.11

Published

Adaptive library for JS/TS development environments

Readme

Adaptive Extender

NPM Version License Bundle Size TypeScript

Extends native prototypes and provides missing standard utilities — with a focus on strict typing, safety, and code readability.

Change log


Installation

npm install adaptive-extender

Packages

| Package | Environment | | :----------------------- | :---------------------- | | adaptive-extender/core | Browser, Node.js, Deno | | adaptive-extender/node | Node.js (includes core) | | adaptive-extender/web | Browser (includes core) |

// Import once — extensions are active everywhere
import "adaptive-extender/core";
import "adaptive-extender/web"; // Browser only

Native Extensions

Number

volume = volume.clamp(0, 1);

// Normalize into [0, 1] or remap between ranges
const normalized = currentValue.lerp(sourceMin, sourceMax);
const pixelX     = angle.lerp(0, 360, 0, canvasWidth);

// True mathematical modulo — works correctly with negative numbers
const wrappedIndex = (-1).mod(items.length); // → items.length - 1

// Chainable fallback for invalid values
const ratio = (numerator / denominator).insteadNaN(0).insteadInfinity(1);

String

if (String.isWhitespace(userInput)) return;

const displayName = username.insteadEmpty("Anonymous");

"hello world".toTitleCase();            // → "Hello World"
"île-de-france".toLocalTitleCase("fr"); // → "Île-De-France"

Array

for (const index of Array.range(0, 10)) { ... }

for (const [user, role] of Array.zip(users, roles)) {
    console.log(`${user.name}: ${role}`);
}

// Collect an async iterable into an array
const packets = await Array.fromAsync(readableStream);

items.swap(0, items.length - 1);
items.resize(10, null);
const wasRemoved = items.remove(targetItem);

Math

const [integer, fractional] = Math.split(3.75); // → [3, 0.75]
Math.sqpw(sideLength);                           // sideLength²
Math.toRadians(90);                              // → Math.PI / 2
Math.toDegrees(Math.PI);                         // → 180

Math.meanArithmetic(1, 2, 3, 4); // → 2.5
Math.meanGeometric(4, 16);
Math.meanHarmonic(1, 2, 4);

Set / Map / Date

// Like classList.toggle
selected.toggle(item);        // add if absent, remove if present
selected.toggle(item, true);  // force add
selected.toggle(item, false); // force remove

// Add only if key is not already present
cache.add(requestKey, response); // → false if key exists

// Detect and replace invalid dates
const safeDate = new Date("invalid").insteadInvalid(new Date());

Promise

const request = fetch("/api/data");

if (await request.isSettled)  { ... }
if (await request.isResolved) { ... }
if (await request.isRejected) { ... }

const responseData = await request.value;  // throws if rejected
const rejectReason = await request.reason; // throws if resolved

Error / Global

// Wrap anything into an Error (string, undefined, object)
const error = Error.from(thrown);

// Assert non-null — throws ReferenceError with a message
const appRoot = ReferenceError.suppress(document.getElementById("app"));

throw new ImplementationError(); // sealed, cannot be subclassed

// Get type name without typeof / manual checks
typename(value); // → "String", "Null", "User", ...

Classes

Timespan

Value object for time intervals. Eliminates raw millisecond arithmetic.

import { Timespan } from "adaptive-extender/core";

const sessionDuration = Timespan.fromComponents(0, 30, 0); // 30 minutes
const uptime          = Timespan.fromValue(process.uptime() * 1000);
const parsed          = Timespan.parse("1.02:30:00.500");  // 1d 2h 30m 0.5s

console.log(uptime.toString()); // "0.00:12:45.123"

uptime.minutes = 0; // other components recalculate automatically

if (uptime.valueOf() > sessionDuration.valueOf()) {
    console.log("Session expired");
}

Color

Full color object with RGB, HSL, HEX support and alpha channel. Changing a component in one color space automatically recalculates the other.

import { Color, ColorFormats } from "adaptive-extender/core";

const orange = Color.fromRGB(255, 128, 0);
const parsed = Color.parse("#ff8800");

orange.hue   = 200; // RGB recalculates automatically
orange.alpha = 0.8;

console.log(orange.toString({ format: ColorFormats.hsl, deep: true }));
// → "hsla(200, 100%, 50%, 0.8)"

Random

import { Random } from "adaptive-extender/core";

const rng = Random.global;

rng.boolean(0.9);        // true with 90% probability
rng.number(0, 1);        // float [0, 1)
rng.integer(1, 6);       // int [1, 6]
rng.item(options);       // random element from array
rng.subarray(items, 3);  // 3 random elements
rng.shuffle(array);      // shuffle in place

// Weighted selection
const loot = rng.case(new Map([
    ["common",    70],
    ["rare",      25],
    ["legendary",  5],
]));

Vector

Mathematical vectors with a LINQ-style iteration API.

import { Vector, Vector2D, Vector3D } from "adaptive-extender/core";

const position  = new Vector2D(3, 4);
const direction = Vector3D.fromScalar(1); // (1, 1, 1)

// LINQ-like methods over components
const normalized = Vector3D.fromVector(
    direction.map(component => component / Math.sqrt(3))
);

Vector.isFinite(position);
const safePosition = position.insteadNaN(Vector2D.newZero);
const point        = Vector2D.parse("(10, 20)"); // → Vector2D { x: 10, y: 20 }

Controller

Abstract base class for async tasks with centralized error handling.

import { Controller } from "adaptive-extender/core";

class InitTask extends Controller {
    async run(): Promise<void> {
        const response = await fetch("/api/init");
        // ...
    }

    async catch(error: Error): Promise<void> {
        console.error("Init failed:", error.message);
    }
}

// Creates an instance and runs it — errors are routed to catch()
await InitTask.launch();

EnvironmentProvider

Deserializes environment variables into a strictly typed model. JSON values are parsed automatically.

import { EnvironmentProvider, Model, Field, Optional } from "adaptive-extender/core";

class AppConfig extends Model {
    @Field(String)           DB_HOST: string  = "localhost";
    @Field(Number)           DB_PORT: number  = 5432;
    @Field(Optional(String)) LOG_LEVEL?: string;
}

const config = EnvironmentProvider.resolve(process.env, AppConfig);
// config.DB_PORT is a number, not a string
// Throws TypeError if a required variable is missing or has the wrong type

Portable Data System

Binds classes to their schema via decorators. Importing from JSON validates types automatically and returns a real class instance.

Basic Usage

import { Model, Field, ArrayOf, Optional, Nullable } from "adaptive-extender/core";

class Tag extends Model {
    @Field(String) name: string = "";
}

class Article extends Model {
    @Field(String)           title: string      = "";
    @Field(Number)           views: number      = 0;
    @Field(ArrayOf(Tag))     tags: Tag[]        = [];
    @Field(Optional(String)) subtitle?: string;
    @Field(Nullable(String)) draft: string | null = null;
}

const article = Article.import(json, "api.article");
// article instanceof Article → true
// TypeError with exact path on failure: "api.article.tags[2].name"

const raw = Article.export(article); // → plain object, ready for JSON.stringify

Polymorphism

import { Model, Field, Descendant, DiscriminatorKey } from "adaptive-extender/core";

class Dog extends Model { @Field(String)  breed: string   = ""; }
class Cat extends Model { @Field(Boolean) indoor: boolean = true; }

@DiscriminatorKey("kind")
@Descendant(Dog, "dog")
@Descendant(Cat, "cat")
abstract class Animal extends Model {}

// { kind: "dog", breed: "Husky" } → Dog instance
// { kind: "cat", indoor: true }   → Cat instance
const animal = Animal.import(json, "api.animal");

Adapters

| Adapter | Type | | :------------- | :------------------------------------- | | ArrayOf(T) | T[] | | SetOf(T) | Set<T>T[] | | RecordOf(T) | Map<string, T>Record<string, T> | | MapOf(K, V) | Map<K, V>[K, V][] | | Optional(T) | T \| undefined | | Nullable(T) | T \| null | | Deferred(fn) | Circular references |


Web

DOM Queries

Type-safe wrappers over querySelector — throw instead of returning null.

import "adaptive-extender/web";

const loginForm      = document.getElement(HTMLFormElement, "#login-form");
const requiredInputs = loginForm.getElements(HTMLInputElement, "input[required]");
const container      = loginForm.getClosest(HTMLDivElement, ".wrapper");

// Async variants — useful with Shadow DOM
const canvas = await document.getElementAsync(HTMLCanvasElement, "#scene");

Game Engines

Three update-loop engines for UI animation or game logic:

import { FastEngine, StaticEngine } from "adaptive-extender/web";

// Fast — requestAnimationFrame based
const engine = new FastEngine({ launch: true });
engine.limit = 60;

engine.addEventListener("trigger", () => {
    const deltaTime = engine.delta; // seconds since last frame
    // update scene...
});

// Static — fixed fps via setTimeout
const fixedEngine = new StaticEngine({ launch: true });
fixedEngine.limit = 30;

Archive (localStorage)

Type-safe localStorage with buffered access and auto-save.

import { ArchiveRepository, Model, Field } from "adaptive-extender/web";

class Settings extends Model {
    @Field(Boolean) darkMode: boolean = false;
    @Field(Number)  volume: number    = 1;
}

const repository = new ArchiveRepository("settings", Settings, new Settings());

const settings = repository.content;
settings.volume = 0.5;

repository.save();       // persist immediately
repository.save(3000);   // debounced — persist after 3 seconds
repository.reset();      // revert to initial state

Promise Utilities

await Promise.asTimeout(1000);

// AbortController is created and aborted automatically on completion
const result = await Promise.withSignal((signal, resolve, reject) => {
    fetch("/api/data", { signal }).then(resolve).catch(reject);
});

Reflect Utilities

// Apply a function only if the value is not null / undefined
const upper   = Reflect.mapNull(maybeNull, text => text.toUpperCase());
const trimmed = Reflect.mapUndefined(maybeUndefined, text => text.trim());
const parsed  = Reflect.mapNullable(maybeNullable, text => parseInt(text));

License

Apache-2.0