wurk 0.0.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.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +43 -0
  3. data/CONTRIBUTING.md +73 -0
  4. data/LICENSE +21 -0
  5. data/README.md +137 -0
  6. data/SECURITY.md +39 -0
  7. data/app/controllers/wurk/api/pagination.rb +67 -0
  8. data/app/controllers/wurk/api/serializers.rb +131 -0
  9. data/app/controllers/wurk/api_controller.rb +248 -0
  10. data/app/controllers/wurk/application_controller.rb +7 -0
  11. data/app/controllers/wurk/dashboard_controller.rb +48 -0
  12. data/config/locales/en.yml +15 -0
  13. data/config/routes.rb +34 -0
  14. data/exe/wurk +22 -0
  15. data/lib/active_job/queue_adapters/wurk_adapter.rb +96 -0
  16. data/lib/generators/wurk/install/install_generator.rb +22 -0
  17. data/lib/generators/wurk/install/templates/wurk.rb +16 -0
  18. data/lib/wurk/active_job/wrapper.rb +32 -0
  19. data/lib/wurk/api/fast.rb +78 -0
  20. data/lib/wurk/batch/buffer.rb +26 -0
  21. data/lib/wurk/batch/callback_job.rb +37 -0
  22. data/lib/wurk/batch/callbacks.rb +176 -0
  23. data/lib/wurk/batch/client_middleware.rb +27 -0
  24. data/lib/wurk/batch/death_handler.rb +39 -0
  25. data/lib/wurk/batch/empty.rb +21 -0
  26. data/lib/wurk/batch/server_middleware.rb +62 -0
  27. data/lib/wurk/batch/status.rb +140 -0
  28. data/lib/wurk/batch.rb +351 -0
  29. data/lib/wurk/batch_set.rb +67 -0
  30. data/lib/wurk/capsule.rb +176 -0
  31. data/lib/wurk/cli.rb +349 -0
  32. data/lib/wurk/client/buffered.rb +372 -0
  33. data/lib/wurk/client.rb +330 -0
  34. data/lib/wurk/compat.rb +136 -0
  35. data/lib/wurk/component.rb +136 -0
  36. data/lib/wurk/configuration.rb +373 -0
  37. data/lib/wurk/context.rb +35 -0
  38. data/lib/wurk/cron.rb +636 -0
  39. data/lib/wurk/dashboard_manifest.rb +39 -0
  40. data/lib/wurk/dead_set.rb +78 -0
  41. data/lib/wurk/deploy.rb +91 -0
  42. data/lib/wurk/embedded.rb +94 -0
  43. data/lib/wurk/encryption.rb +276 -0
  44. data/lib/wurk/engine.rb +81 -0
  45. data/lib/wurk/fetcher/reaper.rb +264 -0
  46. data/lib/wurk/fetcher/reliable.rb +138 -0
  47. data/lib/wurk/fetcher.rb +11 -0
  48. data/lib/wurk/health.rb +193 -0
  49. data/lib/wurk/heartbeat.rb +211 -0
  50. data/lib/wurk/iterable_job.rb +292 -0
  51. data/lib/wurk/job/options.rb +70 -0
  52. data/lib/wurk/job.rb +33 -0
  53. data/lib/wurk/job_logger.rb +68 -0
  54. data/lib/wurk/job_record.rb +156 -0
  55. data/lib/wurk/job_retry.rb +320 -0
  56. data/lib/wurk/job_set.rb +212 -0
  57. data/lib/wurk/job_util.rb +162 -0
  58. data/lib/wurk/keys.rb +52 -0
  59. data/lib/wurk/launcher.rb +289 -0
  60. data/lib/wurk/leader.rb +221 -0
  61. data/lib/wurk/limiter/base.rb +138 -0
  62. data/lib/wurk/limiter/bucket.rb +80 -0
  63. data/lib/wurk/limiter/concurrent.rb +132 -0
  64. data/lib/wurk/limiter/leaky.rb +91 -0
  65. data/lib/wurk/limiter/points.rb +89 -0
  66. data/lib/wurk/limiter/server_middleware.rb +77 -0
  67. data/lib/wurk/limiter/unlimited.rb +48 -0
  68. data/lib/wurk/limiter/window.rb +80 -0
  69. data/lib/wurk/limiter.rb +255 -0
  70. data/lib/wurk/logger.rb +81 -0
  71. data/lib/wurk/lua/loader.rb +53 -0
  72. data/lib/wurk/lua.rb +187 -0
  73. data/lib/wurk/manager.rb +132 -0
  74. data/lib/wurk/metrics/history.rb +151 -0
  75. data/lib/wurk/metrics/query.rb +173 -0
  76. data/lib/wurk/metrics/rollup.rb +169 -0
  77. data/lib/wurk/metrics/statsd.rb +197 -0
  78. data/lib/wurk/metrics.rb +7 -0
  79. data/lib/wurk/middleware/chain.rb +128 -0
  80. data/lib/wurk/middleware/current_attributes.rb +87 -0
  81. data/lib/wurk/middleware/expiry.rb +50 -0
  82. data/lib/wurk/middleware/i18n.rb +63 -0
  83. data/lib/wurk/middleware/interrupt_handler.rb +45 -0
  84. data/lib/wurk/middleware/poison_pill.rb +149 -0
  85. data/lib/wurk/middleware.rb +34 -0
  86. data/lib/wurk/process_set.rb +243 -0
  87. data/lib/wurk/processor.rb +247 -0
  88. data/lib/wurk/queue.rb +108 -0
  89. data/lib/wurk/queues.rb +80 -0
  90. data/lib/wurk/rails.rb +9 -0
  91. data/lib/wurk/railtie.rb +28 -0
  92. data/lib/wurk/redis_pool.rb +79 -0
  93. data/lib/wurk/retry_set.rb +17 -0
  94. data/lib/wurk/scheduled.rb +189 -0
  95. data/lib/wurk/scheduled_set.rb +18 -0
  96. data/lib/wurk/sorted_entry.rb +95 -0
  97. data/lib/wurk/stats.rb +190 -0
  98. data/lib/wurk/swarm/child_boot.rb +105 -0
  99. data/lib/wurk/swarm.rb +260 -0
  100. data/lib/wurk/testing.rb +102 -0
  101. data/lib/wurk/topology.rb +74 -0
  102. data/lib/wurk/unique.rb +240 -0
  103. data/lib/wurk/version.rb +5 -0
  104. data/lib/wurk/web/config.rb +180 -0
  105. data/lib/wurk/web/enterprise.rb +138 -0
  106. data/lib/wurk/web/search.rb +139 -0
  107. data/lib/wurk/web.rb +25 -0
  108. data/lib/wurk/work_set.rb +116 -0
  109. data/lib/wurk/worker/setter.rb +93 -0
  110. data/lib/wurk/worker.rb +216 -0
  111. data/lib/wurk.rb +238 -0
  112. data/vendor/assets/dashboard/assets/index-8P3N_m1X.js +152 -0
  113. data/vendor/assets/dashboard/assets/index-Bqz4_SOQ.css +1 -0
  114. data/vendor/assets/dashboard/index.html +13 -0
  115. data/vendor/assets/dashboard/wurk-manifest.json +4 -0
  116. metadata +232 -0
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'worker/setter'
4
+
5
+ module Wurk
6
+ # The user-facing DSL: `include Wurk::Worker` (aliased to Sidekiq::Worker).
7
+ # Owns `sidekiq_options`, `perform_async`, `perform_in`, `perform_at`,
8
+ # `set`, `sidekiq_retry_in`, etc.
9
+ #
10
+ # Spec: docs/target/sidekiq-free.md §6 (Sidekiq::Job).
11
+ module Worker
12
+ # Interval values below this threshold are interpreted as seconds-from-now;
13
+ # values at or above are treated as absolute epoch timestamps.
14
+ # Threshold matches Sidekiq exactly — wire-compat sacred.
15
+ SCHEDULED_THRESHOLD = 1_000_000_000
16
+
17
+ def self.included(base)
18
+ base.extend(ClassMethods)
19
+ base.module_eval { attr_accessor :jid, :_context }
20
+ base.singleton_class.module_eval do
21
+ attr_accessor :sidekiq_retry_in_block, :sidekiq_retries_exhausted_block
22
+ end
23
+ end
24
+
25
+ # Module-level test helpers: `Sidekiq::Worker.jobs / clear_all / drain_all`
26
+ # operate across every job class (spec §24.3). Resolved lazily so the
27
+ # testing constants need not be loaded when Worker is.
28
+ def self.jobs = ::Wurk::Queues.jobs
29
+ def self.clear_all = ::Wurk::Queues.clear_all
30
+ def self.drain_all = ::Wurk::Testing.drain_all
31
+
32
+ def logger
33
+ Wurk.logger
34
+ end
35
+
36
+ # Cooperative cancellation flag for IterableJob and long-running jobs.
37
+ # Returns false when no processor context has been attached.
38
+ def interrupted?
39
+ ctx = @_context
40
+ ctx.respond_to?(:stopping?) && ctx.stopping?
41
+ end
42
+
43
+ # Batch helpers (Pro). Available on every worker — return nil when the
44
+ # current job did not originate from a batch.
45
+ #
46
+ # Spec: docs/target/sidekiq-pro.md §2.6.
47
+ def bid
48
+ @bid
49
+ end
50
+
51
+ # @api private — Processor sets this from job_hash['bid'] before perform.
52
+ attr_writer :bid
53
+
54
+ def batch
55
+ return nil if @bid.nil?
56
+
57
+ Wurk::Batch.new(@bid)
58
+ end
59
+
60
+ # False if the batch was invalidated. Workers should `return unless
61
+ # valid_within_batch?` to short-circuit work for cancelled batches.
62
+ def valid_within_batch?
63
+ return true if @bid.nil?
64
+
65
+ batch.valid?
66
+ end
67
+
68
+ module ClassMethods # rubocop:disable Metrics/ModuleLength
69
+ def sidekiq_options(opts = {})
70
+ merged = get_sidekiq_options.merge(opts.transform_keys(&:to_s))
71
+ @sidekiq_options_hash = merged
72
+ end
73
+
74
+ # Sidekiq's public API name — wire-compat sacred. Must stay `get_sidekiq_options`.
75
+ def get_sidekiq_options # rubocop:disable Naming/AccessorMethodName
76
+ @sidekiq_options_hash ||= inherited_sidekiq_options # rubocop:disable Naming/MemoizedInstanceVariableName
77
+ end
78
+
79
+ def sidekiq_options_hash
80
+ get_sidekiq_options
81
+ end
82
+
83
+ def queue_as(queue)
84
+ sidekiq_options('queue' => queue.to_s)
85
+ end
86
+
87
+ def sidekiq_retry_in(&block)
88
+ self.sidekiq_retry_in_block = block
89
+ end
90
+
91
+ def sidekiq_retries_exhausted(&block)
92
+ self.sidekiq_retries_exhausted_block = block
93
+ end
94
+
95
+ def perform_async(*)
96
+ Wurk::Worker::Setter.new(self, {}).perform_async(*)
97
+ end
98
+
99
+ def perform_inline(*)
100
+ new.perform(*)
101
+ end
102
+ alias perform_sync perform_inline
103
+
104
+ def perform_in(interval, *)
105
+ Wurk::Worker::Setter.new(self, {}).perform_in(interval, *)
106
+ end
107
+ alias perform_at perform_in
108
+
109
+ def perform_bulk(items, **)
110
+ Wurk::Worker::Setter.new(self, {}).perform_bulk(items, **)
111
+ end
112
+
113
+ def set(opts)
114
+ Wurk::Worker::Setter.new(self, opts)
115
+ end
116
+
117
+ def client_push(item)
118
+ raise ArgumentError, "Job arguments to #{name || self} must have string keys" if symbol_keyed?(item)
119
+
120
+ build_client.push(item)
121
+ end
122
+
123
+ def build_client
124
+ pool = get_sidekiq_options['pool']
125
+ Wurk::Client.new(pool: pool)
126
+ end
127
+
128
+ # --- Sidekiq::Testing class-level helpers (spec §24.3) --------------
129
+ # Only meaningful in :fake / :inline mode; the in-memory store is empty
130
+ # otherwise.
131
+
132
+ def queue
133
+ get_sidekiq_options['queue']
134
+ end
135
+
136
+ # Fake jobs enqueued for this class, across every queue.
137
+ def jobs
138
+ ::Wurk::Queues.jobs_by_class[to_s] || []
139
+ end
140
+
141
+ def clear
142
+ ::Wurk::Queues.clear_class(to_s)
143
+ end
144
+
145
+ # Run & remove every fake job for this class — including ones it enqueues
146
+ # mid-drain. Returns the count processed.
147
+ def drain
148
+ count = 0
149
+ while (job = ::Wurk::Queues.shift_class(to_s))
150
+ process_job(job)
151
+ count += 1
152
+ end
153
+ count
154
+ end
155
+
156
+ # Run & remove the first fake job for this class; EmptyQueueError if none.
157
+ def perform_one
158
+ job = ::Wurk::Queues.shift_class(to_s)
159
+ raise ::Wurk::Testing::EmptyQueueError, "no #{self} jobs were found" if job.nil?
160
+
161
+ process_job(job)
162
+ end
163
+
164
+ # Execute a normalized job hash through the inline server-middleware chain
165
+ # (empty by default — see Wurk::Testing.server_middleware).
166
+ # Returns the value of the server-middleware `invoke` (i.e. the worker's
167
+ # `perform` return), matching Sidekiq::Testing — so `perform_one` yields
168
+ # the job result.
169
+ def process_job(job_hash)
170
+ instance = new
171
+ instance.jid = job_hash['jid']
172
+ instance.bid = job_hash['bid'] if instance.respond_to?(:bid=)
173
+ ::Wurk::Testing.server_middleware.invoke(instance, job_hash, job_hash['queue'] || queue) do
174
+ execute_job(instance, job_hash['args'])
175
+ end
176
+ end
177
+
178
+ def execute_job(worker, args)
179
+ worker.perform(*args)
180
+ end
181
+
182
+ def delay(*)
183
+ raise ArgumentError, "#{name || self}.delay is removed in Sidekiq 7+. Use #{name || 'klass'}.perform_async."
184
+ end
185
+ alias delay_for delay
186
+ alias delay_until delay
187
+
188
+ def inherited(subclass)
189
+ super
190
+ subclass.instance_variable_set(:@sidekiq_options_hash, get_sidekiq_options.dup)
191
+ inherit_block(subclass, :@sidekiq_retry_in_block)
192
+ inherit_block(subclass, :@sidekiq_retries_exhausted_block)
193
+ end
194
+
195
+ private
196
+
197
+ def inherited_sidekiq_options
198
+ if superclass.respond_to?(:get_sidekiq_options)
199
+ superclass.get_sidekiq_options.dup
200
+ else
201
+ Wurk.default_job_options.dup
202
+ end
203
+ end
204
+
205
+ def inherit_block(subclass, ivar)
206
+ return unless instance_variable_defined?(ivar)
207
+
208
+ subclass.instance_variable_set(ivar, instance_variable_get(ivar))
209
+ end
210
+
211
+ def symbol_keyed?(item)
212
+ item.respond_to?(:keys) && item.keys.any?(Symbol)
213
+ end
214
+ end
215
+ end
216
+ end
data/lib/wurk.rb ADDED
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Standalone entry point. Loading "wurk" must work without Rails.
4
+ # The engine and railtie live under "wurk/rails" and are only loaded
5
+ # when the host app opts in.
6
+
7
+ require_relative 'wurk/version'
8
+ require_relative 'wurk/keys'
9
+ require_relative 'wurk/redis_pool'
10
+ require_relative 'wurk/middleware'
11
+ require_relative 'wurk/middleware/chain'
12
+ require_relative 'wurk/component'
13
+ require_relative 'wurk/context'
14
+ require_relative 'wurk/logger'
15
+ require_relative 'wurk/job_logger'
16
+ require_relative 'wurk/capsule'
17
+ require_relative 'wurk/configuration'
18
+ require_relative 'wurk/job_util'
19
+ require_relative 'wurk/client'
20
+ require_relative 'wurk/client/buffered'
21
+ require_relative 'wurk/worker'
22
+ require_relative 'wurk/worker/setter'
23
+ require_relative 'wurk/job'
24
+ require_relative 'wurk/job/options'
25
+ require_relative 'wurk/queues'
26
+ require_relative 'wurk/testing'
27
+ require_relative 'wurk/iterable_job'
28
+ require_relative 'wurk/job_retry'
29
+ require_relative 'wurk/job_record'
30
+ require_relative 'wurk/queue'
31
+ require_relative 'wurk/sorted_entry'
32
+ require_relative 'wurk/job_set'
33
+ require_relative 'wurk/retry_set'
34
+ require_relative 'wurk/scheduled_set'
35
+ require_relative 'wurk/dead_set'
36
+ require_relative 'wurk/stats'
37
+ require_relative 'wurk/process_set'
38
+ require_relative 'wurk/work_set'
39
+ require_relative 'wurk/heartbeat'
40
+ require_relative 'wurk/fetcher'
41
+ require_relative 'wurk/fetcher/reliable'
42
+ require_relative 'wurk/processor'
43
+ require_relative 'wurk/manager'
44
+ require_relative 'wurk/lua'
45
+ require_relative 'wurk/scheduled'
46
+ require_relative 'wurk/launcher'
47
+ require_relative 'wurk/cli'
48
+ require_relative 'wurk/embedded'
49
+ require_relative 'wurk/swarm'
50
+ require_relative 'wurk/topology'
51
+ require_relative 'wurk/batch'
52
+ require_relative 'wurk/batch/status'
53
+ require_relative 'wurk/batch_set'
54
+ require_relative 'wurk/limiter'
55
+ require_relative 'wurk/cron'
56
+ require_relative 'wurk/leader'
57
+ require_relative 'wurk/unique'
58
+ require_relative 'wurk/encryption'
59
+ require_relative 'wurk/metrics'
60
+ require_relative 'wurk/metrics/statsd'
61
+ require_relative 'wurk/metrics/history'
62
+ require_relative 'wurk/metrics/query'
63
+ require_relative 'wurk/web'
64
+ require_relative 'wurk/deploy'
65
+
66
+ require 'json'
67
+
68
+ module Wurk
69
+ class Error < StandardError; end
70
+
71
+ # Raised inside a worker process to abort the run loop. User code must not
72
+ # rescue this — the swarm uses it to signal teardown across thread boundaries.
73
+ # Spec: docs/target/sidekiq-free.md §3.
74
+ class Shutdown < Interrupt; end
75
+
76
+ # Process-wide job option defaults. Per-class options from `sidekiq_options`
77
+ # take precedence; this hash is the floor.
78
+ DEFAULT_JOB_OPTIONS = { 'retry' => true, 'queue' => 'default' }.freeze
79
+
80
+ class << self
81
+ def configure_server(&)
82
+ configuration.configure_server(&)
83
+ end
84
+
85
+ def configure_client(&)
86
+ configuration.configure_client(&)
87
+ end
88
+
89
+ # Embedded mode: caller runs Wurk inside its own process (Puma, rake task,
90
+ # etc.) without forking. Concurrency is defaulted to 2 — the GIL makes
91
+ # higher thread counts counterproductive inside a host process that has
92
+ # its own pool. The block can override anything before the Embedded
93
+ # instance is built. Returns a Wurk::Embedded the caller drives with
94
+ # `#run` / `#quiet` / `#stop`.
95
+ def configure_embed
96
+ if configuration.frozen?
97
+ raise FrozenError, 'Wurk configuration is frozen; build all embedded instances before calling run'
98
+ end
99
+
100
+ configuration.concurrency = 2
101
+ yield configuration if block_given?
102
+ Embedded.new(configuration)
103
+ end
104
+
105
+ def configuration
106
+ @configuration ||= Configuration.new
107
+ end
108
+ alias default_configuration configuration
109
+
110
+ def redis(&)
111
+ redis_pool.with(&)
112
+ end
113
+
114
+ # Capsule-aware pool lookup. Thread-local override wins so per-capsule
115
+ # workers (`Thread.current[:wurk_capsule] = cap`) read from their own
116
+ # connections without leaking to the default capsule.
117
+ def redis_pool
118
+ (Thread.current[:wurk_capsule] || configuration.default_capsule).redis_pool
119
+ end
120
+
121
+ def logger
122
+ configuration.logger
123
+ end
124
+
125
+ # --- JSON ---------------------------------------------------------
126
+
127
+ def load_json(string)
128
+ ::JSON.parse(string)
129
+ end
130
+
131
+ def dump_json(object)
132
+ ::JSON.generate(object)
133
+ end
134
+
135
+ # --- default job options -----------------------------------------
136
+
137
+ def default_job_options
138
+ @default_job_options ||= DEFAULT_JOB_OPTIONS.dup
139
+ end
140
+
141
+ # Merges (does not replace) into the current defaults. Keys are
142
+ # stringified so symbol-keyed callers don't shadow string keys.
143
+ def default_job_options=(hash)
144
+ @default_job_options = default_job_options.merge(hash.transform_keys(&:to_s))
145
+ end
146
+
147
+ # --- strict args -------------------------------------------------
148
+
149
+ # Sets the global mode used by Wurk::JobUtil#verify_json.
150
+ # mode ∈ [:raise, :warn, false].
151
+ def strict_args!(mode = :raise)
152
+ @strict_args_mode = mode
153
+ end
154
+
155
+ # `defined?` distinguishes "never set" from "set to false".
156
+ def strict_args_mode
157
+ defined?(@strict_args_mode) ? @strict_args_mode : Configuration::DEFAULTS[:on_complex_arguments]
158
+ end
159
+
160
+ # --- mode flags --------------------------------------------------
161
+
162
+ # Sidekiq-compatible test-mode entry point. Delegates to Wurk::Testing
163
+ # (the single source of truth for the mode): a block scopes the mode to the
164
+ # current thread; no block sets it globally. `Sidekiq.testing!` aliases here.
165
+ def testing!(mode = :fake, &) = Wurk::Testing.__set_test_mode(mode, &)
166
+
167
+ # True when in :fake or :inline mode (i.e. not pushing to real Redis).
168
+ def testing? = Wurk::Testing.enabled?
169
+
170
+ # True inside the swarm/manager process (set by exe/wurk / the railtie).
171
+ def server?
172
+ !!@server
173
+ end
174
+
175
+ attr_writer :server
176
+
177
+ # Wurk ships Pro+Ent features in the free gem; these flags exist solely
178
+ # for third-party gems that branch on Sidekiq.pro? / Sidekiq.ent?.
179
+ def pro?
180
+ false
181
+ end
182
+
183
+ def ent?
184
+ false
185
+ end
186
+ end
187
+ end
188
+
189
+ # Batch worker classes (Empty, CallbackJob) and middleware load AFTER the
190
+ # Wurk module class << self block — they instantiate workers at load time
191
+ # via `sidekiq_options`, which reads Wurk.default_job_options.
192
+ require_relative 'wurk/batch/empty'
193
+ require_relative 'wurk/batch/callback_job'
194
+ require_relative 'wurk/batch/callbacks'
195
+ require_relative 'wurk/batch/client_middleware'
196
+ require_relative 'wurk/batch/server_middleware'
197
+ require_relative 'wurk/batch/death_handler'
198
+
199
+ # Expiry must register AFTER Batch::ServerMiddleware so it sits inside that
200
+ # middleware's onion — a skipped (expired) job then unwinds back through
201
+ # batch's `yield` and gets counted as a batch success on the way out.
202
+ # Spec: docs/target/sidekiq-pro.md §7.
203
+ require_relative 'wurk/middleware/expiry'
204
+
205
+ # Health probe HTTP server — opt-in via `config.health_check(port:)`. Loaded
206
+ # at top level so the constants resolve in Launcher#build_health_server even
207
+ # in standalone (no-Rails) boot.
208
+ require_relative 'wurk/health'
209
+
210
+ # Poison-pill detection — orphan recovery counter + dead set on threshold.
211
+ # Spec: docs/target/sidekiq-pro.md §3.2.
212
+ require_relative 'wurk/middleware/poison_pill'
213
+
214
+ # Limiter server middleware: catches OverLimit, reschedules onto the same
215
+ # queue with `Time.now + backoff` until `overrated` hits the reschedule cap.
216
+ # Registered AFTER Batch::ServerMiddleware so a rescheduled OverLimit
217
+ # unwinds back through the batch onion without ack'ing success.
218
+ # Spec: docs/target/sidekiq-ent.md §1.4.
219
+ Wurk.configuration.server_middleware.add(Wurk::Limiter::ServerMiddleware)
220
+
221
+ # Statsd metrics middleware: per-job count / success / failure / perform_dist
222
+ # emissions. No-op when `config.dogstatsd` is unset (Statsd.client returns
223
+ # nil and the middleware yields straight through), so auto-registering here
224
+ # has zero overhead for users who don't wire up a client. Matches Sidekiq
225
+ # Pro's behavior of installing the middleware as soon as the gem is loaded.
226
+ # Spec: docs/target/sidekiq-pro.md §9.
227
+ Wurk.configuration.server_middleware.add(Wurk::Metrics::Statsd)
228
+
229
+ # Pro Fast API: Lua-backed Queue#delete_job / #delete_by_class plus
230
+ # SortedSet#scan { |JobRecord| … }. Mixed in via include/prepend on the
231
+ # existing data API classes so the surface is wire-compat with Sidekiq Pro.
232
+ # Spec: docs/target/sidekiq-pro.md §11.
233
+ require_relative 'wurk/api/fast'
234
+
235
+ # Sidekiq aliases load last — every Wurk::* constant they reference must be
236
+ # fully defined first. compat.rb only redefines names, it does not gate
237
+ # behavior, so trailing the load order is safe.
238
+ require_relative 'wurk/compat'