zizq 0.1.0 → 0.2.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: 77ab05fe491ab193d0c5c3b1106aac3e5effe7b1dcb7e0117b70e0e05652426b
4
- data.tar.gz: 05f26bd94ca2127e7e859212348e063bc9354d866945ae500514dcfbcfe57828
3
+ metadata.gz: de36f4f8204a5d5b0c64e77b78249ed34ea6c667ceb6b03dbdca2a291cb0f330
4
+ data.tar.gz: 7cbb62aac1788627bb1047b474e5ca915c5dca059bc6f00be7a2f5adca798a7a
5
5
  SHA512:
6
- metadata.gz: f12216566271dbd68395eeada829ffd68bfd853e86c1a8e598dbac6c3c7f5d8af5008ee084a9a7ebaef99d74144c6d05995fe32e01f0349b47dac6beb082c702
7
- data.tar.gz: 505804d9abd2c1cff8a749bd9cb6ad3d224d09545648a314b54a3094a96f2d18cb18257661833479bf1b59959d6d300730e2cbcbb70572afee5195bb538d8460
6
+ metadata.gz: 95b80cec6e0bb1704bd1244b37c4434b5d5467139e35c61bae39f342825a3e136a413aa55c0a0b7de379024bcdee3486e08cb211062a9d406011093a68de2899
7
+ data.tar.gz: '09952b2563e139e9987473b53524ea993f1c803ea0320082c166132053919f3401cad65c8dc063923837c3b1fe8a505e3e27dcd9ce6a20724313c1d6f82c00e3'
@@ -9,8 +9,9 @@ require_relative "job_config"
9
9
  module Zizq
10
10
  # Zizq configuration DSL for ActiveJob classes.
11
11
  #
12
- # Extend this module in an ActiveJob subclass to gain access to Zizq
13
- # features like unique jobs, backoff, and retention:
12
+ # Extend this module in an ActiveJob subclass to allow enqueueing jobs via
13
+ # `Zizq.enqueue` and to gain access to Zizq features like unique jobs,
14
+ # backoff, and retention:
14
15
  #
15
16
  # class SendEmailJob < ApplicationJob
16
17
  # extend Zizq::ActiveJobConfig
@@ -36,17 +37,28 @@ module Zizq
36
37
  # @rbs!
37
38
  # # ActiveJob::Base.new — invisible to steep without this.
38
39
  # def new: (*untyped, **untyped) -> untyped
40
+ #
41
+ # # ActiveJob::Base.queue_name — invisible to steep without this.
42
+ # def queue_name: () -> String?
43
+
44
+ # Use ActiveJob's `queue_name` as the default queue, falling back to
45
+ # any explicit `zizq_queue` setting, then "default".
46
+ def zizq_queue(name = nil) #: (?String?) -> String
47
+ if name
48
+ super
49
+ else
50
+ @zizq_queue || queue_name || "default"
51
+ end
52
+ end
39
53
 
40
- # Serialize arguments using ActiveJob's serialization format.
54
+ # Serialize using ActiveJob's own format.
41
55
  #
42
56
  # Creates a temporary ActiveJob instance to produce the canonical
43
- # serialized form, including `_aj_ruby2_keywords` markers for kwargs.
44
- # This ensures unique key generation uses the same format as the
45
- # enqueued payload.
46
- #
47
- # This is needed so that unique job keys can be correctly generated.
48
- def zizq_serialize(*args, **kwargs) #: (*untyped, **untyped) -> Array[untyped]
49
- new(*args, **kwargs).serialize["arguments"]
57
+ # serialized form. Returns the full serialized hash (including
58
+ # `job_class`, `arguments`, `queue_name`, etc.) so that the payload
59
+ # stored in Zizq matches what `ActiveJob::Base.execute` expects.
60
+ def zizq_serialize(*args, **kwargs) #: (*untyped, **untyped) -> Hash[String, untyped]
61
+ new(*args, **kwargs).serialize
50
62
  end
51
63
 
52
64
  # Deserialization is handled by ActiveJob::Base.execute on the worker
