signalize 1.0.0 → 1.1.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 +4 -1
- data/README.md +66 -11
- data/lib/signalize/version.rb +1 -1
- data/lib/signalize.rb +27 -15
- data/signalize.gemspec +1 -1
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 023d2e474c347003b12bef1210945016175838261a5761dae81cb90d749fe51b
|
4
|
+
data.tar.gz: 04adf75c67d46adf30fabfdf996eea3ef1555678f96189444d32355571c5d12d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c6cc8aad7188bde45f3c5e535d8a7c9ceb17e0598f35ebe45791cf194b8489e799fb32680c130f4bd3f0c06e399ae900bfa8dd103fd7431fe779856fb1685cc
|
7
|
+
data.tar.gz: e3eacdfae3cecd69c3b17158e4ae5610119d83e778e369479c487a1ac42e88dcb554cb9c3389cf4d1e1689a337f1bab2f0e02230d6a22b59df3b4bb2993069c3
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
1
3
|
## [Unreleased]
|
2
4
|
|
5
|
+
## [1.1.0] - 2023-03-25
|
6
|
+
|
7
|
+
- Provide better signal/computed inspect strings (fixes #1)
|
8
|
+
- Use Concurrent::Map for thread-safe globals (fixes #3)
|
9
|
+
|
10
|
+
## [1.0.1] - 2023-03-08
|
11
|
+
|
12
|
+
- Prevent early returns in effect blocks
|
13
|
+
- Use gem's error class (fixes #2)
|
14
|
+
|
3
15
|
## [1.0.0] - 2023-03-07
|
4
16
|
|
5
17
|
- Initial release
|
data/Gemfile.lock
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
signalize (1.
|
4
|
+
signalize (1.1.0)
|
5
|
+
concurrent-ruby (~> 1.2)
|
5
6
|
|
6
7
|
GEM
|
7
8
|
remote: https://rubygems.org/
|
8
9
|
specs:
|
9
10
|
coderay (1.1.3)
|
11
|
+
concurrent-ruby (1.2.2)
|
10
12
|
ffi (1.15.5)
|
11
13
|
formatador (1.1.0)
|
12
14
|
guard (2.18.0)
|
@@ -44,6 +46,7 @@ GEM
|
|
44
46
|
|
45
47
|
PLATFORMS
|
46
48
|
arm64-darwin-21
|
49
|
+
x86_64-linux
|
47
50
|
|
48
51
|
DEPENDENCIES
|
49
52
|
guard
|
data/README.md
CHANGED
@@ -32,6 +32,8 @@ Signalize's public API consists of four methods (you can think of them almost li
|
|
32
32
|
The first building block is the `Signalize::Signal` class. You can think of this as a reactive value object which wraps an underlying primitive like String, Integer, Array, etc.
|
33
33
|
|
34
34
|
```ruby
|
35
|
+
require "signalize"
|
36
|
+
|
35
37
|
counter = Signalize.signal(0)
|
36
38
|
|
37
39
|
# Read value from signal, logs: 0
|
@@ -44,6 +46,7 @@ counter.value = 1
|
|
44
46
|
You can include the `Signalize::API` mixin to access these methods directly in any context:
|
45
47
|
|
46
48
|
```ruby
|
49
|
+
require "signalize"
|
47
50
|
include Signalize::API
|
48
51
|
|
49
52
|
counter = signal(0)
|
@@ -56,6 +59,7 @@ counter.value += 1
|
|
56
59
|
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".
|
57
60
|
|
58
61
|
```ruby
|
62
|
+
require "signalize"
|
59
63
|
include Signalize::API
|
60
64
|
|
61
65
|
name = signal("Jane")
|
@@ -82,6 +86,7 @@ puts full_name.value
|
|
82
86
|
Effects are callbacks which are executed whenever values which the effect has "subscribed" to by referencing them have changed. An effect callback is run immediately when defined, and then again for any future mutations.
|
83
87
|
|
84
88
|
```ruby
|
89
|
+
require "signalize"
|
85
90
|
include Signalize::API
|
86
91
|
|
87
92
|
name = signal("Jane")
|
@@ -99,6 +104,7 @@ name.value = "John"
|
|
99
104
|
You can dispose of an effect whenever you want, thereby unsubscribing it from signal notifications.
|
100
105
|
|
101
106
|
```ruby
|
107
|
+
require "signalize"
|
102
108
|
include Signalize::API
|
103
109
|
|
104
110
|
name = signal("Jane")
|
@@ -117,11 +123,62 @@ dispose.()
|
|
117
123
|
surname.value = "Doe 2"
|
118
124
|
```
|
119
125
|
|
126
|
+
**IMPORTANT:** you cannot use `return` or `break` within an effect block. Doing so will raise an exception (due to it breaking the underlying execution model).
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
def my_method(signal_obj)
|
130
|
+
effect do
|
131
|
+
return if signal_obj.value > 5 # DON'T DO THIS!
|
132
|
+
|
133
|
+
puts signal_obj.value
|
134
|
+
end
|
135
|
+
|
136
|
+
# more code here
|
137
|
+
end
|
138
|
+
```
|
139
|
+
|
140
|
+
Instead, try to resolve it using more explicit logic:
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
def my_method(signal_obj)
|
144
|
+
should_exit = false
|
145
|
+
|
146
|
+
effect do
|
147
|
+
should_exit = true && next if signal_obj.value > 5
|
148
|
+
|
149
|
+
puts signal_obj.value
|
150
|
+
end
|
151
|
+
|
152
|
+
return if should_exit
|
153
|
+
|
154
|
+
# more code here
|
155
|
+
end
|
156
|
+
```
|
157
|
+
|
158
|
+
However, there's no issue if you pass in a method proc directly:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
def my_method(signal_obj)
|
162
|
+
@signal_obj = signal_obj
|
163
|
+
|
164
|
+
effect &method(:an_effect_method)
|
165
|
+
|
166
|
+
# more code here
|
167
|
+
end
|
168
|
+
|
169
|
+
def an_effect_method
|
170
|
+
return if @signal_obj.value > 5
|
171
|
+
|
172
|
+
puts @signal_obj.value
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
120
176
|
### `batch { }`
|
121
177
|
|
122
|
-
You can write to
|
178
|
+
You can write to multiple signals within a batch, and flush the updates at all once (thereby notifying computed refreshes and effects).
|
123
179
|
|
124
180
|
```ruby
|
181
|
+
require "signalize"
|
125
182
|
include Signalize::API
|
126
183
|
|
127
184
|
name = signal("Jane")
|
@@ -132,8 +189,8 @@ full_name = computed { name.value + " " + surname.value }
|
|
132
189
|
dispose = effect { puts full_name.value }
|
133
190
|
|
134
191
|
batch do
|
135
|
-
|
136
|
-
|
192
|
+
name.value = "Foo"
|
193
|
+
surname.value = "Bar"
|
137
194
|
end
|
138
195
|
```
|
139
196
|
|
@@ -142,6 +199,7 @@ end
|
|
142
199
|
You can explicitly subscribe to a signal signal value and be notified on every change. (Essentially the Observable pattern.) In your block, the new signal value will be supplied as an argument.
|
143
200
|
|
144
201
|
```ruby
|
202
|
+
require "signalize"
|
145
203
|
include Signalize::API
|
146
204
|
|
147
205
|
counter = signal(0)
|
@@ -158,17 +216,18 @@ counter.value = 1 # logs the new value
|
|
158
216
|
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`.
|
159
217
|
|
160
218
|
```ruby
|
219
|
+
require "signalize"
|
161
220
|
include Signalize::API
|
162
221
|
|
163
222
|
counter = signal(0)
|
164
223
|
effect_count = signal(0)
|
165
224
|
|
166
225
|
effect do
|
167
|
-
|
226
|
+
puts counter.value
|
168
227
|
|
169
|
-
|
170
|
-
|
171
|
-
|
228
|
+
# Whenever this effect is triggered, increase `effect_count`.
|
229
|
+
# But we don't want this signal to react to `effect_count`
|
230
|
+
effect_count.value = effect_count.peek + 1
|
172
231
|
end
|
173
232
|
```
|
174
233
|
|
@@ -189,7 +248,3 @@ This project is intended to be a safe, welcoming space for collaboration, and co
|
|
189
248
|
## License
|
190
249
|
|
191
250
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
192
|
-
|
193
|
-
## Code of Conduct
|
194
|
-
|
195
|
-
Everyone interacting in the Signalize project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/whitefusionhq/signalize/blob/main/CODE_OF_CONDUCT.md).
|
data/lib/signalize/version.rb
CHANGED
data/lib/signalize.rb
CHANGED
@@ -1,23 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "concurrent"
|
3
4
|
require_relative "signalize/version"
|
4
5
|
|
5
6
|
module Signalize
|
6
7
|
class Error < StandardError; end
|
7
8
|
|
8
9
|
class << self
|
9
|
-
def
|
10
|
+
def global_map_accessor(name)
|
10
11
|
define_singleton_method "#{name}" do
|
11
|
-
|
12
|
+
GLOBAL_MAP[name]
|
12
13
|
end
|
13
14
|
define_singleton_method "#{name}=" do |value|
|
14
|
-
|
15
|
+
GLOBAL_MAP[name] = value
|
15
16
|
end
|
16
17
|
end
|
17
18
|
end
|
18
19
|
|
19
20
|
def self.cycle_detected
|
20
|
-
raise "Cycle detected"
|
21
|
+
raise Signalize::Error, "Cycle detected"
|
21
22
|
end
|
22
23
|
|
23
24
|
RUNNING = 1 << 0
|
@@ -27,24 +28,26 @@ module Signalize
|
|
27
28
|
HAS_ERROR = 1 << 4
|
28
29
|
TRACKING = 1 << 5
|
29
30
|
|
31
|
+
GLOBAL_MAP = Concurrent::Map.new
|
32
|
+
|
30
33
|
# Computed | Effect | nil
|
31
|
-
|
32
|
-
|
34
|
+
global_map_accessor :eval_context
|
35
|
+
self.eval_context = nil
|
33
36
|
|
34
37
|
# Effects collected into a batch.
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
global_map_accessor :batched_effect
|
39
|
+
self.batched_effect = nil
|
40
|
+
global_map_accessor :batch_depth
|
41
|
+
self.batch_depth = 0
|
42
|
+
global_map_accessor :batch_iteration
|
43
|
+
self.batch_iteration = 0
|
41
44
|
|
42
45
|
# NOTE: we have removed the global version optimization for Ruby, due to
|
43
46
|
# the possibility of long-running server processes and the number reaching
|
44
47
|
# a dangerously high integer value.
|
45
48
|
#
|
46
|
-
#
|
47
|
-
#
|
49
|
+
# global_map_accessor :global_version
|
50
|
+
# self.global_version = 0
|
48
51
|
|
49
52
|
Node = Struct.new(
|
50
53
|
:_version,
|
@@ -339,7 +342,7 @@ module Signalize
|
|
339
342
|
end
|
340
343
|
|
341
344
|
def end_effect(effect, prev_context, *_) # allow additional args for currying
|
342
|
-
raise "Out-of-order effect" if eval_context != effect
|
345
|
+
raise Signalize::Error, "Out-of-order effect" if eval_context != effect
|
343
346
|
|
344
347
|
cleanup_sources(effect)
|
345
348
|
self.eval_context = prev_context
|
@@ -436,6 +439,10 @@ module Signalize
|
|
436
439
|
end
|
437
440
|
|
438
441
|
def peek = @value
|
442
|
+
|
443
|
+
def inspect
|
444
|
+
"#<#{self.class} value: #{peek.inspect}>"
|
445
|
+
end
|
439
446
|
end
|
440
447
|
|
441
448
|
class Computed < Signal
|
@@ -586,8 +593,13 @@ module Signalize
|
|
586
593
|
finis = _start
|
587
594
|
|
588
595
|
begin
|
596
|
+
compute_executed = false
|
589
597
|
@_cleanup = _compute.() if (@_flags & DISPOSED).zero? && @_compute.nil?.!
|
598
|
+
compute_executed = true
|
590
599
|
ensure
|
600
|
+
unless compute_executed
|
601
|
+
raise Signalize::Error, "Early return or break detected in effect block"
|
602
|
+
end
|
591
603
|
finis.(nil) # TODO: figure out this weird shit
|
592
604
|
end
|
593
605
|
end
|
data/signalize.gemspec
CHANGED
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.require_paths = ["lib"]
|
28
28
|
|
29
29
|
# Uncomment to register a new dependency of your gem
|
30
|
-
|
30
|
+
spec.add_dependency "concurrent-ruby", "~> 1.2"
|
31
31
|
|
32
32
|
# For more information and examples about making a new gem, check out our
|
33
33
|
# guide at: https://bundler.io/guides/creating_gem.html
|
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.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jared White
|
@@ -9,8 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-03-
|
13
|
-
dependencies:
|
12
|
+
date: 2023-03-25 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: concurrent-ruby
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.2'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.2'
|
14
28
|
description: A Ruby port of Signals, providing reactive variables, derived computed
|
15
29
|
state, side effect callbacks, and batched updates.
|
16
30
|
email:
|