debounced 0.1.17 → 0.1.20

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: beb111cd5043d97b903d9e89278437564f65fd5794f4531d6e5c6e57dca79859
4
- data.tar.gz: b872450de202d28e51d32eac774b27238a328972a7b3363c0ca77d5b137897f4
3
+ metadata.gz: f0b2a5d98cbf80bf222866ad69db51d71b000ea1e13a7bf16d7ddc638aa91923
4
+ data.tar.gz: f031cae66cd65f40da805111ac8b31140de3e901f19f742bffe1ab593627bb27
5
5
  SHA512:
6
- metadata.gz: f70040e9197b72f7e8b91b6bae9d736a3f840c6da34817722949f23a4d82c6b798a9a0011345ca623ba7bd4fc2bab48fa498d41cebf70dd59a9d7459d277416d
7
- data.tar.gz: af31341ddf36fa276c5ad79db3cecdf975795bd1ac130005f18b9cbbab880b157645aa7e2d5c63199a98b999eae1b85e9daee061a2717162c27567d1d11c2564
6
+ metadata.gz: d498b7e15fb3aef46c5789d1df43d143970daf40a48bd9f71d5f0664330cda646d9d6d36e8936a334acefdb62d40c3e966ccff7b5fcfeaabf2ea47cf41edada9
7
+ data.tar.gz: 05ec409dbede721aab6457c29b564a8d8565d87208b3816ec37f1e8a4e3aa2049a4f663e8612e3969ab4e0023058e52f14351529f27f6e8eb710969d9be7b130
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Debounced
2
2
 
3
- A Ruby gem that provides a NodeJS-based event debouncing service for Ruby applications. It uses the JavaScript micro event loop to efficiently debounce events.
3
+ Efficient debouncing mechanism for Ruby events. Use it for rate limiting, deduplication, or other
4
+ scenarios where you want to wait for a certain amount of time before processing a given event.
4
5
 
5
6
  ## Installation
6
7
 
@@ -26,15 +27,7 @@ $ gem install debounced
26
27
 
27
28
  This gem requires Node.js to be installed on your system, as it uses a Node.js server to handle the debouncing logic. You'll need:
28
29
 
29
- - Node.js >= 14.0.0
30
- - npm (to install the required node packages)
31
-
32
- After installing the gem, run:
33
-
34
- ```bash
35
- $ cd $(bundle show debounced)
36
- $ npm install
37
- ```
30
+ - Node.js >= 20.0.0
38
31
 
39
32
  ## Usage
40
33
 
@@ -44,51 +37,70 @@ $ npm install
44
37
  # config/initializers/debounced.rb
45
38
  Debounced.configure do |config|
46
39
  config.socket_descriptor = '/tmp/my_app.debounceEvents'
40
+ config.wait_timeout = 3 # idle timeout in seconds for a given activity descriptor
47
41
  end
48
42
  ```
49
43
 
50
44
  ### Starting the server
51
45
 
52
- You can start the debounce server with:
46
+ Start the nodeJS debounce server with:
53
47
 
54
48
  ```bash
55
- $ bundle exec debounced-server
49
+ $ bundle exec debounced:server
56
50
  ```
57
51
 
58
- Or in your application code:
52
+ In your Ruby application code:
59
53
 
60
54
  ```ruby
61
55
  require 'debounced'
62
56
 
63
- # Start the listener thread
57
+ # Start a background thread to receive notification that events are ready to be handled after debounce wait is complete
64
58
  proxy = Debounced::ServiceProxy.new
65
- listener_thread = proxy.listen
59
+ proxy.listen
66
60
 
67
- # Debounce an event
61
+ # Define your event class; create a helper method that will produce a Debounced::Callback object, which
62
+ # is used to notify the server that the event is ready to be handled
68
63
  class MyEvent
69
- attr_reader :attributes
64
+ attr_reader :test_id
70
65
 
71
- def initialize(data)
72
- @attributes = data
66
+ def initialize(test_id:)
67
+ @test_id = test_id
73
68
  end
74
69
 
75
- def self.publish(data)
76
- # Publish logic here
77
- puts "Publishing event with data: #{data.inspect}"
70
+ def publish
71
+ # put logic here to publish the event after debouncing
72
+ puts "Publishing event: #{inspect}"
73
+ end
74
+
75
+ def debounce_callback
76
+ Debounced::Callback.new(
77
+ class_name: self.class.name,
78
+ params: { test_id: },
79
+ method_name: 'publish',
80
+ method_params: []
81
+ )
78
82
  end
79
83
  end
80
84
 
