A declarative, event-driven framework for orchestrating business logic in TypeScript & Node.js applications.
Entangle.ts was born from a central idea: what if we could organize the complex logic of an application not as a tangled mess of method calls, but as a universe governed by clear, elegant physical laws?
Modern physics teaches us that reality is fundamentally based on Fields and Interactions. The particles we see are merely "excitations" or "waves" in these fields, and they interact by exchanging "messenger" particles.
This is the philosophy of Entangle.ts. Your business objects are the Particles. The events that travel through your application are the messenger Bosons. And the rules you define are the Laws of Physics, dictating how everything must behave. The result is a decoupled, resilient system with a logic that can be read like a story.
Our universe is supported by a small set of conceptual components, each with a well-defined role.
- The Analogy: The principle of Quantum Superposition states that a particle, when unobserved, exists in all its possible states at once. The famous Schrödinger's Cat is simultaneously alive and dead until the box is opened. The act of observing ("measurement") forces the universe to "choose" a single reality.
- The Role: The
Superpositionclass is the central orchestrator, the brain that holds all the "possible realities" of your application in the form of rules. When an event occurs (the "measurement"),Superpositionanalyzes the rules and "collapses" the possibilities into a concrete sequence of actions and interactions, setting the rest of our universe in motion. - Key Method:
upon(event)- Begins the definition of a law of physics that will be triggered by an event.
- The Analogy: The
Aetherwas the hypothetical medium through which light was once thought to travel.Bosons, in modern physics, are the messenger particles that carry forces and enable interactions. - The Role: We merge these concepts. The
Aetheris our event bus. Its responsibility is to emit and listen to events. TheBosonis the payload object that each event carries, containing the interaction's data and metadata (like theentanglementId). - Key Methods:
emit(),on(),once().
- The Analogy: The Higgs Field is what gives fundamental particles their mass, allowing complex matter to form. We extend this to the idea of "bubble universes" from cosmology—temporary, self-contained realities that can "bud" off from our main universe to perform specific processes.
- The Role: The
HiggsFieldis our Dependency Injection (DI) container. It gives "mass" (existence and a lifecycle) to your service particles. This is where you register, retrieve, and manage your long-lived, foundational particles (like singletons). The.createScope()method allows you to create a temporary, isolated "bubble universe" (anotherHiggsFieldinstance) to safely assemble complex dependency graphs without polluting the main scope. - Key Methods:
register(),get(),createScope().
- The Analogy: A black hole's Event Horizon is the boundary from which no information can escape. However, Hawking Radiation theorizes that information can, in fact, slowly "leak" out.
- The Role: The
EventHorizonis the immutable, long-term memory of the system, recording every event that has ever occurred.HawkingRadiationis our query tool, a "lazy pointer" that represents an intent to search theEventHorizon. It allows us to "extract" information about past events to make decisions in the present. - Key Methods:
add(),query().
- The Analogy: These are our high-precision instruments. A
QuantumPointeris a "lazy" reference to a particle that is expected to exist in a specific scope (a "bubble universe").Notationis our tool for navigating the internal structure of data with type-safety. - The Role: They solve the problem of "deferred resolution." When defining a rule, you often need to reference a particle or a piece of data that doesn't exist yet. These classes act as placeholders that are resolved only at execution time, making the API robust and declarative.
- Key Methods:
QuantumPointer.create(),Notation.create().
Important Notice: Entangle.ts is currently in an early, experimental phase and is not yet ready for production use.
This framework was born from a passion for clean, event-driven architectures and is under active and enthusiastic development. We have solidified the core concepts and the main API, creating a functional foundation that proves the model's potential.
However, many features that are critical for a production-ready system are still being built, as outlined in our Roadmap. This includes, but is not limited to:
- A comprehensive error-handling system.
- Robust circular dependency detection.
- A full suite of automated tests.
- Detailed internal logging for debugging.
If you, like me, believe in the potential of this approach and the philosophy behind Entangle.ts, your support at this early stage is invaluable.
The best way to show your support right now is to give this repository a star ⭐!
A simple star does wonders for an open-source project. It not only motivates the development but also increases the project's visibility, helping to attract new contributors and accelerating our journey toward a stable version 1.0.
Feel free to explore the code, run the examples, and open an Issue with your feedback, ideas, or bug reports. Welcome to our universe!
npm install entangle.tsimport { Superposition, InMemoryAether, HiggsField, EventHorizon, Notation } from 'entangle.ts';
// 1. Create the components of your universe
const aether = new InMemoryAether();
const higgs = new HiggsField();
const horizon = new EventHorizon();
const sp = new Superposition(aether, higgs, horizon);
// 2. Define and register your service particles
class Greeter {
sayHello(name: string) {
console.log(`Hello, ${name}!`);
}
}
higgs.register(Greeter, () => new Greeter());
// 3. Define the "laws of physics"
const conversationId = 'welcome-flow';
// 3.1 When this event happens
sp.upon('user.joined')
// 3.2 Use the already built Greeter service
.use(Greeter)
// 3.3 Specify the entanglement ID to group related events
.entanglement(conversationId)
// 3.4 Define the action to take
.call('sayHello')
// 3.5 Define which data to pass to the action
.with(
HawkingRadiation.from(
horizon
.query()
// The first array is the list of events, our history
// The second one is the list of arguments generated by the event we are looking for
.using(Notation.create<[[{ name: string }]]>().index(0).index(0).property('name'))
)
)
// 3.6 Finally, save the rule to be executed when needed
.then();
// 4. Put the universe in motion
aether.emit('user.joined', conversationId, { name: 'Alex' });
// Expected Output: Hello, Alex!This example shows how to use scopes ("bubble universes") and pointers to assemble a complex dependency graph in a decoupled way.
// --- 1. Define Particles ---
class Logger {
info(message: string) {
console.log(`[INFO] ${message}`);
}
}
class Database {
constructor(private connectionString: string) {}
connect() {
console.log('DB Connected!');
return this;
}
}
class UserRepository {
constructor(private db: Database) {}
// ... methods to interact with the database
}
// --- 2. Setup The Universe ---
const mainHiggs = new HiggsField();
mainHiggs.register(Logger, () => new Logger());
const startupScope = mainHiggs.createScope(); // Create our temporary lab
const STARTUP_ID = 'system_startup';
// --- 3. Define Bootstrap Rules ---
// Rule A: On 'app.start', create the DB connection in the temporary scope
sp.upon('app.start')
.build(Database)
.entanglement(STARTUP_ID)
.using('user:pass@host:port')
.inScope(startupScope)
.then(() => aether.emit('db.ready', STARTUP_ID));
// Rule B: When the DB is ready, build the Repository
sp.upon('db.ready')
.build(UserRepository)
.entanglement(STARTUP_ID)
.using(
QuantumPointer.create(Database, startupScope) // Pointer to the particle in the scope
)
.then((repo) => {
// Promote the fully built repository to the main HiggsField as a singleton
mainHiggs.register(UserRepository, () => repo);
console.log('Application is ready!');
aether.emit('app.ready', STARTUP_ID);
});
// --- 4. Initiate The Process ---
console.log('Starting application...');
aether.emit('app.start', STARTUP_ID);This is an open-source project, and we would love your help! If you've found a bug, have an idea for a new feature, or want to improve the documentation, please open an Issue on GitHub.
Distributed under the MIT License.