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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 023d2e474c347003b12bef1210945016175838261a5761dae81cb90d749fe51b
4
- data.tar.gz: 04adf75c67d46adf30fabfdf996eea3ef1555678f96189444d32355571c5d12d
3
+ metadata.gz: a0caca6f4fbd9e03da42b9a5d6774f41cd3a75e0c3531e6a4a856a6db46fda31
4
+ data.tar.gz: 405900c040ca95a168ab288cdd44d3bbfb563c3513ba34bf17ca4054262591ba
5
5
  SHA512:
6
- metadata.gz: 1c6cc8aad7188bde45f3c5e535d8a7c9ceb17e0598f35ebe45791cf194b8489e799fb32680c130f4bd3f0c06e399ae900bfa8dd103fd7431fe779856fb1685cc
7
- data.tar.gz: e3eacdfae3cecd69c3b17158e4ae5610119d83e778e369479c487a1ac42e88dcb554cb9c3389cf4d1e1689a337f1bab2f0e02230d6a22b59df3b4bb2993069c3
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- signalize (1.1.0)
4
+ signalize (1.3.0)
5
5
  concurrent-ruby (~> 1.2)
6
6
 
7
7
  GEM
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 four methods (you can think of them almost like functions): `signal`, `computed`, `effect`, and `batch`.
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Signalize
4
- VERSION = "1.1.0"
4
+ VERSION = "1.3.0"
5
5
  end
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
- prevContext = Signalize.eval_context
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 = prevContext
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.1.0
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-03-25 00:00:00.000000000 Z
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