debounced 0.1.16 → 0.1.19
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/lib/debounced/callback.rb +38 -0
- data/lib/debounced/javascript/server.mjs +2 -1
- data/lib/debounced/javascript/service.mjs +140 -0
- data/lib/debounced/service_proxy.rb +43 -65
- data/lib/debounced/version.rb +1 -1
- data/lib/debounced.rb +9 -5
- metadata +23 -37
- data/lib/debounced/javascript/debounce.mjs +0 -68
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 5469038458279f90317b22acd88274c9a9cd5ddfe7d932e0fba6141230da1f65
         | 
| 4 | 
            +
              data.tar.gz: 2776dc85b634f88d5f9ec7f4903de124a092455e25cffba08b91aaab903d7667
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 9559170057bc4e13012e8d3d7234d1d2e2d1ed9eaa7100c8b2120b5c5b7b0d57cebfd4aa7e881b059bb9a6475b1dd45c4854467a883d09f7e88e159636faea4f
         | 
| 7 | 
            +
              data.tar.gz: 5dadd3b4312c8985a2e3ad44b7aceb948250031bd9ca0e26da903211a42dc7a9ff0b26f57fa3e30062a05947c7738af6b999c809548a1781ccfe08176be0fb13
         | 
| @@ -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
         | 
| @@ -1,8 +1,9 @@ | |
| 1 1 | 
             
            #!/usr/bin/env node
         | 
| 2 2 |  | 
| 3 | 
            -
            import DebounceEventService from './ | 
| 3 | 
            +
            import DebounceEventService from './service.mjs'
         | 
| 4 4 | 
             
            let socketDescriptor = '/tmp/app.debounceEvents';
         | 
| 5 5 | 
             
            if (process.argv.length > 2) {
         | 
| 6 6 | 
             
              socketDescriptor = process.argv[2];
         | 
| 7 7 | 
             
            }
         | 
| 8 | 
            +
             | 
| 8 9 | 
             
            new DebounceEventService(socketDescriptor).listen();
         | 
| @@ -0,0 +1,140 @@ | |
| 1 | 
            +
            import net from 'net';
         | 
