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

@perplexdotgg/mecs

v0.1.0

Published

MECS - Monomorph ECS - A high-performance Entity Component System for TypeScript and JavaScript, designed for games and simulations.

Downloads

101

Readme

MECS is an ECS library for Typescript and Javascript games and simulations, built around Monomorph, a library for performant objects. The API is inspired by Miniplex, though the implementation is quite different.

MECS focuses on the developer's experience in their IDE, especially around auto-complete and compile-type type-checking, while maintaining very high performance, about as high as you can achieve with an AoS design.

Features

  • Entities and monomorph-based components automatically gain object-pooling features, eliminating most garbage collection through automatic object re-use. Just create and destroy entities and components, and the pooling and re-use is taken care of.
  • Simple and minimal: There are only entities, components and queries
  • Queries, aka archetypes, automatically match and track entities based on which components they have, or don't have
  • Strong compile-time type safety if you use Typescript (recommended)
  • Coming soon: automatic serialization/deserialization (to/from number arrays) of entities (fully or a subset of components) to create snapshots

MECS creates a single entity pool, or world, when it creates your entity class from the schema you provide.

MECS has no built-in concept of "systems" in the ECS sense, instead it provides queries to help you quickly write efficient code that processes specific archetypes of entities. Your systems can just be functions that iterate over entities matching a query, and your systems can also subscribe to entities newly matching, or no longer matching, a query

MECS has been created primarily around having a single entity schema/pool where all entities can have all possible components types, but most entities only have a subset of them. If all your entities have all the same components, it is likely simpler to use Monomorph on its own.

Usage

Install into your project

npm install @perplexdotgg/mecs

Defining an Entity schema to create your Entity class

The following is Typescript code. The Javascript version is below this.

import { createClass } from 'monomorph';
import { createEntityClass, type QueryMap, type VirtualComponent } from '@perplexdotgg/mecs';

// imagine some monomorph classes in your project
// see the monomorph docs for more on those:
// https://codeberg.org/perplexdotgg/monomorph
class Projectile extends createClass<Projectile, typeof projectileProps>(projectileProps) {}
class Transform extends createClass<Transform, typeof transformProps>(transformProps) {}

// and some non-monomorph class
class MyClass {}

// define all the components you want entities to potentially have
const components = {
  // defining components when they are monomorph classes:
  localTransform: Transform,
  globalTransform: Transform,
  Projectile, // this is shorthand for: projectile: Projectile

  // for non-monomorph classes, pass a key with value null.
  // this syntax allows for the correct types to be expected and inferred.
  // you are responsible for creating and managing
  // this field, including any needed clean-up before it is removed
  myClass: null! as VirtualComponent<MyClass>,
} as const;

const queries = {
  projectiles: {
    // an entity must have all of these components to match this query
    with: ['globalTransform', 'projectile'],
    // AND it can have none of these components to match this query
    without: ['myClass'],

    // listeners can added now, or later (see the Queries docs below)
    afterEntityAdded: (entity) => {
      // this is called when an entity newly matches this query
    },
    beforeEntityRemoved: (entity) => {
      // this is called when an entity no longer matches this query,
      // just before a relevant component is removed from the entity
    },
  },
  query2: {
    with: ['localTransform', 'globalTransform'],
    without: [],
  },
} as const satisfies QueryMap<Entity, typeof components>;

let debugLogging: boolean | undefined = false;

// automatically create our entity class from the schemas above
// note that there are two function calls after createEntityClass
// to circumvent a typescript limitation, see https://github.com/microsoft/TypeScript/issues/10571
class Entity extends createEntityClass<Entity>(debugLogging)(components, queries) {

  // most methods will likely exist on your components directly,
  // but you can also extend the generated entity class
  myEntityMethod() {
    // your code here! use 'this' like you would expect
    if (this.hasComponent('example')) {
      this.example.whatever = 5;
    }
  }
}
import { createClass } from 'monomorph';
import { createEntityClass } from '@perplexdotgg/mecs';

// imagine some monomorph classes in your project
// see the monomorph docs for more on those:
// https://codeberg.org/perplexdotgg/monomorph
class Projectile extends createClass(projectileProps) {}
class Transform extends createClass(transformProps) {}

// and some non-monomorph class
class MyClass {}

// define all the components you want entities to potentially have
const components = {
  // defining components when they are monomorph classes:
  localTransform: Transform,
  globalTransform: Transform,
  Projectile, // this is shorthand for: projectile: Projectile

  // for non-monomorph classes, pass a key with value null.
  // you are responsible for creating and managing
  // this field, including any needed clean-up before it is removed
  myClass: null,
};

