snerdmq 0.2.1 → 0.3.1

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: afc0a9b03631b02d3f9e17a6a998dde6e09620bb240c8f34c28c6c99593919d2
4
- data.tar.gz: 874f0ab2ccc9b6335cbdf42ea1735aba9e2350df03dbb12b2f6113293a73524e
3
+ metadata.gz: 7b03b1fe8237e88a2fac994242af4955576569853c3794dbc49671644c506921
4
+ data.tar.gz: be2955f250acc99685168e2b5f9193faa5e9b8910fa5b7b209f3d027f96b7383
5
5
  SHA512:
6
- metadata.gz: a54654999159b9985a6bb90930dcbdd54a917c95b0dc36ba85c4450b0ade16f93cf491fef47eafa1b6887f3ccd9f599ddc3574ff6b344ea3bd4937e53d53d4a8
7
- data.tar.gz: be4656365456108a065abfe48fbde90e65102218eb46c591ba772c77aedeeb0b2498c04bb9ddd21ca996c3711ea40088672c03a9efdecafed0af303a6292380b
6
+ metadata.gz: 27ddbdaa2acb42240c500878c509affb9d32e7acf9e4ffac3679b2a80cb1039582420670cfc11092bceb872cf347c7ffc9436bef2346aada6a0dcaada070da7d
7
+ data.tar.gz: ebbe4b904bdd68cd8750aaa339cc29cffa32437431686b36ca69547c95abf831b3faed39d1987544e614044fc729bbcd78c98fb66f3b869f9be8880eb12a69bd
@@ -0,0 +1,19 @@
1
+ ## Description
2
+ Please include a summary of the change and which issue is fixed (if any). Please also include relevant motivation and context.
3
+
4
+ ## Type of Change
5
+ Please check the options that are relevant.
6
+ - [ ] Bug fix (non-breaking change which fixes an issue)
7
+ - [ ] New feature (non-breaking change which adds functionality)
8
+ - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
9
+ - [ ] Documentation update
10
+ - [ ] Performance improvement
11
+
12
+ ## Checklist
13
+ - [ ] I have read the `CONTRIBUTING.md` document.
14
+ - [ ] My code follows the style guidelines of this project.
15
+ - [ ] I have performed a self-review of my own code.
16
+ - [ ] I have commented my code, particularly in hard-to-understand areas.
17
+ - [ ] I have made corresponding changes to the documentation.
18
+ - [ ] I have added tests that prove my fix is effective or that my feature works.
19
+ - [ ] New and existing unit tests pass locally with my changes.
@@ -1 +1,2 @@
1
1
  {"taskId":"ruby-job-1","retryCount":0,"maxRetries":3,"retryAfterHours":0.0,"retryAfterTime":"2026-08-14T09:43:50.902732Z","taskData":"{\"user_id\":\"ruby_master\",\"message\":\"matz\"}","taskType":"test_ruby_job"}
