An application state management library to solve the "Single-Timeline State Paradox" by introducing Git-style branching semantics directly into synchronous local memory space.
In modern React, Vue, or Vanilla frontend apps, we constantly struggle with Optimistic UI Updates and Modal Drafts. Consider opening a modal to edit a complex data object. You have two choices:
- Deep clone the object into a temporary
draftDatastate, and write custom logic to apply changes back to the main store when the user hits "Save". - Mutate the main store immediately, and write complex logic to reverse mutations if the user hits "Cancel" or if an API call fails.
This is because we live in a Single-Timeline paradigm.
What if your local application state worked like Git branches?
With multiverse-js, you fork your state. You mutate your sandbox timeline exactly like you would mutate the main timeline, with O(K) performance thanks to under-the-hood Proxy lazy-evaluation.
If the API call fails or the user cancels? You simply discard() the branch. The timeline collapses painlessly.
If the API call succeeds? You merge() the branch. The universe patches effortlessly fold your changes into the main timeline, gracefully handling conflicts (even if the main timeline advanced or deleted objects while you were branched).
import { Multiverse } from 'multiverse-js';
const state = new Multiverse({
metrics: { connections: 0 },
users: [{ id: 1, name: 'Alice', role: 'Admin' }],
lastSync: new Date() // Complex instances are preserved natively!
});Open your modal component and just render the isolated branch!
// Create a temporary timeline
state.fork('optimistic-save');
state.mutate(draft => {
// This draft mutation is fully isolated!
// Powered by a lazy Proxy engine, it only clones what you actually touch (Copy-on-Write).
const alice = draft.users.find(u => u.id === 1);
if (alice) alice.name = 'Alice Wonderland';
}, 'optimistic-save');multiverse-js ships with a useMultiverse hook powered by useSyncExternalStore for tear-free concurrent rendering.
import { useMultiverse } from 'multiverse-js/react';
import { state } from './store';
function OptimisticProfile() {
// Subscribe specifically to the 'optimistic-save' branch.
// The component only re-renders when the `users` array changes in this branch.
const users = useMultiverse(state, (s) => s.users, 'optimistic-save');
return <div>{users[0].name}</div>; // Renders "Alice Wonderland"
}React to async API boundaries without writing complex rollback reducers.
try {
await api.saveUser(1, { name: 'Alice Wonderland' });
// It worked! Effortlessly push the patched timeline to reality.
state.merge('optimistic-save');
state.squash(); // Optional: Garbage collect the patch history to free memory
} catch (error) {
// It failed! Throw the sandbox away. No rollbacks required.
state.discard('optimistic-save');
}These demos run locally with tsc + node and do not require React or API keys.
npm run demo:optimistic
npm run demo:merge
npm run demo:agentsIf you want the full pass:
npm run demo:allWhat each demo shows:
demo:optimistic: modal draft workflow with isolated edits, cancel, and save.demo:merge: primitive arrays, entity arrays, and last-merge-wins conflict semantics.demo:agents: async branch orchestration without depending on an external LLM.
There is also a small browser playground in examples/react-demo that uses the shipped useMultiverse hook directly from src/.
npm run demo:react:dev
npm run demo:react:buildIt includes:
- A modal-style draft editor with
fork,merge, anddiscard. - A merge playground for relative array patching and conflict resolution.
- An async orchestration scene that simulates parallel agent branches.
Due to our strict Identity Contract and custom Patch Engine, multiverse-js intelligently applies only the semantic delta.
To guarantee structural safety, objects inside arrays must have an id property. If Branch A is modifying a deeply nested property (e.g., user.profile.age), but Main deleted the user entirely before Branch A could merge, multiverse-js gracefully ignores the orphan patch. It tracks the entity by its id, recognizes it is natively gone, and collapses the mutation. No silent array-index overwriting.
To prevent silent data loss, multiverse-js enforces strict array homogeneity. An array must consist of 100% primitives OR 100% objects with id properties. Mixing primitives and objects in the same array will throw a runtime error during mutation, keeping your data structures predictable.
If two branches both modify config.theme simultaneously, the branch that merges last functionally overwrites the property, natively mirroring expectations of asynchronous API resolutions.
If your array contains primitive values (e.g., ['A', 'B', 'A']), the engine uses a rigorous dynamic programming LCS diffing algorithm. If a branch appends 'C' and the main timeline unshifts 'X', merging results perfectly in ['X', 'A', 'B', 'A', 'C'] without destroying sequence data or getting confused by duplicates.