kaal 0.2.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +340 -0
- data/Rakefile +6 -0
- data/app/models/kaal/cron_definition.rb +71 -0
- data/app/models/kaal/cron_dispatch.rb +50 -0
- data/app/models/kaal/cron_lock.rb +38 -0
- data/config/locales/en.yml +46 -0
- data/lib/generators/kaal/install/install_generator.rb +67 -0
- data/lib/generators/kaal/install/templates/create_kaal_definitions.rb.tt +21 -0
- data/lib/generators/kaal/install/templates/create_kaal_dispatches.rb.tt +20 -0
- data/lib/generators/kaal/install/templates/create_kaal_locks.rb.tt +17 -0
- data/lib/generators/kaal/install/templates/kaal.rb.tt +31 -0
- data/lib/generators/kaal/install/templates/scheduler.yml.tt +22 -0
- data/lib/kaal/backend/adapter.rb +147 -0
- data/lib/kaal/backend/dispatch_logging.rb +79 -0
- data/lib/kaal/backend/memory_adapter.rb +99 -0
- data/lib/kaal/backend/mysql_adapter.rb +170 -0
- data/lib/kaal/backend/postgres_adapter.rb +134 -0
- data/lib/kaal/backend/redis_adapter.rb +145 -0
- data/lib/kaal/backend/sqlite_adapter.rb +116 -0
- data/lib/kaal/configuration.rb +231 -0
- data/lib/kaal/coordinator.rb +437 -0
- data/lib/kaal/cron_humanizer.rb +182 -0
- data/lib/kaal/cron_utils.rb +233 -0
- data/lib/kaal/definition/database_engine.rb +45 -0
- data/lib/kaal/definition/memory_engine.rb +61 -0
- data/lib/kaal/definition/redis_engine.rb +93 -0
- data/lib/kaal/definition/registry.rb +46 -0
- data/lib/kaal/dispatch/database_engine.rb +94 -0
- data/lib/kaal/dispatch/memory_engine.rb +99 -0
- data/lib/kaal/dispatch/redis_engine.rb +103 -0
- data/lib/kaal/dispatch/registry.rb +62 -0
- data/lib/kaal/idempotency_key_generator.rb +26 -0
- data/lib/kaal/railtie.rb +183 -0
- data/lib/kaal/rake_tasks.rb +184 -0
- data/lib/kaal/register_conflict_support.rb +54 -0
- data/lib/kaal/registry.rb +242 -0
- data/lib/kaal/scheduler_config_error.rb +6 -0
- data/lib/kaal/scheduler_file_loader.rb +316 -0
- data/lib/kaal/scheduler_hash_transform.rb +40 -0
- data/lib/kaal/scheduler_placeholder_support.rb +80 -0
- data/lib/kaal/version.rb +10 -0
- data/lib/kaal.rb +571 -0
- data/lib/tasks/kaal_tasks.rake +10 -0
- metadata +142 -0
data/lib/kaal.rb
ADDED
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Codevedas Inc. 2025-present
|
|
4
|
+
#
|
|
5
|
+
# This source code is licensed under the MIT license found in the
|
|
6
|
+
# LICENSE file in the root directory of this source tree.
|
|
7
|
+
|
|
8
|
+
require 'kaal/version'
|
|
9
|
+
require 'kaal/configuration'
|
|
10
|
+
require 'kaal/registry'
|
|
11
|
+
require 'kaal/dispatch/registry'
|
|
12
|
+
require 'kaal/dispatch/memory_engine'
|
|
13
|
+
require 'kaal/dispatch/redis_engine'
|
|
14
|
+
require 'kaal/dispatch/database_engine'
|
|
15
|
+
require 'kaal/definition/registry'
|
|
16
|
+
require 'kaal/definition/memory_engine'
|
|
17
|
+
require 'kaal/definition/redis_engine'
|
|
18
|
+
require 'kaal/definition/database_engine'
|
|
19
|
+
require 'kaal/backend/adapter'
|
|
20
|
+
require 'kaal/backend/memory_adapter'
|
|
21
|
+
require 'kaal/backend/redis_adapter'
|
|
22
|
+
require 'kaal/backend/postgres_adapter'
|
|
23
|
+
require 'kaal/backend/mysql_adapter'
|
|
24
|
+
require 'kaal/backend/sqlite_adapter'
|
|
25
|
+
require 'kaal/idempotency_key_generator'
|
|
26
|
+
require 'kaal/cron_utils'
|
|
27
|
+
require 'kaal/cron_humanizer'
|
|
28
|
+
require 'kaal/scheduler_config_error'
|
|
29
|
+
require 'kaal/scheduler_file_loader'
|
|
30
|
+
require 'kaal/register_conflict_support'
|
|
31
|
+
require 'kaal/rake_tasks'
|
|
32
|
+
require 'kaal/coordinator'
|
|
33
|
+
require 'kaal/railtie'
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# Kaal module is the main namespace for the gem.
|
|
37
|
+
# Provides configuration, job registration, and registry access.
|
|
38
|
+
#
|
|
39
|
+
# @example Configure Kaal
|
|
40
|
+
# Kaal.configure do |config|
|
|
41
|
+
# config.tick_interval = 5
|
|
42
|
+
# config.backend = Kaal::Backend::RedisAdapter.new(Redis.new(url: ENV["REDIS_URL"]))
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# @example Register a cron job
|
|
46
|
+
# Kaal.register(
|
|
47
|
+
# key: "reports:daily",
|
|
48
|
+
# cron: "0 9 * * *",
|
|
49
|
+
# enqueue: ->(fire_time:, idempotency_key:) { MyJob.perform_later }
|
|
50
|
+
# )
|
|
51
|
+
module Kaal
|
|
52
|
+
class << self
|
|
53
|
+
include RegisterConflictSupport
|
|
54
|
+
|
|
55
|
+
##
|
|
56
|
+
# Get the current configuration instance.
|
|
57
|
+
#
|
|
58
|
+
# @return [Configuration] the global configuration object
|
|
59
|
+
def configuration
|
|
60
|
+
@configuration ||= Configuration.new
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
##
|
|
64
|
+
# Get the current registry instance.
|
|
65
|
+
#
|
|
66
|
+
# @return [Registry] the global registry object
|
|
67
|
+
def registry
|
|
68
|
+
@registry ||= Registry.new
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
##
|
|
72
|
+
# Get the coordinator instance.
|
|
73
|
+
#
|
|
74
|
+
# @return [Coordinator] the global coordinator object
|
|
75
|
+
def coordinator
|
|
76
|
+
@coordinator ||= Coordinator.new(configuration: configuration, registry: registry)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
##
|
|
80
|
+
# Reset configuration to defaults. Primarily used in tests.
|
|
81
|
+
#
|
|
82
|
+
# @return [Configuration] a fresh configuration object
|
|
83
|
+
def reset_configuration!
|
|
84
|
+
@configuration = Configuration.new
|
|
85
|
+
@coordinator = nil # Invalidate coordinator so it rebuilds with new config
|
|
86
|
+
@definition_registry = nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
##
|
|
90
|
+
# Reset registry to empty state. Primarily used in tests.
|
|
91
|
+
#
|
|
92
|
+
# @return [Registry] a fresh registry object
|
|
93
|
+
def reset_registry!
|
|
94
|
+
@registry = Registry.new
|
|
95
|
+
@definition_registry&.clear
|
|
96
|
+
@coordinator = nil # Invalidate coordinator so it rebuilds with new registry
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
##
|
|
100
|
+
# Reset coordinator to initial state. Primarily used in tests.
|
|
101
|
+
#
|
|
102
|
+
# Stops any running coordinator and creates a fresh instance.
|
|
103
|
+
#
|
|
104
|
+
# @return [Coordinator] a fresh coordinator object
|
|
105
|
+
# @raise [RuntimeError] if the running coordinator cannot be stopped within timeout
|
|
106
|
+
def reset_coordinator!
|
|
107
|
+
# Stop the existing coordinator if it's running
|
|
108
|
+
if @coordinator&.running?
|
|
109
|
+
stopped = @coordinator.stop!
|
|
110
|
+
raise 'Failed to stop coordinator thread within timeout' unless stopped
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Create and return a fresh coordinator
|
|
114
|
+
@coordinator = nil
|
|
115
|
+
coordinator
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
##
|
|
119
|
+
# Configure Kaal via a block.
|
|
120
|
+
#
|
|
121
|
+
# @yield [config] yields the configuration object
|
|
122
|
+
# @yieldparam config [Configuration] the configuration instance to customize
|
|
123
|
+
# @return [void]
|
|
124
|
+
#
|
|
125
|
+
# @example
|
|
126
|
+
# Kaal.configure do |config|
|
|
127
|
+
# config.tick_interval = 10
|
|
128
|
+
# config.lease_ttl = 120
|
|
129
|
+
# end
|
|
130
|
+
def configure
|
|
131
|
+
yield(configuration) if block_given?
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
##
|
|
135
|
+
# Register a new cron job.
|
|
136
|
+
#
|
|
137
|
+
# @param key [String] unique identifier for the cron task
|
|
138
|
+
# @param cron [String] cron expression (e.g., "0 9 * * *", "@daily")
|
|
139
|
+
# @param enqueue [Proc, Lambda] callable executed when cron fires
|
|
140
|
+
# @return [Registry::Entry] the registered entry
|
|
141
|
+
#
|
|
142
|
+
# @raise [ArgumentError] if parameters are invalid
|
|
143
|
+
# @raise [RegistryError] if key is already registered
|
|
144
|
+
#
|
|
145
|
+
# @example
|
|
146
|
+
# Kaal.register(
|
|
147
|
+
# key: "reports:weekly_summary",
|
|
148
|
+
# cron: "0 9 * * 1",
|
|
149
|
+
# enqueue: ->(fire_time:, idempotency_key:) { WeeklySummaryJob.perform_later }
|
|
150
|
+
# )
|
|
151
|
+
def register(key:, cron:, enqueue:)
|
|
152
|
+
existing_definition = definition_registry.find_definition(key)
|
|
153
|
+
existing_entry = registry.find(key)
|
|
154
|
+
if existing_entry
|
|
155
|
+
conflict_result = resolve_register_conflict(
|
|
156
|
+
key: key,
|
|
157
|
+
cron: cron,
|
|
158
|
+
enqueue: enqueue,
|
|
159
|
+
existing_definition: existing_definition,
|
|
160
|
+
existing_entry: existing_entry
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
return conflict_result if conflict_result
|
|
164
|
+
|
|
165
|
+
raise RegistryError, "Key '#{key}' is already registered"
|
|
166
|
+
end
|
|
167
|
+
persisted_attributes = {
|
|
168
|
+
enabled: true,
|
|
169
|
+
source: 'code',
|
|
170
|
+
metadata: {}
|
|
171
|
+
}.merge(existing_definition&.slice(:enabled, :metadata) || {})
|
|
172
|
+
definition_registry.upsert_definition(key: key, cron: cron, **persisted_attributes)
|
|
173
|
+
with_registered_definition_rollback(key, existing_definition) do
|
|
174
|
+
registry.add(key: key, cron: cron, enqueue: enqueue)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
##
|
|
179
|
+
# Load scheduler definitions from the configured scheduler YAML file.
|
|
180
|
+
#
|
|
181
|
+
# @return [Array<Hash>] normalized jobs applied from scheduler file
|
|
182
|
+
# @raise [SchedulerConfigError] if scheduler file is invalid
|
|
183
|
+
def load_scheduler_file!
|
|
184
|
+
loader = SchedulerFileLoader.new(
|
|
185
|
+
configuration: configuration,
|
|
186
|
+
definition_registry: definition_registry,
|
|
187
|
+
registry: registry,
|
|
188
|
+
logger: configuration.logger
|
|
189
|
+
)
|
|
190
|
+
loader.load
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
##
|
|
194
|
+
# Unregister (remove) a cron job by key.
|
|
195
|
+
#
|
|
196
|
+
# @param key [String] the unique identifier of the job to remove
|
|
197
|
+
# @return [Registry::Entry, nil] the removed entry, or nil if not found
|
|
198
|
+
def unregister(key:)
|
|
199
|
+
definition_registry.remove_definition(key)
|
|
200
|
+
registry.remove(key)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def rollback_registered_definition(key, existing_definition)
|
|
204
|
+
if existing_definition
|
|
205
|
+
definition_registry.upsert_definition(
|
|
206
|
+
key: existing_definition[:key],
|
|
207
|
+
cron: existing_definition[:cron],
|
|
208
|
+
enabled: existing_definition[:enabled],
|
|
209
|
+
source: existing_definition[:source],
|
|
210
|
+
metadata: existing_definition[:metadata]
|
|
211
|
+
)
|
|
212
|
+
elsif !registry.registered?(key)
|
|
213
|
+
definition_registry.remove_definition(key)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
private :rollback_registered_definition
|
|
217
|
+
|
|
218
|
+
##
|
|
219
|
+
# Get all registered cron jobs.
|
|
220
|
+
#
|
|
221
|
+
# @return [Array<Registry::Entry>] array of all registered entries
|
|
222
|
+
#
|
|
223
|
+
# @example
|
|
224
|
+
# Kaal.registered.each { |entry| puts entry.key }
|
|
225
|
+
def registered
|
|
226
|
+
definition_registry.all_definitions.map do |definition|
|
|
227
|
+
key = definition[:key]
|
|
228
|
+
callback = registry.find(key)&.enqueue
|
|
229
|
+
Registry::Entry.new(key: key, cron: definition[:cron], enqueue: callback).freeze
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
##
|
|
234
|
+
# Check if a cron job is registered by key.
|
|
235
|
+
#
|
|
236
|
+
# @param key [String] the unique identifier to check
|
|
237
|
+
# @return [Boolean] true if the key is registered, false otherwise
|
|
238
|
+
#
|
|
239
|
+
# @example
|
|
240
|
+
# Kaal.registered?(key: "reports:daily") # => true
|
|
241
|
+
def registered?(key:)
|
|
242
|
+
definition_registry.find_definition(key).present?
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
##
|
|
246
|
+
# Enable a registered cron definition by key.
|
|
247
|
+
#
|
|
248
|
+
# @param key [String] the unique identifier to enable
|
|
249
|
+
# @return [Hash, nil] enabled definition hash or nil if missing
|
|
250
|
+
def enable(key:)
|
|
251
|
+
definition_registry.enable_definition(key)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
##
|
|
255
|
+
# Disable a registered cron definition by key.
|
|
256
|
+
#
|
|
257
|
+
# @param key [String] the unique identifier to disable
|
|
258
|
+
# @return [Hash, nil] disabled definition hash or nil if missing
|
|
259
|
+
def disable(key:)
|
|
260
|
+
definition_registry.disable_definition(key)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
##
|
|
264
|
+
# Start the scheduler background thread.
|
|
265
|
+
#
|
|
266
|
+
# The coordinator will calculate due fire times for each registered cron
|
|
267
|
+
# on each tick and attempt to dispatch work.
|
|
268
|
+
#
|
|
269
|
+
# @return [Thread] the started thread, or nil if already running
|
|
270
|
+
#
|
|
271
|
+
# @example
|
|
272
|
+
# Kaal.start!
|
|
273
|
+
def start!
|
|
274
|
+
coordinator.start!
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
##
|
|
278
|
+
# Stop the scheduler gracefully.
|
|
279
|
+
#
|
|
280
|
+
# Signals the coordinator to stop after the current tick completes,
|
|
281
|
+
# then waits for the thread to finish.
|
|
282
|
+
#
|
|
283
|
+
# @param timeout [Integer] seconds to wait for graceful shutdown (default: 30)
|
|
284
|
+
# @return [Boolean] true if stopped successfully
|
|
285
|
+
#
|
|
286
|
+
# @example
|
|
287
|
+
# Kaal.stop!
|
|
288
|
+
# @example
|
|
289
|
+
# Kaal.stop!(timeout: 60)
|
|
290
|
+
def stop!(timeout: 30)
|
|
291
|
+
coordinator.stop!(timeout: timeout)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
##
|
|
295
|
+
# Check if the scheduler is currently running.
|
|
296
|
+
#
|
|
297
|
+
# @return [Boolean] true if running, false otherwise
|
|
298
|
+
#
|
|
299
|
+
# @example
|
|
300
|
+
# if Kaal.running?
|
|
301
|
+
# puts "Scheduler is active"
|
|
302
|
+
# end
|
|
303
|
+
def running?
|
|
304
|
+
coordinator.running?
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
##
|
|
308
|
+
# Restart the scheduler (stop then start).
|
|
309
|
+
#
|
|
310
|
+
# @return [Thread] the started thread
|
|
311
|
+
#
|
|
312
|
+
# @example
|
|
313
|
+
# Kaal.restart!
|
|
314
|
+
def restart!
|
|
315
|
+
coordinator.restart!
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
##
|
|
319
|
+
# Execute a single scheduler tick manually.
|
|
320
|
+
#
|
|
321
|
+
# This is useful for testing and Rake tasks that want to trigger
|
|
322
|
+
# the scheduler without running the background loop.
|
|
323
|
+
#
|
|
324
|
+
# @return [void]
|
|
325
|
+
#
|
|
326
|
+
# @example
|
|
327
|
+
# Kaal.tick!
|
|
328
|
+
def tick!
|
|
329
|
+
coordinator.tick!
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
##
|
|
333
|
+
# Generate an idempotency key for a cron job and yield to a block.
|
|
334
|
+
#
|
|
335
|
+
# Useful for advanced use cases where you need to generate an idempotency key
|
|
336
|
+
# outside of the normal enqueue flow, or for internal utilities.
|
|
337
|
+
#
|
|
338
|
+
# @param key [String] the cron job key
|
|
339
|
+
# @param fire_time [Time] the fire time
|
|
340
|
+
# @yield [String] yields the generated idempotency key
|
|
341
|
+
# @return [Object] the result of the block
|
|
342
|
+
#
|
|
343
|
+
# @raise [ArgumentError] if no block is provided
|
|
344
|
+
#
|
|
345
|
+
# @example
|
|
346
|
+
# Kaal.with_idempotency('reports:daily', Time.current) do |idempotency_key|
|
|
347
|
+
# MyJob.perform_later(key: idempotency_key)
|
|
348
|
+
# end
|
|
349
|
+
def with_idempotency(key, fire_time)
|
|
350
|
+
raise ArgumentError, 'block required' unless block_given?
|
|
351
|
+
|
|
352
|
+
idempotency_key = IdempotencyKeyGenerator.call(key, fire_time, configuration: configuration)
|
|
353
|
+
yield(idempotency_key)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
##
|
|
357
|
+
# Check if a cron job has already been dispatched for a given fire time.
|
|
358
|
+
#
|
|
359
|
+
# Useful for implementing deduplication logic to prevent duplicate job enqueues.
|
|
360
|
+
# Returns true if dispatch logging is enabled and the job was previously dispatched,
|
|
361
|
+
# returns false if not found or dispatch logging is disabled.
|
|
362
|
+
#
|
|
363
|
+
# Safe to call from enqueue callbacks - will return false on any error (e.g., backend
|
|
364
|
+
# misconfiguration or temporary failure), log via configuration.logger, and never raise.
|
|
365
|
+
#
|
|
366
|
+
# @param key [String] the cron job key
|
|
367
|
+
# @param fire_time [Time] the fire time to check
|
|
368
|
+
# @return [Boolean] true if dispatch exists, false otherwise (never raises)
|
|
369
|
+
#
|
|
370
|
+
# @example
|
|
371
|
+
# Kaal.dispatched?('reports:daily', Time.current)
|
|
372
|
+
# # => true if already dispatched, false otherwise
|
|
373
|
+
def dispatched?(key, fire_time)
|
|
374
|
+
adapter = configuration.backend
|
|
375
|
+
return false if adapter.nil? || !adapter.respond_to?(:dispatch_registry)
|
|
376
|
+
|
|
377
|
+
registry = adapter.dispatch_registry
|
|
378
|
+
return false if registry.nil?
|
|
379
|
+
|
|
380
|
+
registry.dispatched?(key, fire_time)
|
|
381
|
+
rescue StandardError => e
|
|
382
|
+
configuration.logger&.warn("Error checking dispatch status for #{key}: #{e.message}")
|
|
383
|
+
false
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
##
|
|
387
|
+
# Get the dispatch log registry for querying dispatch history.
|
|
388
|
+
#
|
|
389
|
+
# Returns the underlying dispatch registry engine which allows querying
|
|
390
|
+
# dispatch records. The specific methods available depend on the adapter type:
|
|
391
|
+
#
|
|
392
|
+
# **Common methods (all adapters):**
|
|
393
|
+
# - `find_dispatch(key, fire_time)` - Find a specific dispatch record
|
|
394
|
+
# - `dispatched?(key, fire_time)` - Check if a dispatch exists
|
|
395
|
+
#
|
|
396
|
+
# **Database adapter specific methods:**
|
|
397
|
+
# - `find_by_key(key)` - Find all dispatches for a job key
|
|
398
|
+
# - `find_by_node(node_id)` - Find all dispatches from a node, ordered by fire_time
|
|
399
|
+
# - `find_by_status(status)` - Find dispatches by status ('dispatched', 'failed', etc.)
|
|
400
|
+
# - `cleanup(recovery_window: 86400)` - Delete dispatch records older than window
|
|
401
|
+
#
|
|
402
|
+
# **Redis adapter:**
|
|
403
|
+
# - Uses automatic TTL expiration (no cleanup needed)
|
|
404
|
+
#
|
|
405
|
+
# **Memory adapter (development/testing):**
|
|
406
|
+
# - `clear()` - Clear all stored records
|
|
407
|
+
# - `size()` - Get count of stored records
|
|
408
|
+
#
|
|
409
|
+
# Safe for production diagnostics - will return nil on any error (e.g., backend
|
|
410
|
+
# misconfiguration or temporary failure), log via configuration.logger, and never raise.
|
|
411
|
+
#
|
|
412
|
+
# @return [Dispatch::Registry, nil] the dispatch registry instance, or nil if adapter doesn't support it or on error
|
|
413
|
+
#
|
|
414
|
+
# @example Query dispatches with database adapter
|
|
415
|
+
# registry = Kaal.dispatch_log_registry
|
|
416
|
+
# # Find all dispatches for a job
|
|
417
|
+
# registry.find_by_key('reports:daily')
|
|
418
|
+
# # Find failed attempts
|
|
419
|
+
# registry.find_by_status('failed')
|
|
420
|
+
# # Clean up old records (over 30 days old)
|
|
421
|
+
# registry.cleanup(recovery_window: 30 * 24 * 60 * 60)
|
|
422
|
+
#
|
|
423
|
+
# @example Query dispatches with memory adapter
|
|
424
|
+
# registry = Kaal.dispatch_log_registry
|
|
425
|
+
# record = registry.find_dispatch('reports:daily', Time.current)
|
|
426
|
+
# total = registry.size
|
|
427
|
+
# registry.clear
|
|
428
|
+
def dispatch_log_registry
|
|
429
|
+
adapter = configuration.backend
|
|
430
|
+
return nil if adapter.nil? || !adapter.respond_to?(:dispatch_registry)
|
|
431
|
+
|
|
432
|
+
registry = adapter.dispatch_registry
|
|
433
|
+
return nil if registry.nil?
|
|
434
|
+
|
|
435
|
+
registry
|
|
436
|
+
rescue StandardError => e
|
|
437
|
+
configuration.logger&.warn("Error accessing dispatch registry: #{e.message}")
|
|
438
|
+
nil
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
##
|
|
442
|
+
# Configuration accessors for convenience.
|
|
443
|
+
def tick_interval
|
|
444
|
+
configuration.tick_interval
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def tick_interval=(value)
|
|
448
|
+
configuration.tick_interval = value
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def window_lookback
|
|
452
|
+
configuration.window_lookback
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
def window_lookback=(value)
|
|
456
|
+
configuration.window_lookback = value
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def window_lookahead
|
|
460
|
+
configuration.window_lookahead
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
def window_lookahead=(value)
|
|
464
|
+
configuration.window_lookahead = value
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
def lease_ttl
|
|
468
|
+
configuration.lease_ttl
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def lease_ttl=(value)
|
|
472
|
+
configuration.lease_ttl = value
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
def namespace
|
|
476
|
+
configuration.namespace
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def namespace=(value)
|
|
480
|
+
configuration.namespace = value
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
def backend
|
|
484
|
+
configuration.backend
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def backend=(value)
|
|
488
|
+
configuration.backend = value
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
##
|
|
492
|
+
# Definition registry access.
|
|
493
|
+
#
|
|
494
|
+
# Uses backend-provided definition registry when available, otherwise a
|
|
495
|
+
# process-local in-memory fallback.
|
|
496
|
+
#
|
|
497
|
+
# @return [Kaal::Definition::Registry]
|
|
498
|
+
def definition_registry
|
|
499
|
+
configured_backend = configuration.backend
|
|
500
|
+
registry = configured_backend&.definition_registry
|
|
501
|
+
return registry if registry
|
|
502
|
+
|
|
503
|
+
@definition_registry ||= Definition::MemoryEngine.new
|
|
504
|
+
rescue NoMethodError
|
|
505
|
+
@definition_registry ||= Definition::MemoryEngine.new
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
def logger
|
|
509
|
+
configuration.logger
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
def logger=(value)
|
|
513
|
+
configuration.logger = value
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
def time_zone
|
|
517
|
+
configuration.time_zone
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
def time_zone=(value)
|
|
521
|
+
configuration.time_zone = value
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
def validate
|
|
525
|
+
configuration.validate
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def validate!
|
|
529
|
+
configuration.validate!
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
##
|
|
533
|
+
# Validate a cron expression.
|
|
534
|
+
#
|
|
535
|
+
# @param expression [String] cron expression
|
|
536
|
+
# @return [Boolean] true if valid, false otherwise
|
|
537
|
+
def valid?(expression)
|
|
538
|
+
CronUtils.valid?(expression)
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
##
|
|
542
|
+
# Simplify a cron expression to a predefined macro when possible.
|
|
543
|
+
#
|
|
544
|
+
# @param expression [String] cron expression
|
|
545
|
+
# @return [String] simplified expression or canonical input
|
|
546
|
+
# @raise [ArgumentError] when expression is invalid
|
|
547
|
+
def simplify(expression)
|
|
548
|
+
CronUtils.simplify(expression)
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
##
|
|
552
|
+
# Lint a cron expression and return warnings/errors.
|
|
553
|
+
#
|
|
554
|
+
# @param expression [String] cron expression
|
|
555
|
+
# @return [Array<String>] lint warnings/errors
|
|
556
|
+
def lint(expression)
|
|
557
|
+
CronUtils.lint(expression)
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
##
|
|
561
|
+
# Convert a cron expression to a human-friendly phrase.
|
|
562
|
+
#
|
|
563
|
+
# @param expression [String] cron expression
|
|
564
|
+
# @param locale [Symbol, String, nil] locale override (defaults to current I18n.locale)
|
|
565
|
+
# @return [String] localized phrase
|
|
566
|
+
# @raise [ArgumentError] when expression is invalid
|
|
567
|
+
def to_human(expression, locale: nil)
|
|
568
|
+
CronHumanizer.to_human(expression, locale: locale)
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Codevedas Inc. 2025-present
|
|
4
|
+
#
|
|
5
|
+
# This source code is licensed under the MIT license found in the
|
|
6
|
+
# LICENSE file in the root directory of this source tree.
|
|
7
|
+
|
|
8
|
+
require 'kaal/rake_tasks'
|
|
9
|
+
|
|
10
|
+
Kaal::RakeTasks.install
|