UNPKG

tiny-essentials

Version:

Collection of small, essential scripts designed to be used across various projects. These simple utilities are crafted for speed, ease of use, and versatility.

174 lines (126 loc) 6.79 kB
# 🛠️ TinyClassManager Welcome to **TinyClassManager**! This is a lightweight, linear mixin manager designed to compose base classes with optional modules (like features or "DLCs") in a safe, predictable, and sequential way. It handles dependencies and prevents multiple class reuses automatically. --- ## 🚀 Key Features * **🔒 Immutable Chain State:** Each `.insert()` call consumes the previous manager, preventing unwanted side effects or weird mutations. * **🧩 Dependency Verification:** Automatically ensures that a required plugin is loaded before the dependent plugin is applied. * **🛑 Conflict Protection:** Prevents you from installing the exact same plugin twice on the same class chain. * **⚡ Clean Architecture:** Uses static class properties to manage metadata, keeping your plugin functions pure and direct. --- ## 📐 API Overview ### `TinyClassManager<T>` #### Properties * **`appliedPlugins`** (`string[]`): Returns an array of the names of all successfully installed plugins so far. * **`size`** (`number`): The total count of applied plugins plus the core base class. * **`currentClass`** (`T`): Accesses the current state of the class chain inside the manager. * **`used`** (`boolean`): Indicates whether this specific manager instance has already been transitioned or finalized. #### Methods * **`constructor(coreClass)`**: Initializes the manager with your foundational core class. * **`insert(applyFn)`**: Applies a new plugin to the chain by passing a function that returns the extended class. The returned class **must** define a `static _tinyDepName` string, and can optionally define a `static _tinyDeps` array of strings. Returns a *new* `TinyClassManager` instance. * **`use(plugin)`**: *[Deprecated]* Legacy method for applying plugins using a definition object. Use `insert` instead. * **`build()`**: Finalizes the composition chain, marks the instance as consumed, and returns the fully compiled class ready for instantiation. --- ## 📖 Detailed Usage Guide & Step-by-Step Example Let's break down exactly how to design, extend, and instantiate a class structure using `TinyClassManager`. You can mirror this step-by-step blueprint for your own features! ### Step 1: Establish the Core Base 🧱 Every structure needs a solid foundation. The core class is the absolute minimum requirement and contains properties that every subsequent plugin might rely on. ```javascript import TinyClassManager from '../TinyClassManager.mjs'; // The foundational class class EntityCore { /** * @param {string} id */ constructor(id) { /** @type {string} */ this.id = id; } } ``` ### Step 2: Create an Independent Module 🏷️ Now, let's create our first module. A plugin is a function that receives the base class and returns an extended version of it. We use static properties (`_tinyDepName` and `_tinyDeps`) to tell the manager what this plugin is and what it needs. This module doesn't depend on anything else, so its dependencies array is empty. ```javascript /** * @param {typeof EntityCore} Base - The incoming base class to extend. */ const applyHealth = (Base) => class HealthModule extends Base { static _tinyDepName = 'Health'; /** @type {string[]} */ static _tinyDeps = []; // Works fine straight out of the box with the Core /** * @param {string} id */ constructor(id) { super(id); // Always remember to pass arguments down! /** @type {number} */ this.hp = 100; } /** * Deducts health from the entity. * @param {number} amount - The amount of raw damage to deal. */ takeDamage(amount) { this.hp -= amount; console.log(`Took ${amount} damage. HP left: ${this.hp}`); } }; ``` ### Step 3: Create a Dependent Module 🛡️ Here is where the magic happens! The `Armor` module strictly requires the `Health` module to be loaded first because it relies on overriding the `takeDamage` logic. We enforce this relationship inside the `static _tinyDeps` array. ```javascript /** * @param {ReturnType<typeof applyHealth>} Base - The class containing Health features. */ const applyArmor = (Base) => class ArmorModule extends Base { static _tinyDepName = 'Armor'; static _tinyDeps = ['Health']; // Strictly requires the "Health" module! /** * @param {string} id */ constructor(id) { super(id); /** @type {number} */ this.armor = 50; } /** * Deducts health, factoring in damage mitigation provided by armor. * @param {number} amount - The initial damage before armor reduction. */ takeDamage(amount) { // Mitigates damage using the armor stat const reducedDamage = Math.max(0, amount - this.armor * 0.1); // Pass the mitigated damage up to the Health layer super.takeDamage(reducedDamage); } }; ``` ### Step 4: Chain and Build Your Custom Class 🏗️ Finally, use the manager to safely piece your modules together. Remember that `TinyClassManager` uses an explosive state pattern: once you run `.insert()` or `.build()`, that specific instance is closed, forcing you to use the new returned chain instance. ```javascript try { // 1. Wrap your core class const CoreTest = new TinyClassManager(EntityCore); // 2. Chain your modules and compile! // If you try to swap the order (.insert(applyArmor).insert(applyHealth)), it will throw an explicit dependency error. const FullyArmoredEntity = CoreTest.insert(applyHealth).insert(applyArmor).build(); // 3. Create your custom instance! const myEntity = new FullyArmoredEntity('Player_1'); // 4. Test out the behaviors myEntity.takeDamage(30); // Output: "Took 25 damage. HP left: 75" (30 damage - 5 armor reduction = 25 damage) console.log(`Armor value: ${myEntity.armor}`); // Output: Armor value: 50 console.log(`Current HP: ${myEntity.hp}`); // Output: Current HP: 75 } catch (error) { if (error instanceof Error) { console.error(error.message); } } ``` --- ## 💡 How to Mimic This Pattern When you want to build a new set of mechanics for your game or application using this architecture, always remember to follow this loop: 1. **Define your Base State**: Keep it clean and focused on global data configurations. 2. **Declare Plugins as Functions**: Create a function that accepts a `Base` class and returns an extended class. Equip it with `static _tinyDepName` (string) and `static _tinyDeps` (array of strings). 3. **Use `super` Inheritances**: Inside your plugin classes, always call `super(...)` in constructors and use `super.methodName(...)` if you are overriding an existing skill or function to allow all mixed behaviors to fire smoothly down the pipeline!