@@ -56,6 +68,15 @@ module Zizq
56
68
  "ActiveJob handles deserialization via ActiveJob::Base.execute"
57
69
  end
58
70
 
71
+ # Override unique key generation to hash only the arguments portion
72
+ # of the serialized payload. The full payload contains volatile fields
73
+ # (job_id, enqueued_at, etc.) that change per instance.
74
+ def zizq_unique_key(*args, **kwargs) #: (*untyped, **untyped) -> String
75
+ arguments = new(*args, **kwargs).serialize["arguments"]
76
+ payload = normalize_payload(arguments)
77
+ "#{name}:#{Digest::SHA256.hexdigest(JSON.generate(payload))}"
78
+ end
79
+
59
80
  # Generate a jq expression that exactly matches payloads with the given
60
81
  # arguments.
61
82
  #
@@ -65,8 +86,8 @@ module Zizq
65
86
  #
66
87
  # .arguments == ["a","b",{"example":true,"_aj_ruby2_keywords":["example"]}]
67
88
  def zizq_payload_filter(*args, **kwargs) #: (*untyped, **untyped) -> String
68
- payload = zizq_serialize(*args, **kwargs)
69
- ".arguments == #{JSON.generate(payload)}"
89
+ arguments = zizq_serialize(*args, **kwargs)["arguments"]
90
+ ".arguments == #{JSON.generate(arguments)}"
70
91
  end
71
92
 
72
93
  # Generate a jq expression that matches jobs whose positional args
@@ -85,27 +106,27 @@ module Zizq
85
106
  # (.arguments[-1] | has("_aj_ruby2_keywords")) and
86
107
  # (.arguments[-1] | contains({"example":true}))
87
108
  def zizq_payload_subset_filter(*args, **kwargs) #: (*untyped, **untyped) -> String
88
- payload = zizq_serialize(*args, **kwargs)
109
+ arguments = zizq_serialize(*args, **kwargs)["arguments"]
89
110
 
90
111
  # ActiveJob flattens arguments into a single array, but marks kwargs with
91
112
  # "_aj_ruby2_keywords" => ["key1", "key2", ...] in the last element of
92
113
  # the array where kwargs are present. We need to detect this to generate
93
114
  # a suitable expression.
94
115
  serialized_args, serialized_kwargs =
95
- if payload.size > 0
116
+ if arguments.size > 0
96
117
  # See what the last argument looks like. It might be kwargs.
97
- maybe_kwargs = payload.pop
118
+ maybe_kwargs = arguments.pop
98
119
 
99
120
  # If it's got "_aj_ruby2_keywords" then it is kwargs.
100
121
  if maybe_kwargs.is_a?(Hash) && maybe_kwargs["_aj_ruby2_keywords"]
101
122
  # We only want the actual kwargs, not the marker.
102
- [payload, maybe_kwargs.except("_aj_ruby2_keywords")]
123
+ [arguments, maybe_kwargs.except("_aj_ruby2_keywords")]
103
124
  else
104
125
  # It wasn't kwargs, so put it back.
105
- [payload.push(maybe_kwargs), nil]
126
+ [arguments.push(maybe_kwargs), nil]
106
127
  end
107
128
  else
108
- [payload, nil]
129
+ [arguments, nil]
109
130
  end
110
131
 
111
132
  parts = [] #: Array[String]
data/lib/zizq/client.rb CHANGED
@@ -249,6 +249,37 @@ module Zizq
249
249
  Resources::JobPage.new(self, data)
250
250
  end
251
251
 
