tessl install tessl/npm-ecos@0.2.0Entity Component System for JavaScript that enables component-based entity creation and management using a factory pattern
Complete API documentation for the factory module, which provides entity factory creation and component registration.
const { factory } = require('ecos');Creates a new entity factory with specified configuration. Returns a factory instance with a create method and optional custom constructor methods.
/**
* Creates a new entity factory
* @param {object} options - Factory configuration
* @param {string} options.name - Factory identifier (required)
* @param {string[]} options.props - Property names to initialize (optional)
* @param {string[]} options.methods - Method names to attach (optional, must be registered)
* @param {string[]} options.extend - Extender names to apply (optional, must be registered)
* @param {string[]} options.presets - Preset names to apply (optional, must be registered)
* @param {object} options.default - Default property values (optional)
* @param {object} options.custom - Custom constructor definitions (optional)
* @returns {Factory} Factory instance
*/
factory.create(options);options.name (string, required)
type propertyoptions.props (string[], optional)
null by default unless overridden by default optionfactoryInstance.create()options.default (object, optional)
props are initializedfactoryInstance.create()options.methods (string[], optional)
factory.registerMethod() before factory creationoptions.extend (string[], optional)
factory.registerExtender() before factory creationoptions.presets (string[], optional)
factory.registerPreset() before factory creationoptions.custom (object, optional)
Factory instance with the following members:
Properties:
name (string) - The factory nameblueprint (object) - Configuration object containing props, extend, methods, presetsdefault (object) - Default property valuescustom (object|null) - Custom constructor definitionsMethods:
create(options?, customName?) - Creates new entityoptions.custom provided)const basicFactory = factory.create({
name: 'player',
props: ['name', 'level'],
default: { level: 1 }
});
const player = basicFactory.create({ name: 'Alice' });
// { type: 'player', name: 'Alice', level: 1, id: 0 }factory.registerMethod('levelUp', function() {
this.level += 1;
});
const characterFactory = factory.create({
name: 'character',
props: ['name', 'level'],
default: { level: 1 },
methods: ['levelUp']
});
const char = characterFactory.create({ name: 'Bob' });
char.levelUp();
console.log(char.level); // 2const { extenders } = require('ecos');
factory.registerExtender('timestamp', {
type: extenders.FUNCTION,
handler: function(entity) {
entity.createdAt = Date.now();
}
});
const trackedFactory = factory.create({
name: 'tracked',
props: ['data'],
extend: ['timestamp']
});
const entity = trackedFactory.create({ data: 'test' });
// { type: 'tracked', data: 'test', createdAt: 1609459200000, id: 0 }factory.registerMethod('move', function(dx, dy) {
this.x += dx;
this.y += dy;
});
factory.registerPreset('positioned', {
props: ['x', 'y'],
methods: ['move']
});
const spriteFactory = factory.create({
name: 'sprite',
presets: ['positioned'],
default: { x: 0, y: 0 } // Must include preset props
});
const sprite = spriteFactory.create({ x: 10, y: 20 });
sprite.move(5, -3);
// sprite: { type: 'sprite', x: 15, y: 17, id: 0, move: [Function] }const unitFactory = factory.create({
name: 'unit',
props: ['health', 'attack', 'defense'],
default: { health: 100, attack: 10, defense: 5 },
custom: {
warrior: { health: 150, attack: 20, defense: 15 },
mage: { health: 80, attack: 30, defense: 5 },
tank: { health: 200, attack: 10, defense: 30 }
}
});
const warrior = unitFactory.warrior();
// { type: 'unit.warrior', health: 150, attack: 20, defense: 15, id: 0 }
const mage = unitFactory.mage();
// { type: 'unit.mage', health: 80, attack: 30, defense: 5, id: 1 }options is not an objectoptions.name is not providedCreates a new entity based on the factory's configuration. The entity is automatically stored in the entities container and assigned a unique ID.
/**
* Creates a new entity
* @param {object} options - Property values to override defaults (optional)
* @param {string} customName - Custom type suffix for the entity (optional, used internally by custom constructors)
* @returns {object} New entity with id, type, and configured properties/methods
*/
factoryInstance.create(options, customName);options (object, optional)
default optioncustomName (string, optional)
factoryName.customNameEntity object with:
id (number) - Auto-generated unique identifiertype (string) - Entity type (factoryName or factoryName.customName)type property to factory nameentities.set() to assign idprops arraydefault objectcreate(options)const factory1 = factory.create({
name: 'item',
props: ['name', 'value'],
default: { value: 0 }
});
const sword = factory1.create({ name: 'Sword', value: 100 });
// { type: 'item', name: 'Sword', value: 100, id: 0 }
const shield = factory1.create({ name: 'Shield' });
// { type: 'item', name: 'Shield', value: 0, id: 1 }const enemyFactory = factory.create({
name: 'enemy',
props: ['health', 'damage'],
default: { health: 100, damage: 10 }
});
const goblin = enemyFactory.create(); // Uses defaults
// { type: 'enemy', health: 100, damage: 10, id: 2 }
const boss = enemyFactory.create({ health: 500, damage: 50 }); // Overrides defaults
// { type: 'enemy', health: 500, damage: 50, id: 3 }factory.registerMethod('attack', function(target) {
const damage = this.attackPower;
console.log(`${this.name} attacks for ${damage} damage!`);
return damage;
});
const mobFactory = factory.create({
name: 'mob',
props: ['name', 'attackPower'],
default: { attackPower: 10 },
methods: ['attack']
});
const orc = mobFactory.create({ name: 'Orc', attackPower: 15 });
orc.attack(); // "Orc attacks for 15 damage!"Registers a reusable method that can be attached to entities via factory configuration.
/**
* Registers a method that can be used by entities
* @param {string} name - Method identifier
* @param {function} handler - Method implementation
* @throws {Error} If name is not a valid string
* @throws {Error} If handler is not a function
* @throws {Error} If method name is already registered
*/
factory.registerMethod(name, handler);name (string, required)
handler (function, required)
this contextfactory.registerMethod('sayHello', function() {
console.log(`Hello from ${this.name}!`);
});
const factory2 = factory.create({
name: 'greeter',
props: ['name'],
methods: ['sayHello']
});
const entity = factory2.create({ name: 'Alice' });
entity.sayHello(); // "Hello from Alice!"factory.registerMethod('takeDamage', function(amount) {
this.health = Math.max(0, this.health - amount);
if (this.health === 0) {
console.log(`${this.name} has been defeated!`);
}
return this.health;
});
const combatFactory = factory.create({
name: 'combatant',
props: ['name', 'health'],
default: { health: 100 },
methods: ['takeDamage']
});
const warrior = combatFactory.create({ name: 'Warrior' });
warrior.takeDamage(30); // health: 70
warrior.takeDamage(80); // health: 0, logs "Warrior has been defeated!"factory.registerMethod('calculateDamage', function(targetDefense) {
const baseDamage = this.attack;
const actualDamage = Math.max(1, baseDamage - targetDefense);
return actualDamage;
});
const attackerFactory = factory.create({
name: 'attacker',
props: ['attack'],
methods: ['calculateDamage']
});
const attacker = attackerFactory.create({ attack: 50 });
const damage = attacker.calculateDamage(10); // Returns 40factory.registerMethod('heal', function(amount) {
this.health = Math.min(this.maxHealth, this.health + amount);
return this.health;
});
factory.registerMethod('isAlive', function() {
return this.health > 0;
});
factory.registerMethod('isDead', function() {
return this.health <= 0;
});
factory.registerMethod('getHealthPercent', function() {
return (this.health / this.maxHealth) * 100;
});
const lifeFactory = factory.create({
name: 'living',
props: ['health', 'maxHealth'],
default: { health: 100, maxHealth: 100 },
methods: ['heal', 'isAlive', 'isDead', 'getHealthPercent']
});name is not a stringhandler is not a functionRegisters an extender that can modify entity creation. Extenders add custom getters/setters (GETSET) or run initialization functions (FUNCTION).
/**
* Registers an extender that modifies entity creation
* @param {string} name - Extender identifier
* @param {object} extender - Extender configuration
* @throws {Error} If name is not a valid string
* @throws {Error} If extender is not an object
* @throws {Error} If extender name is already registered
*/
factory.registerExtender(name, extender);name (string, required)
extender (object, required)
type property (either extenders.GETSET or extenders.FUNCTION){
type: extenders.GETSET,
name: 'propertyName', // Property name to create
get: function() { }, // Optional getter function
set: function(value) { } // Optional setter function
}{
type: extenders.FUNCTION,
handler: function(entity, extender) { } // Required handler function
}See extenders-api.md for comprehensive extender examples.
const { extenders } = require('ecos');
factory.registerExtender('fullName', {
type: extenders.GETSET,
name: 'fullName',
get: function() {
return `${this.firstName} ${this.lastName}`;
},
set: function(value) {
const parts = value.split(' ');
this.firstName = parts[0];
this.lastName = parts[1] || '';
}
});
const personFactory = factory.create({
name: 'person',
props: ['firstName', 'lastName'],
extend: ['fullName']
});
const person = personFactory.create({ firstName: 'John', lastName: 'Doe' });
console.log(person.fullName); // "John Doe"
person.fullName = 'Jane Smith';
console.log(person.firstName); // "Jane"
console.log(person.lastName); // "Smith"factory.registerExtender('timestamp', {
type: extenders.FUNCTION,
handler: function(entity) {
entity.createdAt = Date.now();
entity.updatedAt = Date.now();
}
});
factory.registerExtender('uuid', {
type: extenders.FUNCTION,
handler: function(entity) {
entity.uuid = Math.random().toString(36).substr(2, 9);
}
});
const trackedFactory = factory.create({
name: 'tracked',
props: ['data'],
extend: ['timestamp', 'uuid']
});
const entity = trackedFactory.create({ data: 'test' });
// { type: 'tracked', data: 'test', id: 0, createdAt: 1609459200000, updatedAt: 1609459200000, uuid: 'a1b2c3d4e' }name is not a stringextender is not an objectRegisters a reusable preset configuration that can be applied to multiple factories. Presets bundle properties, methods, and extenders.
/**
* Registers a preset configuration
* @param {string} name - Preset identifier
* @param {object} preset - Preset configuration
* @param {string[]} preset.props - Property names to initialize (optional, MUST exist in factory's default option)
* @param {string[]} preset.methods - Method names to attach (optional)
* @param {string[]} preset.extend - Extender names to apply (optional)
* @throws {Error} If name is not a valid string
* @throws {Error} If preset is not an object
* @throws {Error} If preset name is already registered
*/
factory.registerPreset(name, preset);name (string, required)
preset (object, required)
props, methods, and/or extend arrayspreset.props (string[], optional)
default optionpreset.methods (string[], optional)
preset.extend (string[], optional)
factory.registerMethod('move', function(dx, dy) {
this.x += dx;
this.y += dy;
});
factory.registerMethod('draw', function() {
console.log(`Drawing at (${this.x}, ${this.y})`);
});
factory.registerPreset('positioned', {
props: ['x', 'y'],
methods: ['move', 'draw']
});
// Use preset in factory
const spriteFactory = factory.create({
name: 'sprite',
presets: ['positioned'],
default: { x: 0, y: 0 } // REQUIRED: must include preset props
});
const sprite = spriteFactory.create({ x: 10, y: 20 });
sprite.move(5, -3);
sprite.draw(); // "Drawing at (15, 17)"factory.registerPreset('mortal', {
props: ['health', 'maxHealth']
});
factory.registerPreset('movable', {
props: ['x', 'y', 'speed'],
methods: ['move']
});
const gameEntityFactory = factory.create({
name: 'gameEntity',
presets: ['positioned', 'mortal', 'movable'],
default: { x: 0, y: 0, speed: 5, health: 100, maxHealth: 100 }
});const { extenders } = require('ecos');
factory.registerExtender('autoId', {
type: extenders.FUNCTION,
handler: function(entity) {
entity.uuid = Math.random().toString(36).substr(2, 9);
}
});
factory.registerPreset('identifiable', {
extend: ['autoId']
});
const entityFactory = factory.create({
name: 'entity',
presets: ['identifiable'],
default: {}
});
const entity = entityFactory.create();
// { type: 'entity', id: 0, uuid: 'a1b2c3d4e' }name is not a stringpreset is not an objectdefaultClears all registered methods, extenders, and presets. Useful for testing or resetting application state.
/**
* Clears all registered methods, extenders, and presets
*/
factory.reset();factory.registerMethod('method1', function() {});
factory.registerMethod('method2', function() {});
factory.registerExtender('ext1', { type: extenders.FUNCTION, handler: () => {} });
factory.reset(); // Clears all registrations
// Can now re-register with same names
factory.registerMethod('method1', function() {
console.log('New implementation');
});Factory instances created by factory.create() expose several properties:
factoryInstance.name; // string - Factory name
factoryInstance.blueprint; // object - Configuration { props, extend, methods, presets }
factoryInstance.default; // object - Default property values
factoryInstance.custom; // object|null - Custom constructor definitionsconst myFactory = factory.create({
name: 'item',
props: ['name', 'value'],
default: { value: 0 },
methods: ['use']
});
console.log(myFactory.name); // 'item'
console.log(myFactory.default); // { value: 0 }
console.log(myFactory.blueprint.props); // ['name', 'value']
console.log(myFactory.blueprint.methods); // ['use']const { factory, entities, extenders } = require('ecos');
// Register reusable components
factory.registerMethod('attack', function(target) {
const damage = this.attackPower;
target.takeDamage(damage);
});
factory.registerMethod('takeDamage', function(amount) {
this.health = Math.max(0, this.health - amount);
});
factory.registerExtender('timestamp', {
type: extenders.FUNCTION,
handler: function(entity) {
entity.createdAt = Date.now();
}
});
factory.registerPreset('combatant', {
props: ['health', 'attackPower'],
methods: ['attack', 'takeDamage']
});
// Create factory
const unitFactory = factory.create({
name: 'unit',
presets: ['combatant'],
extend: ['timestamp'],
default: { health: 100, attackPower: 10 },
custom: {
warrior: { health: 150, attackPower: 20 },
mage: { health: 80, attackPower: 30 }
}
});
// Create entities
const warrior = unitFactory.warrior();
const mage = unitFactory.mage();
// Use entities
warrior.attack(mage);
console.log(mage.health); // 70
// Access from container
const retrieved = entities.get(warrior.id);
console.log(retrieved === warrior); // true