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

cat-herder

v0.2.2

Published

An ECS implementation in TypeScript

Readme

Cat Herder

An ECS (Entity Component System) implementation written in TypeScript.

npm install cat-herder

There is a umd build available:

<script src="https://unpkg.com/cat-herder@latest/dist/cat-herder.global.js"></script>

Usage

import { World, Entity } from 'cat-herder';

// Component factories are of form (...args: any) => Record<string, any>
const Name = (name: string) => ({ name });
const Velocity = (vx: number, vy: number) => ({ vx, vy });

const world = new World({});

world
  .register(Name)
  .register(Velocity);

// Entities
const bob = world.entity()
  .with(Name)("Bob")
  .build();
const roger = world.entity()
  .with(Name)("Roger")
  .with(Velocity)(0, 1)
  .build();

// Querying
for (const [name] of world.query(Name).collect()) {
  console.log(name.name); // "Bob", "Roger"
}

for (const [name] of world.query(Name).not(Velocity).collect()) {
  console.log(name.name); // "Bob"
}

for (const [name, velocity] of world.query(Name, Velocity).collect()) {
  console.log(name.name); // "Roger"
  console.log(velocity); // { vx: 0, vy: 1 }
}

API

World

World expects initial shared resources to be T.
T defaults to Record<string, any>.

interface IResources {
  time: number,
  someOtherResource?: ISomeOtherResource,
}

const world = new World<IResources>({ time: Date.now() }); 

Entities

entity

Creates an entity with attached components.

const bob = world
  .entity()
  .with(Name)("Bob")
  .with(Position)(10, 10)
  .with(Velocity)(1, 2)
  .build();

delete

Removes entity and any attached components.

world.delete(bob);

Components

register

Makes component available to attach to entities.
Component factories must have form (...args: any) => Record<string, any>
Attempting to attach an unregistered component will throw an error.
Support for class-based components is on the roadmap.

const Name = (name: string) => ({ name });

world.register(Name);

world
  .entity()
  .with(Name)("Bob") // will give appropriate type hints with TS
  .build();

add

Attaches component to entity. Attempting to attach an unregistered component will throw an error.
Attempting to attach to an unknown entity will throw an error.

const myEntity = world.entity().build();

world.add(Name, myEntity)("Roger");

get

Returns given component for entity. Will return undefined if not found.
Attempting to retrieve an unregistered component will throw an error.
Attempting to retrieve from an unknown entity will throw an error.

world.get(Name, myEntity);

remove

Removes component from entity.
Attempting to remove an unregistered component will throw an error.
Attempting to remove from an unknown entity will throw an error.

world.remove(Name, myEntity);

query

Querys accesses data lazily, so you can terminate lookups on demand.
Can call .collect() to access as array, or pass to Array.from().
Will throw if .collect() or .not() are called after iteration has started.

for (const [life] of world.query(Life, Ally)) {
  if (life <= 0) {
    world.resources.game_over = true;
    break;
  }
}

// to access as array
const allies_count = world.query(Ally).collect().length;
import { Entity, World } from 'cat-herder';

const Name = (name: string) => ({ name });
const Position = (vx: number, vy: number) => ({ vx, vy });
const Dead = createTag(); // Tag components lack a component store

const world = World({})
  .register(Name)
  .register(Position)
  .register(Dead);

const mario = world
  .entity()
  .with(Name)("Mario")
  .with(Position)(10, 10)
  .build();
const luigi = world
  .entity()
  .with(Name)("Luigi")
  .with(Position)(5, 10)
  .build();
const toad = world
  .entity()
  .with(Name)("Toad")
  .with(Position)(15, 10)
  .build();

// oops, Luigi had an accident
world.add(Dead, luigi);

for (const [entity, name, pos] of world.query(Entity, Name, Position).not(Dead)) {
  // Contains valid type hints in TS
  console.log(name.name); // Mario ; Toad
  console.log(`${pos.x}, ${pos.y}`); // 10, 10 ; 15 , 10
}

Systems

system

Registers a function which should run each game tick.
They occur in the order registered.

// system setup
import { System, Resource } from "cat-herder";

function movementSystem(world: IWorld) {
  const { time } = world.resources;

  for (const [pos, vel] of world.query(Position, Velocity)) {
    pos.x += vel.vx * time.elapsed;
    pos.y += vel.vy * time.elapsed;
  }
}

// init
world.system(movementSystem);

// each game tick
world.update();

update

Trigger all registered systems.

world.update();

Resources

Initial state should be passed in to the World on creation.
Use it as a dictionary of shared resources.

const world = World({
  time: Date.now(),
  delta: 1,
})

world.system(world => {
  const time = Date.now();
  const delta = (time - world.resources.time) * 60 / 1000;
  world.resources.time = time;
  world.resources.delta = delta;
});

Dependencies

For now, cat-herder requires BitSet.

Example

https://github.com/schtauffen/ts-ecs

Warning

This is an early project and breaking changes are to be expected.