252
+ # Count jobs matching the given filters.
253
+ #
254
+ # Accepts the same filter arguments as `list_jobs` (minus pagination).
255
+ # Returns the count as an integer.
256
+ #
257
+ # @rbs id: (String | Array[String])?
258
+ # @rbs status: (String | Array[String])?
259
+ # @rbs queue: (String | Array[String])?
260
+ # @rbs type: (String | Array[String])?
261
+ # @rbs filter: String?
262
+ # @rbs return: Integer
263
+ def count_jobs(id: nil,
264
+ status: nil,
265
+ queue: nil,
266
+ type: nil,
267
+ filter: nil)
268
+ options = { id:, status:, queue:, type:, filter: }.compact #: Hash[Symbol, untyped]
269
+
270
+ multi_keys = %i[id status queue type]
271
+ params = build_where_params(options, multi_keys:)
272
+
273
+ # An empty filter ([] or "") matches nothing — short-circuit.
274
+ multi_keys.each do |key|
275
+ return 0 if params[key] == ""
276
+ end
277
+
278
+ response = get("/jobs/count", params:)
279
+ data = handle_response!(response, expected: 200)
280
+ data.fetch("count")
281
+ end
282
+
252
283
  # Delete a single job by ID.
253
284
  #
254
285
  # @rbs id: String
data/lib/zizq/query.rb CHANGED
@@ -283,6 +283,32 @@ module Zizq
283
283
  limit(2).to_a.size == 1
284
284
  end
285
285
 
286
+ # Count matching jobs via the server-side count endpoint.
287
+ #
288
+ # Without a block or argument, uses `GET /jobs/count` for an efficient
289
+ # server-side count. When a limit is set, caps the result locally with
290
+ # `[total, limit].min`.
291
+ #
292
+ # With a block or argument, falls back to Enumerable (iterates and counts
293
+ # matching jobs).
294
+ #
295
+ # @rbs *args: untyped
296
+ # @rbs &block: ?(Resources::Job) -> bool
297
+ # @rbs return: Integer
298
+ def count(*args, &block)
299
+ return super if block || !args.empty?
300
+
301
+ total = Zizq.client.count_jobs(
302
+ id: @id,
303
+ queue: @queue,
304
+ type: @type,
305
+ status: @status,
306
+ filter: @jq_filter,
307
+ )
308
+
309
+ @limit ? [total, @limit].min : total
310
+ end
311
+
286
312
  # Iterate over matching jobs in reverse order.
287
313
  #
288
314
  # Optimised: pushes the reverse ordering to the server instead of
data/lib/zizq/version.rb CHANGED
@@ -5,5 +5,5 @@
5
5
  # frozen_string_literal: true
6
6
 
7
7
  module Zizq
8
- VERSION = "0.1.0" #: String
8
+ VERSION = "0.2.1" #: String
9
9
  end
data/lib/zizq.rb CHANGED
@@ -253,8 +253,8 @@ module Zizq
253
253
  # @rbs &block: ?(EnqueueRequest) -> void
254
254
  # @rbs return: EnqueueRequest
255
255
  def build_enqueue_request(job_class, *args, **kwargs, &block)
256
- unless job_class.is_a?(Class) && job_class < Zizq::Job
257
- raise ArgumentError, "#{job_class.inspect} must include Zizq::Job"
256
+ unless job_class.is_a?(Class) && job_class.is_a?(Zizq::JobConfig)
257
+ raise ArgumentError, "#{job_class.inspect} must include Zizq::Job or extend Zizq::ActiveJobConfig"
258
258
  end
259
259
 
260
260
  zizq_job_class = job_class #: Zizq::job_class
@@ -3,8 +3,9 @@
3
3
  module Zizq
4
4
  # Zizq configuration DSL for ActiveJob classes.
5
5
  #
6
- # Extend this module in an ActiveJob subclass to gain access to Zizq
7
- # features like unique jobs, backoff, and retention:
6
+ # Extend this module in an ActiveJob subclass to allow enqueueing jobs via
7
+ # `Zizq.enqueue` and to gain access to Zizq features like unique jobs,
8
+ # backoff, and retention:
8
9
  #
9
10
  # class SendEmailJob < ApplicationJob
10
11
  # extend Zizq::ActiveJobConfig
@@ -30,20 +31,30 @@ module Zizq
30
31
  # ActiveJob::Base.new — invisible to steep without this.
31
32
  def new: (*untyped, **untyped) -> untyped
32
33
 
