custom-factory
v3.0.0
Published
more easily add the factory ability to your class for node and javascript
Maintainers
Readme
CustomFactory

CustomFactory is a powerful utility that adds factory capabilities to your classes or objects. It allows you to manage classes and instances through registration, aliasing, and hierarchical structures.
Whether you need a simple flat registry or a complex hierarchical system (like a file system), CustomFactory has you covered. It supports both Inheritance (extending a base class) and Ability (mixin/trait) patterns, making it easy to integrate into existing codebases.
Features
- Factory Patterns:
- Flat Factory (
BaseFactory): A simple, single-level registry. - Hierarchical Factory (
CustomFactory): A multi-level registry where factories can contain other factories (like folders).
- Flat Factory (
- Flexible Registration: Register classes with unique names, display names, and multiple aliases.
- Smart Naming: Automatically deduces registered names by stripping suffixes (e.g.,
TextCodec->Textin aCodecfactory). - Singleton Support: Retrieve or create singleton instances.
- Integration Styles:
- Inheritance: Extend
BaseFactoryorCustomFactory. - Ability (Mixin): Add factory features to any existing class without breaking the inheritance chain.
- Inheritance: Extend
- Automatic Inheritance: Automatically inherits from the factory class upon registration (configurable).
- Case Sensitivity: Configurable name formatting (case-sensitive by default).
Installation
npm install custom-factoryQuick Start
Method 1: Inheritance (Recommended for new projects)
Flat Factory (BaseFactory)
Use BaseFactory for a simple, non-nested registry.
import { BaseFactory } from 'custom-factory'
class ShapeFactory extends BaseFactory {}
// 1. Register classes
class Circle extends ShapeFactory {} // Auto-registers as 'Circle'
ShapeFactory.register(Circle)
// 2. Register with aliases and options
class Square {}
ShapeFactory.register(Square, {
name: 'Square',
aliases: ['sq', 'box']
})
// 3. Usage
const shape = ShapeFactory.createObject('Circle') // Returns a Circle instance
const sq = ShapeFactory.get('sq') // Returns the Square classHierarchical Factory (CustomFactory)
Use CustomFactory when you need a nested structure (e.g., Codec -> Image -> Png).
import { CustomFactory } from 'custom-factory'
class RootFactory extends CustomFactory {}
// Level 1
class Codec extends RootFactory {}
RootFactory.register(Codec)
// Level 2
class ImageCodec extends Codec {}
Codec.register(ImageCodec, { name: 'Image' }) // Registered as 'Image' inside 'Codec'
// Level 3
class PngCodec extends ImageCodec {}
ImageCodec.register(PngCodec, { name: 'Png' })
// Usage
console.log(RootFactory.path(PngCodec)) // Output: '/Codec/Image/Png'
const PngClass = RootFactory.get('Codec').get('Image').get('Png')Method 2: Ability / Mixin (Recommended for existing classes)
If you already have a class hierarchy and want to add factory powers without changing the superclass, use the Ability functions.
import { addBaseFactoryAbility, addFactoryAbility } from 'custom-factory'
class MyBaseClass {
constructor(name) { this.name = name }
}
// Add Flat Factory capability
addBaseFactoryAbility(MyBaseClass)
class Plugin extends MyBaseClass {}
MyBaseClass.register(Plugin, { name: 'MyPlugin' })
const instance = MyBaseClass.createObject('MyPlugin', 'instanceName')For hierarchical capabilities, use addFactoryAbility(MyBaseClass).
Core Concepts
1. Flat Factory (BaseFactory)
The BaseFactory provides a single namespace for all registered items. It's ideal for simple plugin systems or type registries.
Key Methods:
register(Class, [options]): Registers a class.get(name): Retrieves a class by name or alias.createObject(name, ...args): Creates an instance of the registered class.forEach(callback): Iterates through registered classes.
2. Hierarchical Factory (CustomFactory)
The CustomFactory extends BaseFactory to support nesting. Registered items can themselves be factories for other items.
Key Methods (extends BaseFactory):
register(Class, [parent], [options]): Registers a class, optionally specifying a parent factory.path(Class): Returns the full path string (e.g.,/Root/Parent/Child).pathArray(Class): Returns the path as an array.
3. Automatic Name Generation
When you register a class without providing an explicit name, the factory attempts to generate a clean name by stripping redundant suffixes.
Explicit Name: You can provide a name during registration, or define a
static nameandstatic aliasin the class:// Via registration Factory.register(MyClass, { name: "custom-name", aliases: ["c", "alias"] }); // OR via static properties in class (Declarative Style) class MyClass { static name = "custom-name"; static alias = ["c", "alias"]; } Factory.register(MyClass);baseNameOnly(default: 1): The factory checks if the class name ends with the factory's name (or ancestor names in a hierarchy) and strips it.- Flat Example: Registering
TextCodectoCodecFactory-> Name becomesText. - Hierarchy Example:
- Root:
Codec - Child:
ImageCodec(Registered asImage) - Grandchild:
PngImageCodec(Registered asPng) -> StripsImageCodec(parent) andCodec(root).
- Root:
- Flat Example: Registering
baseNameOnly: 0: Disables suffix stripping. Uses the full class name.
4. Customizing Name Formatting
By default, names are case-sensitive. You can change this behavior by overriding the static formatName method in your factory.
class MyFactory extends BaseFactory {
static formatName(name) {
return name.toLowerCase(); // Make everything case-insensitive
}
}When using abilities, you can also override formatName by either defining it on the class before adding the ability, or by passing it in the options:
class MyCodec {
static formatName(name) { return name.toLowerCase() }
}
addFactoryAbility(MyCodec)
// OR
addFactoryAbility(MyCodec, { formatName: (name) => name.toLowerCase() })5. Advanced Registration: isFactory and autoInherits
The register method accepts advanced options to control inheritance and factory behavior.
isFactory(boolean|Class, default:true):true: The registered class is treated as a Factory Node. It can have its own children.false: The registered class is a Product/Leaf. It cannot have children.Class(Constructor): The registered class is a factory and must inherit from this specific class.
Note on
isFactoryPriority: The value is determined in the following order:options.isFactorypassed toregister().static _isFactorydefined on the registered class.static _isFactorydefined on the current Factory class.static _isFactorydefined on the root Factory class (this.Factory).- Defaults to
true.
autoInherits(boolean, default:true):true: If the registered item (which is a Factory) does not already inherit from the parent Factory, CustomFactory will automatically modify its prototype chain to inherit from it. This is useful for "Mix-and-Match" composition.false: Disables automatic inheritance. If the class does not inherit correctly, aTypeErrorwill be thrown.
6. Aliases
Aliases allow you to refer to the same registered class by different names. This is useful for providing shortcuts or maintaining backward compatibility.
During Registration:
Factory.register(MyClass, { aliases: ['m', 'my'] });After Registration:
Factory.setAliases(MyClass, 'shortcut', 'another'); // Or for single alias Factory.setAlias(MyClass, 'short');Using Properties: If the class is already registered, you can use the
aliasesproperty:MyClass.aliases = ['a', 'b']; // Replaces current aliases console.log(MyClass.aliases); // ['a', 'b']Important Note for TypeScript/Static Blocks: Always use
static alias(singular) for declarative alias definitions in your classes. Avoid usingstatic aliases(plural) becauseBaseFactorydefines a static setter foraliases. Some compilers (like TypeScript or Vitest) may transformstatic aliases = [...]into a static block:class MyClass extends Factory { static { this.aliases = [...]; } }This will trigger the setter on the
Factorybase class instead of defining a property onMyClass, leading to incorrect alias registration. Usingstatic aliasavoids this conflict.Best Practice for Hierarchies: If you are designing your own factory hierarchies, prefer using simple static properties (direct assignment) for configuration. If you must use a static setter in a parent class, remember that descendant classes must also define a setter (or getter/setter) to properly override it; otherwise, the parent's setter will be invoked when assigning the property in the child.
Retrieval: Use any alias with
get()orcreateObject():const instance = Factory.createObject('shortcut');Note: Aliases are also processed by the factory's
formatNamemethod (e.g., if the factory is case-insensitive, aliases will be too).
API Reference
Static Properties
_isFactory: The defaultisFactoryvalue for the factory (default:true)._baseNameOnly: The defaultbaseNameOnlyvalue for the factory (default:1).
Static Methods
register(class, [options]): Register a class.unregister(name|class): Remove a class from the factory.get(name): Get a registered class.createObject(name, ...args): Create an instance.setAliases(class, ...aliases): Set aliases.getAliases(class): Get aliases.forEach(callback): Iterate over registered items.formatName(name): Override this to change name matching (e.g., for case-insensitivity).
Instance Methods
initialize(...args): Called by the constructor. Override this for initialization logic.
(For CustomFactory only)
path(class): Get the hierarchical path.pathArray(class): Get the hierarchical path as an array.
License
MIT
