zizq 0.3.5 → 0.3.7
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 +41 -8
- data/lib/zizq/router.rb +100 -0
- data/lib/zizq/test/client.rb +20 -2
- data/lib/zizq/test.rb +7 -1
- data/lib/zizq/version.rb +1 -1
- data/lib/zizq.rb +1 -0
- data/sig/generated/zizq/router.rbs +81 -0
- data/sig/generated/zizq/test/client.rbs +14 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3252547a350b856e8122d15a77275769f62a4495efe36b36e252eba2246b51cf
|
|
4
|
+
data.tar.gz: 570e3ef065cbe42cfc038d6c1dd6669940d62f051d53965c48bea548f329e941
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 545bc4510f7df1acc51817c48ec2fae04c3a96df3589c2e3f12017d8bebb311a69cd7d18b14941ed3206829d08dd1cf28dd1d0461d44c7d2a265eead57f70710
|
|
7
|
+
data.tar.gz: 00b8916adf6921aded73d0f16cde1c2c52c98f338b3a9e4ad79a4131f06aef42b4001623c2e43d53a7c9d67e289c62f97b6abfbee6b0bb240ed89726e273ae7e
|
data/README.md
CHANGED
|
@@ -12,7 +12,7 @@ API.
|
|
|
12
12
|
## Features
|
|
13
13
|
|
|
14
14
|
* Multi-thread and/or multi-fiber concurrent worker (via [`async`](https://github.com/socketry/async))
|
|
15
|
-
* `Zizq::Job` based job classes, Active Job support, or
|
|
15
|
+
* `Zizq::Job` based job classes, Active Job support, or low-level/custom
|
|
16
16
|
* Enqueue and process jobs from one language to another
|
|
17
17
|
* Arbitrary named queues
|
|
18
18
|
* Granular job priorities
|
|
@@ -22,6 +22,7 @@ API.
|
|
|
22
22
|
* Recurring jobs (cron)
|
|
23
23
|
* Job introspection and management APIs, with support for `jq` query filters
|
|
24
24
|
* Unique jobs
|
|
25
|
+
* Testing helpers
|
|
25
26
|
|
|
26
27
|
## Installation
|
|
27
28
|
|
|
@@ -32,13 +33,13 @@ API.
|
|
|
32
33
|
Add it to your application's `Gemfile`:
|
|
33
34
|
|
|
34
35
|
```ruby
|
|
35
|
-
gem 'zizq', '~> 0.3.
|
|
36
|
+
gem 'zizq', '~> 0.3.7'
|
|
36
37
|
```
|
|
37
38
|
|
|
38
39
|
Or install it manually:
|
|
39
40
|
|
|
40
41
|
```shell
|
|
41
|
-
$ gem install zizq -v 0.3.
|
|
42
|
+
$ gem install zizq -v 0.3.7
|
|
42
43
|
```
|
|
43
44
|
|
|
44
45
|
Ruby **3.2.8 or newer** is required. Client and server share version
|
|
@@ -62,7 +63,7 @@ Zizq.configure do |c|
|
|
|
62
63
|
# Optional worker defaults — applied to every Zizq::Worker
|
|
63
64
|
# instance and to runs of the `zizq-worker` executable. Explicit
|
|
64
65
|
# kwargs or CLI flags override these.
|
|
65
|
-
c.worker.queues
|
|
66
|
+
c.worker.queues = ['emails', 'payments']
|
|
66
67
|
c.worker.fiber_count = 25
|
|
67
68
|
end
|
|
68
69
|
```
|
|
@@ -148,10 +149,42 @@ Zizq.enqueue_bulk do |b|
|
|
|
148
149
|
end
|
|
149
150
|
```
|
|
150
151
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
Jobs can also be enqueued without `Zizq::Job` via `Zizq.enqueue_raw` —
|
|
153
|
+
designed for lower-level code style, and for cross-language workflows where,
|
|
154
|
+
for example, a Ruby app enqueues jobs consumed by a Go service.
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
Zizq.enqueue_raw(
|
|
158
|
+
type: "send_email",
|
|
159
|
+
queue: "comms",
|
|
160
|
+
payload: { user_id: 42, template: "welcome" }
|
|
161
|
+
)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Cross-language and low-level dispatch
|
|
165
|
+
|
|
166
|
+
When a Ruby app needs to *process* jobs enqueued by another language
|
|
167
|
+
(or by `Zizq.enqueue_raw`), `Zizq::Router` maps `type` strings to
|
|
168
|
+
handler blocks operating on plain JSON payloads:
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
Zizq.configure do |c|
|
|
172
|
+
c.dispatcher = Zizq::Router.new do
|
|
173
|
+
route('send_email') do |payload|
|
|
174
|
+
Mailer.deliver(payload['user_id'], payload['template'])
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Apps that mix the two styles can fall back to Zizq::Job
|
|
178
|
+
# for anything not handled by an explicit route.
|
|
179
|
+
fallback { |job| Zizq::Job.call(job) }
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
See [Custom Dispatchers](https://zizq.io/docs/clients/ruby/dispatchers.html)
|
|
185
|
+
for full details. Dispatchers in Zizq are just objects that implement `#call`
|
|
186
|
+
with a single `Zizq::Resources::Job` argument, and `Zizq::Router` is just a
|
|
187
|
+
dispatcher itself.
|
|
155
188
|
|
|
156
189
|
### Running a worker
|
|
157
190
|
|
data/lib/zizq/router.rb
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Copyright (c) 2026 Chris Corbyn <chris@zizq.io>
|
|
2
|
+
# Licensed under the MIT License. See LICENSE file for details.
|
|
3
|
+
|
|
4
|
+
# rbs_inline: enabled
|
|
5
|
+
# frozen_string_literal: true
|
|
6
|
+
|
|
7
|
+
module Zizq
|
|
8
|
+
# Dispatch jobs by `type` string, mapping each to a handler block.
|
|
9
|
+
#
|
|
10
|
+
# Designed for cross-language workflows: payloads are plain JSON
|
|
11
|
+
# values (Hashes / Arrays / strings / numbers), `type` is a String
|
|
12
|
+
# the producer agrees on with the consumer, and routes are
|
|
13
|
+
# registered explicitly — no `Zizq::Job` mixin involved.
|
|
14
|
+
#
|
|
15
|
+
# Zizq.configure do |c|
|
|
16
|
+
# c.dispatcher = Zizq::Router.new do
|
|
17
|
+
# route("send_email") do |payload|
|
|
18
|
+
# Mailer.deliver(payload["user_id"], payload["template"])
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# route("expire_tokens") do
|
|
22
|
+
# TokenSweeper.run
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# route("generate_report") do |payload, job|
|
|
26
|
+
# Reports.generate(payload["id"], attempts: job.attempts)
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# # `def` inside the block defines singleton methods on the
|
|
30
|
+
# # router. Route blocks captured *inside* the constructor
|
|
31
|
+
# # have lexical `self == router`, so they can call these
|
|
32
|
+
# # helpers; routes added outside (`router.route("…") { … }`)
|
|
33
|
+
# # keep their own lexical `self` and would need to go through
|
|
34
|
+
# # the router explicitly (`router.logger`).
|
|
35
|
+
# def logger
|
|
36
|
+
# Zizq.configuration.logger
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# # Anything else falls back. A common pattern is delegating
|
|
40
|
+
# # to `Zizq::Job` for the apps that mix the two styles.
|
|
41
|
+
# fallback { |job| Zizq::Job.call(job) }
|
|
42
|
+
# end
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# Routes can also be registered outside the constructor block:
|
|
46
|
+
#
|
|
47
|
+
# router = Zizq::Router.new
|
|
48
|
+
# router.route("send_email") { |payload| ... }
|
|
49
|
+
#
|
|
50
|
+
# Handlers are called as `handler.call(payload, job)`. Block-arity
|
|
51
|
+
# rules let `{ |payload| }` or `{ }` ignore either argument.
|
|
52
|
+
# Strict-arity lambdas need to declare both.
|
|
53
|
+
class Router
|
|
54
|
+
# Raised when a job arrives with a type that has no registered
|
|
55
|
+
# route and no fallback. Caught by Zizq's normal worker error
|
|
56
|
+
# path, which nacks the job for retry (or dead-letters it once
|
|
57
|
+
# the retry limit is hit).
|
|
58
|
+
class UnknownJobType < Zizq::Error; end
|
|
59
|
+
|
|
60
|
+
# @rbs &block: ?(self) [self: Router] -> void
|
|
61
|
+
def initialize(&block)
|
|
62
|
+
@routes = {} #: Hash[String, ^(untyped, Resources::Job) -> void]
|
|
63
|
+
@fallback = nil #: (^(Resources::Job) -> void)?
|
|
64
|
+
instance_eval(&block) if block
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Register `handler` for jobs whose `type` matches.
|
|
68
|
+
#
|
|
69
|
+
# @rbs type: String | Symbol
|
|
70
|
+
# @rbs &handler: (untyped, Resources::Job) -> void
|
|
71
|
+
def route(type, &handler)
|
|
72
|
+
@routes[type.to_s] = handler
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Register a fallback handler invoked when no route matches.
|
|
76
|
+
# Receives the full `Resources::Job` (not split into payload /
|
|
77
|
+
# job pair), since the canonical use is delegation to another
|
|
78
|
+
# dispatcher:
|
|
79
|
+
#
|
|
80
|
+
# fallback { |job| Zizq::Job.call(job) }
|
|
81
|
+
#
|
|
82
|
+
# @rbs &handler: (Resources::Job) -> void
|
|
83
|
+
def fallback(&handler)
|
|
84
|
+
@fallback = handler
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Dispatch a single job. Looks up the handler by `job.type`,
|
|
88
|
+
# falls back to the registered fallback if any, otherwise
|
|
89
|
+
# raises `UnknownJobType`.
|
|
90
|
+
def call(job) #: (Resources::Job) -> void
|
|
91
|
+
handler = @routes[job.type]
|
|
92
|
+
|
|
93
|
+
return handler.call(job.payload, job) if handler
|
|
94
|
+
return @fallback.call(job) if @fallback
|
|
95
|
+
|
|
96
|
+
raise UnknownJobType,
|
|
97
|
+
"no handler registered for job type #{job.type.inspect}"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
data/lib/zizq/test/client.rb
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
# rbs_inline: enabled
|
|
5
5
|
# frozen_string_literal: true
|
|
6
6
|
|
|
7
|
+
require "json"
|
|
8
|
+
|
|
7
9
|
module Zizq
|
|
8
10
|
module Test
|
|
9
11
|
# A `Zizq::Client` stand-in for use in test suites.
|
|
@@ -143,7 +145,7 @@ module Zizq
|
|
|
143
145
|
req = EnqueueRequest.new(
|
|
144
146
|
queue:,
|
|
145
147
|
type:,
|
|
146
|
-
payload
|
|
148
|
+
payload: self.class.normalize_payload(payload),
|
|
147
149
|
priority:,
|
|
148
150
|
ready_at:,
|
|
149
151
|
retry_limit:,
|
|
@@ -162,7 +164,7 @@ module Zizq
|
|
|
162
164
|
req = EnqueueRequest.new(
|
|
163
165
|
queue: params[:queue],
|
|
164
166
|
type: params[:type],
|
|
165
|
-
payload: params[:payload],
|
|
167
|
+
payload: self.class.normalize_payload(params[:payload]),
|
|
166
168
|
priority: params[:priority],
|
|
167
169
|
ready_at: params[:ready_at],
|
|
168
170
|
retry_limit: params[:retry_limit],
|
|
@@ -236,6 +238,22 @@ module Zizq
|
|
|
236
238
|
"#{ID_PREFIX}#{counter.to_s.rjust(ID_LENGTH - ID_PREFIX.length, '0')}"
|
|
237
239
|
end
|
|
238
240
|
|
|
241
|
+
public
|
|
242
|
+
|
|
243
|
+
# Round-trip the payload through JSON so the in-memory
|
|
244
|
+
# representation matches what a consumer would receive over the
|
|
245
|
+
# wire: symbol keys / Symbol values become strings, nested
|
|
246
|
+
# hashes and arrays are normalized recursively, and non-JSON-
|
|
247
|
+
# safe values (BigDecimal, custom objects) raise here rather
|
|
248
|
+
# than surviving in test mode only to break in production.
|
|
249
|
+
#
|
|
250
|
+
# Also used by `Zizq::Test.enqueued_raw?` / `enqueued_raw_count`
|
|
251
|
+
# to normalize the query side so symbol-keyed assertion payloads
|
|
252
|
+
# still match the (now string-keyed) buffer.
|
|
253
|
+
def self.normalize_payload(payload) #: (untyped) -> untyped
|
|
254
|
+
JSON.parse(JSON.generate(payload))
|
|
255
|
+
end
|
|
256
|
+
|
|
239
257
|
# Returns entries matching every named filter AND the predicate.
|
|
240
258
|
# All filter kwargs are optional; unset means "don't filter on
|
|
241
259
|
# this axis." Callers must hold `@mutex` — public accessors do
|
data/lib/zizq/test.rb
CHANGED
|
@@ -168,7 +168,13 @@ module Zizq
|
|
|
168
168
|
filters = {}
|
|
169
169
|
filters[:only_queues] = queue if queue
|
|
170
170
|
filters[:only_types] = type if type
|
|
171
|
-
|
|
171
|
+
unless payload.nil?
|
|
172
|
+
# Normalize the assertion-side payload the same way enqueue
|
|
173
|
+
# normalizes the buffered one, so symbol-keyed test payloads
|
|
174
|
+
# still match the (string-keyed) wire-format buffer.
|
|
175
|
+
normalized = Client.normalize_payload(payload)
|
|
176
|
+
filters[:filter] = ->(job) { job.payload == normalized }
|
|
177
|
+
end
|
|
172
178
|
client.enqueued_jobs(**filters).size
|
|
173
179
|
end
|
|
174
180
|
|
data/lib/zizq/version.rb
CHANGED
data/lib/zizq.rb
CHANGED
|
@@ -29,6 +29,7 @@ module Zizq
|
|
|
29
29
|
autoload :Lifecycle, "zizq/lifecycle"
|
|
30
30
|
autoload :Query, "zizq/query"
|
|
31
31
|
autoload :Resources, "zizq/resources"
|
|
32
|
+
autoload :Router, "zizq/router"
|
|
32
33
|
autoload :Test, "zizq/test"
|
|
33
34
|
autoload :TlsConfiguration, "zizq/tls_configuration"
|
|
34
35
|
autoload :Worker, "zizq/worker"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Generated from lib/zizq/router.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Zizq
|
|
4
|
+
# Dispatch jobs by `type` string, mapping each to a handler block.
|
|
5
|
+
#
|
|
6
|
+
# Designed for cross-language workflows: payloads are plain JSON
|
|
7
|
+
# values (Hashes / Arrays / strings / numbers), `type` is a String
|
|
8
|
+
# the producer agrees on with the consumer, and routes are
|
|
9
|
+
# registered explicitly — no `Zizq::Job` mixin involved.
|
|
10
|
+
#
|
|
11
|
+
# Zizq.configure do |c|
|
|
12
|
+
# c.dispatcher = Zizq::Router.new do
|
|
13
|
+
# route("send_email") do |payload|
|
|
14
|
+
# Mailer.deliver(payload["user_id"], payload["template"])
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# route("expire_tokens") do
|
|
18
|
+
# TokenSweeper.run
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# route("generate_report") do |payload, job|
|
|
22
|
+
# Reports.generate(payload["id"], attempts: job.attempts)
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# # `def` inside the block defines singleton methods on the
|
|
26
|
+
# # router. Route blocks captured *inside* the constructor
|
|
27
|
+
# # have lexical `self == router`, so they can call these
|
|
28
|
+
# # helpers; routes added outside (`router.route("…") { … }`)
|
|
29
|
+
# # keep their own lexical `self` and would need to go through
|
|
30
|
+
# # the router explicitly (`router.logger`).
|
|
31
|
+
# def logger
|
|
32
|
+
# Zizq.configuration.logger
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# # Anything else falls back. A common pattern is delegating
|
|
36
|
+
# # to `Zizq::Job` for the apps that mix the two styles.
|
|
37
|
+
# fallback { |job| Zizq::Job.call(job) }
|
|
38
|
+
# end
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# Routes can also be registered outside the constructor block:
|
|
42
|
+
#
|
|
43
|
+
# router = Zizq::Router.new
|
|
44
|
+
# router.route("send_email") { |payload| ... }
|
|
45
|
+
#
|
|
46
|
+
# Handlers are called as `handler.call(payload, job)`. Block-arity
|
|
47
|
+
# rules let `{ |payload| }` or `{ }` ignore either argument.
|
|
48
|
+
# Strict-arity lambdas need to declare both.
|
|
49
|
+
class Router
|
|
50
|
+
# Raised when a job arrives with a type that has no registered
|
|
51
|
+
# route and no fallback. Caught by Zizq's normal worker error
|
|
52
|
+
# path, which nacks the job for retry (or dead-letters it once
|
|
53
|
+
# the retry limit is hit).
|
|
54
|
+
class UnknownJobType < Zizq::Error
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @rbs &block: ?(self) [self: Router] -> void
|
|
58
|
+
def initialize: () ?{ (self) [self: Router] -> void } -> untyped
|
|
59
|
+
|
|
60
|
+
# Register `handler` for jobs whose `type` matches.
|
|
61
|
+
#
|
|
62
|
+
# @rbs type: String | Symbol
|
|
63
|
+
# @rbs &handler: (untyped, Resources::Job) -> void
|
|
64
|
+
def route: (String | Symbol type) { (untyped, Resources::Job) -> void } -> untyped
|
|
65
|
+
|
|
66
|
+
# Register a fallback handler invoked when no route matches.
|
|
67
|
+
# Receives the full `Resources::Job` (not split into payload /
|
|
68
|
+
# job pair), since the canonical use is delegation to another
|
|
69
|
+
# dispatcher:
|
|
70
|
+
#
|
|
71
|
+
# fallback { |job| Zizq::Job.call(job) }
|
|
72
|
+
#
|
|
73
|
+
# @rbs &handler: (Resources::Job) -> void
|
|
74
|
+
def fallback: () { (Resources::Job) -> void } -> untyped
|
|
75
|
+
|
|
76
|
+
# Dispatch a single job. Looks up the handler by `job.type`,
|
|
77
|
+
# falls back to the registered fallback if any, otherwise
|
|
78
|
+
# raises `UnknownJobType`.
|
|
79
|
+
def call: (untyped job) -> untyped
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -120,6 +120,20 @@ module Zizq
|
|
|
120
120
|
|
|
121
121
|
def synthetic_id: (untyped counter) -> untyped
|
|
122
122
|
|
|
123
|
+
public
|
|
124
|
+
|
|
125
|
+
# Round-trip the payload through JSON so the in-memory
|
|
126
|
+
# representation matches what a consumer would receive over the
|
|
127
|
+
# wire: symbol keys / Symbol values become strings, nested
|
|
128
|
+
# hashes and arrays are normalized recursively, and non-JSON-
|
|
129
|
+
# safe values (BigDecimal, custom objects) raise here rather
|
|
130
|
+
# than surviving in test mode only to break in production.
|
|
131
|
+
#
|
|
132
|
+
# Also used by `Zizq::Test.enqueued_raw?` / `enqueued_raw_count`
|
|
133
|
+
# to normalize the query side so symbol-keyed assertion payloads
|
|
134
|
+
# still match the (now string-keyed) buffer.
|
|
135
|
+
def self.normalize_payload: (untyped payload) -> untyped
|
|
136
|
+
|
|
123
137
|
# Returns entries matching every named filter AND the predicate.
|
|
124
138
|
# All filter kwargs are optional; unset means "don't filter on
|
|
125
139
|
# this axis." Callers must hold `@mutex` — public accessors do
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: zizq
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Corbyn <chris@zizq.io>
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: async-http
|
|
@@ -100,6 +100,7 @@ files:
|
|
|
100
100
|
- lib/zizq/resources/job_template.rb
|
|
101
101
|
- lib/zizq/resources/page.rb
|
|
102
102
|
- lib/zizq/resources/resource.rb
|
|
103
|
+
- lib/zizq/router.rb
|
|
103
104
|
- lib/zizq/test.rb
|
|
104
105
|
- lib/zizq/test/client.rb
|
|
105
106
|
- lib/zizq/tls_configuration.rb
|
|
@@ -135,6 +136,7 @@ files:
|
|
|
135
136
|
- sig/generated/zizq/resources/job_template.rbs
|
|
136
137
|
- sig/generated/zizq/resources/page.rbs
|
|
137
138
|
- sig/generated/zizq/resources/resource.rbs
|
|
139
|
+
- sig/generated/zizq/router.rbs
|
|
138
140
|
- sig/generated/zizq/test.rbs
|
|
139
141
|
- sig/generated/zizq/test/client.rbs
|
|
140
142
|
- sig/generated/zizq/tls_configuration.rbs
|