2
+ {"taskId":"ruby-job-1","retryCount":0,"maxRetries":3,"retryAfterHours":0.0,"retryAfterTime":"2026-08-16T10:42:24.239760Z","taskData":"{\"user_id\":\"ruby_master\",\"message\":\"matz\"}","taskType":"test_ruby_job","executeAt":"2026-08-16T10:42:24.239760Z"}
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,34 @@
1
+ # Contributing to SnerdMQ
2
+
3
+ First off, thank you for considering contributing to SnerdMQ! It's people like you that make open-source software such a great community to learn, inspire, and create.
4
+
5
+ ## How to Contribute
6
+
7
+ ### 1. Fork and Clone
8
+ We use the standard GitHub Fork and Pull Request workflow.
9
+ 1. Click the "Fork" button in the top right corner of the repository.
10
+ 2. Clone your fork locally: `git clone https://github.com/YOUR_USERNAME/snerdmq-ruby.git`
11
+
12
+ ### 2. Branching Strategy
13
+ Create a branch for your feature or bug fix:
14
+ ```bash
15
+ git checkout -b feat/your-feature-name
16
+ # or
17
+ git checkout -b fix/your-bugfix-name
18
+ ```
19
+ Never commit directly to the `main` or `master` branch of your fork.
20
+
21
+ ### 3. Making Changes
22
+ - Ensure your code follows the existing conventions and paradigms in the repository.
23
+ - Write clear, concise, and descriptive commit messages.
24
+ - If you're fixing a bug, please add a test that catches the bug to ensure it doesn't happen again.
25
+ - If you're adding a feature, please add appropriate tests and update the `README.md` if necessary.
26
+
27
+ ### 4. Submitting a Pull Request
28
+ 1. Push your branch to your fork: `git push origin your-branch-name`
29
+ 2. Go to the original SnerdMQ repository and click "Compare & pull request".
30
+ 3. Fill out the provided Pull Request template completely. Provide context, what you've changed, and how you tested it.
31
+ 4. Wait for a maintainer to review your code. We may request some changes before it can be merged!
32
+
33
+ ## Code of Conduct
34
+ By participating in this project, you are expected to uphold a welcoming, respectful, and inclusive environment for everyone.
data/Gemfile.lock CHANGED
@@ -1,13 +1,29 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- snerdmq (1.0.0)
4
+ snerdmq (0.3.0)
5
+ faye-websocket
6
+ puma
7
+ rack
5
8
 
6
9
  GEM
7
10
  remote: https://rubygems.org/
8
11
  specs:
12
+ base64 (0.1.1)
13
+ eventmachine (1.2.7)
14
+ faye-websocket (0.12.0)
15
+ eventmachine (>= 0.12.0)
16
+ websocket-driver (>= 0.8.0)
9
17
  minitest (5.27.0)
18
+ nio4r (2.7.5)
19
+ puma (8.0.2)
20
+ nio4r (~> 2.0)
21
+ rack (3.2.7)
10
22
  rake (13.4.2)
23
+ websocket-driver (0.8.2)
24
+ base64
25
+ websocket-extensions (>= 0.1.0)
26
+ websocket-extensions (0.1.5)
11
27
 
12
28
  PLATFORMS
13
29
  arm64-darwin-24
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
  <img src="./assets/Designer-9.png" height="120" alt="SnerdMQ Ruby Logo" />
3
- <h1>💎 SnerdMQ Ruby SDK v0.2.1</h1>
3
+ <h1>💎 SnerdMQ Ruby SDK v0.3.1</h1>
4
4
  <p>A zero-config, C-speed background job queue for Ruby. Ditch Redis and Sidekiq for lightweight, persistent background jobs.</p>
5
5
 
