Entity Component System for JavaScript that enables component-based entity creation and management using a factory pattern
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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: 1Start 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