const queries = {
  projectiles: {
    // an entity must have all of these components to match this query
    with: ['globalTransform', 'projectile'],
    // AND it can have none of these components to match this query
    without: ['myClass'],

    // listeners can added now, or later (see the Queries docs below)
    afterEntityAdded: (entity) => {
      // this is called when an entity newly matches this query
    },
    beforeEntityRemoved: (entity) => {
      // this is called when an entity no longer matches this query,
      // just before a relevant component is removed from the entity
    },
  },
  query2: {
    with: ['localTransform', 'globalTransform'],
    without: [],
  },
};

let debugLogging = false;

// automatically create our entity class from the schemas above
// note that there are two function calls after createEntityClass
// to circumvent a typescript limitation (sorry, vanilla JS folks!)
class Entity extends createEntityClass(debugLogging)(components, queries) {

  // most methods will likely exist on your components directly,
  // but you can also extend the generated entity class
  myEntityMethod() {
    // your code here! use 'this' like you would expect
    if (this.hasComponent('example')) {
      this.example.whatever = 5;
    }
  }
}

Creating and destroying entities

Like monomorph's create method, entities accept the same object representations as inputs for each component. All components instances will be created automatically as needed

const entity1 = Entity.create({
  localTransform: {
    position: { x: 0, y: 0, z: 0, },
    orientation: { x: 0, y: 0, z: 0, w: 1 },
    scale: 1,
  },
  projectile: {
    speed: 4,
    direction: { x: 1, y: 0, z: 0 },
  },
});

// now entity1 is of the Entity class, and entity1.localTransform is of the Transform class
// so you can call methods on it like so:
entity1.localTransform.composeMatrix();

const entity2 = Entity.create({
  projectile: {
    speed: 4,
    direction: { x: 1, y: 0, z: 0 },
  },

  // you are responsible for instantiating your custom classes
  myClass: new MyClass(),
});

// destroy() to return the entity to the pool, it will automatically be re-used later
// this iterates on all components to remove them first, and triggers queries just
// before the component(s) needed for the query are deleted
// each destroyed component also gets recycled in its own component pool
entity2.destroy();

// note: if you need to do a cleanup on your custom class (like MyClass above),
// use a query's beforeEntityRemoved method/listeners for that purpose

Accessing components and methods

// calling component methods
entity1.projectile.checkCollisions();

// calling entity methods
entity1.myEntityMethod();

entity1.addComponent('globalTransform', {
  position: { x: 1, y: 2, z: 3, },
  orientation: { x: 0, y: 0, z: 0, w: 1 },
  scale: 1,
});
entity1.globalTransform.position.x += 5;

// add a component, using another component as input data
entity2.addComponent('localTransform', entity1.localTransform);
// ^^ they will not be the same objects, they will just have the same data
// this is also true for nested objects

// remove a component
entity1.removeComponent('projectile');

// checking if a component exists on an entity, two ways to do so:
if (entity1.globalTransform) { /* */ }
if (entity1.hasCompoonent('globalTransform')) { /* */ }

Iterating on entities and components

// all entities
for (const entity of Entity.pool) {

}

// only for monomorph-class components, iterating all instances of a component
for (const transform of Entity.components.localTransform) {

}

// only for monomorph-class components, iterating all instances of a component,
// along with the entity for each
for (const [transform, entity] of Entity.componentsWithEntities.localTransform) {

}

Iterating on queries, and adding/removing listeners

For defining queries, see the entity schema definition section above

// all entities matching a query
for (const entity of Entity.queries.projectiles) {

}

// in addition to being able to define `afterEntityAdded` and `beforeEntityRemoved` functions in
// the schema, you can dynamically add a new listener for entities matching a query,
// useful if you want to put this logic in your system code for example
function afterProjectileExists(entity) {
  // do whatever
}

Entity.queries.projectiles.afterEntityAdded.addListener(afterProjectileExists);
// you can also remove listeners
Entity.queries.projectiles.afterEntityAdded.removeListener(afterProjectileExists);

Entity.queries.projectiles.beforeEntityRemoved.addListener(someListener);
Entity.queries.projectiles.beforeEntityRemoved.removeListener(someListener);

Roadmap

  • Integrating the upcoming version of monomorph where non-monomorth classes are better supported (e.g. to allow a component to contain a reference to an html element)
  • Example project/template coming soon
  • Basic benchmarks
  • Entity/component data debugging tool(s)
  • Want to see something here? Please create an issue on codeberg

How to contribute

If you like this project and would like to support our work, please consider contributing code via pull requests, or donating via open collective. Contributions are greatly appreciated!