81
- event = MyEvent.new({ id: 1, message: "Hello World" })
82
- proxy.debounce_event("my-event-123", event, 5) # Debounce for 5 seconds
85
+ event = MyEvent.new({ test_id: "Hello World" })
86
+
87
+ # request the server to debounce the event, ignoring it if another event with the
88
+ # same descriptor arrives before the timeout
89
+ proxy.debounce_activity("my-event-123", 5, event.debounce_callback)
90
+ # 2 seconds later
91
+ proxy.debounce_activity("my-event-123", 5, event.debounce_callback)
92
+ # 4 seconds later
93
+ proxy.debounce_activity("my-event-123", 5, event.debounce_callback)
94
+ # 5 seconds later the event is published!
95
+ # > Publishing event: #<MyEvent:0x00007f9b1b8b3b40 @test_id="Hello World">
83
96
  ```
84
97
 
85
98
  ## How It Works
86
99
 
87
100
  1. The gem creates a Unix socket for communication between Ruby and Node.js
88
- 2. When you call `debounce_event`, it sends the event to the Node.js server
89
- 3. The Node.js server keeps track of events with the same descriptor
90
- 4. If another event with the same descriptor arrives before the timeout, it resets the timer
91
- 5. When the timeout expires, it sends the event back to Ruby to be published
101
+ 2. When you call `debounce_activity`, it sends the event to the Node.js server
102
+ 3. The Node.js server restarts a timer every time an event with a given activity_descriptor is received
103
+ 4. When the timeout expires, it sends the event back to Ruby to be published
92
104
 
93
105
  ## License
94
106
 
@@ -0,0 +1,38 @@
1
+ require 'debug'
2
+
3
+ module Debounced
4
+ class Callback
5
+
6
+ attr_reader :class_name, :params, :method_name, :method_params
7
+ def initialize(class_name:, params:, method_name:, method_params:)
8
+ @class_name = class_name.to_s
9
+ @params = params || {}
10
+ @method_name = method_name.to_s
11
+ @method_params = method_params || []
12
+ end
13
+
14
+ def self.json_create(data)
15
+ new(
16
+ class_name: data['class_name'],
17
+ params: data['params'],
18
+ method_name: data['method_name'],
19
+ method_params: data['method_params']
20
+ )
21
+ end
22
+
23
+ def as_json
24
+ {
25
+ class_name:,
26
+ params:,
27
+ method_name:,
28
+ method_params:
29
+ }
30
+ end
31
+
32
+ def call
33
+ Object.const_get(class_name)
34
+ .new(**params.transform_keys(&:to_sym))
35
+ .send(method_name, *method_params)
36
+ end
37
+ end
38
+ end
@@ -102,11 +102,11 @@ export default class DebounceService {
102
102
  });
103
103
  }
104
104
 
105
- publishEvent(descriptor, data) {
106
- console.log(`Debounce period expired for ${descriptor}, publishing ${data.klass} event`);
105
+ publishEvent(descriptor, callback) {
106
+ console.log(`Debounce period expired for ${descriptor}`);
107
107
  const message = JSON.stringify({
108
108
  type: 'publishEvent',
109
- data: data
109
+ callback: callback
110
110
  });
111
111
 
112
112
  try {
@@ -117,7 +117,7 @@ export default class DebounceService {
117
117
  }
118
118
  }
119
119
 
120
- debounceEvent({ descriptor, attributes, klass, timeout }) {
120
+ debounceEvent({ descriptor, timeout, callback }) {
121
121
  if (this._timers[descriptor]) {
122
122
  clearTimeout(this._timers[descriptor]);
123
123
  }
@@ -125,7 +125,7 @@ export default class DebounceService {
125
125
  console.log("Debouncing", descriptor);
126
126
  this._timers[descriptor] = setTimeout(() => {
127
127
  delete this._timers[descriptor];
128
- this.publishEvent(descriptor, { attributes, klass });
128
+ this.publishEvent(descriptor, callback);
129
129
  }, timeout * 1000);
130
130
  }
131
131
 
@@ -33,13 +33,13 @@ module Debounced
33
33
  end
34
34
  end
35
35
 
36
- def debounce_activity(activity_descriptor, object, timeout)
36
+ def debounce_activity(activity_descriptor, timeout, callback)
37
37
  if socket.nil?
38
38
  log_debug("No connection to #{server_name}; skipping debounce step.")
39
- trigger_callback(object)
39
+ callback.call
40
40
  else
41
- log_debug("Sending #{object.inspect} to #{server_name}")
42
- transmit(build_request(activity_descriptor, object, timeout))
41
+ log_debug("Debouncing #{activity_descriptor} to #{server_name}")
42
+ transmit(build_request(activity_descriptor, timeout, callback))
43
43
  end
44
44
  end
45
45
 
@@ -68,12 +68,14 @@ module Debounced
68
68
  log_debug("Parsed #{payload}")
69
69
  next unless payload['type'] == 'publishEvent'
70
70
 
71
- trigger_callback(instantiate_debounced_object(payload['data']))
71
+ instantiate_callback(payload['callback']).call
72
72
  rescue IO::TimeoutError
73
73
  # Ignored - normal flow of loop: check abort_signal (L48), get data (L56), timeout waiting for data (69)
74
74
  end
75
75
  rescue StandardError => e
76
76
  log_warn("Unable to listen for messages from #{server_name}: #{e.message}")
77
+ log_warn(e.backtrace.join("\n"))
78
+ ensure
77
79
  end
78
80
 
79
81
  private
@@ -86,28 +88,17 @@ module Debounced
86
88
  @socket = nil
87
89
  end
88
90
 
89
- def build_request(descriptor, object, timeout)
91
+ def build_request(descriptor, timeout, callback)
90
92
  {
91
93
  type: 'debounceEvent',
92
94
  data: {
93
- timeout:,
94
95
  descriptor:,
95
- klass: object.class.name,
96
- attributes: extract_attributes(object)
96
+ timeout:,
97
+ callback: callback.as_json
97
98
  }
98
99
  }
99
100
  end
100
101
 
101
- def extract_attributes(object)
102
- if object.respond_to?(:attributes)
103
- object.attributes
104
- else
105
- object.instance_variables.each_with_object({}) do |var, hash|
106
- hash[var.to_s.delete('@')] = object.instance_variable_get(var)
107
- end
108
- end
109
- end
110
-
111
102
  def transmit(request)
112
103
  socket.send serialize_payload(request), 0
113
104
  end
@@ -124,14 +115,8 @@ module Debounced
124
115
  JSON.parse(payload)
125
116
  end
126
117
 
127
- def trigger_callback(object)
128
- object.send(Debounced.configuration.callback_method)
129
- end
130
-
131
- def instantiate_debounced_object(data)
132
- klass = Object.const_get(data['klass'])
133
- data = data['attributes'].transform_keys(&:to_sym)
134
- klass.new(**data)
118
+ def instantiate_callback(data)
119
+ Callback.json_create(data)
135
120
  end
136
121
 
137
122
  def socket_descriptor
@@ -141,7 +126,7 @@ module Debounced
141
126
  def socket
142
127
  @socket ||= begin
143
128
  log_debug("Connecting to #{server_name} at #{socket_descriptor}")
144
- UNIXSocket.new(socket_descriptor).tap { it.timeout = @wait_timeout }
129
+ UNIXSocket.new(socket_descriptor).tap { |s| s.timeout = @wait_timeout }
145
130
  end
146
131
  rescue Errno::ECONNREFUSED, Errno::ENOENT
147
132
  ###
@@ -1,3 +1,3 @@
1
1
  module Debounced
2
- VERSION = "0.1.17"
2
+ VERSION = "0.1.20"
3
3
  end
data/lib/debounced.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'debounced/version'
2
2
  require 'debounced/railtie' if defined?(Rails)
3
3
  require 'debounced/service_proxy'
4
+ require 'debounced/callback'
4
5
  require 'semantic_logger'
5
6
 
6
7
  module Debounced
@@ -17,12 +18,11 @@ module Debounced
17
18
  end
18
19
 
19
20
  class Configuration
20
- attr_accessor :socket_descriptor, :wait_timeout, :callback_method, :logger
21
+ attr_accessor :socket_descriptor, :wait_timeout, :logger
21
22
 
22
23
  def initialize
23
24
  @socket_descriptor = ENV['DEBOUNCED_SOCKET'] || '/tmp/app.debounceEvents'
24
25
  @wait_timeout = ENV['DEBOUNCED_TIMEOUT']&.to_i || 3
25
- @callback_method = :publish
26
26
  SemanticLogger.add_appender(file_name: 'debounced_proxy.log', formatter: :color)
27
27
  SemanticLogger.default_level = ENV.fetch('LOG_LEVEL', 'info')
28
28
  @logger = SemanticLogger['ServiceProxy']
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: debounced
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.17
4
+ version: 0.1.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gary Passero
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-21 00:00:00.000000000 Z
10
+ date: 2025-03-22 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: json
@@ -82,6 +82,7 @@ files:
82
82
  - README.md
83
83
  - lib/debounced.rb
84
84
  - lib/debounced/abort_signal.rb
85
+ - lib/debounced/callback.rb
85
86
  - lib/debounced/javascript/server.mjs
86
87
  - lib/debounced/javascript/service.mjs
87
88
  - lib/debounced/railtie.rb