Skip to content

jorgeguberte/multiverse

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Multiverse State (multiverse-js)

Vitest Tests Code Coverage License

An application state management library to solve the "Single-Timeline State Paradox" by introducing Git-style branching semantics directly into synchronous local memory space.

Leanness by Design — Unlike Redux/Immer-history setups that accumulate memory leaks over long sessions, multiverse automatically destroys patch history the moment a branch is merged or discarded via native Garbage Collection. No commit graphs, no time-travel overhead — just clean, predictable memory.

Deterministic UI Synchronization — Built for the single-threaded React event loop. The Last-Write-Wins (LWW) merge strategy mirrors how async server responses and background workers commit mutations sequentially through deterministic event handlers. No complex 3-way merge UI needed — because in React, mutations are already serialized.


The Problem

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:

  1. Deep clone the object into a temporary draftData state, and write custom logic to apply changes back to the main store when the user hits "Save".
  2. 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.

The Paradigm Shift

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).


Usage for Mammalian Brains

1. Initialize your Universe

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!
});

2. Forking a Draft (Optimistic Updates)

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');

3. Native React Integration

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"
}

4. Merging or Discarding

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');
}

Quick Demos

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:agents

If you want the full pass:

npm run demo:all

What 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.

🚀 Performance (Production Benchmarks)

Tested on WSL2 Ubuntu, Node.js 22.22.0 — 10,000 element tree, single deep mutation:

Operation Throughput Latency Complexity
Fork Branch 2.9M ops/sec 431 ns O(1) — pointer copy
Single Path Mutation 65 ops/sec 15 ms O(K) — only mutated path
Dual Branch + LWW Merge 50K ops/sec 22 μs O(K) — batched patches
Atomic Date/Map/Set Replace ~225K ops/sec 5–7 μs O(1) — no Proxy overhead

Key differentiators:

  • fork() = pure pointer assignment (3M ops/sec)
  • mutate() = 15ms on 10k tree (no O(N) scan — lineage key tracking)
  • merge() = 50K ops/sec (short-circuit finalizeDraft via mutatedChildren Map)
  • Date/Map/Set/custom classes = atomic replacement (internal slot protection)

Run locally: npm run bench

React Demo

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:build

It includes:

  • A modal-style draft editor with fork, merge, and discard.
  • A merge playground for relative array patching and conflict resolution.
  • An async orchestration scene that simulates parallel agent branches.

Advanced Edge-Case Resolution (CRDT-Lite)

Due to our strict Identity Contract and custom Patch Engine, multiverse-js intelligently applies only the semantic delta.

The Orphan Problem & Identity Tracking

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.

Strict Array Homogeneity

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.

"Last-Branch-Wins" = Deterministic UI Sync

If two branches both modify config.theme simultaneously, the branch that merges last functionally overwrites the property. This is not a limitation — it mirrors the single-threaded React event loop where async responses arrive sequentially and the last setState wins. No visual conflict resolver needed; the UI thread serializes naturally.

True Longest Common Subsequence (LCS) for Primitives

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.

Design Trade-offs (Honest)

We Don't Do Why What You Get Instead
Time-travel / checkout / commit graph No memory for abandoned history; GC cleans on discard()/merge() Leanness by Design — zero memory leaks, predictable O(K) memory
3-way visual merge / CRDT semantic resolution React event loop is single-threaded; mutations serialize naturally Deterministic UI Sync — LWW matches React's setState semantics
Proxies for Date, Map, Set, custom classes Internal slots break Proxy traps Atomic Replacement — safe, predictable replace patches
Rebase / concurrent branch divergence Scope: optimistic UI drafts, not multi-user real-time Micro-Branching — modal drafts, async agent orchestration

Use multiverse for: Modal drafts, optimistic UI, async agent pipelines, form isolation. Look elsewhere (Yjs, Automerge, Redux) for: Multi-user collab, full history audit, offline-first sync.


📄 License & Liability

This project is open-source and licensed under the permissive Boost Software License 1.0 (BSL-1.0).

⚠️ Disclaimer

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software.

You are free to use it in commercial apps, startups, or personal projects entirely at your own risk.

Support the Project

If multiverse helps you manage complex UI branching state or simplifies your architecture, consider buying me a coffee or sponsoring development via GitHub Sponsors! Your support helps keep the project maintained and feature-rich.

About

Git-like branching for synchronous local UI state. Fork, mutate, and merge application state to painlessly handle optimistic updates and complex drafts. Features an O(K) copy-on-write Proxy engine, CRDT-lite array conflict resolution, and a native React hook.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors