From 41d59cdfade19ec4dc9572c23f2576b52d12cfc6 Mon Sep 17 00:00:00 2001 From: littensy <56808540+littensy@users.noreply.github.com> Date: Mon, 12 Jan 2026 17:24:36 -0800 Subject: [PATCH] chore: bump deps --- .lute/check.luau | 78 +++++++++--------------------------- README.md | 52 +++++++++++++----------- packages/charm/src/init.luau | 18 +++++---- rokit.toml | 4 +- 4 files changed, 58 insertions(+), 94 deletions(-) diff --git a/.lute/check.luau b/.lute/check.luau index 4f9e96a..4117286 100644 --- a/.lute/check.luau +++ b/.lute/check.luau @@ -1,51 +1,7 @@ local fs = require("@std/fs") -local json = require("@std/json") local net = require("@lute/net") local process = require("@lute/process") -local CURRENT_FFLAGS = "https://clientsettingscdn.roblox.com/v1/settings/application?applicationName=PCStudioApp" -local FFLAG_KINDS = { "FFlag", "FInt" } - -local function analyze() - local args = { - "luau-lsp", - "analyze", - "--sourcemap=sourcemap.json", - "--ignore=**/node_modules/**", - "--base-luaurc=.luaurc", - "--definitions=roblox.d.luau", - "--no-flags-enabled", - } - - local response = net.request(CURRENT_FFLAGS, { method = "GET" }) - local data: any = json.deserialize(response.body) - - for name, value in data.applicationSettings do - if type(name) ~= "string" then - continue - end - - for _, kind in FFLAG_KINDS do - if string.find(name, `^{kind}Luau`) then - table.insert(args, `--flag:{string.sub(name, #kind + 1)}={value}`) - end - end - end - - table.insert(args, "packages") - table.insert(args, "tests") - table.insert(args, "benches") - - local result = process.run(args) - local stderr = string.gsub(result.stderr, "Unknown FFlag[^\n]+\n", "") - - print(stderr) - - if result.exitcode == 1 then - process.exit(1) - end -end - process.run({ "rojo", "sourcemap", "--output=sourcemap.json" }, { stdio = "inherit" }) fs.writestringtofile( @@ -53,24 +9,26 @@ fs.writestringtofile( net.request("https://luau-lsp.pages.dev/globalTypes.None.d.luau", { method = "GET" }).body ) -analyze() - --- process.run({ --- "luau-lsp", --- "analyze", --- "--sourcemap=sourcemap.json", --- "--ignore=**/node_modules/**", --- "--base-luaurc=.luaurc", --- "--defs=roblox.d.luau", --- "packages", --- "tests", --- "benches", --- }, { stdio = "inherit" }) +local analyze = process.run({ + "luau-lsp", + "analyze", + "--sourcemap=sourcemap.json", + "--ignore=**/node_modules/**", + "--base-luaurc=.luaurc", + "--definitions=roblox.d.luau", + "packages", + "tests", + "benches", +}, { stdio = "inherit" }) fs.remove("roblox.d.luau") -process.run({ "selene", "packages", "tests", "benches" }, { stdio = "inherit" }) +local selene = process.run({ "selene", "packages", "tests", "benches" }, { stdio = "inherit" }) -process.run({ "stylua", "--check", "packages", "tests", "benches" }, { stdio = "inherit" }) +local stylua = process.run({ "stylua", "--check", "packages", "tests", "benches" }, { stdio = "inherit" }) -process.run({ "pnpm", "eslint", "packages" }, { stdio = "inherit" }) +local eslint = process.run({ "pnpm", "eslint", "packages" }, { stdio = "inherit" }) + +if not (analyze.ok and selene.ok and stylua.ok and eslint.ok) then + process.exit(1) +end diff --git a/README.md b/README.md index b5a7a2f..a31b977 100644 --- a/README.md +++ b/README.md @@ -233,10 +233,9 @@ Effects are fundamental to reactivity, allowing you to react to signal updates. ```luau local getCounter, setCounter = signal(0) --- Count is 0 effect(function() print(`Count is {getCounter()}`) -end) +end) -- Count is 0 setCounter(1) -- Count is 1 ``` @@ -257,22 +256,33 @@ setCounter(1) -- Cleanup 0 dispose() -- Cleanup 1 ``` -Effects can be nested and they will clean up automatically when the parent effect re-runs or gets disposed. This means you don't have to manually clean up inner effects, and this applies to functions like `subscribe()` and `observe()`. +### Nested Effects + +Effects can be nested inside other effects. When the outer effect re-runs, inner effects from the previous run are automatically cleaned up, and new inner effects are created if needed. The system ensures proper execution order; outer effects always run before their inner effects: ```luau -local getCounter, setCounter = signal(0) +local getShow, setShow = signal(true) +local getCount, setCount = signal(1) effect(function() - print(`Outer: {getCounter()}`) - effect(function() - print(`Inner: {getcounter()}`) - end) -end) + if getShow() then + -- This inner effect is created when show() is true + effect(function() + print(`Count is: {getCount()}`) + end) + end +end) -- Output: Count is: 1 -setCounter(1) -- Outer: 1, Inner: 1 -setCounter(2) -- Outer: 2, Inner: 2 +setCount(2) -- Count is: 2 + +-- When show becomes false, the inner effect is cleaned up +setShow(false) -- No output + +setCount(3) -- No output (inner effect no longer exists) ``` +Nested effect cleanup also applies to every reaction API (`subscribe()`, `listen()`, `observe()`) because they are built upon effects. + > [!NOTE] > To run code that is "detached" from the parent effect or scope, use `untracked()` or a detached effect scope. If you suspect that the new nested effect behavior is causing issues with migration, try disabling the `flags.trackInnerEffects` flag to assist with debugging. @@ -289,17 +299,15 @@ local users, setUsers = reactive({ { name = "John", surname = "Doe" }, }) --- Output: John Doe effect(function() print(`{users[1].name} {users[1].surname}`) -end) +end) -- Output: John Doe --- Output: Jane Smith setUsers(function(state) state[1].name = "Jane" state[1].surname = "Smith" table.insert(state, { name = "Steve", surname = "Doe" }) -end) +end) -- Output: Jane Smith -- You can also mutate the reactive proxy directly: users[1].name = "John" -- John Smith @@ -327,10 +335,9 @@ In case you want to opt-out of dependency tracking in an effect, you can use `un local getTracked, setTracked = signal(0) local getUntracked, setUntracked = signal(0) --- Tracked: 0, Untracked: 0 effect(function() print(`Tracked: {getTracked()}, Untracked: {untracked(getUntracked)}`) -end) +end) -- Output: Tracked: 0, Untracked: 0 setTracked(1) -- Tracked: 1, Untracked: 0 setUntracked(1) -- No output @@ -364,10 +371,9 @@ Similar to `untracked()`, but does not prevent the parent effect from tracking n ```luau local getCounter, setCounter = signal(0) --- Count is 0 local disposeOuter = effect(function() print(`Count is {peek(getCounter)}`) -end) +end) -- Output: Count is 0 setCounter(1) -- No output; count was accessed in peek() ``` @@ -430,7 +436,7 @@ The callback also receives the previous value, or `nil` when running for the fir ```luau local getCounter, setCounter = signal(0) --- Count is 0 (was nil) +-- Output: Count is 0 (was nil) listen(getCounter, function(count, prevCount) print(`Count is {count} (was {prevCount})`) end) @@ -595,9 +601,7 @@ effect(function() setCounter(function(count) return math.min(count + 1, 3) end) -end) - --- Output: 0, 1, 2, 3 +end) -- Output: 0, 1, 2, 3 ``` --- @@ -931,7 +935,7 @@ This means nilable values may be replaced with `None` in patches, and code worki Charm v0.11 introduces _a lot_ of breaking changes, so below are some tips that might help you migrate from an older version.
-Terminology changes +Terminology - Signal: A state container with one function to get the state, and another to update it - Atom: A signal with the getter and setter combined into one function diff --git a/packages/charm/src/init.luau b/packages/charm/src/init.luau index a1058d0..0fbacc7 100644 --- a/packages/charm/src/init.luau +++ b/packages/charm/src/init.luau @@ -191,7 +191,9 @@ local function runCleanups(cleanups: { () -> () }) for key, cleanup in cleanups do cleanups[key] = nil local prevSub = setActiveSub(nil) - xpcall(cleanup, warn or print) + xpcall(cleanup, function(err) + warn("Error occurred during effect cleanup:", debug.traceback(tostring(err), 2)) + end) activeSub = prevSub end end @@ -799,14 +801,14 @@ local function batch(fn: (Args...) -> Results..., ...: Args end --[[ - Converts a non-signal into a computed signal. If the function is already a - signal, it is returned as-is. Useful for memoizing a getter function - without creating unnecessary nested signals. + Wraps a function in a computed signal if it is not already a reactive + signal. Useful for memoizing a getter function without creating unnecessary + nested signals. @param fn A signal or function that accesses signals - @return Computed signal if `fn` is not already a signal + @return `fn` wrapped in a computed signal if needed ]] -local function toSignal(fn: () -> T): () -> T +local function toComputed(fn: () -> T): () -> T local name = debug.info(fn, "n") return if name == "readSignal" or name == "readComputed" or name == "atomOper" then fn else computed(fn) end @@ -825,7 +827,7 @@ end @see https://github.com/littensy/charm?tab=readme-ov-file#listengetter-callback ]] local function listen(getter: () -> T, fn: (state: T, previous: T?) -> ()): () -> () - local tracked = toSignal(getter) + local tracked = toComputed(getter) local current: T return effect(function() @@ -849,7 +851,7 @@ end @see https://github.com/littensy/charm?tab=readme-ov-file#subscribegetter-callback ]] local function subscribe(getter: () -> T, fn: (state: T, lastState: T) -> ()): () -> () - local tracked = toSignal(getter) + local tracked = toComputed(getter) local current: T local firstRun = true diff --git a/rokit.toml b/rokit.toml index a9c0ebb..3393915 100644 --- a/rokit.toml +++ b/rokit.toml @@ -3,5 +3,5 @@ wally = "UpliftGames/wally@0.3.2" rojo = "rojo-rbx/rojo@7.6.1" stylua = "johnnymorganz/stylua@2.3.1" selene = "Kampfkarren/selene@0.29.0" -luau-lsp = "johnnymorganz/luau-lsp@1.57.1" -lute = "luau-lang/lute@0.1.0-nightly.20251213" +luau-lsp = "johnnymorganz/luau-lsp@1.60.0" +lute = "luau-lang/lute@0.1.0-nightly.20260112"