33
- # Serialize arguments using ActiveJob's serialization format.
34
+ # ActiveJob::Base.queue_name invisible to steep without this.
35
+ def queue_name: () -> String?
36
+
37
+ # Use ActiveJob's `queue_name` as the default queue, falling back to
38
+ # any explicit `zizq_queue` setting, then "default".
39
+ def zizq_queue: (?untyped name) -> untyped
40
+
41
+ # Serialize using ActiveJob's own format.
34
42
  #
35
43
  # Creates a temporary ActiveJob instance to produce the canonical
36
- # serialized form, including `_aj_ruby2_keywords` markers for kwargs.
37
- # This ensures unique key generation uses the same format as the
38
- # enqueued payload.
39
- #
40
- # This is needed so that unique job keys can be correctly generated.
44
+ # serialized form. Returns the full serialized hash (including
45
+ # `job_class`, `arguments`, `queue_name`, etc.) so that the payload
46
+ # stored in Zizq matches what `ActiveJob::Base.execute` expects.
41
47
  def zizq_serialize: (*untyped args, **untyped kwargs) -> untyped
42
48
 
43
49
  # Deserialization is handled by ActiveJob::Base.execute on the worker
44
50
  # side. This method is not used in the ActiveJob dispatch path.
45
51
  def zizq_deserialize: (untyped _payload) -> untyped
46
52
 
53
+ # Override unique key generation to hash only the arguments portion
54
+ # of the serialized payload. The full payload contains volatile fields
55
+ # (job_id, enqueued_at, etc.) that change per instance.
56
+ def zizq_unique_key: (*untyped args, **untyped kwargs) -> untyped
57
+
47
58
  # Generate a jq expression that exactly matches payloads with the given
48
59
  # arguments.
49
60
  #
@@ -108,6 +108,19 @@ module Zizq
108
108
  # @rbs return: Resources::JobPage
109
109
  def list_jobs: (?id: (String | Array[String])?, ?status: (String | Array[String])?, ?queue: (String | Array[String])?, ?type: (String | Array[String])?, ?filter: String?, ?from: String?, ?order: Zizq::sort_direction?, ?limit: Integer?) -> Resources::JobPage
110
110
 
111
+ # Count jobs matching the given filters.
112
+ #
113
+ # Accepts the same filter arguments as `list_jobs` (minus pagination).
114
+ # Returns the count as an integer.
115
+ #
116
+ # @rbs id: (String | Array[String])?
117
+ # @rbs status: (String | Array[String])?
118
+ # @rbs queue: (String | Array[String])?
119
+ # @rbs type: (String | Array[String])?
120
+ # @rbs filter: String?
121
+ # @rbs return: Integer
122
+ def count_jobs: (?id: (String | Array[String])?, ?status: (String | Array[String])?, ?queue: (String | Array[String])?, ?type: (String | Array[String])?, ?filter: String?) -> Integer
123
+
111
124
  # Delete a single job by ID.
112
125
  #
113
126
  # @rbs id: String
@@ -208,6 +208,20 @@ module Zizq
208
208
  # @rbs return: bool
209
209
  def one?: () ?{ (Resources::Job) -> bool } -> bool
210
210
 
211
+ # Count matching jobs via the server-side count endpoint.
212
+ #
213
+ # Without a block or argument, uses `GET /jobs/count` for an efficient
214
+ # server-side count. When a limit is set, caps the result locally with
215
+ # `[total, limit].min`.
216
+ #
217
+ # With a block or argument, falls back to Enumerable (iterates and counts
218
+ # matching jobs).
219
+ #
220
+ # @rbs *args: untyped
221
+ # @rbs &block: ?(Resources::Job) -> bool
222
+ # @rbs return: Integer
223
+ def count: (*untyped args) ?{ (Resources::Job) -> bool } -> Integer
224
+
211
225
  # Iterate over matching jobs in reverse order.
212
226
  #
213
227
  # Optimised: pushes the reverse ordering to the server instead of
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.1.0
4
+ version: 0.2.1
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-04-25 00:00:00.000000000 Z
11
+ date: 2026-05-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-http