6
6
  [![Gem Version](https://badge.fury.io/rb/snerdmq.svg)](https://badge.fury.io/rb/snerdmq)
@@ -8,7 +8,7 @@
8
8
 
9
9
  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.
10
10
 
11
- ## ✨ v0.2.1 AI-Era Features
11
+ ## ✨ v0.3.1 AI Features
12
12
  - **Smart API Rate-Limiting**: Natively tracks `rate_limit_group` execution velocity to prevent 429 "Too Many Requests" API errors.
13
13
  - **Payload-Hashing Deduplication**: Automatically computes cryptographic hashes to drop duplicate tasks instantly.
14
14
  - **Dynamic Float Prioritization**: A native Binary Max-Heap bypasses standard FIFO rules for high urgency tasks.
@@ -16,13 +16,31 @@ This is the official Ruby SDK wrapper for **SnerdMQ**. It handles all JSON-RPC c
16
16
  - **Zero Rust Required**: Our gem installation script automatically downloads the pre-compiled C-speed Rust binary for your OS.
17
17
  - **Thread Safe**: Uses native Ruby `Thread`s and `Mutex` locks to orchestrate I/O without blocking your main event loop.
18
18
 
19
- ### ⚙️ Advanced Task Configuration (v0.2.1)
19
+ ### ⚙️ Advanced Task Configuration (v0.3.1)
20
20
  To power complex AI workflows, tasks can now be configured with advanced orchestration parameters:
21
21
 
22
22
  * **`auto_dedupe` (`true/false`)**: If set to `true`, the daemon computes a cryptographic hash of the `task_type` and `data`. If an identical payload is currently sitting in the queue pending execution, this new task is silently dropped. Excellent for preventing duplicate generative AI requests from trigger-happy users!
23
23
  * **`urgency_score` (`Float`)**: A value (e.g. `0.99`) used to bypass the standard FIFO queue. SnerdMQ uses a true Binary Max-Heap to continually float tasks with the highest urgency score to the very front of the execution line. Standard tasks default to `0.0`.
24
24
  * **`rate_limit_group` (`String`)**: A custom string (e.g. `"openai_api"` or `"db_writes"`) that groups tasks together for backpressure control.
25
25
  * **`max_per_minute` (`Integer`)**: Used in conjunction with `rate_limit_group`. If the queue processes more tasks in this group than the allowed limit within a 60-second rolling window, further tasks in this group are temporarily paused. This natively prevents 429 "Too Many Requests" errors when bursting third-party APIs.
26
+ * **`execute_at` (`String` | `Time`)**: A timestamp of when the job should be executed in the future.
27
+ * **`cron` (`String`)**: A cron expression (e.g. `"0 * * * *"`) for recurring jobs. Shorthands like `"2h"` or `"10m"` are also supported.
28
+ * **`webhook_url` (`String`)**: By providing a webhook URL, SnerdMQ will completely bypass your local Ruby blocks and dispatch the task payload via an HTTP POST request directly to the specified URL.
29
+ * **`max_execution_seconds` (`Integer`)**: Optional hard timeout in seconds. If execution takes longer, it's marked as failed.
30
+
31
+ ### Note on Hard Timeouts (`max_execution_seconds`)
32
+ When `max_execution_seconds` is provided, the Ruby SDK wraps the execution of your handler in a `Timeout.timeout` block. If the task takes longer than the timeout, a `Timeout::Error` is raised and the execution will be marked as failed. The background Rust daemon also enforces this timeout at the IPC level.
33
+
34
+ ### 🌐 HTTP Webhooks (Serverless Execution)
35
+ You can configure a task to execute externally via an HTTP POST request. By setting a `webhook_url`, the internal background processor will skip any registered handlers (`queue.register_handler`) and directly invoke the HTTP endpoint.
36
+
37
+ If the HTTP endpoint returns a non-200 status code, it triggers a retry. If it permanently fails (reaches `max_retries`), the Dead Letter Queue event is automatically fired via a final HTTP POST to the same `webhook_url` but with the header `X-SnerdMQ-Event: MaxRetriesReached`.
38
+
39
+ ### 🕒 Cron Jobs vs. Retryable Jobs
40
+ When using the new scheduling features, it is important to understand the difference between Cron and Retry behaviors:
41
+ > - **A Cron Job** is a *Repeatable Job* that executes again **only after a success**, on a fixed schedule.
42
+ > - **A Retryable Job** is a *Recovery Job* that executes again **only after a failure**, attempting to recover using the `retry_after_hours` backoff.
43
+ > - **Combined:** If a Cron Job fails, it temporarily uses `retry_after_hours` to retry until it recovers. Once it succeeds, it goes back to ticking on its standard cron schedule!
26
44
 
27
45
  ## 📦 Installation
28
46
 
@@ -75,7 +93,10 @@ queue.enqueue(
75
93
  rate_limit_group: "email_api",
76
94
  max_per_minute: 100,
77
95
  auto_dedupe: true,
78
- urgency_score: 0.99
96
+ urgency_score: 0.99,
97
+ cron: "1h",
98
+ webhook_url: "https://api.example.com/webhook",
99
+ max_execution_seconds: 300
79
100
  )
80
101
 
81
102
  # Keep main thread alive
data/lib/snerdmq/queue.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'json'
2
2
  require 'thread'
3
+ require 'timeout'
3
4
 
4
5
  module Snerdmq
5
6
  class SnerdQueue
@@ -72,7 +73,7 @@ module Snerdmq
72
73
  end
73
74
  end
74
75
 
75
- def enqueue(task_id:, task_type:, data:, max_retries: 3, retry_after_hours: 0.0, rate_limit_group: nil, max_per_minute: nil, auto_dedupe: false, urgency_score: nil)
76
+ def enqueue(task_id:, task_type:, data:, max_retries: 3, retry_after_hours: 0.0, rate_limit_group: nil, max_per_minute: nil, auto_dedupe: false, urgency_score: nil, execute_at: nil, cron: nil, webhook_url: nil, max_execution_seconds: nil)
76
77
  raise "[Snerd] Cannot enqueue task: Queue is not running. Call start_listening first." if @io.nil? || @shutting_down
77
78
 
78
79
  payload = {
@@ -88,6 +89,13 @@ module Snerdmq
88
89
  payload[:max_per_minute] = max_per_minute if max_per_minute
89
90
  payload[:auto_dedupe] = auto_dedupe if auto_dedupe
90
91
  payload[:urgency_score] = urgency_score if urgency_score
92
+
93
+ if execute_at
94
+ payload[:execute_at] = execute_at.respond_to?(:iso8601) ? execute_at.iso8601 : execute_at.to_s
95
+ end
96
+ payload[:cron] = cron if cron
97
+ payload[:webhook_url] = webhook_url if webhook_url
98
+ payload[:max_execution_seconds] = max_execution_seconds if max_execution_seconds
91
99
 
92
100
  cond = ConditionVariable.new
93
101
  result = nil
@@ -208,6 +216,7 @@ module Snerdmq
208
216
  def handle_execute(msg)
209
217
  task_id = msg["task_id"]
210
218
  task_type = msg["task_type"]
219
+ max_execution_seconds = msg["max_execution_seconds"]
211
220
 
212
221
  raw_data = msg["task_data"]
213
222
  task_data = raw_data.is_a?(String) ? JSON.parse(raw_data) : raw_data
@@ -229,12 +238,25 @@ module Snerdmq
229
238
 
230
239
  begin
231
240
  Thread.current[:snerd_task_id] = task_id
232
- handler.call(task_data)
241
+ if max_execution_seconds
242
+ Timeout.timeout(max_execution_seconds) do
243
+ handler.call(task_data)
244
+ end
245
+ else
246
+ handler.call(task_data)
247
+ end
233
248
  send_message({
234
249
  action: "result",
235
250
  task_id: task_id,
236
251
  status: "success"
237
252
  })
253
+ rescue Timeout::Error
254
+ send_message({
255
+ action: "result",
256
+ task_id: task_id,
257
+ status: "error",
258
+ error_msg: "Task execution timed out after #{max_execution_seconds} seconds"
259
+ })
238
260
  rescue => e
239
261
  send_message({
240
262
  action: "result",
data/snerdmq.gemspec CHANGED
@@ -3,7 +3,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
 
4
4
  Gem::Specification.new do |spec|
5
5
  spec.name = "snerdmq"
6
- spec.version = "0.2.1"
6
+ spec.version = "0.3.1"
7
7
  spec.authors = ["Greyhands2"]
8
8
  spec.email = ["developer@example.com"]
9
9
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: snerdmq
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Greyhands2
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-15 00:00:00.000000000 Z
11
+ date: 2026-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -103,8 +103,10 @@ executables:
103
103
  extensions: []
104
104
  extra_rdoc_files: []
105
105
  files:
106
+ - ".github/PULL_REQUEST_TEMPLATE.md"
106
107
  - ".gitignore"
107
108
  - ".snerdata/tasks/tasks.log"
109
+ - CONTRIBUTING.md
108
110
  - Gemfile
109
111
  - Gemfile.lock
110
112
  - README.md