or run

tessl search
Log in

Version

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

docs

index.md
tile.json

tessl/npm-ecos

tessl install tessl/npm-ecos@0.2.0

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

factory-api.mddocs/api-reference/

Factory API Reference

Complete API documentation for the factory module, which provides entity factory creation and component registration.

Module Import

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

factory.create()

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);

Parameters

options.name (string, required)

  • Entity type identifier
  • Used as the entity's type property
  • Must be unique within your application

options.props (string[], optional)

  • Array of property names to initialize on entities
  • Properties are set to null by default unless overridden by default option
  • Can be overridden when calling factoryInstance.create()

options.default (object, optional)

  • Default values for entity properties
  • Applied after properties from props are initialized
  • Can be overridden when calling factoryInstance.create()
  • CRITICAL: Must include all properties referenced by presets

options.methods (string[], optional)

  • Array of method names to attach to entities
  • Methods must be registered via factory.registerMethod() before factory creation
  • Methods are bound to the entity instance

options.extend (string[], optional)

  • Array of extender names to apply during entity creation
  • Extenders must be registered via factory.registerExtender() before factory creation
  • Extenders execute before properties are assigned

options.presets (string[], optional)

  • Array of preset names to apply
  • Presets must be registered via factory.registerPreset() before factory creation
  • Preset extenders execute before factory extenders

options.custom (object, optional)

  • Custom constructor definitions mapping names to property values
  • Each key becomes a method on the factory instance
  • Values are objects with property overrides

Returns

Factory instance with the following members:

Properties:

  • name (string) - The factory name
  • blueprint (object) - Configuration object containing props, extend, methods, presets
  • default (object) - Default property values
  • custom (object|null) - Custom constructor definitions

Methods:

  • create(options?, customName?) - Creates new entity
  • Custom constructor methods (if options.custom provided)

Examples

Basic Factory

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 with Methods

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); // 2

Factory with Extenders

const { 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 with Presets

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] }

Factory with Custom Constructors

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 }

Error Conditions

  • Throws if options is not an object
  • Throws if options.name is not provided
  • Throws if referenced method names are not registered
  • Throws if referenced extender names are not registered
  • Throws if referenced preset names are not registered

factoryInstance.create()

Creates 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);

Parameters

options (object, optional)

  • Property values to set on the entity
  • Overrides values from factory's default option
  • Merged after default values are applied

customName (string, optional)

  • Custom type suffix appended to factory name
  • Used internally by custom constructor methods
  • Entity type becomes factoryName.customName
  • Rarely used directly; prefer custom constructors

Returns

Entity object with:

  • id (number) - Auto-generated unique identifier
  • type (string) - Entity type (factoryName or factoryName.customName)
  • Properties from factory configuration
  • Methods from factory configuration
  • Properties/methods added by extenders

Entity Creation Flow

  1. Create base entity object
  2. Set type property to factory name
  3. Call entities.set() to assign id
  4. Apply preset extenders
  5. Apply factory extenders
  6. Initialize properties from props array
  7. Apply properties from default object
  8. Apply properties from create(options)
  9. Attach methods

Examples

Basic Entity Creation

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 }

Overriding Defaults

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 }

Entity with Methods

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!"

factory.registerMethod()

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);

Parameters

name (string, required)

  • Unique identifier for the method
  • Used to reference the method in factory configuration
  • Must not conflict with existing registrations

handler (function, required)

  • Function implementation of the method
  • Executed with entity as this context
  • Can accept any number of arguments
  • Return value available to caller

Examples

Simple Method

factory.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!"

Method with Parameters

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!"

Method with Return Value

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 40

Multiple Related Methods

factory.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']
});

Error Conditions

  • Throws if name is not a string
  • Throws if handler is not a function
  • Throws if method name is already registered

factory.registerExtender()

Registers 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);

Parameters

name (string, required)

  • Unique identifier for the extender
  • Used to reference the extender in factory configuration
  • Must not conflict with existing registrations

extender (object, required)

  • Extender configuration object
  • Must have a type property (either extenders.GETSET or extenders.FUNCTION)
  • Additional properties depend on type (see structures below)

GETSET Extender Structure

{
    type: extenders.GETSET,
    name: 'propertyName',          // Property name to create
    get: function() { },           // Optional getter function
    set: function(value) { }       // Optional setter function
}

FUNCTION Extender Structure

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

Examples

See extenders-api.md for comprehensive extender examples.

GETSET Example: Computed Property

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"

FUNCTION Example: Initialization

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' }

Error Conditions

  • Throws if name is not a string
  • Throws if extender is not an object
  • Throws if extender name is already registered

factory.registerPreset()

Registers 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);

Parameters

name (string, required)

  • Unique identifier for the preset
  • Used to reference the preset in factory configuration
  • Must not conflict with existing registrations

preset (object, required)

  • Preset configuration object
  • Can include props, methods, and/or extend arrays
  • All referenced methods and extenders must be registered

preset.props (string[], optional)

  • Property names to initialize on entities
  • CRITICAL: These do NOT add new properties - they only ensure existing properties are initialized
  • All props listed here MUST also be defined in the factory's default option

preset.methods (string[], optional)

  • Method names to attach to entities
  • Methods must be registered before preset is used

preset.extend (string[], optional)

  • Extender names to apply during entity creation
  • Extenders must be registered before preset is used

Examples

Basic Preset

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)"

Multiple Presets

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 }
});

Preset with Extenders

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' }

Error Conditions

  • Throws if name is not a string
  • Throws if preset is not an object
  • Throws if preset name is already registered
  • TypeError during entity creation if preset props not in factory's default

factory.reset()

Clears all registered methods, extenders, and presets. Useful for testing or resetting application state.

/**
 * Clears all registered methods, extenders, and presets
 */
factory.reset();

Examples

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');
});

Use Cases

  • Resetting state between test suites
  • Clearing registrations when switching game levels
  • Reinitializing the factory system

Factory Instance Properties

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 definitions

Examples

const 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']

Complete Example

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