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 +4 -4
- data/README.md +40 -28
- data/lib/debounced/callback.rb +38 -0
- data/lib/debounced/javascript/service.mjs +5 -5
- data/lib/debounced/service_proxy.rb +13 -28
- data/lib/debounced/version.rb +1 -1
- data/lib/debounced.rb +2 -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: f0b2a5d98cbf80bf222866ad69db51d71b000ea1e13a7bf16d7ddc638aa91923
|
4
|
+
data.tar.gz: f031cae66cd65f40da805111ac8b31140de3e901f19f742bffe1ab593627bb27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d498b7e15fb3aef46c5789d1df43d143970daf40a48bd9f71d5f0664330cda646d9d6d36e8936a334acefdb62d40c3e966ccff7b5fcfeaabf2ea47cf41edada9
|
7
|
+
data.tar.gz: 05ec409dbede721aab6457c29b564a8d8565d87208b3816ec37f1e8a4e3aa2049a4f663e8612e3969ab4e0023058e52f14351529f27f6e8eb710969d9be7b130
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# Debounced
|
2
2
|
|
3
|
-
|
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 >=
|
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
|
-
|
46
|
+
Start the nodeJS debounce server with:
|
53
47
|
|
54
48
|
```bash
|
55
|
-
$ bundle exec debounced
|
49
|
+
$ bundle exec debounced:server
|
56
50
|
```
|
57
51
|
|
58
|
-
|
52
|
+
In your Ruby application code:
|
59
53
|
|
60
54
|
```ruby
|
61
55
|
require 'debounced'
|
62
56
|
|
63
|
-
# Start
|
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
|
-
|
59
|
+
proxy.listen
|
66
60
|
|
67
|
-
#
|
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 :
|
64
|
+
attr_reader :test_id
|
70
65
|
|
71
|
-
def initialize(
|
72
|
-
@
|
66
|
+
def initialize(test_id:)
|
67
|
+
@test_id = test_id
|
73
68
|
end
|
74
69
|
|
75
|
-
def
|
76
|
-
#
|
77
|
-
puts "Publishing event
|
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({
|
82
|
-
|
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 `
|
89
|
-
3. The Node.js server
|
90
|
-
4.
|
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,
|
106
|
-
console.log(`Debounce period expired for ${descriptor}
|
105
|
+
publishEvent(descriptor, callback) {
|
106
|
+
console.log(`Debounce period expired for ${descriptor}`);
|
107
107
|
const message = JSON.stringify({
|
108
108
|
type: 'publishEvent',
|
109
|
-
|
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,
|
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,
|
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,
|
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
|
-
|
39
|
+
callback.call
|
40
40
|
else
|
41
|
-
log_debug("
|
42
|
-
transmit(build_request(activity_descriptor,
|
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
|
-
|
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,
|
91
|
+
def build_request(descriptor, timeout, callback)
|
90
92
|
{
|
91
93
|
type: 'debounceEvent',
|
92
94
|
data: {
|
93
|
-
timeout:,
|
94
95
|
descriptor:,
|
95
|
-
|
96
|
-
|
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
|
128
|
-
|
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 {
|
129
|
+
UNIXSocket.new(socket_descriptor).tap { |s| s.timeout = @wait_timeout }
|
145
130
|
end
|
146
131
|
rescue Errno::ECONNREFUSED, Errno::ENOENT
|
147
132
|
###
|
data/lib/debounced/version.rb
CHANGED
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, :
|
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.
|
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-
|
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
|