CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ecos

Entity Component System for JavaScript that enables component-based entity creation and management using a factory pattern

Overview
Eval results
Files

ECOS (Entity Component System)

ECOS is an Entity Component System library for JavaScript that enables component-based entity creation and management using a factory pattern with centralized entity storage.

Package Information

  • Package Name: ecos
  • Type: npm
  • Language: JavaScript
  • Installation: npm install ecos

Quick Start

const { factory, entities } = require('ecos');

// Register a method
factory.registerMethod('greet', function() {
    console.log(`Hello, I'm ${this.name}!`);
});

// Create a factory
const playerFactory = factory.create({
    name: 'player',
    props: ['name', 'health'],
    default: { health: 100 },
    methods: ['greet']
});

// Create entities
const player = playerFactory.create({ name: 'Alice', health: 150 });
// Result: { type: 'player', name: 'Alice', health: 150, id: 0, greet: [Function] }

player.greet(); // "Hello, I'm Alice!"

// Retrieve from container
const retrieved = entities.get(player.id); // Returns the same player object

API Quick Reference

Factory Module

const { factory } = require('ecos');

// Create entity factory
factory.create(options);
// options: { name, props?, methods?, extend?, presets?, default?, custom? }
// returns: Factory instance with create() method

// Register reusable components
factory.registerMethod(name, handler);
factory.registerExtender(name, extender);
factory.registerPreset(name, preset);

// Reset all registrations
factory.reset();

Factory Instance Methods:

// Create entity
factoryInstance.create(options?, customName?);
// returns: Entity object with id, type, properties, and methods

// Custom constructors (if defined)
factoryInstance.customName();
// returns: Entity with predefined values from custom config

Entities Module

const { entities } = require('ecos');

// Store entity (auto-called by factories)
entities.set(obj);
// returns: Object with id property added

// Retrieve entity
entities.get(id);
// returns: Entity object or null

// Remove entity
entities.remove(id);

// Get count
entities.size();
// returns: Number of entities

// Clear all entities
entities.removeAll();

Extenders Module

const { extenders } = require('ecos');

// Extender type constants
extenders.GETSET;   // 'getterssetter' - for custom getters/setters
extenders.FUNCTION; // 'function' - for initialization logic

GETSET Extender Structure:

{
    type: extenders.GETSET,
    name: 'propertyName',
    get: function() { /* return value */ },
    set: function(value) { /* assign value */ }
}

FUNCTION Extender Structure:

{
    type: extenders.FUNCTION,
    handler: function(entity, extender) { /* modify entity */ }
}

Core Concepts

Factory System

Factories define entity templates with properties, methods, and behaviors. They create entities with consistent structure.

const itemFactory = factory.create({
    name: 'item',               // Required: entity type identifier
    props: ['name', 'value'],   // Properties to initialize
    default: { value: 0 },      // Default property values
    methods: ['use']            // Registered methods to attach
});

Entity Container

Centralized storage for all entities. Prevents memory leaks by maintaining single reference point. Entities are automatically registered when created.

const entity = itemFactory.create({ name: 'Sword', value: 100 });
// entity.id assigned automatically (0, 1, 2, ...)

const same = entities.get(entity.id); // Retrieve by ID
entities.remove(entity.id);           // Remove from container

Extender System

Extend entity creation with custom property accessors (GETSET) or initialization logic (FUNCTION). Extenders run during entity creation before properties are assigned.

// GETSET example: computed property
factory.registerExtender('fullName', {
    type: extenders.GETSET,
    name: 'fullName',
    get: function() { return `${this.first} ${this.last}`; }
});

// FUNCTION example: initialization
factory.registerExtender('timestamp', {
    type: extenders.FUNCTION,
    handler: function(entity) { entity.createdAt = Date.now(); }
});

Common Tasks

Task: Create Entities with Properties

const factory1 = factory.create({
    name: 'player',
    props: ['name', 'health'],
    default: { health: 100 }
});

const player = factory1.create({ name: 'Alice', health: 150 });
// { type: 'player', name: 'Alice', health: 150, id: 0 }

📘 Complete Guide

Task: Add Methods to Entities

// 1. Register method
factory.registerMethod('takeDamage', function(amount) {
    this.health = Math.max(0, this.health - amount);
});

// 2. Reference in factory
const factory2 = factory.create({
    name: 'character',
    props: ['health'],
    methods: ['takeDamage']
});

const char = factory2.create({ health: 100 });
char.takeDamage(30); // health is now 70

📘 Complete Guide

Task: Use Extenders for Computed Properties

factory.registerExtender('healthPercent', {
    type: extenders.GETSET,
    name: 'healthPercent',
    get: function() {
        return (this.health / this.maxHealth) * 100;
    }
});

const factory3 = factory.create({
    name: 'entity',
    props: ['health', 'maxHealth'],
    extend: ['healthPercent']
});

const e = factory3.create({ health: 75, maxHealth: 100 });
console.log(e.healthPercent); // 75

📘 Complete Guide

Task: Create Reusable Presets

// Register methods
factory.registerMethod('move', function(dx, dy) {
    this.x += dx; this.y += dy;
});

// Create preset
factory.registerPreset('positioned', {
    props: ['x', 'y'],
    methods: ['move']
});

// Use preset in factory
const factory4 = factory.create({
    name: 'sprite',
    presets: ['positioned'],
    default: { x: 0, y: 0 }  // REQUIRED: preset props must be in default
});