| 2 | 
            +
            import fs from 'fs';
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            export default class DebounceService {
         | 
| 5 | 
            +
              constructor(socketDescriptor) {
         | 
| 6 | 
            +
                this._socketDescriptor = socketDescriptor;
         | 
| 7 | 
            +
                this._timers = {};
         | 
| 8 | 
            +
                this._client = null;
         | 
| 9 | 
            +
                this.publishEvent = this.publishEvent.bind(this);
         | 
| 10 | 
            +
                this.debounceEvent = this.debounceEvent.bind(this);
         | 
| 11 | 
            +
                this.reset = this.reset.bind(this);
         | 
| 12 | 
            +
                this.listen = this.listen.bind(this);
         | 
| 13 | 
            +
                this.handleError = this.handleError.bind(this);
         | 
| 14 | 
            +
                this.onClientConnected = this.onClientConnected.bind(this);
         | 
| 15 | 
            +
                this.onClientDisconnected = this.onClientDisconnected.bind(this);
         | 
| 16 | 
            +
                this.onConnectionError = this.onConnectionError.bind(this);
         | 
| 17 | 
            +
                this.handleMessage = this.handleMessage.bind(this);
         | 
| 18 | 
            +
                this.configureServer();
         | 
| 19 | 
            +
              }
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              onConnectionError(err) {
         | 
| 22 | 
            +
                console.log('DebounceService client connection error');
         | 
| 23 | 
            +
                this._client = null
         | 
| 24 | 
            +
                this.handleError(err);
         | 
| 25 | 
            +
              }
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              onClientDisconnected() {
         | 
| 28 | 
            +
                console.log('DebounceService client disconnected');
         | 
| 29 | 
            +
                this._client = null
         | 
| 30 | 
            +
              }
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              handleMessage(message) {
         | 
| 33 | 
            +
                try {
         | 
| 34 | 
            +
                  const object = JSON.parse(message);
         | 
| 35 | 
            +
                  if (object.type === 'debounceEvent') {
         | 
| 36 | 
            +
                    this.debounceEvent(object.data);
         | 
| 37 | 
            +
                  } else if (object.type === 'reset') {
         | 
| 38 | 
            +
                    this.reset();
         | 
| 39 | 
            +
                  } else {
         | 
| 40 | 
            +
                    console.log('DebounceService unknown message', message);
         | 
| 41 | 
            +
                  }
         | 
| 42 | 
            +
                } catch (e) {
         | 
| 43 | 
            +
                  console.log('unable to parse message', e);
         | 
| 44 | 
            +
                }
         | 
| 45 | 
            +
              }
         | 
| 46 | 
            +
             | 
| 47 | 
            +
              onClientConnected(socket) {
         | 
| 48 | 
            +
                if (this._client) {
         | 
| 49 | 
            +
                  console.log('DebounceService rejecting connection: already has a client');
         | 
| 50 | 
            +
                  socket.end();
         | 
| 51 | 
            +
                  return;
         | 
| 52 | 
            +
                }
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                console.log('DebounceService client connected');
         | 
| 55 | 
            +
                let connectionBuffer = '';
         | 
| 56 | 
            +
                this._client = socket;
         | 
| 57 | 
            +
                socket.on('end', this.onClientDisconnected)
         | 
| 58 | 
            +
                socket.on('error', this.onConnectionError)
         | 
| 59 | 
            +
                socket.on('data', data => {
         | 
| 60 | 
            +
                  console.log('DebounceService data received', data.toString());
         | 
| 61 | 
            +
                  connectionBuffer += data.toString();
         | 
| 62 | 
            +
                  const messages = connectionBuffer.split('\f');
         | 
| 63 | 
            +
                  connectionBuffer = ''
         | 
| 64 | 
            +
                  if (!connectionBuffer.endsWith('\f')) {
         | 
| 65 | 
            +
                    connectionBuffer = messages.pop();
         | 
| 66 | 
            +
                  }
         | 
| 67 | 
            +
                  messages.forEach(this.handleMessage);
         | 
| 68 | 
            +
                })
         | 
| 69 | 
            +
              }
         | 
| 70 | 
            +
             | 
| 71 | 
            +
              configureServer() {
         | 
| 72 | 
            +
                this.server = net.createServer(this.onClientConnected)
         | 
| 73 | 
            +
                this.server.on('error', this.handleError);
         | 
| 74 | 
            +
              }
         | 
| 75 | 
            +
             | 
| 76 | 
            +
              listen() {
         | 
| 77 | 
            +
                // Remove the existing socket file if it exists
         | 
| 78 | 
            +
                if (fs.existsSync(this._socketDescriptor)) {
         | 
| 79 | 
            +
                  console.log('DebounceEventService removing stale socket file ', this._socketDescriptor);
         | 
| 80 | 
            +
                  fs.unlinkSync(this._socketDescriptor);
         | 
| 81 | 
            +
                }
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                process.on('exit', (code) => {
         | 
| 84 | 
            +
                  console.log(`Process exiting with code: ${code}`);
         | 
| 85 | 
            +
                  this.server.close();
         | 
| 86 | 
            +
                });
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                process.on('SIGTERM', () => {
         | 
| 89 | 
            +
                  console.log('Process received SIGTERM');
         | 
| 90 | 
            +
                  this.server.close();
         | 
| 91 | 
            +
                  process.exit(0);
         | 
| 92 | 
            +
                });
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                process.on('SIGINT', () => {
         | 
| 95 | 
            +
                  console.log('Process received SIGINT');
         | 
| 96 | 
            +
                  this.server.close();
         | 
| 97 | 
            +
                  process.exit(0);
         | 
| 98 | 
            +
                });
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                this.server.listen(this._socketDescriptor, () => {
         | 
| 101 | 
            +
                  console.log('DebounceService listening on', this._socketDescriptor);
         | 
| 102 | 
            +
                });
         | 
| 103 | 
            +
              }
         | 
| 104 | 
            +
             | 
| 105 | 
            +
              publishEvent(descriptor, callback) {
         | 
| 106 | 
            +
                console.log(`Debounce period expired for ${descriptor}`);
         | 
| 107 | 
            +
                const message = JSON.stringify({
         | 
| 108 | 
            +
                  type: 'publishEvent',
         | 
| 109 | 
            +
                  callback: callback
         | 
| 110 | 
            +
                });
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                try {
         | 
| 113 | 
            +
                  this._client.write(message);
         | 
| 114 | 
            +
                  this._client.write("\f");
         | 
| 115 | 
            +
                } catch (err) {
         | 
| 116 | 
            +
                  this.handleError(err);
         | 
| 117 | 
            +
                }
         | 
| 118 | 
            +
              }
         | 
| 119 | 
            +
             | 
| 120 | 
            +
              debounceEvent({ descriptor, timeout, callback }) {
         | 
| 121 | 
            +
                if (this._timers[descriptor]) {
         | 
| 122 | 
            +
                  clearTimeout(this._timers[descriptor]);
         | 
| 123 | 
            +
                }
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                console.log("Debouncing", descriptor);
         | 
| 126 | 
            +
                this._timers[descriptor] = setTimeout(() => {
         | 
| 127 | 
            +
                  delete this._timers[descriptor];
         | 
| 128 | 
            +
                  this.publishEvent(descriptor, callback);
         | 
| 129 | 
            +
                }, timeout * 1000);
         | 
| 130 | 
            +
              }
         | 
| 131 | 
            +
             | 
| 132 | 
            +
              reset() {
         | 
| 133 | 
            +
                Object.values(this._timers).forEach(timerID => clearTimeout(timerID));
         | 
| 134 | 
            +
                this._timers = {};
         | 
| 135 | 
            +
              }
         | 
| 136 | 
            +
             | 
| 137 | 
            +
              handleError(err) {
         | 
| 138 | 
            +
                console.log('\n\n######\nERROR: ', err);
         | 
| 139 | 
            +
              }
         | 
| 140 | 
            +
            }
         | 
