snerdmq 1.0.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a66bf4a4c973c7d263b27734aa0daff33fa3f098b7faec5b05c0a10dd6baa773
4
+ data.tar.gz: '02592325c65fe165bb87f20ec34a4b34601f16b677dc9fb15f1942239150a1df'
5
+ SHA512:
6
+ metadata.gz: 931dbad342f26fa5cbe24542de79cd5f740973a06e2b214be4d4274ba1ebfd5d79921d02db6365df01dcc6f64d3fc2aa4d98ae8d466aaae3945da7c51f9ce52d
7
+ data.tar.gz: 7c636234b92c48b47f9c82f3b6fde858a3fd19be8328935f5bf2067107a88f5f3dc07c5cb72b74def125491b21cbd92aef58a49c3bd9b86296269f482f834ebc
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+ /bin/snerdmq*
13
+ !/bin/snerdmq-install
@@ -0,0 +1 @@
1
+ {"taskId":"ruby-job-1","retryCount":0,"maxRetries":3,"retryAfterHours":0.0,"retryAfterTime":"2026-08-12T15:48:11.816678Z","taskData":"{\"user_id\":\"ruby_master\",\"message\":\"matz\"}","taskType":"test_ruby_job"}
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,22 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ snerdmq (1.0.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ minitest (5.27.0)
10
+ rake (13.4.2)
11
+
12
+ PLATFORMS
13
+ arm64-darwin-24
14
+
15
+ DEPENDENCIES
16
+ bundler (~> 2.0)
17
+ minitest (~> 5.0)
18
+ rake (~> 13.0)
19
+ snerdmq!
20
+
21
+ BUNDLED WITH
22
+ 2.4.10
data/README.md ADDED
@@ -0,0 +1,85 @@
1
+ <div align="center">
2
+ <h1>💎 SnerdMQ Ruby SDK</h1>
3
+ <p>A zero-config, C-speed background job queue for Ruby. Ditch Redis and Sidekiq for lightweight, persistent background jobs.</p>
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/snerdmq.svg)](https://badge.fury.io/rb/snerdmq)
6
+ </div>
7
+
8
+ This is the official Ruby SDK wrapper for **SnerdMQ**. It handles all JSON-RPC communication and `IO.popen` orchestration so you can write lightning-fast background jobs without managing any external databases like Redis or Postgres.
9
+
10
+ ## ✨ Features
11
+ - **Ditch Sidekiq & Redis**: Gives your Ruby apps persistent state, automatic retries, and dead-letter queues right out of the box with zero external infrastructure.
12
+ - **Zero Rust Required**: Our gem installation script automatically downloads the pre-compiled C-speed Rust binary for your OS.
13
+ - **Thread Safe**: Uses native Ruby `Thread`s and `Mutex` locks to orchestrate I/O without blocking your main event loop.
14
+
15
+ ## 📦 Installation
16
+
17
+ Installing the SDK is a simple two-step process:
18
+
19
+ **1. Install the gem:**
20
+ ```bash
21
+ gem install snerdmq
22
+ # Or add `gem 'snerdmq'` to your Gemfile
23
+ ```
24
+
25
+ **2. Download the Rust Engine:**
26
+ Run this script from your terminal immediately after installing the gem. It will fetch the correct highly-optimized SnerdMQ binary for your operating system (macOS/Linux/Windows) and place it securely inside the gem installation directory:
27
+ ```bash
28
+ snerdmq-install
29
+ ```
30
+
31
+ ---
32
+
33
+ ## ⚡ Quickstart
34
+
35
+ Using the SDK is incredibly simple. Initialize the queue, register your handler blocks, and start listening!
36
+
37
+ ```ruby
38
+ require 'snerdmq'
39
+
40
+ # 1. Initialize the daemon in the background
41
+ queue = Snerdmq::SnerdQueue.new
42
+
43
+ # 2. Register your background job logic using a standard block
44
+ queue.register_handler("send_email") do |data|
45
+ to = data["to"]
46
+ subject = data["subject"]
47
+ puts "Sending email to #{to} with subject: #{subject}..."
48
+
49
+ # Raise an exception here to automatically trigger SnerdMQ's retry logic!
50
+ end
51
+
52
+ # 3. Start the non-blocking Thread listeners
53
+ queue.start_listening
54
+ puts "SnerdMQ Ruby SDK is listening for jobs..."
55
+
56
+ # 4. Enqueue a job from anywhere in your codebase
57
+ queue.enqueue(
58
+ task_id: "email-123",
59
+ task_type: "send_email",
60
+ data: { "to" => "john@wick.com", "subject" => "Continental Update" },
61
+ max_retries: 3,
62
+ retry_after_hours: 0.0
63
+ )
64
+
65
+ # Keep main thread alive
66
+ sleep
67
+ ```
68
+
69
+ ---
70
+
71
+ ## 🌍 Advanced: Distributed Scaling
72
+
73
+ By default, the SDK spins up the Rust daemon which writes the queue to a local file (`.snerdata/tasks/tasks.log`).
74
+
75
+ If you have multiple Ruby microservices (or Rails instances) running behind a load balancer and want them to share the exact same queue, simply mount a **Shared Network Drive** (like AWS EFS or NFS) to all of your servers and pass the shared path:
76
+
77
+ ```ruby
78
+ require 'snerdmq'
79
+
80
+ # All of your Ruby servers point to the exact same shared file!
81
+ # SnerdMQ's native OS file-locking guarantees zero data corruption.
82
+ queue = Snerdmq::SnerdQueue.new(storage_path: "/mnt/aws-efs-shared-drive/snerd_tasks.log")
83
+ ```
84
+
85
+ *Built with ❤️ for John Wick tier engineering.*
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "rake/testtask"
2
+
3
+ Rake::TestTask.new(:test) do |t|
4
+ t.libs << "test"
5
+ t.libs << "lib"
6
+ t.test_files = FileList["test/**/test_*.rb"]
7
+ end
8
+
9
+ task default: :test
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'fileutils'
6
+ require 'rbconfig'
7
+
8
+ REPO = "greyhands2/snerdmq"
9
+ VERSION = "v0.1.1"
10
+
11
+ # Determine OS and Architecture
12
+ host_os = RbConfig::CONFIG['host_os']
13
+ host_cpu = RbConfig::CONFIG['host_cpu']
14
+
15
+ platform = case host_os
16
+ when /darwin|mac os/
17
+ 'macos'
18
+ when /linux/
19
+ 'linux'
20
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
21
+ 'windows'
22
+ else
23
+ puts "[Snerd] Unsupported OS: #{host_os}"
24
+ exit 1
25
+ end
26
+
27
+ architecture = case host_cpu
28
+ when /x86_64|amd64/
29
+ 'x64'
30
+ when /arm64|aarch64/
31
+ 'arm64'
32
+ else
33
+ puts "[Snerd] Unsupported Architecture: #{host_cpu}"
34
+ exit 1
35
+ end
36
+
37
+ extension = platform == 'windows' ? '.exe' : ''
38
+ binary_name = "snerdmq-#{platform}-#{architecture}#{extension}"
39
+
40
+ download_url = "https://github.com/#{REPO}/releases/download/#{VERSION}/#{binary_name}"
41
+
42
+ # In Ruby, gems are installed in a global read-only directory.
43
+ # It's best to download the binary into the gem's `bin` directory so it ships alongside the library.
44
+ dest_dir = File.expand_path(__dir__)
45
+ dest_path = File.join(dest_dir, "snerdmq#{extension}")
46
+
47
+ puts "[Snerd] Downloading pre-compiled engine from GitHub: #{binary_name}..."
48
+
49
+ uri = URI.parse(download_url)
50
+ response = Net::HTTP.get_response(uri)
51
+
52
+ if response.is_a?(Net::HTTPRedirection)
53
+ uri = URI.parse(response['location'])
54
+ response = Net::HTTP.get_response(uri)
55
+ end
56
+
57
+ if response.code == '404'
58
+ puts "\n[Snerd] WARN: Binary not found at #{download_url}"
59
+ puts "[Snerd] (This is expected if you haven't published a GitHub Release yet)"
60
+ puts "[Snerd] Please provide binary_path manually when initializing SnerdQueue.\n"
61
+ exit 0
62
+ elsif response.code != '200'
63
+ puts "[Snerd] Failed to download binary: HTTP #{response.code}"
64
+ exit 1
65
+ end
66
+
67
+ File.open(dest_path, "wb") do |file|
68
+ file.write(response.body)
69
+ end
70
+
71
+ FileUtils.chmod(0755, dest_path)
72
+
73
+ puts "[Snerd] Successfully installed Snerd Engine to #{dest_path}!"
@@ -0,0 +1,161 @@
1
+ require 'json'
2
+ require 'thread'
3
+
4
+ module Snerdmq
5
+ class SnerdQueue
6
+ def initialize(binary_path: nil, storage_path: nil)
7
+ @binary_path = binary_path
8
+ @storage_path = storage_path
9
+
10
+ if @binary_path.nil?
11
+ ext = RbConfig::CONFIG['host_os'].match?(/mswin|msys|mingw|cygwin|bccwin|wince|emc/) ? '.exe' : ''
12
+ # Assume the binary was downloaded into the gem's bin/ directory via snerdmq-install
13
+ @binary_path = File.expand_path("../../bin/snerdmq#{ext}", __dir__)
14
+ end
15
+
16
+ unless File.exist?(@binary_path)
17
+ raise "[Snerd] Binary not found at #{@binary_path}. Ensure it is compiled or run 'snerdmq-install'."
18
+ end
19
+
20
+ @handlers = {}
21
+ @handlers_mutex = Mutex.new
22
+
23
+ @stdin_mutex = Mutex.new
24
+ @shutting_down = false
25
+ @io = nil
26
+ @listener_thread = nil
27
+ end
28
+
29
+ def register_handler(task_type, &block)
30
+ @handlers_mutex.synchronize do
31
+ @handlers[task_type] = block
32
+ end
33
+
34
+ if @io && !@shutting_down
35
+ send_message({
36
+ action: "register",
37
+ task_type: task_type
38
+ })
39
+ end
40
+ end
41
+
42
+ def start_listening
43
+ args = []
44
+ args << @storage_path if @storage_path
45
+
46
+ # Open a bidirectional pipe to the Rust daemon
47
+ @io = IO.popen([@binary_path] + args, "r+")
48
+
49
+ # Re-register all existing handlers
50
+ @handlers_mutex.synchronize do
51
+ @handlers.keys.each do |task_type|
52
+ send_message({
53
+ action: "register",
54
+ task_type: task_type
55
+ })
56
+ end
57
+ end
58
+
59
+ @listener_thread = Thread.new do
60
+ listen_to_stdout
61
+ end
62
+ end
63
+
64
+ def enqueue(task_id:, task_type:, data:, max_retries: 3, retry_after_hours: 0.0)
65
+ raise "[Snerd] Cannot enqueue task: Queue is not running. Call start_listening first." if @io.nil? || @shutting_down
66
+
67
+ send_message({
68
+ action: "enqueue",
69
+ task_id: task_id,
70
+ task_type: task_type,
71
+ task_data: data.to_json,
72
+ max_retries: max_retries,
73
+ retry_after_hours: retry_after_hours
74
+ })
75
+ end
76
+
77
+ def shutdown
78
+ @shutting_down = true
79
+ begin
80
+ Process.kill("TERM", @io.pid) if @io && @io.pid
81
+ rescue Errno::ESRCH, Errno::ECHILD
82
+ # Process already dead
83
+ end
84
+
85
+ @listener_thread.join(2) if @listener_thread
86
+ @io.close if @io && !@io.closed?
87
+ end
88
+
89
+ private
90
+
91
+ def send_message(msg)
92
+ @stdin_mutex.synchronize do
93
+ return if @shutting_down || @io.nil? || @io.closed?
94
+ @io.puts(msg.to_json)
95
+ @io.flush
96
+ end
97
+ rescue Errno::EPIPE
98
+ # Broken pipe if the daemon died unexpectedly
99
+ end
100
+
101
+ def listen_to_stdout
102
+ @io.each_line do |line|
103
+ next if line.strip.empty?
104
+
105
+ begin
106
+ msg = JSON.parse(line)
107
+
108
+ if msg["action"] == "execute"
109
+ # Execute in a short-lived thread so we don't block the stdout listener loop
110
+ Thread.new { handle_execute(msg) }
111
+ elsif msg["action"] == "max_retries_reached"
112
+ warn "[Snerd] Dead Letter Queue: Task #{msg['task_id']} (#{msg['task_type']}) permanently failed."
113
+ end
114
+ rescue JSON::ParserError
115
+ # Ignore malformed stdout lines
116
+ end
117
+ end
118
+ rescue IOError, Errno::EPIPE
119
+ # Normal when shutting down
120
+ end
121
+
122
+ def handle_execute(msg)
123
+ task_id = msg["task_id"]
124
+ task_type = msg["task_type"]
125
+
126
+ raw_data = msg["task_data"]
127
+ task_data = raw_data.is_a?(String) ? JSON.parse(raw_data) : raw_data
128
+
129
+ handler = nil
130
+ @handlers_mutex.synchronize do
131
+ handler = @handlers[task_type]
132
+ end
133
+
134
+ unless handler
135
+ send_message({
136
+ action: "result",
137
+ task_id: task_id,
138
+ status: "error",
139
+ error_msg: "No handler registered."
140
+ })
141
+ return
142
+ end
143
+
144
+ begin
145
+ handler.call(task_data)
146
+ send_message({
147
+ action: "result",
148
+ task_id: task_id,
149
+ status: "success"
150
+ })
151
+ rescue => e
152
+ send_message({
153
+ action: "result",
154
+ task_id: task_id,
155
+ status: "error",
156
+ error_msg: e.message
157
+ })
158
+ end
159
+ end
160
+ end
161
+ end
data/lib/snerdmq.rb ADDED
@@ -0,0 +1,5 @@
1
+ require_relative "snerdmq/queue"
2
+
3
+ module Snerdmq
4
+ class Error < StandardError; end
5
+ end
data/snerdmq.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "snerdmq"
6
+ spec.version = "1.0.0"
7
+ spec.authors = ["Greyhands2"]
8
+ spec.email = ["developer@example.com"]
9
+
10
+ spec.summary = "A zero-config, persistent background job queue for Ruby."
11
+ spec.description = "The official Ruby SDK for the SnerdMQ Rust daemon. Execute robust, lightning-fast background jobs without Redis."
12
+ spec.homepage = "https://github.com/greyhands2/snerdmq-ruby"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
16
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ end
18
+ spec.bindir = "bin"
19
+ spec.executables = ["snerdmq-install"]
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 2.0"
23
+ spec.add_development_dependency "rake", "~> 13.0"
24
+ spec.add_development_dependency "minitest", "~> 5.0"
25
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: snerdmq
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Greyhands2
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-08-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ description: The official Ruby SDK for the SnerdMQ Rust daemon. Execute robust, lightning-fast
56
+ background jobs without Redis.
57
+ email:
58
+ - developer@example.com
59
+ executables:
60
+ - snerdmq-install
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - ".gitignore"
65
+ - ".snerdata/tasks/tasks.log"
66
+ - Gemfile
67
+ - Gemfile.lock
68
+ - README.md
69
+ - Rakefile
70
+ - bin/snerdmq-install
71
+ - lib/snerdmq.rb
72
+ - lib/snerdmq/queue.rb
73
+ - snerdmq.gemspec
74
+ homepage: https://github.com/greyhands2/snerdmq-ruby
75
+ licenses:
76
+ - MIT
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubygems_version: 3.4.10
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: A zero-config, persistent background job queue for Ruby.
97
+ test_files: []