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

@alex6357/rustyjs

v1.0.0

Published

Introduce Rust traits to JavaScript

Readme

RustyJS

Write JavaScript with Rust's mental model. No prototype pollution, no gc overhead, no hidden classes.

Usage

Setup

import { initRustyJs } from "@alex6357/rustyjs";
// By default, rustyjs uses a symbol to store state.
// This will assign a property to the class.
// The symbol cannot be changed or enumerated, but can be get by `Object.getOwnPropertySymbols`.
// If you want to make internal state completely private, initialize with `initRustyJs("weakmap")`.
// This will setup a WeakMap to store state. It's less efficient, but more secure.
const { STATE, ImplFor } = initRustyJs();

Normal usage

import { initRustyJs } from "@alex6357/rustyjs";
const { STATE, ImplFor } = initRustyJs();

// Traits
interface TraitCanBark {
  bark(): void;
}
interface TraitCanEat {
  eat(): void;
}
interface TraitCanCharge {
  charge(): void;
}
interface TraitCanBarkAfterEat {
  eatAndBark(): void;
}
interface TraitCanSpeakName {
  speakName(): void;
}
interface TraitHasName {
  // Can also define properties.
  readonly name: string;
}

// Structs, or rather, State
interface StateDog {
  name: string;
}
interface StateRobotDog {
  name: string;
  battery: number;
}

// Class is still entry point.
// Interface is used to let language server know what methods are available.
interface Dog extends TraitCanBark, TraitCanEat, TraitCanBarkAfterEat, TraitHasName {}
class Dog {
  constructor(name: string) {
    const state = STATE.init(this, { name } as StateDog);
  }
}
interface RobotDog extends TraitCanBark, TraitCanCharge, TraitCanSpeakName, TraitHasName {}
class RobotDog {
  constructor(name: string, battery: number) {
    const state = STATE.init(this, { name, battery } as StateRobotDog);
  }
}

// Implementations
// We use decorator to make it clearer.
// The name of the classes are not important. But they must be unique in the scope.
// You can also manually call the decorator function, for example:
// let implForDogAndRobotDog = ImplFor(Dog, RobotDog);
// implForDogAndRobotDog(ImplCanBarkForDogAndRobotDog);
// Or simply ImplFor(Dog, RobotDog)(ImplCanBarkForDogAndRobotDog);
// The `implForDogAndRobotDog` function can be reused.
@ImplFor(Dog, RobotDog)
class ImplCanBarkForDogAndRobotDog implements TraitCanBark {
  bark() {
    console.log("Woof!");
  }
}
@ImplFor(Dog)
class ImplCanEatForDog implements TraitCanEat {
  eat() {
    console.log("Nom nom nom");
  }
}
@ImplFor(RobotDog)
class ImplCanChargeForRobotDog implements TraitCanCharge {
  charge(): void {
    console.log("Charging...");
  }
}

// Blanket implementations
// Similar to Rust's `impl<T: CanEat + CanBark> CanBarkAfterEat for T`.
// You still need to specify all target classes in @ImplFor.
@ImplFor(Dog)
class ImplCanBarkAfterEat implements TraitCanBarkAfterEat {
  eatAndBark(this: TraitCanEat & TraitCanBark): void {
    this.eat();
    this.bark();
  }
}

// When writing a blanket implementation that relies on data (like `TraitCanSpeakName`), you have two architectural choices:

// Approach 1: Using State Slots (data-driven & faster)
// Fetch the internal state directly inside the implementation function itself. Similar to Ruby's mixins requiring target members.
// Best for scenarios where you need to access a large amount of diverse internal data.
// It is highly recommended to keep your state data structure flat for memory access speed, unless a fixed nested structure provides specific semantic value.
// Note: If you worry about property naming conflicts across mixins, use a unique symbol:
// const kName: unique symbol = Symbol("TraitSlotCanSpeakName.name");
interface TraitSlotCanSpeakName {
  name: string; // Requires the target's internal STATE to have a `name` member variable
}

@ImplFor(RobotDog) // You still need to specify all the target classes.
class ImplCanSpeakName implements TraitCanSpeakName {
  speakName(this: any): void {
    // Get internal state first. This has a tiny performance cost, so cache it in a local variable.
    const state = STATE.get(this) as TraitSlotCanSpeakName;

    console.log(`My name is ${state.name}`);
    // console.log(`My name is ${state[kName]}`);
  }
}

