A simple, intuitive, and strictly-typed reactive state manager.
Heavily inspired by fewkz/cells and dphfox/fusion.
A cell represents a value that can change — a reactive state object. When a cell’s value is updated via .set(), any subscribers added via .subscribe() will be triggered with the new value. You can create a cell using the cell() function and passing an initial value:
local Chaconne = require('./chaconne')
local cell = Chaconne.cell
local name = cell("Adrian")
name:subscribe(function(new_name)
print(`My name is {new_name}!`)
end)
name:set("Steven") -- Output: My name is Steven!
name:set("Yuvin") -- Output: My name is Yuvin!A derived cell is a reactive value that depends on other cells to compute its result. Derived cells are created using the formula() function, and they must explicitly access other cells using the provided use() helper as without it, changes to the cell it is dependent on won't propagate to that derived cell.
-- ...
local formula = Chaconne.formula
local applePrice = cell(1)
local money = cell(20)
local applesCanBuy = formula(function(use)
return use(money) // use(applePrice)
end)
applesCanBuy:subscribe(function(amount)
print(`You can buy {amount} apples with {money:get()} dollars.`)
end)
applePrice:set(2) -- Output: You can buy 10 apples with 20 dollars.
money:set(100) -- Output: You can buy 50 apples with 100 dollars.cellobjects are plain tables. All internal data (value, subscribers, janitor functions) is stored in the.internalfield. This field should be treated as private — do not mutate it directly.- If the new value passed to
.set()is the same as the current value (as determined by==), the update is skipped. This makes Chaconne lazy in propagation — it only reacts when values actually change.
-
☁ Super duper lightweight — This library is a single file with under 100 lines of code. No dependencies, no setup — just drop it in and go.
-
🔒 Strictly typed — Designed with Luau’s strict type system in mind for reliable autocompletion and early error detection. Also — no
getfenvtricks like fewkz's cells (no shade on fewkz though!). -
🧹 Explicit memory control — Built-in
.dispose()support and ajanitorsystem mean you decide exactly when a reactive chain should be cleaned up. No leaks, no surprises. -
🧪 Predictable and testable — No hidden globals or scheduler black magic. You can test logic in isolation with pure Lua semantics.
-
⚙️ Fusion just works — If you’re already deep in the Fusion ecosystem — or rely heavily on its reactive graph, bindings, and automatic lifecycle — then yeah, stick with Fusion. It’s mature, powerful, and built for UI-heavy use cases.
-
🔁 No automatic batching or dependency graphs — Chaconne is simple and direct. It doesn't track dependency chains or defer updates. If you need efficient batched updates or graph-level diffing, this isn’t it.
-
🔍 You’re allergic to manual disposal — Chaconne gives you control, but with that comes responsibility. If you don’t call
.dispose()when you should, your program will leak.
If you value minimalism, tight control, and clear mental models over automation and complexity — Chaconne’s for you.