| @@ -1,7 +1,14 @@ | |
| 1 1 | 
             
            require 'socket'
         | 
| 2 | 
            -
            require ' | 
| 2 | 
            +
            require 'json'
         | 
| 3 | 
            +
            require 'json/add/core'
         | 
| 4 | 
            +
            require 'debug'
         | 
| 3 5 |  | 
| 4 6 | 
             
            module Debounced
         | 
| 7 | 
            +
              ###
         | 
| 8 | 
            +
              # Ruby interface to the debounce service
         | 
| 9 | 
            +
              # Input is an activity descriptor, and an object.
         | 
| 10 | 
            +
              # When the activity is debounced, a callback method is invoked on the object.
         | 
| 11 | 
            +
              # Assumes the object class has an initializer that accepts a hash of attributes, which are the instance variables
         | 
| 5 12 | 
             
              class ServiceProxy
         | 
| 6 13 | 
             
                DELIMITER = "\f".freeze
         | 
| 7 14 |  | 
| @@ -11,16 +18,12 @@ module Debounced | |
| 11 18 |  | 
| 12 19 | 
             
                def listen(abort_signal = nil)
         | 
| 13 20 | 
             
                  Thread.new do
         | 
| 14 | 
            -
                     | 
| 15 | 
            -
                      Rails.application.executor.wrap do
         | 
| 16 | 
            -
                        receive(abort_signal)
         | 
| 17 | 
            -
                      end
         | 
| 18 | 
            -
                    else
         | 
| 19 | 
            -
                      receive(abort_signal)
         | 
| 20 | 
            -
                    end
         | 
| 21 | 
            +
                    receive(abort_signal)
         | 
| 21 22 | 
             
                  end
         | 
| 22 23 | 
             
                end
         | 
| 23 24 |  | 
| 25 | 
            +
                ###
         | 
| 26 | 
            +
                # Send message to server to reset its state. Useful for automated testing.
         | 
| 24 27 | 
             
                def reset
         | 
| 25 28 | 
             
                  if socket.nil?
         | 
| 26 29 | 
             
                    log_debug("No connection to #{server_name}; unable to reset server.")
         | 
| @@ -30,13 +33,13 @@ module Debounced | |
| 30 33 | 
             
                  end
         | 
| 31 34 | 
             
                end
         | 
| 32 35 |  | 
| 33 | 
            -
                def  | 
| 36 | 
            +
                def debounce_activity(activity_descriptor, timeout, callback)
         | 
| 34 37 | 
             
                  if socket.nil?
         | 
