tessl install tessl/npm-ecos@0.2.0Entity Component System for JavaScript that enables component-based entity creation and management using a factory pattern
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.
npm install ecosconst { 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 objectconst { 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 configconst { 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();const { extenders } = require('ecos');
// Extender type constants
extenders.GETSET; // 'getterssetter' - for custom getters/setters
extenders.FUNCTION; // 'function' - for initialization logicGETSET 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 */ }
}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
});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 containerExtend 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(); }
});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 }// 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 70factory.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// 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
});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 }// 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));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 run early in entity creation, BEFORE properties from default or create(options) are assigned. Inside extender handlers, these properties will be undefined.
Execution Order:
type and id// β 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;
});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);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
Start here (index.md) β Quick Start β Common Tasks
ECOS follows these key architectural principles:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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()Common errors and solutions:
TypeError: Cannot read property of undefined
Error: Method 'X' not found
factory.registerMethod() before creating factoryError: Extender 'X' not found
factory.registerExtender() before creating factoryError: Preset 'X' not found
factory.registerPreset() before creating factoryTypeError during entity creation with preset
default optiondefault object