// Approach 2: Using Trait Constraints / Properties
// State is fetched via another Trait's getter. You constrain `this` to that Trait. Similar to Rust's `impl<T: TraitHasName> TraitCanSpeakName for T`.
// Best for scenarios where you only need a specific category of data. It provides a clear, highly decoupled code structure.
// Accessing `this.name` triggers the getter, which calls `STATE.get(this)` again under the hood, resulting in an extra function call overhead. If multiple data is needed, `STATE.get(this)` will be called multiple times, leading to a performance decrease.

@ImplFor(Dog, RobotDog)
class ImplHasNameForDogAndRobotDog implements TraitHasName {
  // Exposing internal state via a Trait property (getter).
  // You can think of this as a public getter on the class itself, rather than the internal State.
  get name() {
    return (STATE.get(this) as StateDog | StateRobotDog).name;
  }
}

/*
// Example of Approach 2 (Blanket Impl relying on TraitHasName):
@ImplFor(RobotDog)
class ImplCanSpeakName implements TraitCanSpeakName {
  // Require `this` to implement `TraitHasName`
  speakName(this: TraitHasName) {
    // Structurally very clear, but triggers the `get name()` function overhead
    console.log(`My name is ${this.name}`); 
  }
}
*/

// Now we can use them
const dog = new Dog("Buddy");
console.log("Dog barking");
dog.bark();
console.log("\nDog eating");
dog.eat();
console.log("\nDog eating and barking");
dog.eatAndBark();
console.log("\nDog's name");
console.log(dog.name);

const robotDog = new RobotDog("R2-D2", 100);
console.log("\nRobot dog barking");
robotDog.bark();
console.log("\nRobot dog eating");
robotDog.charge();
console.log("\nRobot dog speaking name");
robotDog.speakName();
console.log("\nRobot dog's name");
console.log(robotDog.name);

If you need inheritance

import { initRustyJs } from "@alex6357/rustyjs";
const { STATE, ImplFor } = initRustyJs();

// States
interface StateEventTarget {
  listeners: Map<string, Function[]>;
}
// inherit father's state
interface StateNode extends StateEventTarget {
  nodeName: string;
  childNodes: any[];
}

// Traits, also type announcements
// Methods writen in Traits are automatically inherited by subclasses
// If you don't want a method to be inherited, write it in class, not in Traits.
interface EventTarget {
  addEventListener(type: string, cb: Function): void;
}
interface Node extends EventTarget {
  appendChild(child: any): void;
}

class EventTarget {
  constructor() {
    // State initialized by base class
    STATE.init(this, {
      listeners: new Map(),
    } as StateEventTarget);
  }
}

class Node extends EventTarget {
  constructor(nodeName: string) {
    super(); // State already initialized by base class
    // Get state rather than creating a new one
    const state = STATE.get(this) as StateNode;
    // Set state used in subclass
    state.nodeName = nodeName;
    state.childNodes = [];
  }
}

// Implementations
@ImplFor(EventTarget)
// This is EventTarget's implementation block, i.e. impl EventTarget
class ImplEventTarget {
  addEventListener(this: EventTarget, type: string, cb: Function) {
    const state = STATE.get(this) as StateEventTarget;
    if (!state.listeners.has(type)) state.listeners.set(type, []);
    state.listeners.get(type)!.push(cb);
    console.log(`[EventTarget] Added listener ${type}`);
  }
}
@ImplFor(Node)
class ImplNode {
  appendChild(this: Node, child: any) {
    const state = STATE.get(this) as StateNode;
    state.childNodes.push(child);
    console.log(`[Node] ${state.nodeName} appended a child`);
  }
  // If you need a method from a superclass, you can't use super.methodName, because
  // in the implementation class, super is `object`, not actual superclass.
  // You need to call method directly from superclass's prototype
  // For example: Node.prototype.addEventListener.call(this, type, cb);
  // This also means superclass's implementation block must be before subclass's
}

const div = new Node("DIV");
console.log(div instanceof Node);
console.log(div instanceof EventTarget);
div.addEventListener("click", () => {}); // Inherited from EventTarget
div.appendChild(new Node("SPAN"));

Extend foreign classes

const { ImplFor } = initRustyJs();

