Abstract technique that aids information retention: Instance Inheritance System
... allows us to make inherited descriptions of mappings of transformations from predecessor structured data types to the successors, as if it was math f(x)=>y ... and we will use this keyword as a persistent data structure where we will apply that transformations
- ?. : state : mad science
- ?. : type : asynchronous monad descriptor => this
- ?. : prod ready : we wonder about
- ?. : example : git clone && npm run example
- Overview
- Installation
- Quick Start
- Core Concepts
- TypeScript Support
- API Reference
- Configuration Options
- AI Agent Usage Guide
- Usage with TypeØmatica
- Examples
Mnemonica helps create ordered sequences of data transformations using prototype chain inheritance. It combines Object Instances with Inheritance through the Prototype Chain, enabling you to create new instances inherited from existing ones.
Think of it as a mathematical function f(x) => y where this is your persistent data structure and transformations are applied sequentially.
"O Great Mnemosyne! Please! Save us from Oblivion..."
— from the source, where memory persists
Key Features:
- Factory of Constructors with Prototype Chain Inheritance
- Instance-level inheritance (not just class-level)
- Async constructor support with chainable awaits
- Type-safe data flow definition
- Comprehensive hook system for lifecycle events
Related Reading:
- Inheritance in JavaScript: Factory of Constructors with Prototype Chain
- Architecture of Prototype Inheritance in JavaScript
- Dead Simple type checker for JavaScript
npm install mnemonicaRequirements: Node.js >=18 <24
const { define } = require('mnemonica');
// Define a type
const UserType = define('UserType', function (data) {
Object.assign(this, data);
});
// Create an instance
const user = new UserType({ name: 'John', email: 'john@example.com' });
// Define a subtype
const AdminType = UserType.define('AdminType', function () {
this.role = 'admin';
});
// Create nested instance (inherits from user)
const admin = new user.AdminType();
console.log(admin.name); // 'John' (inherited)
console.log(admin.role); // 'admin' (own property)import { define, lookup } from 'mnemonica/module';Define types using constructors, factory functions, or classes:
// Using a constructor function
const SomeType = define('SomeType', function (opts) {
Object.assign(this, opts);
});
// Using a factory function
const AnotherType = define(() => {
const AnotherTypeConstructor = function (opts) {
Object.assign(this, opts);
};
AnotherTypeConstructor.prototype.description = 'SomeType Constructor';
return AnotherTypeConstructor;
});
// Using a class
const ClassType = define(() => {
class MyClass {
constructor(opts) {
Object.assign(this, opts);
}
}
return MyClass;
});// Define nested types
SomeType.define('SubType', function (opts) {
this.other = opts.other;
}, {
description: 'SomeSubType Constructor'
});
// Or using assignment
SomeType.SubType = function (opts) {
this.other = opts.other;
};
SomeType.SubType.prototype = {
description: 'SomeSubType Constructor'
};const someInstance = new SomeType({
some: 'arguments',
data: 'necessary'
});
const subInstance = new someInstance.SubType({
other: 'data needed'
});
// All properties are inherited
console.log(subInstance.some); // 'arguments' (inherited)
console.log(subInstance.other); // 'data needed' (own)Extract all inherited properties into a flat object:
const extracted = subInstance.extract();
// Result: { data, description, other, some }
// Or use the standalone utility
const { utils: { extract } } = require('mnemonica');
const extracted2 = extract(subInstance);The define function has full TypeScript support with comprehensive type definitions:
import { define, apply, call, bind } from 'mnemonica';
interface UserData {
email: string;
password: string;
}
const UserType = define('UserType', function (this: UserData, data: UserData) {
Object.assign(this, data);
});
// Nested constructors work with apply/call/bind for type inference
const user = new UserType({ email: 'test@test.com', password: 'secret' });For complex nested types, use apply, call, or bind for better type inference:
const SomeSubType = SomeType.define('SomeSubType', function (...args: string[]) {
// ...
});
const someInstance = new SomeType();
const subInstance = call(someInstance, SomeSubType, 'arg1', 'arg2');The following types are available for advanced TypeScript usage:
import {
// Core constructor types
IDEF, // Base constructor function type: { new(): T } | { (this: T, ...args): void }
ConstructorFunction, // Constructor with prototype
Constructor, // Generic constructor type
// Instance types
MnemonicaInstance, // Instance methods interface (extract, pick, parent, fork, etc.)
Props, // Internal instance properties (__type__, __args__, __parent__, etc.)
SiblingAccessor, // Sibling type accessor type
// Type definition types
TypeClass, // Base type constructor returned by define()
IDefinitorInstance, // Definitor instance with define/lookup methods and subtypes
DecoratedClass, // Type for @decorate decorated classes
TypeDef, // Type definition object structure
// Configuration types
constructorOptions, // Type config options (strictChain, blockErrors, etc.)
hooksTypes, // 'preCreation' | 'postCreation' | 'creationError'
hook, // Hook callback type
hooksOpts, // Hook options passed to callbacks
CollectionDef, // Types collection definition
// Utility function types
ApplyFunction, // apply(entity, Ctor, args) => S
CallFunction, // call(entity, Ctor, ...args) => S
BindFunction, // bind(entity, Ctor) => (...args) => S
// createTypesCollection types
CreateTypesCollectionFunction, // (config?: constructorOptions) => TypesCollection
TypesCollection, // Interface returned by createTypesCollection
// Configuration helper types
HideInstanceMethodsOptions, // constructorOptions & { exposeInstanceMethods: true }
IsHidingMethods<Config>, // Conditional type for exposeInstanceMethods: false detection
// Utility types
Proto<P, T>, // Merges parent P and child T types
SN, // String-Name map for nested constructors
SubtypesMap, // Map<string, TypeClass>
TypeAbsorber, // Main define() function interface with overloads
} from 'mnemonica';Define types with proper generic constraints for full type safety:
// Using IDEF with interface definitions
interface UserData {
email: string;
password: string;
}
// Type-safe constructor with 'this' context
const UserType = define('UserType', function (this: UserData, data: UserData) {
Object.assign(this, data);
});
// Type-safe nested types with merged interfaces
interface AdminData {
role: string;
}
const AdminType = UserType.define('AdminType', function (this: UserData & AdminData, role: string) {
this.role = role;
this.email; // string - inherited from UserData
});// Async type with proper return type
const AsyncType = define('AsyncType', async function (this: UserData, data: string) {
await someAsyncOperation();
return Object.assign(this, { data });
});
// With explicit awaitReturn option (no return required)
const AsyncTypeNoReturn = define('AsyncType', async function () {
// No return needed
}, { awaitReturn: false });Defines a new type constructor. Returns a TypeClass.
const MyType = define('MyType', function (data) {
Object.assign(this, data);
}, {
strictChain: true,
blockErrors: true
});Parameters:
typeName(string): Name of the type (optional if using factory function)constructHandler(Function): Constructor functionconfig(object, optional): Configuration options
Looks up a type by its nested path.
const { lookup } = require('mnemonica');
const SomeType = lookup('SomeType');
const SomeNestedType = lookup('SomeType.SomeNestedType');Type-safe lookup function for use with tactica-generated type definitions. Requires TypeRegistry augmentation from tactica.
import { lookupTyped } from 'mnemonica';
// Type-safe lookup - returns properly typed constructor
const UserType = lookupTyped('UserType');
const user = new UserType({ name: 'John' }); // Full type safety!
// Works with nested types too
const SubType = lookupTyped('Parent.SubType');To enable type safety, generate types with tactica:
npx tacticaThen include the generated types in your project. See Usage with @mnemonica/tactica for details.
Applies a constructor to an entity with an array of arguments.
const { apply } = require('mnemonica');
const subInstance = apply(parentInstance, SubType, ['arg1', 'arg2']);Calls a constructor on an entity with spread arguments.
const { call } = require('mnemonica');
const subInstance = call(parentInstance, SubType, 'arg1', 'arg2');Binds a constructor to an entity, returning a function.
const { bind } = require('mnemonica');
const createSub = bind(parentInstance, SubType);
const subInstance = createSub('arg1', 'arg2');TypeScript decorator for class-based definitions.
Usage Patterns:
import { decorate } from 'mnemonica';
// 1. Basic decoration
@decorate()
class MyClass {
field: number = 123;
}
// 2. With configuration
@decorate({ strictChain: false, blockErrors: true })
class ConfiguredClass {
field: number = 123;
}
// 3. Nested decoration (define as subtype)
@decorate()
class ParentClass {
parentField: string = 'parent';
}
@decorate(ParentClass)
class ChildClass {
childField: string = 'child';
}
// Create parent instance, then child from it
const parent = new ParentClass();
const child = new parent.ChildClass();
// 4. Parent with configuration
@decorate(ParentClass, { strictChain: false })
class ConfiguredChildClass {
field: number = 123;
}
// 5. Using decorated class as decorator (advanced)
// After a class is decorated with @decorate(), it can be used
// as a decorator for nested types
@decorate()
class BaseDecorator {
baseField: number = 100;
}
// Use BaseDecorator as a decorator
// @ts-ignore - TypeScript limitation with callable class types
@BaseDecorator()
class ExtendedClass {
extField: number = 200;
}Note: When using decorated classes as decorators (pattern 5), TypeScript may require @ts-ignore due to type checking limitations with callable class types.
Registers a hook for a specific constructor.
const { registerHook } = require('mnemonica');
registerHook(MyType, 'preCreation', (hookData) => {
console.log('Creating:', hookData.TypeName);
});For advanced TypeScript usage, the following types are exported from mnemonica:
| Type | Description | Usage |
|---|---|---|
IDEF<T> |
Base constructor function type | define('Name', fn: IDEF<MyType>) |
MnemonicaInstance |
Instance methods interface | instance.extract(), instance.pick() |
TypeClass |
Base type constructor | const MyType: TypeClass = define(...) |
DecoratedClass<T> |
Decorated class type | @decorate() class MyClass {} |
IDefinitorInstance<N, S> |
Constructor with subtypes | Returned by define() with .define() method |
ConstructorFunction<T> |
Constructor with prototype | Generic constructor function signature |
constructorOptions |
Configuration options | { strictChain: true, blockErrors: true } |
hooksTypes |
Hook type literals | 'preCreation' | 'postCreation' | 'creationError' |
hook |
Hook callback type | (opts: hooksOpts) => void |
hooksOpts |
Hook options object | Passed to hook callbacks |
TypeDef |
Type definition structure | instance.__type__ structure |
CollectionDef |
Types collection | instance.__collection__ structure |
ApplyFunction |
apply() function type | apply<E, T, S>(entity, Ctor, args) => S |
CallFunction |
call() function type | call<E, T, S>(entity, Ctor, ...args) => S |
BindFunction |
bind() function type | bind<E, T, S>(entity, Ctor) => (...args) => S |
These types enable complete type safety when defining and using mnemonica types in TypeScript projects.
The default types collection. All types defined with the top-level define() are stored here.
const { defaultTypes } = require('mnemonica');
const MyType = defaultTypes.MyType;Creates a new isolated types collection.
const { createTypesCollection } = require('mnemonica');
const myCollection = createTypesCollection();
const MyType = myCollection.define('MyType', function () {});TypesCollection Interface:
createTypesCollection() returns a TypesCollection object with the following interface:
interface TypesCollection {
// Define a new type in this collection
define: TypeAbsorber;
// Look up a type by its nested path (e.g., 'TypeName.SubType')
lookup: (path: string) => TypeClass | undefined;
// Register a hook for all types in this collection
registerHook(hookType: hooksTypes, callback: hook): void;
// Invoke hooks manually (advanced usage)
invokeHook(hookType: hooksTypes, opts: hooksOpts): void;
// Register a flow checker that runs before hooks
registerFlowChecker(callback: () => unknown): void;
// Map of all types defined in this collection
subtypes: Map<string, TypeClass>;
// Registered hooks for this collection
hooks: Record<string, hook[]>;
}Example with full configuration:
const { createTypesCollection } = require('mnemonica');
// Create isolated collection with custom config
const myCollection = createTypesCollection({
strictChain: false,
blockErrors: false,
exposeInstanceMethods: false // Hide instance methods for cleaner API
});
// Define types in isolation
const MyType = myCollection.define('MyType', function (data) {
Object.assign(this, data);
});
// Collection-level hooks
myCollection.registerHook('preCreation', (opts) => {
console.log('Creating in myCollection:', opts.TypeName);
});
// Look up types within the collection
const FoundType = myCollection.lookup('MyType');Get or set internal properties of an instance.
const { getProps, setProps } = require('mnemonica');
const props = getProps(instance);
console.log(props.__type__, props.__args__);
// Set properties
setProps(instance, { __timestamp__: Date.now() });All mnemonica instances have the following methods:
Note: You can disable instance methods by setting
exposeInstanceMethods: falsein the type configuration. When disabled, these methods are still accessible viagetProps(instance).__self__or the standaloneutilsexport.
Extracts all inherited properties into a single flat object.
const extracted = instance.extract();Picks specific properties from the instance and its inheritance chain.
const picked = instance.pick('email', 'password');
// or
const picked = instance.pick(['email', 'password']);Gets the parent instance. If constructorName is provided, walks up the chain.
const immediateParent = instance.parent();
const specificParent = instance.parent('UserType');Property that returns a cloned instance (same parent, same args).
const cloned = instance.clone;
// Note: For async constructors, use: await instance.cloneCreates a forked instance from the same parent with optional new arguments.
const forked = instance.fork(); // same args
const forkedWithNewArgs = instance.fork('new', 'args');
// Note: For async constructors, use: await instance.fork(...)Forks with a different this context (useful for Directed Acyclic Graphs).
const dagInstance = instanceA.fork.call(instanceB, 'args');Creates an exception instance from the current instance.
const error = someInstance.exception(new Error('Something went wrong'));
throw error;Access sibling types from the same collection.
const siblingType = instance.sibling('OtherType');
const sibling = instance.sibling.OtherType;All instances have non-enumerable internal properties:
| Property | Type | Description |
|---|---|---|
.__args__ |
unknown[] |
Arguments used for instance creation |
.__type__ |
TypeDef |
Type definition object |
.__parent__ |
object |
Parent instance reference |
.__subtypes__ |
Map<string, object> |
Map of available subtypes |
.__collection__ |
CollectionDef |
Types collection where type was defined |
.__stack__ |
string |
Stack trace (if submitStack: true in config) |
.__creator__ |
TypeDef |
Instance creator reference |
.__timestamp__ |
number |
Creation timestamp (ms since epoch) |
.__self__ |
object |
Self reference to the instance (useful when exposeInstanceMethods: false) |
Access via utils export:
const { utils } = require('mnemonica');Standalone extract function.
Standalone pick function.
Standalone parent function.
Parses an instance structure, returning:
name: constructor nameprops: extracted propertiesself: the instance itselfproto: prototype objectjoint: prototype propertiesparent: parent prototype
const { utils: { parse } } = require('mnemonica');
const parsed = parse(instance);Merges two instances using fork semantics.
const merged = utils.merge(instanceA, instanceB, 'args');
// Note: For async constructors, use: await utils.merge(...)Serializes an instance to JSON.
const json = utils.toJSON(instance);Collects all constructors in the instance's prototype chain.
const constructors = utils.collectConstructors(instance, true);'preCreation'- Called before instance creation'postCreation'- Called after instance creation'creationError'- Called when instance creation throws an error
Register a hook on a specific type.
MyType.registerHook('preCreation', (hookData) => {
console.log('Creating:', hookData.TypeName);
});Register a hook on a types collection.
defaultTypes.registerHook('postCreation', (hookData) => {
console.log('Created:', hookData.inheritedInstance.constructor.name);
});Register a flow checker that runs before hooks.
defaultTypes.registerFlowChecker((opts) => {
console.log('Flow check:', opts.TypeName);
});interface HookData {
TypeName: string; // Constructor name
type: TypeDef; // The type being constructed
args: unknown[]; // Arguments passed to constructor
existentInstance: object; // Parent instance
inheritedInstance: object; // New instance (postCreation only)
throwModificationError(error: Error): void; // Throw error from hook
}MyType.registerHook('postCreation', (hookData) => {
// Throw custom error from hook
hookData.throwModificationError(new Error('Custom hook error'));
});Note: In preCreation hooks, existentInstance refers to the parent; in postCreation hooks, it refers to the instance used for inheritance.
All mnemonica errors extend BASE_MNEMONICA_ERROR:
const { errors } = require('mnemonica');
// Available error types:
errors.BASE_MNEMONICA_ERROR
errors.WRONG_TYPE_DEFINITION
errors.WRONG_INSTANCE_INVOCATION
errors.WRONG_MODIFICATION_PATTERN
errors.ALREADY_DECLARED
errors.TYPENAME_MUST_BE_A_STRING
errors.HANDLER_MUST_BE_A_FUNCTION
errors.WRONG_ARGUMENTS_USED
errors.WRONG_HOOK_TYPE
errors.MISSING_HOOK_CALLBACK
errors.MISSING_CALLBACK_ARGUMENT
errors.FLOW_CHECKER_REDEFINITION
errors.OPTIONS_ERROR
errors.WRONG_STACK_CLEANERWhen creating exceptions using instance.exception():
const error = instance.exception(new Error('Original error'));
// Properties:
error.originalError // The original error
error.exceptionReason // { methodName, ... }
error.BaseStack // Base stack trace
error.parse() // Parse the exception structure
error.extract() // Extract properties from the exceptionconst { defineStackCleaner } = require('mnemonica');
// Add regex patterns to clean stack traces
defineStackCleaner(/node_modules\/some-package/);const {
SymbolParentType, // Parent type symbol
SymbolConstructorName, // Constructor name symbol
SymbolDefaultTypesCollection, // Default collection symbol
SymbolConfig, // Config symbol
MNEMONICA, // Library name
MNEMOSYNE // Collection identifier
} = require('mnemonica');Pass options as the third argument to define():
define('SomeType', function () {}, {
strictChain: true, // Only allow sub-instances from current type
blockErrors: true, // Disallow construction if error in prototype chain
submitStack: false, // Collect stack trace as __stack__ property
awaitReturn: true, // Ensure await new Constructor() returns value
ModificationConstructor: fn, // Custom modification constructor
asClass: false, // Force class mode (auto-detected by default)
exposeInstanceMethods: true // Expose instance methods (default: true for backward compatibility)
});| Option | Type | Default | Description |
|---|---|---|---|
strictChain |
boolean |
true |
If true, only direct subtypes can be instantiated. If false, allows using subtypes from parent chains. |
blockErrors |
boolean |
true |
If true, prevents construction when errors exist in the prototype chain. |
submitStack |
boolean |
false |
If true, collects stack trace and stores as __stack__ property on instances. |
awaitReturn |
boolean |
true |
For async constructors, ensures await new Constructor() returns the instance. |
asClass |
boolean |
auto |
Force class mode detection. Usually auto-detected from constructor syntax. |
exposeInstanceMethods |
boolean |
true |
Expose instance methods on the instance. Set to false to hide from TypeScript types. See details below. |
ModificationConstructor |
Function |
- | Custom constructor function for internal instance modification. |
Controls whether instance methods (extract(), pick(), parent(), clone, fork(), exception(), sibling()) are exposed on the instance type.
| Value | Behavior |
|---|---|
true (default) |
All instance methods are available directly on instances |
false |
Methods are hidden from TypeScript types but still accessible via prototype chain |
Use Case: Set to false when you want a cleaner public API and don't want internal mnemonica methods cluttering autocomplete/IntelliSense.
import { define, getProps, utils } from 'mnemonica';
// With exposeInstanceMethods: false
const CleanType = define('CleanType', function (data) {
Object.assign(this, data);
}, { exposeInstanceMethods: false });
const instance = new CleanType({ value: 42 });
// Methods not available directly on instance (TypeScript error)
// instance.extract(); // Error!
// But still accessible via getProps
const props = getProps(instance);
props.__self__.extract(); // Works!
// Or using utils
utils.extract(instance); // Works!import { defaultTypes, SymbolConfig } from 'mnemonica';
defaultTypes[SymbolConfig].blockErrors = false;TypeØmatica is a companion library that provides strict runtime type checking using JavaScript Proxies. It enforces types at runtime exactly as TypeScript expects at compile time.
import { BaseClass } from 'typeomatica';
import { decorate } from 'mnemonica';
@decorate()
class User extends BaseClass {
name: string = 'default';
age: number = 0;
}
const user = new User();
user.name = 'John'; // ✓ Works - string to string
user.age = 25; // ✓ Works - number to number
// @ts-ignore
user.age = '25'; // ✗ TypeError: Type Mismatch at runtime!For complete documentation including integration patterns, error handling, and advanced usage, see TypeØmatica.md.
This section helps AI agents understand and work with mnemonica programmatically.
Use utils.parse(instance) to understand instance structure:
const { utils: { parse } } = require('mnemonica');
const parsed = parse(instance);
// Returns: { name, props, self, proto, joint, parent, constructor }Always use getProps() instead of direct property access:
const { getProps } = require('mnemonica');
const props = getProps(instance);
// props.__type__, props.__args__, props.__parent__, props.__subtypes__, etc.const { defaultTypes } = require('mnemonica');
// List all types in default collection
const typeNames = [...defaultTypes.subtypes.keys()];
// List subtypes of a specific type
const myType = defaultTypes.MyType;
const subTypeNames = [...myType.subtypes.keys()];
// Check if a type exists
const hasType = defaultTypes.lookup('MyType') !== undefined;const { getProps } = require('mnemonica');
// Walk up the inheritance chain
function traverseChain(instance) {
const chain = [];
let current = instance;
while (current) {
const props = getProps(current);
if (!props) break;
chain.push({
typeName: props.__type__.TypeName,
timestamp: props.__timestamp__,
args: props.__args__
});
current = props.__parent__;
}
return chain;
}const { lookup } = require('mnemonica');
// Always check if type exists before construction
const MyType = lookup('MyType');
if (MyType) {
const instance = new MyType(data);
}
// For nested construction with proper error handling
try {
const subInstance = new instance.SubType(data);
} catch (error) {
// Handle WRONG_MODIFICATION_PATTERN if SubType not defined
console.error('Subtype not available:', error.message);
}const { getProps } = require('mnemonica');
function analyzeType(typeConstructor) {
return {
name: typeConstructor.TypeName,
isSubType: typeConstructor.isSubType,
subTypesCount: typeConstructor.subtypes?.size || 0,
hasHooks: Object.keys(typeConstructor.hooks || {}).length > 0,
config: typeConstructor.config
};
}const { createTypesCollection, defaultTypes } = require('mnemonica');
// Create isolated collection for testing
const testCollection = createTypesCollection({
strictChain: false,
blockErrors: false
});
// Define types in isolation
const TestType = testCollection.define('TestType', function () {});
// Register collection-level hooks
testCollection.registerHook('preCreation', (data) => {
console.log('Creating in test collection:', data.TypeName);
});const AsyncType = define('AsyncType', async function (data) {
await someAsyncOperation();
return Object.assign(this, { data });
});
// Usage
const asyncInstance = await new AsyncType('tada');
// Nested async types
const NestedAsync = AsyncType.define('NestedAsync', async function (data) {
return Object.assign(this, { nestedData: data });
});
const nested = await new asyncInstance.NestedAsync('nested');async (req, res) => {
const result = await new UserTypeConstructor({
email: req.body.email,
password: req.body.password
})
.UserEntityValidate('valid sign')
.WithoutPassword()
.AsyncPushToStorage()
.AsyncGetStorageResponse()
.SyncValidateStorageData()
.AsyncReplyToRequest(res);
};// Combine with process, window, document, etc.
const usingProcessAsProto = Singletoned.call(process, {
some: 'arguments'
});
// With React
import ReactDOM from "react-dom";
const ReactDOOMed = define("ReactDOOMed", function() {});
const usingReactAsProto = ReactDOOMed.call(ReactDOM);// Fork from different parent
const dagInstance = instanceA.fork.call(instanceB, 'args');
// Or use merge utility
const { utils: { merge } } = require('mnemonica');
const merged = merge(instanceA, instanceB, 'args');So, now you can craft as many types as you wish, combine them, re-define them and spend much more time playing with them:
- test : instances & arguments
- track : moments of creation
- check : if the order of creation is OK
- validate : everything, 4 example use sort of TS in runtime
- and even
.parsethem usingmnemonica.utils.parse
Good Luck!
MIT
Copyright (c) 2019 https://github.com/wentout

