signalize 1.1.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +1 -1
- data/README.md +73 -3
- data/lib/signalize/struct.rb +91 -0
- data/lib/signalize/version.rb +1 -1
- data/lib/signalize.rb +27 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0caca6f4fbd9e03da42b9a5d6774f41cd3a75e0c3531e6a4a856a6db46fda31
|
4
|
+
data.tar.gz: 405900c040ca95a168ab288cdd44d3bbfb563c3513ba34bf17ca4054262591ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2620410cdb72ea66cf1a6a9deef3d2d945b9208fd80c3d351a4946f31ae3288302c095420a3c8c4351688bef6fb788c936de5e2aead20e6003ae001d5303290c
|
7
|
+
data.tar.gz: 44c96f16d953c00127df8a21905515e29accdac583117ccd52d7c5f410fe190bc98962c173968bda2f460227745a462fade990aba26a4c4ddbbf55b6f23904b1
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,18 @@
|
|
2
2
|
|
3
3
|
## [Unreleased]
|
4
4
|
|
5
|
+
## [1.3.0] - 2023-10-04
|
6
|
+
|
7
|
+
- Provide `Signalize::Struct`, a struct-like object to hold multiple signals (including computed)
|
8
|
+
(optional via `require "signalize/struct"`)
|
9
|
+
|
10
|
+
## [1.2.0] - 2023-10-03
|
11
|
+
|
12
|
+
- Add `untracked` method (implements #5)
|
13
|
+
- Add `mutation_detected` check for `computed`
|
14
|
+
|
15
|
+
Gem now roughly analogous to `@preact/signals-core` v1.5
|
16
|
+
|
5
17
|
## [1.1.0] - 2023-03-25
|
6
18
|
|
7
19
|
- Provide better signal/computed inspect strings (fixes #1)
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -25,7 +25,7 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
25
25
|
|
26
26
|
## Usage
|
27
27
|
|
28
|
-
Signalize's public API consists of
|
28
|
+
Signalize's public API consists of five methods (you can think of them almost like functions): `signal`, `untracked`, `computed`, `effect`, and `batch`.
|
29
29
|
|
30
30
|
### `signal(initial_value)`
|
31
31
|
|
@@ -54,6 +54,27 @@ counter = signal(0)
|
|
54
54
|
counter.value += 1
|
55
55
|
```
|
56
56
|
|
57
|
+
### `untracked { }`
|
58
|
+
|
59
|
+
In case when you're receiving a callback that can read some signals, but you don't want to subscribe to them, you can use `untracked` to prevent any subscriptions from happening.
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
require "signalize"
|
63
|
+
include Signalize::API
|
64
|
+
|
65
|
+
counter = signal(0)
|
66
|
+
effect_count = signal(0)
|
67
|
+
fn = proc { effect_count.value + 1 }
|
68
|
+
|
69
|
+
effect do
|
70
|
+
# Logs the value
|
71
|
+
puts counter.value
|
72
|
+
|
73
|
+
# Whenever this effect is triggered, run `fn` that gives new value
|
74
|
+
effect_count.value = untracked(&fn)
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
57
78
|
### `computed { }`
|
58
79
|
|
59
80
|
You derive computed state by accessing a signal's value within a `computed` block and returning a new value. Every time that signal value is updated, a computed value will likewise be updated. Actually, that's not quite accurate — the computed value only computes when it's read. In this sense, we can call computed values "lazily-evaluated".
|
@@ -211,7 +232,7 @@ end
|
|
211
232
|
counter.value = 1 # logs the new value
|
212
233
|
```
|
213
234
|
|
214
|
-
### `peek`
|
235
|
+
### `signal.peek`
|
215
236
|
|
216
237
|
If you need to access a signal's value inside an effect without subscribing to that signal's updates, use the `peek` method instead of `value`.
|
217
238
|
|
@@ -231,6 +252,55 @@ effect do
|
|
231
252
|
end
|
232
253
|
```
|
233
254
|
|
255
|
+
## Signalize Struct
|
256
|
+
|
257
|
+
An optional add-on to Signalize, the `Singalize::Struct` class lets you define multiple signal or computed variables to hold in struct-like objects. You can even add custom methods to your classes with a simple DSL. (The API is intentionally similar to `Data` in Ruby 3.2+, although these objects are of course mutable.)
|
258
|
+
|
259
|
+
Here's what it looks like:
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
require "signalize/struct"
|
263
|
+
|
264
|
+
include Signalize::API
|
265
|
+
|
266
|
+
TestSignalsStruct = Signalize::Struct.define(
|
267
|
+
:str,
|
268
|
+
:int,
|
269
|
+
:multiplied_by_10
|
270
|
+
) do # optional block for adding methods
|
271
|
+
def increment!
|
272
|
+
self.int += 1
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
struct = TestSignalsStruct.new(
|
277
|
+
int: 0,
|
278
|
+
str: "Hello World",
|
279
|
+
multiplied_by_10: computed { struct.int * 10 }
|
280
|
+
)
|
281
|
+
|
282
|
+
effect do
|
283
|
+
puts struct.multiplied_by_10 # 0
|
284
|
+
end
|
285
|
+
|
286
|
+
effect do
|
287
|
+
puts struct.str # "Hello World"
|
288
|
+
end
|
289
|
+
|
290
|
+
struct.increment! # above effect will now output 10
|
291
|
+
struct.str = "Goodbye!" # above effect will now output "Goodbye!"
|
292
|
+
```
|
293
|
+
|
294
|
+
If you ever need to get at the actual `Signal` object underlying a value, just call `*_signal`. For example, you could call `int_signal` for the above example to get a signal object for `int`.
|
295
|
+
|
296
|
+
Signalize structs require all of their members to be present when initializing…you can't pass only some keyword arguments.
|
297
|
+
|
298
|
+
Signalize structs support `to_h` as well as `deconstruct_keys` which is used for pattern matching and syntax like `struct => { str: }` to set local variables.
|
299
|
+
|
300
|
+
You can call `members` (as both object/class methods) to get a list of the value names in the struct.
|
301
|
+
|
302
|
+
Finally, both `inspect` and `to_s` let you debug the contents of a struct.
|
303
|
+
|
234
304
|
## Development
|
235
305
|
|
236
306
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/rake test` to run the tests, or `bin/guard` or run them continuously in watch mode. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -239,7 +309,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
239
309
|
|
240
310
|
## Contributing
|
241
311
|
|
242
|
-
Signalize is considered a direct port of the [original Signals JavaScript library](https://github.com/preactjs/signals). This means we are unlikely to accept any additional features other than what's provided by Signals. If Signals adds new functionality in the future, we will endeavor to replicate it in Signalize. Furthermore, if there's some unwanted behavior in Signalize that's also present in Signals, we are unlikely to modify that behavior.
|
312
|
+
Signalize is considered a direct port of the [original Signals JavaScript library](https://github.com/preactjs/signals). This means we are unlikely to accept any additional features other than what's provided by Signals (unless it's completely separate, like our `Signalize::Struct` add-on). If Signals adds new functionality in the future, we will endeavor to replicate it in Signalize. Furthermore, if there's some unwanted behavior in Signalize that's also present in Signals, we are unlikely to modify that behavior.
|
243
313
|
|
244
314
|
However, if you're able to supply a bugfix or performance optimization which will help bring Signalize _more_ into alignment with its Signals counterpart, we will gladly accept your PR!
|
245
315
|
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require "signalize"
|
2
|
+
|
3
|
+
module Signalize
|
4
|
+
class Struct
|
5
|
+
module Accessors
|
6
|
+
def members
|
7
|
+
@members ||= []
|
8
|
+
end
|
9
|
+
|
10
|
+
def signal_accessor(*names)
|
11
|
+
names.each do |name|
|
12
|
+
members.push(name.to_sym) unless members.find { _1 == name.to_sym }
|
13
|
+
signal_getter_name = "#{name}_signal".freeze
|
14
|
+
ivar_name = "@#{name}".freeze
|
15
|
+
|
16
|
+
define_method "#{name}_signal" do
|
17
|
+
instance_variable_get(ivar_name)
|
18
|
+
end
|
19
|
+
|
20
|
+
define_method name do
|
21
|
+
send(signal_getter_name)&.value
|
22
|
+
end
|
23
|
+
|
24
|
+
define_method "#{name}=" do |val|
|
25
|
+
if instance_variable_defined?(ivar_name)
|
26
|
+
raise Signalize::Error, "Cannot assign a signal to a signal value" if val.is_a?(Signalize::Signal)
|
27
|
+
|
28
|
+
sig = instance_variable_get(ivar_name)
|
29
|
+
if sig.is_a?(Signalize::Computed)
|
30
|
+
raise Signalize::Error, "Cannot set value of computed signal `#{ivar_name.delete_prefix("@")}'"
|
31
|
+
end
|
32
|
+
|
33
|
+
sig.value = val
|
34
|
+
else
|
35
|
+
val = Signalize.signal(val) unless val.is_a?(Signalize::Computed)
|
36
|
+
instance_variable_set(ivar_name, val)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
extend Accessors
|
44
|
+
|
45
|
+
def self.define(*names, &block)
|
46
|
+
Class.new(self).tap do |struct|
|
47
|
+
struct.signal_accessor(*names)
|
48
|
+
struct.class_eval(&block) if block
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize(**data)
|
53
|
+
# The below code is all to replicate native Ruby ergonomics
|
54
|
+
unknown_keys = data.keys - members
|
55
|
+
unless unknown_keys.empty?
|
56
|
+
plural_suffix = unknown_keys.length > 1 ? "s" : ""
|
57
|
+
raise ArgumentError, "unknown keyword#{plural_suffix}: #{unknown_keys.map { ":#{_1}" }.join(", ")}"
|
58
|
+
end
|
59
|
+
|
60
|
+
missing_keys = members - data.keys
|
61
|
+
unless missing_keys.empty?
|
62
|
+
plural_suffix = missing_keys.length > 1 ? "s" : ""
|
63
|
+
raise ArgumentError, "missing keyword#{plural_suffix}: #{missing_keys.map { ":#{_1}" }.join(", ")}"
|
64
|
+
end
|
65
|
+
|
66
|
+
# Initialize with keyword arguments
|
67
|
+
data.each do |k, v|
|
68
|
+
send("#{k}=", v)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def members = self.class.members
|
73
|
+
|
74
|
+
def deconstruct_keys(...) = to_h.deconstruct_keys(...)
|
75
|
+
|
76
|
+
def to_h = members.each_with_object({}) { _2[_1] = send("#{_1}_signal").peek }
|
77
|
+
|
78
|
+
def inspect
|
79
|
+
var_peeks = instance_variables.filter_map do |var_name|
|
80
|
+
var = instance_variable_get(var_name)
|
81
|
+
"#{var_name.to_s.delete_prefix("@")}=#{var.peek.inspect}" if var.is_a?(Signalize::Signal)
|
82
|
+
end.join(", ")
|
83
|
+
|
84
|
+
"#<#{self.class}#{var_peeks.empty? ? nil : " #{var_peeks}"}>"
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_s
|
88
|
+
inspect
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/signalize/version.rb
CHANGED
data/lib/signalize.rb
CHANGED
@@ -21,6 +21,10 @@ module Signalize
|
|
21
21
|
raise Signalize::Error, "Cycle detected"
|
22
22
|
end
|
23
23
|
|
24
|
+
def self.mutation_detected
|
25
|
+
raise Signalize::Error, "Computed cannot have side-effects"
|
26
|
+
end
|
27
|
+
|
24
28
|
RUNNING = 1 << 0
|
25
29
|
NOTIFIED = 1 << 1
|
26
30
|
OUTDATED = 1 << 2
|
@@ -34,6 +38,10 @@ module Signalize
|
|
34
38
|
global_map_accessor :eval_context
|
35
39
|
self.eval_context = nil
|
36
40
|
|
41
|
+
# Used by `untracked` method
|
42
|
+
global_map_accessor :untracked_depth
|
43
|
+
self.untracked_depth = 0
|
44
|
+
|
37
45
|
# Effects collected into a batch.
|
38
46
|
global_map_accessor :batched_effect
|
39
47
|
self.batched_effect = nil
|
@@ -414,6 +422,8 @@ module Signalize
|
|
414
422
|
end
|
415
423
|
|
416
424
|
def value=(value)
|
425
|
+
Signalize.mutation_detected if Signalize.eval_context.is_a?(Signalize::Computed)
|
426
|
+
|
417
427
|
if value != @value
|
418
428
|
Signalize.cycle_detected if Signalize.batch_iteration > 100
|
419
429
|
|
@@ -484,7 +494,7 @@ module Signalize
|
|
484
494
|
return true
|
485
495
|
end
|
486
496
|
|
487
|
-
|
497
|
+
prev_context = Signalize.eval_context
|
488
498
|
begin
|
489
499
|
Signalize.prepare_sources(self)
|
490
500
|
Signalize.eval_context = self
|
@@ -499,7 +509,7 @@ module Signalize
|
|
499
509
|
@_flags |= HAS_ERROR
|
500
510
|
@_version += 1
|
501
511
|
end
|
502
|
-
Signalize.eval_context =
|
512
|
+
Signalize.eval_context = prev_context
|
503
513
|
Signalize.cleanup_sources(self)
|
504
514
|
@_flags &= ~RUNNING
|
505
515
|
|
@@ -667,6 +677,21 @@ module Signalize
|
|
667
677
|
Signalize.end_batch
|
668
678
|
end
|
669
679
|
end
|
680
|
+
|
681
|
+
def untracked
|
682
|
+
return yield unless Signalize.untracked_depth.zero?
|
683
|
+
|
684
|
+
prev_context = Signalize.eval_context
|
685
|
+
Signalize.eval_context = nil
|
686
|
+
Signalize.untracked_depth += 1
|
687
|
+
|
688
|
+
begin
|
689
|
+
return yield
|
690
|
+
ensure
|
691
|
+
Signalize.untracked_depth -= 1
|
692
|
+
Signalize.eval_context = prev_context
|
693
|
+
end
|
694
|
+
end
|
670
695
|
end
|
671
696
|
|
672
697
|
extend API
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: signalize
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jared White
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-
|
12
|
+
date: 2023-10-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: concurrent-ruby
|
@@ -42,6 +42,7 @@ files:
|
|
42
42
|
- README.md
|
43
43
|
- Rakefile
|
44
44
|
- lib/signalize.rb
|
45
|
+
- lib/signalize/struct.rb
|
45
46
|
- lib/signalize/version.rb
|
46
47
|
- signalize.gemspec
|
47
48
|
homepage: https://github.com/whitefusionhq/signalize
|