interface TraitFormat {
  format(): string;
}
// Use declare global to mixin extension for global types
// If you are extending a not-builtin type, an interface should be enough, for example:
// interface ForeignClass extends YourTrait {}
declare global {
  interface Date extends TraitFormat {}
}

@ImplFor(Date)
class ImplFormatForDate implements TraitFormat {
  format(this: Date) {
    return `${this.getFullYear()}-${this.getMonth() + 1}`;
  }
}

const now = new Date();
console.log(now.format());

Build and test

# Build
pnpm build
# Build test (need to build first)
pnpm build:test
# Test
pnpm test

How this works?

Core of RustyJS is a decorator.

function ImplFor(...targets: any[]) {
  return function (implClass: any) {
    const descriptors = Object.getOwnPropertyDescriptors(implClass.prototype);
    // @ts-ignore
    delete descriptors.constructor;
    for (const target of targets) {
      Object.defineProperties(target.prototype, descriptors);
    }
  };
}

This decorator will directly assign all properties (except for constructor) of implClass.prototype to all target prototypes.

Unlike traditional Mixins or Class Factories that create deeply nested prototype chains, RustyJS operates at load-time. It extracts all methods from your Impl class and stamps them directly onto the target class prototype. No prototype chain crawling.

To mimic Rust's strict separation of Struct (Data) and Impl (Behavior), RustyJS forces you to keep instance data out of the this context directly. Instead, you manage data through a unified STATE accessor.

let accessor: StateAccessor<any>;

if (config?.mode === "weakmap") {
  const map = new WeakMap<object, any>();
  accessor = {
    get: (obj) => {
      const state = map.get(obj);
      if (!state) throw new Error("State uninitialized!");
      return state;
    },
    init: (obj, state) => {
      if (map.has(obj)) throw new Error("State already initialized!");
      map.set(obj, state);
      return state;
    },
  };
} else {
  const State = Symbol("rustyJsState");
  accessor = {
    get: (obj) => {
      const state = obj[State];
      if (!state) throw new Error("State uninitialized!");
      return state;
    },
    init: (obj, state) => {
      if (obj[State]) throw new Error("State already initialized!");
      Object.defineProperty(obj, State, {
        value: state,
        enumerable: false,
        writable: false,
        configurable: false,
      });
      return state;
    },
  };
}

The accessor supports two modes:

Symbol (Performance First, Default)

When configured to "symbol" mode, RustyJS creates a unique Symbol captured within the closure. When you call STATE.init, it uses Object.defineProperty to attach your state object directly to the instance using this Symbol. It is explicitly set as non-enumerable, non-writable, and non-configurable. It's more performant. But you can still get the symbol by using Object.getOwnPropertySymbols and access internal state.

WeakMap (Absolute Privacy)

When configured to "weakmap" mode, RustyJS creates a WeakMap captured within the closure. When you call STATE.init, the instance itself is used as a reference key, and the data is stored in the map. It's true absolute privacy. It is physically impossible for external code to get the state. Automatic garbage collection is handled by the JS engine when the instance is destroyed.

Comparison

Environment: Windows x86_64, 10,000,000 iterations.

Compare Initialization (Init) and High-Frequency Access (Get) performance between Symbol mode and WeakMap mode across different JavaScript runtimes. See tests/test-speed.js for details.

| Runtime | Engine | Symbol Init | WeakMap Init | Init Winner | Symbol Get | WeakMap Get | Get Winner | | :-------------------- | :------------- | :---------- | :----------- | :------------------ | :----------- | :---------- | :------------------ | | Bun 1.3.9 | JavaScriptCore | 673 ms | 1,072 ms | Symbol (1.59x) | 11.72 ms | 73.57 ms | Symbol (6.28x) | | Node.js 24.13.0 | V8 | 1,032 ms | 6,543 ms | Symbol (6.34x) | 6.70 ms | 59.45 ms | Symbol (8.87x) | | Deno 2.7.1 | V8 | 1,877 ms | 3,710 ms | Symbol (1.98x) | 4.88 ms | 56.90 ms | Symbol (11.66x) | | QuickJS NG 0.11.0 | QuickJS | 8,838 ms | 5,299 ms | WeakMap (1.67x) | 1,852 ms | 2,046 ms | Symbol (1.10x) | | Boa | Boa (Rust) | 17,309 ms | 10,377 ms | WeakMap (1.67x) | 6,014 ms | 5,980 ms | Tie |

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.