📘 Complete Guide

Task: Define Custom Constructors

const unitFactory = factory.create({
    name: 'unit',
    props: ['health', 'attack'],
    default: { health: 100, attack: 10 },
    custom: {
        warrior: { health: 150, attack: 20 },
        mage: { health: 80, attack: 30 }
    }
});

const warrior = unitFactory.warrior(); // { type: 'unit.warrior', health: 150, attack: 20, id: 0 }
const mage = unitFactory.mage();       // { type: 'unit.mage', health: 80, attack: 30, id: 1 }

📘 Complete Guide

Task: Manage Parent-Child Relationships

// Store child IDs, not direct references
const parent = factory5.create({ name: 'parent', childIds: [] });
const child1 = factory5.create({ name: 'child1' });
const child2 = factory5.create({ name: 'child2' });

parent.childIds = [child1.id, child2.id]; // Store IDs

// Retrieve children
parent.childIds.forEach(id => {
    const child = entities.get(id);
    console.log(child.name);
});

// Clean up
entities.remove(parent.id);
parent.childIds.forEach(id => entities.remove(id));

Critical Constraints

⚠️ Preset Props Must Exist in Factory Default

IMPORTANT: Preset props do NOT add new properties. They only initialize properties already defined in the factory's default option. If a preset references a prop not in default, a TypeError will occur during entity creation.

// ❌ WRONG - will throw TypeError
factory.registerPreset('positioned', { props: ['x', 'y'] });
const factory6 = factory.create({
    name: 'sprite',
    presets: ['positioned']
    // Missing default: { x: 0, y: 0 }
});

// ✅ CORRECT
const factory7 = factory.create({
    name: 'sprite',
    presets: ['positioned'],
    default: { x: 0, y: 0 }  // Props must be here
});

⚠️ Extenders Execute Before Properties Are Assigned

Extenders run early in entity creation, BEFORE properties from default or create(options) are assigned. Inside extender handlers, these properties will be undefined.

Execution Order:

  1. Entity object created with type and id
  2. Preset extenders applied
  3. Factory extenders applied
  4. Then properties assigned (from props, default, create options)
  5. Methods attached
// ❌ WRONG - baseHealth not yet available in extender
factory.registerExtender('badInit', {
    type: extenders.FUNCTION,
    handler: function(entity) {
        entity.health = this.baseHealth; // undefined!
    }
});

// ✅ CORRECT - use method called after creation
factory.registerMethod('initHealth', function() {
    this.health = this.baseHealth || 100;
});

📘 Complete Execution Order

⚠️ Use ID-Based References, Not Direct References

Entities should reference each other by ID to enable proper garbage collection when entities are removed.

// ❌ WRONG - direct reference
parent.child = childEntity;

// ✅ CORRECT - ID reference
parent.childId = childEntity.id;
const child = entities.get(parent.childId);

⚠️ Entity IDs Are Auto-Incrementing

IDs start at 0 and increment sequentially. IDs are never reused unless entities.removeAll() is called, which resets the counter to 0.

entities.removeAll(); // Resets ID counter
const entity1 = factory8.create(); // id: 0
const entity2 = factory8.create(); // id: 1

📘 All Constraints and Gotchas


Navigation Guide

For Basic Usage

Start here (index.md) → Quick Start → Common Tasks

For Specific Tasks

For Complete API Reference

For Advanced Topics


Design Principles

ECOS follows these key architectural principles:

  1. Centralized Storage: All entities stored in unified container to prevent memory leaks
  2. ID-Based References: Entities reference each other by ID for proper garbage collection
  3. Factory Pattern: Entities created through factories ensuring consistent structure
  4. Shared Behaviors: Methods and extenders shared across entities without prototype inheritance
  5. Composition Over Inheritance: Build entities by composing properties, methods, and extenders

Architecture Overview

┌─────────────────────────────────────────────────┐
│                   Your Code                      │
└─────────────────┬───────────────────────────────┘
                  │
        ┌─────────┴──────────┐
        │                    │
┌───────▼────────┐   ┌──────▼───────┐
│     Factory    │   │   Entities   │
│                │   │  Container   │
│ - create()     │◄──┤              │
│ - register...()│   │ - get/set    │
└───────┬────────┘   │ - remove     │
        │            └──────────────┘
        │
┌───────▼────────┐
│   Extenders    │
│                │
│ - GETSET       │
│ - FUNCTION     │
└────────────────┘

Flow:
1. Register methods/extenders/presets with factory
2. Create factory with configuration
3. Factory creates entities (auto-stored in container)
4. Access entities via container by ID
5. Clean up entities with remove()

Error Handling

Common errors and solutions:

TypeError: Cannot read property of undefined

  • Cause: Accessing property in extender before it's assigned
  • Solution: Use methods instead of extenders for property-dependent initialization

Error: Method 'X' not found

  • Cause: Referenced method not registered
  • Solution: Call factory.registerMethod() before creating factory

Error: Extender 'X' not found

  • Cause: Referenced extender not registered
  • Solution: Call factory.registerExtender() before creating factory

Error: Preset 'X' not found

  • Cause: Referenced preset not registered
  • Solution: Call factory.registerPreset() before creating factory

TypeError during entity creation with preset

  • Cause: Preset props not defined in factory's default option
  • Solution: Add all preset props to factory default object

Additional Resources

  • Package repository: npm package

Install with Tessl CLI

npx tessl i tessl/npm-ecos
Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/ecos@0.2.x