@rbxts/rl 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,39 @@
1
+ # rl
2
+ `rl` is a simple reactive library for Luau, inspired by [Vide](https://centau.github.io/vide)'s sources and effects system.
3
+
4
+ # API Reference
5
+ You can read `rl`'s API reference [here](https://pesde.dev/packages/wiam/rl/latest/any/docs/api_ref).
6
+
7
+ # Demo
8
+ ```luau
9
+ --!strict
10
+
11
+ local rl = require("path/to/rl")
12
+ local s = rl.state
13
+ local e = rl.effect
14
+
15
+ local name = s("john", true)
16
+ local login_year = s(2020)
17
+
18
+ local login_d = e(function(last_login : number)
19
+ print(`{name.value}, it's been {login_year() - (last_login or 0)} years!`)
20
+ end)
21
+
22
+ login_year(2025) -- "wiam, it's been 5 years!"
23
+ login_year(2025) -- doesn't print because value didn't change
24
+ login_year(2025, true) -- "wiam, it's been 0 years!" - this prints since it's forced
25
+
26
+ name("doe") -- doesn't print because we are reading with name.value, and not name()
27
+
28
+ e(function(old_name : string)
29
+ print(`hey {old_name}, your name has been changed to '{name()}'`)
30
+ end)
31
+
32
+ name("jane") -- "hey doe, your name has been changed to jane"
33
+ name("jane") -- "hey jane, your name has been changed to jane" - this one is forced by default
34
+
35
+ login_d()
36
+ login_year(2026) -- doesn't print because the effect was disconnected
37
+
38
+ rl.cleanup() -- disconnect all effects
39
+ ```
package/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # rl
2
+ `rl` is a simple reactive library for Luau, inspired by [Vide](https://centau.github.io/vide)'s sources and effects system.
3
+
4
+ # Installation
5
+ ## Pesde
6
+ ```console
7
+ # pesde add wiam/rl && pesde install
8
+ ```
9
+
10
+ ## Wally
11
+ Add the following to your `wally.toml`, in the `[dependencies]` section:
12
+ ```toml
13
+ rl = "wiam/rl@<version>"
14
+ ```
15
+
16
+ Then run:
17
+ ```console
18
+ # wally install
19
+ ```
20
+
21
+ # API Reference
22
+ You can read the API reference in `rl`'s [Pesde package page](https://pesde.dev/packages/wiam/rl/latest/any/docs/api_ref).
23
+
24
+ # Demo
25
+ ```luau
26
+ --!strict
27
+
28
+ local rl = require("path/to/rl")
29
+ local s = rl.state
30
+ local e = rl.effect
31
+
32
+ local name = s("john", true)
33
+ local login_year = s(2020)
34
+
35
+ local login_d = e(function(last_login : number)
36
+ print(`{name.value}, it's been {login_year() - (last_login or 0)} years!`)
37
+ end)
38
+
39
+ login_year(2025) -- "wiam, it's been 5 years!"
40
+ login_year(2025) -- doesn't print because value didn't change
41
+ login_year(2025, true) -- "wiam, it's been 0 years!" - this prints since it's forced
42
+
43
+ name("doe") -- doesn't print because we are reading with name.value, and not name()
44
+
45
+ e(function(old_name : string)
46
+ print(`hey {old_name}, your name has been changed to '{name()}'`)
47
+ end)
48
+
49
+ name("jane") -- "hey doe, your name has been changed to jane"
50
+ name("jane") -- "hey jane, your name has been changed to jane" - this one is forced by default
51
+
52
+ login_d()
53
+ login_year(2026) -- doesn't print because the effect was disconnected
54
+
55
+ rl.cleanup() -- disconnect all effects
56
+ ```
@@ -0,0 +1,10 @@
1
+ name = "wiam/rl"
2
+ version = "0.1.11"
3
+ description = "Simple reactive library for Luau."
4
+ authors = ["wiam"]
5
+ repository = "https://codeberg.org/wiam/rl"
6
+ license = "GPL-3.0-or-later"
7
+ includes = ["src/*", "pesde.toml", "COPYING", "README.md", "docs/*"]
8
+
9
+ [indices]
10
+ default = "https://github.com/pesde-pkg/index"
package/build.luau ADDED
@@ -0,0 +1,30 @@
1
+ --!strict
2
+
3
+ local fs = require("@lune/fs")
4
+ local serde = require("@lune/serde")
5
+
6
+ local TARGETS = {"luau", "lune", "roblox"}
7
+ local INCLUDES = {"src", "COPYING", "PESDE_README.md", "docs"}
8
+
9
+ local base_manifest = fs.readFile("base_pesde.toml")
10
+
11
+ if fs.isDir("build") then
12
+ fs.removeDir("build")
13
+ end
14
+
15
+ fs.writeDir("build")
16
+
17
+ for _, target in TARGETS do
18
+ local out_dir = "build/"..target
19
+ fs.writeDir(out_dir)
20
+
21
+ for _, include in INCLUDES do
22
+ fs.copy(include, out_dir.."/"..if include == "PESDE_README.md" then "README.md" else include)
23
+ end
24
+
25
+ fs.writeFile(out_dir.."/pesde.toml", base_manifest.."\n"..serde.encode("toml", {target = {
26
+ environment = target,
27
+ lib = "src/init.luau",
28
+ build_files = target == "roblox" and {"src"}
29
+ }}, true))
30
+ end
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "rl",
3
+ "tree": {
4
+ "$path": "src"
5
+ }
6
+ }
@@ -0,0 +1,202 @@
1
+ # rl API Reference
2
+ ## Types
3
+ ### state\<T>
4
+ The [state](https://pesde.dev/packages/wiam/rl/latest/any/docs/concepts#state) type.
5
+
6
+ ```luau
7
+ type state<T> = (() -> T) & ((value : T, force : boolean?) -> T) & {
8
+ value : T
9
+ }
10
+ ```
11
+
12
+ ### callback
13
+ Function which gets called when its [effect](https://pesde.dev/packages/wiam/rl/latest/any/docs/concepts#effect) is triggered.
14
+ Takes the previous value of the state which triggered the effect, as well as its "index", determined by the order in which it appears within the function.
15
+
16
+ ```luau
17
+ type callback<T> = (old_value : T, state_i : number?) -> any
18
+ ```
19
+
20
+ ## Functions
21
+ ### state
22
+ Creates a new [state](https://pesde.dev/packages/wiam/rl/latest/any/docs/concepts#state), with the given initial value, and whether to always [force](https://pesde.dev/packages/wiam/rl/latest/any/docs/concepts#forcing), and returns it.
23
+
24
+ ```luau
25
+ function state<T>(value : T?, force : boolean?) : state<T>
26
+
27
+ -- Example
28
+ local health = state(100)
29
+ local stamina = state(100, true)
30
+ ```
31
+
32
+ ### derive
33
+ Wraps an Effect around the given callback which sets a newly created State's value
34
+ to be the return value of the callback.
35
+ Returns the created State, the Effect's disconnect function and a flag which can be ignored.
36
+
37
+ This is useful for States which depend on the value of another State, since their value will
38
+ be updated only when the State they depend on is also updated, and not every time it is read,
39
+ as how would happen with a function which's return value depends on a State.
40
+
41
+ ```luau
42
+ function derive<T, U>(callback : () -> U) : (state<U>, () -> (), true)
43
+
44
+ -- Example
45
+ -- without derive() (normal function)
46
+ local half_health = function()
47
+ print("some computation")
48
+ return health() / 2
49
+ end
50
+
51
+ print(half_health()) -- "some computation" 50
52
+ print(half_health()) -- "some computation" 50
53
+ health(50)
54
+ print(half_health()) -- "some computation" 25
55
+ print(half_health()) -- "some computation" 25
56
+
57
+ -- with derive()
58
+ local derive_half_health = derive(half_health) -- "some computation"
59
+
60
+ print(derive_half_health()) -- 25
61
+ print(derive_half_health()) -- 25
62
+ health(20) -- "some computation"
63
+ print(derive_half_health()) -- 10
64
+ print(derive_half_health()) -- 10
65
+ ```
66
+
67
+ ### effect
68
+ Creates a new [effect](https://pesde.dev/packages/wiam/rl/latest/any/docs/concepts#effect), with the given [callback](https://pesde.dev/packages/wiam/rl/latest/any/docs/api_ref#callback) function, and returns its disconnect function.
69
+
70
+ ```luau
71
+ function effect<T>(callback : callback<T>) : () -> ()
72
+
73
+ -- Example
74
+ effect(function()
75
+ print(`new health: {health()}`)
76
+ end)
77
+
78
+ health(10) -- "new health: 10"
79
+ health(10) -- doesn't print because the new value is the same as the current one
80
+ health(10, true) -- "new health: 10"; this prints because we forced it
81
+
82
+ local disconnect = effect(function()
83
+ print(`new stamina: {stamina()}`)
84
+ end)
85
+
86
+ stamina(90) -- "new stamina: 90"
87
+ stamina(90) -- "new stamina: 90"; this prints because `stamina` is always forced.
88
+ disconnect()
89
+
90
+ stamina(40) -- doesn't print because we disconnected the effect.
91
+ ```
92
+
93
+ ### batch
94
+ Batches any State write operation up until the function is done running.
95
+
96
+ ```luau
97
+ function batch(f : () -> any) : ()
98
+
99
+ -- Example
100
+ batch(function()
101
+ health(420)
102
+ health(1337, true)
103
+ health(69)
104
+ end)
105
+
106
+ -- "new health: 69"
107
+
108
+ ```
109
+
110
+ ### scope
111
+ Returns a function which calls the given functions.
112
+ Any non-function or function which precedes a "true" value by 2 indices is going to be skipped (the second happens because of derives).
113
+ This is intended to be used for disconnecting Effects in bulk.
114
+
115
+ ```luau
116
+ function scope<T>(... : T) : () -> ()
117
+
118
+ -- Example
119
+ -- without scope
120
+ local disconnect_health = effect(function()
121
+ print(`your health is : {health()}`)
122
+ end)
123
+
124
+ local disconnect_stamina = effect(function()
125
+ print(`your stamina is : {stamina()}`)
126
+ end)
127
+
128
+ -- ...
129
+
130
+ disconnect_health()
131
+ disconnect_stamina()
132
+
133
+ -- with scope
134
+ local vitals_scope = scope(
135
+ effect(function()
136
+ print(`your health is : {health()}`)
137
+ end),
138
+ effect(function()
139
+ print(`your stamina is : {stamina()}`)
140
+ end)
141
+ )
142
+
143
+ -- ...
144
+
145
+ vitals_scope()
146
+ ```
147
+
148
+ ### branch
149
+ Alternative `if`-like function to be used within effects. Learn more about it in the [tracking section of the Concepts page](https://pesde.dev/packages/wiam/rl/latest/any/docs/concepts#tracking).
150
+ Pass a condition to be checked, and after it a function to be run if it's successful.
151
+ The last value, if a function, and if not after a condition, will be threated as the "else" branch.
152
+
153
+ ```luau
154
+ function branch(...) : ()
155
+
156
+ -- Example
157
+ -- A single effect for the same functionality as the examples above, thanks to the use of state_i and the branching function.
158
+ effect(function(_, state_i)
159
+ branch(
160
+ state_i == 1, function()
161
+ print(`new health: {health()}`)
162
+ end,
163
+ state_i == 2, function()
164
+ print(`new stamina: {stamina()}`)
165
+ end
166
+ )
167
+ end)
168
+ ```
169
+
170
+ ### mirror
171
+ Takes a table and for every function or state, creates an effect which automatically updates the element to be either the function's return value, or, if a state, to its value.
172
+ The created effects' disconnect functions are put at t\["disconnect"..k], where k is either the key, if it's a string, or the index at which it was iterated over.
173
+ Returns the given table, as well as a function which disconnects all created effects.
174
+
175
+ ```luau
176
+ function mirror<K, V>(t : {[K] : V}) : ({[K] : V}, () -> ())
177
+
178
+ -- Example
179
+ local name = state("john")
180
+ local data = mirror {
181
+ username = name,
182
+ }
183
+
184
+ print(data.username) -- "john"
185
+ name("joe")
186
+ print(data.username) -- "joe"
187
+ data.disconnect_username()
188
+
189
+ name("doe")
190
+ print({data.username}) -- "joe"; didn't update because we disconnected the effect
191
+ ```
192
+
193
+ ### cleanup
194
+ Disconnects all [effects](https://pesde.dev/packages/wiam/rl/latest/any/docs/concepts#effect).
195
+
196
+ ```luau
197
+ function cleanup() : ()
198
+
199
+ -- Example
200
+ cleanup()
201
+ health(77) -- doesn't print because no effect is connected to this state (we just disconnected)
202
+ ```
@@ -0,0 +1,21 @@
1
+ # rl concepts
2
+ ## Effect
3
+ An Effect is the binding of a function to an event triggered when States read (with tracking) within it are written.
4
+
5
+ ## Tracking
6
+ Tracking, or registering, is the process done internally by `rl` for assigning States to Effects, so that the latters' functions can be ran when the formers' values are written.\
7
+ This works by calling an Effect's function once when created to check which States are read (with tracking) within it.\
8
+ But this introduces a problem: when branching within the function, only the branch which satisfies the condition is going to be checked.\
9
+ That could be solved by reading all the States that are going to be used before any branch, but it misses the point of the library.\
10
+ Instead, `rl` solves this by providing the `branch` function, which works like standard branching, but runs all branches when it's doing the tracking process, so that all States are correctly assigned.
11
+
12
+ ## State
13
+ A State is a container for a value.\
14
+ Internally, it is an userdata that has a metatable with the `__index` and `__newindex` metamethods, which permit reading and writing the value with special mechanisms.\
15
+ Call it with no arguments for reading with tracking.\
16
+ Call with arguments for writing, where the first argument is the new value, and the second one is whether to force for this specific call.\
17
+ Access the `.value` field for reading without tracking. Note that this is read-only, and attempting to modify it will cause an error. `rawset` doesn't work either, since an userdata is not a table.
18
+
19
+ ## Forcing
20
+ Forcing is when you write a new value to a State, and always trigger its Effects, even when the new value is the same as the old one.\
21
+ States, when created, can be opted in to always force their values; you can overwrite this in individual write operations.
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@rbxts/rl",
3
+ "version": "0.1.11",
4
+ "description": "simple reactive library",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://codeberg.org/wiam/rl"
11
+ },
12
+ "license": "GPL-3.0-or-later",
13
+ "author": "wi_am",
14
+ "main": "src/init.luau",
15
+ "typings": "src/index.d.ts"
16
+ }
package/pesde.toml ADDED
@@ -0,0 +1,14 @@
1
+ name = "wiam/rl_root"
2
+ version = "1.0.0"
3
+ license = "GPL-3.0-or-later"
4
+ workspace_members = ["build/*"]
5
+ private = true
6
+
7
+ [target]
8
+ environment = "lune"
9
+
10
+ [scripts]
11
+ build = "./build.luau"
12
+
13
+ [indices]
14
+ default = "https://github.com/pesde-pkg/index"
package/publish.sh ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ pesde run build && pesde update && pesde publish -y && wally publish
package/rokit.toml ADDED
@@ -0,0 +1,4 @@
1
+ [tools]
2
+ wally = "UpliftGames/wally@0.3.2"
3
+ rojo = "rojo-rbx/rojo@7.6.1"
4
+ lune = "lune-org/lune@0.10.4"
package/src/index.d.ts ADDED
@@ -0,0 +1,74 @@
1
+ export type state<T> = {
2
+ (new_value: T, force?: boolean): T
3
+ (): T
4
+ readonly value: T
5
+ }
6
+
7
+ export type callback<T> = (old_value: T, state_i?: number) => any
8
+
9
+ /**
10
+ * Disconnects all Effects.
11
+ */
12
+ export function cleanup(): void
13
+
14
+ /**
15
+ * Creates a State.
16
+ *
17
+ * @param value T -- Initial value.
18
+ * @param force? boolean -- Whether Effects connected to this State will trigger, despite the fact that the value didn't change.
19
+ * @returns state<T> -- State. Call with no arguments for reading with tracking, call with arguments for writing (2nd argument is for forcing), read .value field for reading without tracking.
20
+ */
21
+ export function state<T>(value: T, force?: boolean): state<T>
22
+
23
+ /**
24
+ * Batches any State write operation up until the function is done running.
25
+ * @param f () => any -- The function.
26
+ */
27
+ export function batch(f: () => any): void
28
+
29
+ /**
30
+ * Creates an Effect, a binding of the given function to the event of States read (by calling) within it being updated.
31
+ *
32
+ * @param callback callback<T> -- The function to be bound.
33
+ * @returns () => void -- Function which disconnects the Effect from the States.
34
+ */
35
+ export function effect<T>(callback: callback<T>): () => void
36
+
37
+ /**
38
+ * Returns a function which calls the given functions.
39
+ * Any non-function or function which precedes a "true" value by 2 indices is going to be skipped (the second happens because of derives).
40
+ * This is intended to be used for disconnecting Effects in bulk.
41
+ * @param ... () => () -- The functions.
42
+ * @returns () => () -- The function caller.
43
+ */
44
+ export function scope<T>(...t : T[]): () => void
45
+
46
+ /**
47
+ * Wraps an Effect around the given callback which sets a newly created State's value
48
+ * to be the return value of the callback.
49
+ * Returns the created State, the Effect's disconnect function and a flag which can be ignored.
50
+ * This is useful for States which depend on the value of another State, since their value will
51
+ * be updated only when the State they depend on is also updated, and not every time it is read,
52
+ * as how would happen with a function which's return value depends on a State.
53
+ * @param callback () => U -- the callback
54
+ * @returns state<U> -- the State that was created
55
+ * @returns () => void -- the created Effect's disconnect function
56
+ * @returns true -- a flag used internally by rl.scope(); can be ignored.
57
+ */
58
+ export function derive<T, U>(callback: () => U) : [state<U>, () => void, true]
59
+
60
+ /**
61
+ * Branching function to be used within Effects.
62
+ * Pass a condition to be checked, and after it a function to be run if it's successful.
63
+ * The last value, if a function, and if not after a condition, will be threated as the "else" branch.
64
+ */
65
+ export function branch(...a : any) : void
66
+
67
+ /**
68
+ * Creates an effect for every function or State within the given table, which updates the key to either the return value of the function, or the value of the State.
69
+ * Each Effect's disconnect function gets put in the table as t["disconnect_"..key], when key is a string, or as t["disconnect"_..i], where i is the numerical index at which the key was iterated over.
70
+ * @param t {[K] : V} -- The table.
71
+ * @return {[K] : V} -- The same table.
72
+ * @returns () => void -- Function which disconnects and eliminates each disconnect function of the created Effects.
73
+ */
74
+ export function mirror<K, V>(t : Map<K, V>): [Map<K, V>, () => void]