| 35 | 
            -
                    log_debug("No connection to #{server_name};  | 
| 36 | 
            -
                     | 
| 38 | 
            +
                    log_debug("No connection to #{server_name}; skipping debounce step.")
         | 
| 39 | 
            +
                    callback.call
         | 
| 37 40 | 
             
                  else
         | 
| 38 | 
            -
                    log_debug(" | 
| 39 | 
            -
                    transmit( | 
| 41 | 
            +
                    log_debug("Debouncing #{activity_descriptor} to #{server_name}")
         | 
| 42 | 
            +
                    transmit(build_request(activity_descriptor, timeout, callback))
         | 
| 40 43 | 
             
                  end
         | 
| 41 44 | 
             
                end
         | 
| 42 45 |  | 
| @@ -47,28 +50,32 @@ module Debounced | |
| 47 50 | 
             
                    break if abort_signal&.set?
         | 
| 48 51 |  | 
| 49 52 | 
             
                    if socket.nil?
         | 
| 50 | 
            -
                      log_debug("Waiting for #{server_name}...")
         | 
| 53 | 
            +
                      log_debug("Waiting for #{server_name} to start...")
         | 
| 51 54 | 
             
                      sleep(@wait_timeout)
         | 
| 52 55 | 
             
                      next
         | 
| 53 56 | 
             
                    end
         | 
| 54 57 |  | 
| 58 | 
            +
                    log_debug("Waiting for data from #{server_name}...")
         | 
| 55 59 | 
             
                    message = socket.gets(DELIMITER, chomp: true)
         | 
| 56 | 
            -
             | 
| 57 | 
            -
                    # gets => nil when server crashed.... try to reconnect
         | 
| 58 60 | 
             
                    if message.nil?
         | 
| 61 | 
            +
                      log_info("Server #{server_name} ended connection")
         | 
| 59 62 | 
             
                      close
         | 
| 60 | 
            -
                       | 
| 63 | 
            +
                      break
         | 
| 61 64 | 
             
                    end
         | 
| 62 65 |  | 
| 66 | 
            +
                    log_debug("Received #{message}")
         | 
| 63 67 | 
             
                    payload = deserialize_payload(message)
         | 
| 68 | 
            +
                    log_debug("Parsed #{payload}")
         | 
| 64 69 | 
             
                    next unless payload['type'] == 'publishEvent'
         | 
| 65 70 |  | 
| 66 | 
            -
                     | 
| 71 | 
            +
                    instantiate_callback(payload['callback']).call
         | 
| 67 72 | 
             
                  rescue IO::TimeoutError
         | 
| 68 | 
            -
                    # Ignored
         | 
| 73 | 
            +
                    # Ignored - normal flow of loop: check abort_signal (L48), get data (L56), timeout waiting for data (69)
         | 
| 69 74 | 
             
                  end
         | 
| 70 75 | 
             
                rescue StandardError => e
         | 
| 71 76 | 
             
                  log_warn("Unable to listen for messages from #{server_name}: #{e.message}")
         | 
| 77 | 
            +
                  log_warn(e.backtrace.join("\n"))
         | 
| 78 | 
            +
                ensure
         | 
| 72 79 | 
             
                end
         | 
| 73 80 |  | 
| 74 81 | 
             
                private
         | 
| @@ -81,28 +88,17 @@ module Debounced | |
| 81 88 | 
             
                  @socket = nil
         | 
| 82 89 | 
             
                end
         | 
| 83 90 |  | 
| 84 | 
            -
                def  | 
| 91 | 
            +
                def build_request(descriptor, timeout, callback)
         | 
| 85 92 | 
             
                  {
         | 
| 86 93 | 
             
                    type: 'debounceEvent',
         | 
| 87 94 | 
             
                    data: {
         | 
| 88 | 
            -
                       | 
| 89 | 
            -
                       | 
| 90 | 
            -
                       | 
| 91 | 
            -
                      attributes: event_attributes(event)
         | 
| 95 | 
            +
                      descriptor:,
         | 
| 96 | 
            +
                      timeout:,
         | 
| 97 | 
            +
                      callback: callback.as_json
         | 
| 92 98 | 
             
                    }
         | 
| 93 99 | 
             
                  }
         | 
| 94 100 | 
             
                end
         | 
| 95 101 |  | 
| 96 | 
            -
                def event_attributes(event)
         | 
| 97 | 
            -
                  if event.respond_to?(:attributes)
         | 
| 98 | 
            -
                    event.attributes
         | 
| 99 | 
            -
                  else
         | 
| 100 | 
            -
                    event.instance_variables.each_with_object({}) do |var, hash|
         | 
| 101 | 
            -
                      hash[var.to_s.delete('@')] = event.instance_variable_get(var)
         | 
| 102 | 
            -
                    end
         | 
| 103 | 
            -
                  end
         | 
| 104 | 
            -
                end
         | 
| 105 | 
            -
             | 
| 106 102 | 
             
                def transmit(request)
         | 
| 107 103 | 
             
                  socket.send serialize_payload(request), 0
         | 
| 108 104 | 
             
                end
         | 
| @@ -112,21 +108,15 @@ module Debounced | |
| 112 108 | 
             
                end
         | 
| 113 109 |  | 
| 114 110 | 
             
                def serialize_payload(payload)
         | 
| 115 | 
            -
                  "#{ | 
| 111 | 
            +
                  "#{JSON.generate(payload)}#{DELIMITER}" # inject EOM delimiter (form feed character)
         | 
| 116 112 | 
             
                end
         | 
| 117 113 |  | 
| 118 114 | 
             
                def deserialize_payload(payload)
         | 
| 119 | 
            -
                   | 
| 115 | 
            +
                  JSON.parse(payload)
         | 
| 120 116 | 
             
                end
         | 
| 121 117 |  | 
| 122 | 
            -
                def  | 
| 123 | 
            -
                   | 
| 124 | 
            -
                end
         | 
| 125 | 
            -
             | 
| 126 | 
            -
                def class_callback(data)
         | 
| 127 | 
            -
                  klass = data['klass'].constantize
         | 
| 128 | 
            -
                  data = data['attributes']
         | 
| 129 | 
            -
                  klass.send(Debounced.configuration.callback_method, data)
         | 
| 118 | 
            +
                def instantiate_callback(data)
         | 
| 119 | 
            +
                  Callback.json_create(data)
         | 
| 130 120 | 
             
                end
         | 
| 131 121 |  | 
| 132 122 | 
             
                def socket_descriptor
         | 
| @@ -134,40 +124,28 @@ module Debounced | |
| 134 124 | 
             
                end
         | 
| 135 125 |  | 
| 136 126 | 
             
                def socket
         | 
| 137 | 
            -
                  @socket ||=  | 
| 138 | 
            -
             | 
| 139 | 
            -
                     | 
| 127 | 
            +
                  @socket ||= begin
         | 
| 128 | 
            +
                    log_debug("Connecting to #{server_name} at #{socket_descriptor}")
         | 
| 129 | 
            +
                    UNIXSocket.new(socket_descriptor).tap { |s| s.timeout = @wait_timeout }
         | 
| 140 130 | 
             
                  end
         | 
| 141 | 
            -
                ###
         | 
| 142 | 
            -
                # Errno::ENOENT is raised if the socket file does not exist.
         | 
| 143 | 
            -
                # Errno::ECONNREFUSED is raised if the socket file exists but no process is listening on it.
         | 
| 144 131 | 
             
                rescue Errno::ECONNREFUSED, Errno::ENOENT
         | 
| 132 | 
            +
                  ###
         | 
| 133 | 
            +
                  # Errno::ENOENT is raised if the socket file does not exist.
         | 
| 134 | 
            +
                  # Errno::ECONNREFUSED is raised if the socket file exists but no process is listening on it.
         | 
| 145 135 | 
             
                  log_debug("#{server_name} is not running")
         | 
| 146 136 | 
             
                  nil
         | 
| 147 137 | 
             
                end
         | 
| 148 138 |  | 
| 149 139 | 
             
                def log_debug(message)
         | 
| 150 | 
            -
                   | 
| 151 | 
            -
                    Rails.logger.debug { message }
         | 
| 152 | 
            -
                  else
         | 
| 153 | 
            -
                    puts "[DEBUG] #{message}" if ENV['DEBUG']
         | 
| 154 | 
            -
                  end
         | 
| 140 | 
            +
                  Debounced.configuration.logger.debug { message }
         | 
| 155 141 | 
             
                end
         | 
| 156 142 |  | 
| 157 143 | 
             
                def log_info(message)
         | 
| 158 | 
            -
                   | 
| 159 | 
            -
                    Rails.logger.info(message)
         | 
| 160 | 
            -
                  else
         | 
| 161 | 
            -
                    puts "[INFO] #{message}"
         | 
| 162 | 
            -
                  end
         | 
| 144 | 
            +
                  Debounced.configuration.logger.info(message)
         | 
| 163 145 | 
             
                end
         | 
| 164 146 |  | 
| 165 147 | 
             
                def log_warn(message)
         | 
| 166 | 
            -
                   | 
| 167 | 
            -
                    Rails.logger.warn(message)
         | 
| 168 | 
            -
                  else
         | 
| 169 | 
            -
                    puts "[WARNING] #{message}"
         | 
| 170 | 
            -
                  end
         | 
| 148 | 
            +
                  Debounced.configuration.logger.warn(message)
         | 
| 171 149 | 
             
                end
         | 
| 172 150 | 
             
              end
         | 
| 173 151 | 
             
            end
         | 
    
        data/lib/debounced/version.rb
    CHANGED
    
    
    
        data/lib/debounced.rb
    CHANGED
    
    | @@ -1,6 +1,8 @@ | |
| 1 | 
            -
            require  | 
| 2 | 
            -
            require  | 
| 3 | 
            -
            require  | 
| 1 | 
            +
            require 'debounced/version'
         | 
| 2 | 
            +
            require 'debounced/railtie' if defined?(Rails)
         | 
| 3 | 
            +
            require 'debounced/service_proxy'
         | 
| 4 | 
            +
            require 'debounced/callback'
         | 
| 5 | 
            +
            require 'semantic_logger'
         | 
| 4 6 |  | 
| 5 7 | 
             
            module Debounced
         | 
| 6 8 | 
             
              class Error < StandardError; end
         | 
| @@ -16,12 +18,14 @@ module Debounced | |
| 16 18 | 
             
              end
         | 
| 17 19 |  | 
| 18 20 | 
             
              class Configuration
         | 
| 19 | 
            -
                attr_accessor :socket_descriptor, :wait_timeout, : | 
| 21 | 
            +
                attr_accessor :socket_descriptor, :wait_timeout, :logger
         | 
| 20 22 |  | 
| 21 23 | 
             
                def initialize
         | 
| 22 24 | 
             
                  @socket_descriptor = ENV['DEBOUNCED_SOCKET'] || '/tmp/app.debounceEvents'
         | 
| 23 25 | 
             
                  @wait_timeout = ENV['DEBOUNCED_TIMEOUT']&.to_i || 3
         | 
| 24 | 
            -
                   | 
| 26 | 
            +
                  SemanticLogger.add_appender(file_name: 'debounced_proxy.log', formatter: :color)
         | 
| 27 | 
            +
                  SemanticLogger.default_level = ENV.fetch('LOG_LEVEL', 'info')
         | 
| 28 | 
            +
                  @logger = SemanticLogger['ServiceProxy']
         | 
| 25 29 | 
             
                end
         | 
| 26 30 | 
             
              end
         | 
| 27 31 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,92 +1,77 @@ | |
| 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.19
         | 
| 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 | 
            -
              name:  | 
| 13 | 
            +
              name: json
         | 
| 14 14 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 15 15 | 
             
                requirements:
         | 
| 16 16 | 
             
                - - "~>"
         | 
| 17 17 | 
             
                  - !ruby/object:Gem::Version
         | 
| 18 | 
            -
                    version:  | 
| 19 | 
            -
                - - ">="
         | 
| 20 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 21 | 
            -
                    version: 7.0.0
         | 
| 18 | 
            +
                    version: 2.10.2
         | 
| 22 19 | 
             
              type: :runtime
         | 
| 23 20 | 
             
              prerelease: false
         | 
| 24 21 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 25 22 | 
             
                requirements:
         | 
| 26 23 | 
             
                - - "~>"
         | 
| 27 24 | 
             
                  - !ruby/object:Gem::Version
         | 
| 28 | 
            -
                    version:  | 
| 29 | 
            -
                - - ">="
         | 
| 30 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 31 | 
            -
                    version: 7.0.0
         | 
| 25 | 
            +
                    version: 2.10.2
         | 
| 32 26 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 33 | 
            -
              name:  | 
| 27 | 
            +
              name: semantic_logger
         | 
| 34 28 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 35 29 | 
             
                requirements:
         | 
| 36 30 | 
             
                - - "~>"
         | 
| 37 31 | 
             
                  - !ruby/object:Gem::Version
         | 
| 38 | 
            -
                    version:  | 
| 39 | 
            -
              type: : | 
| 32 | 
            +
                    version: 4.15.0
         | 
| 33 | 
            +
              type: :runtime
         | 
| 40 34 | 
             
              prerelease: false
         | 
| 41 35 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 42 36 | 
             
                requirements:
         | 
| 43 37 | 
             
                - - "~>"
         | 
| 44 38 | 
             
                  - !ruby/object:Gem::Version
         | 
| 45 | 
            -
                    version:  | 
| 39 | 
            +
                    version: 4.15.0
         | 
| 46 40 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 47 | 
            -
              name:  | 
| 41 | 
            +
              name: rspec
         | 
| 48 42 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 49 43 | 
             
                requirements:
         | 
| 50 44 | 
             
                - - "~>"
         | 
| 51 45 | 
             
                  - !ruby/object:Gem::Version
         | 
| 52 | 
            -
                    version: ' | 
| 46 | 
            +
                    version: '3.0'
         | 
| 53 47 | 
             
              type: :development
         | 
| 54 48 | 
             
              prerelease: false
         | 
| 55 49 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 56 50 | 
             
                requirements:
         | 
| 57 51 | 
             
                - - "~>"
         | 
| 58 52 | 
             
                  - !ruby/object:Gem::Version
         | 
| 59 | 
            -
                    version: ' | 
| 53 | 
            +
                    version: '3.0'
         | 
| 60 54 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 61 | 
            -
              name:  | 
| 55 | 
            +
              name: debug
         | 
| 62 56 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 63 57 | 
             
                requirements:
         | 
| 64 58 | 
             
                - - "~>"
         | 
| 65 59 | 
             
                  - !ruby/object:Gem::Version
         | 
| 66 | 
            -
                    version: ' | 
| 67 | 
            -
             | 
| 68 | 
            -
              prerelease: false
         | 
| 69 | 
            -
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 70 | 
            -
                requirements:
         | 
| 71 | 
            -
                - - "~>"
         | 
| 72 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 73 | 
            -
                    version: '2.12'
         | 
| 74 | 
            -
            - !ruby/object:Gem::Dependency
         | 
| 75 | 
            -
              name: rubocop-rspec
         | 
| 76 | 
            -
              requirement: !ruby/object:Gem::Requirement
         | 
| 77 | 
            -
                requirements:
         | 
| 78 | 
            -
                - - "~>"
         | 
| 60 | 
            +
                    version: '1.0'
         | 
| 61 | 
            +
                - - ">="
         | 
| 79 62 | 
             
                  - !ruby/object:Gem::Version
         | 
| 80 | 
            -
                    version:  | 
| 63 | 
            +
                    version: 1.0.0
         | 
| 81 64 | 
             
              type: :development
         | 
| 82 65 | 
             
              prerelease: false
         | 
| 83 66 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 84 67 | 
             
                requirements:
         | 
| 85 68 | 
             
                - - "~>"
         | 
| 86 69 | 
             
                  - !ruby/object:Gem::Version
         | 
| 87 | 
            -
                    version: ' | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 70 | 
            +
                    version: '1.0'
         | 
| 71 | 
            +
                - - ">="
         | 
| 72 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 73 | 
            +
                    version: 1.0.0
         | 
| 74 | 
            +
            description: Leverage JavaScript micro-event loop to debounce events in Ruby applications
         | 
| 90 75 | 
             
            email:
         | 
| 91 76 | 
             
            - gary@flytedesk.com
         | 
| 92 77 | 
             
            executables: []
         | 
| @@ -97,8 +82,9 @@ files: | |
| 97 82 | 
             
            - README.md
         | 
| 98 83 | 
             
            - lib/debounced.rb
         | 
| 99 84 | 
             
            - lib/debounced/abort_signal.rb
         | 
| 100 | 
            -
            - lib/debounced/ | 
| 85 | 
            +
            - lib/debounced/callback.rb
         | 
| 101 86 | 
             
            - lib/debounced/javascript/server.mjs
         | 
| 87 | 
            +
            - lib/debounced/javascript/service.mjs
         | 
| 102 88 | 
             
            - lib/debounced/railtie.rb
         | 
| 103 89 | 
             
            - lib/debounced/service_proxy.rb
         | 
| 104 90 | 
             
            - lib/debounced/version.rb
         | 
| @@ -1,68 +0,0 @@ | |
| 1 | 
            -
            import ipc from 'node-ipc';
         | 
| 2 | 
            -
            import fs from 'fs';
         | 
| 3 | 
            -
             | 
| 4 | 
            -
            const logLevel = process.env.LOG_LEVEL || 'info';
         | 
| 5 | 
            -
             | 
| 6 | 
            -
            export default class DebounceService {
         | 
| 7 | 
            -
              constructor(socketDescriptor) {
         | 
| 8 | 
            -
                this._socketDescriptor = socketDescriptor;
         | 
| 9 | 
            -
                this._timers = {};
         | 
| 10 | 
            -
                this.publishEvent = this.publishEvent.bind(this);
         | 
| 11 | 
            -
                this.debounceEvent = this.debounceEvent.bind(this);
         | 
| 12 | 
            -
                this.reset = this.reset.bind(this);
         | 
| 13 | 
            -
                this.listen = this.listen.bind(this);
         | 
| 14 | 
            -
                this.handleError = this.handleError.bind(this);
         | 
| 15 | 
            -
                this.configureServer();
         | 
| 16 | 
            -
                this.registerMessageTypes();
         | 
| 17 | 
            -
              }
         | 
| 18 | 
            -
             | 
| 19 | 
            -
              registerMessageTypes() {
         | 
| 20 | 
            -
                ipc.server.on('debounceEvent', this.debounceEvent);
         | 
| 21 | 
            -
                ipc.server.on('reset', this.reset);
         | 
| 22 | 
            -
                ipc.server.on('error', this.handleError);
         | 
| 23 | 
            -
              }
         | 
| 24 | 
            -
             | 
| 25 | 
            -
              configureServer() {
         | 
| 26 | 
            -
                // Remove the existing socket file if it exists
         | 
| 27 | 
            -
                if (fs.existsSync(this._socketDescriptor)) {
         | 
| 28 | 
            -
                  console.log('DebounceEventService removing stale socket file ', this._socketDescriptor);
         | 
| 29 | 
            -
                  fs.unlinkSync(this._socketDescriptor);
         | 
| 30 | 
            -
                }
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                ipc.config.delimiter = '\f'; // incoming messages are terminated by a form feed character
         | 
| 33 | 
            -
                ipc.config.encoding = 'utf8';
         | 
| 34 | 
            -
                ipc.config.silent = (logLevel !== 'debug');
         | 
| 35 | 
            -
             | 
| 36 | 
            -
                ipc.serve(this._socketDescriptor);
         | 
| 37 | 
            -
              }
         | 
| 38 | 
            -
             | 
| 39 | 
            -
              listen() {
         | 
| 40 | 
            -
                console.log('DebounceService listening on', this._socketDescriptor);
         | 
| 41 | 
            -
                ipc.server.start();
         | 
| 42 | 
            -
              }
         | 
| 43 | 
            -
             | 
| 44 | 
            -
              publishEvent(descriptor, data) {
         | 
| 45 | 
            -
                console.log(`Debounce period expired for ${descriptor}, publishing ${data.klass} event`);
         | 
| 46 | 
            -
                ipc.server.broadcast("publishEvent", data);
         | 
| 47 | 
            -
              }
         | 
| 48 | 
            -
             | 
| 49 | 
            -
              debounceEvent({ descriptor, attributes, klass, timeout }) {
         | 
| 50 | 
            -
                if (this._timers[descriptor]) {
         | 
| 51 | 
            -
                  clearTimeout(this._timers[descriptor]);
         | 
| 52 | 
            -
                }
         | 
| 53 | 
            -
             | 
| 54 | 
            -
                this._timers[descriptor] = setTimeout(() => {
         | 
| 55 | 
            -
                  delete this._timers[descriptor];
         | 
| 56 | 
            -
                  this.publishEvent(descriptor, { attributes, klass });
         | 
| 57 | 
            -
                }, timeout * 1000);
         | 
| 58 | 
            -
              }
         | 
| 59 | 
            -
             | 
| 60 | 
            -
              reset() {
         | 
| 61 | 
            -
                Object.values(this._timers).forEach(timerID => clearTimeout(timerID));
         | 
| 62 | 
            -
                this._timers = {};
         | 
| 63 | 
            -
              }
         | 
| 64 | 
            -
             | 
| 65 | 
            -
              handleError(err) {
         | 
| 66 | 
            -
                console.log('\n\n######\nERROR: ', err);
         | 
| 67 | 
            -
              }
         | 
| 68 | 
            -
            }
         |