kaal 0.2.1 → 0.4.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.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +79 -287
  3. data/Rakefile +4 -2
  4. data/config/kaal.rb +15 -0
  5. data/config/scheduler.yml +12 -0
  6. data/{lib/tasks/kaal_tasks.rake → exe/kaal} +5 -3
  7. data/lib/kaal/active_record_support.rb +82 -0
  8. data/lib/kaal/backend/adapter.rb +0 -1
  9. data/lib/kaal/backend/dispatch_attempt_logger.rb +33 -0
  10. data/lib/kaal/backend/dispatch_logging.rb +36 -23
  11. data/lib/kaal/backend/dispatch_registry_accessor.rb +43 -0
  12. data/lib/kaal/backend/memory_adapter.rb +7 -5
  13. data/lib/kaal/backend/mysql.rb +41 -0
  14. data/lib/kaal/backend/postgres.rb +41 -0
  15. data/lib/kaal/backend/redis_adapter.rb +6 -6
  16. data/lib/kaal/backend/sqlite.rb +41 -0
  17. data/lib/kaal/cli.rb +230 -0
  18. data/lib/kaal/{configuration.rb → config/configuration.rb} +0 -1
  19. data/lib/kaal/{scheduler_config_error.rb → config/scheduler_config_error.rb} +0 -1
  20. data/lib/kaal/config/scheduler_time_zone_resolver.rb +50 -0
  21. data/lib/kaal/config.rb +19 -0
  22. data/lib/kaal/{coordinator.rb → core/coordinator.rb} +42 -62
  23. data/lib/kaal/core/enabled_entry_enumerator.rb +51 -0
  24. data/lib/kaal/core/occurrence_finder.rb +38 -0
  25. data/lib/kaal/core.rb +18 -0
  26. data/lib/kaal/definition/database_engine.rb +54 -16
  27. data/lib/kaal/definition/memory_engine.rb +11 -18
  28. data/lib/kaal/definition/persistence_helpers.rb +31 -0
  29. data/lib/kaal/definition/redis_engine.rb +9 -6
  30. data/lib/kaal/definition/registry.rb +24 -2
  31. data/lib/kaal/definitions/registration_service.rb +62 -0
  32. data/lib/kaal/definitions/registry_accessor.rb +33 -0
  33. data/lib/kaal/dispatch/database_engine.rb +87 -61
  34. data/lib/kaal/dispatch/memory_engine.rb +3 -4
  35. data/lib/kaal/dispatch/redis_engine.rb +2 -3
  36. data/lib/kaal/dispatch/registry.rb +0 -1
  37. data/lib/kaal/internal/active_record/base_record.rb +16 -0
  38. data/lib/kaal/internal/active_record/connection_support.rb +96 -0
  39. data/lib/kaal/internal/active_record/database_backend.rb +73 -0
  40. data/lib/kaal/internal/active_record/definition_record.rb +16 -0
  41. data/lib/kaal/internal/active_record/definition_registry.rb +81 -0
  42. data/lib/kaal/internal/active_record/dispatch_record.rb +16 -0
  43. data/lib/kaal/internal/active_record/dispatch_registry.rb +100 -0
  44. data/lib/kaal/internal/active_record/lock_record.rb +16 -0
  45. data/lib/kaal/internal/active_record/migration_templates.rb +108 -0
  46. data/lib/kaal/internal/active_record/mysql_backend.rb +71 -0
  47. data/lib/kaal/internal/active_record/postgres_backend.rb +69 -0
  48. data/lib/kaal/internal/active_record.rb +17 -0
  49. data/lib/kaal/internal/sequel/database_backend.rb +74 -0
  50. data/lib/kaal/internal/sequel/mysql_backend.rb +69 -0
  51. data/lib/kaal/internal/sequel/postgres_backend.rb +67 -0
  52. data/lib/kaal/internal/sequel.rb +12 -0
  53. data/lib/kaal/persistence/database.rb +35 -0
  54. data/lib/kaal/persistence/migration_templates.rb +97 -0
  55. data/lib/kaal/register_conflict_support.rb +0 -1
  56. data/lib/kaal/registry.rb +0 -3
  57. data/lib/kaal/runtime/runtime_context.rb +41 -0
  58. data/lib/kaal/runtime/scheduler_boot_loader.rb +52 -0
  59. data/lib/kaal/runtime/signal_handler_chain.rb +42 -0
  60. data/lib/kaal/runtime/signal_handler_installer.rb +39 -0
  61. data/lib/kaal/runtime.rb +20 -0
  62. data/lib/kaal/scheduler_file/hash_transform.rb +22 -0
  63. data/lib/kaal/scheduler_file/helper_bundle.rb +28 -0
  64. data/lib/kaal/scheduler_file/job_applier.rb +242 -0
  65. data/lib/kaal/scheduler_file/job_normalizer.rb +90 -0
  66. data/lib/kaal/scheduler_file/loader.rb +152 -0
  67. data/lib/kaal/scheduler_file/payload_loader.rb +95 -0
  68. data/lib/kaal/{scheduler_placeholder_support.rb → scheduler_file/placeholder_support.rb} +0 -1
  69. data/lib/kaal/scheduler_file.rb +18 -0
  70. data/lib/kaal/sequel_support.rb +82 -0
  71. data/lib/kaal/support/hash_tools.rb +93 -0
  72. data/lib/kaal/{cron_humanizer.rb → utils/cron_humanizer.rb} +19 -1
  73. data/lib/kaal/{cron_utils.rb → utils/cron_utils.rb} +0 -1
  74. data/lib/kaal/{idempotency_key_generator.rb → utils/idempotency_key_generator.rb} +3 -3
  75. data/lib/kaal/utils.rb +18 -0
  76. data/lib/kaal/version.rb +1 -2
  77. data/lib/kaal.rb +83 -397
  78. metadata +87 -42
  79. data/app/models/kaal/cron_definition.rb +0 -76
  80. data/app/models/kaal/cron_dispatch.rb +0 -50
  81. data/app/models/kaal/cron_lock.rb +0 -38
  82. data/lib/generators/kaal/install/install_generator.rb +0 -72
  83. data/lib/generators/kaal/install/templates/create_kaal_definitions.rb.tt +0 -21
  84. data/lib/generators/kaal/install/templates/create_kaal_dispatches.rb.tt +0 -20
  85. data/lib/generators/kaal/install/templates/create_kaal_locks.rb.tt +0 -17
  86. data/lib/generators/kaal/install/templates/kaal.rb.tt +0 -31
  87. data/lib/generators/kaal/install/templates/scheduler.yml.tt +0 -22
  88. data/lib/kaal/backend/mysql_adapter.rb +0 -170
  89. data/lib/kaal/backend/postgres_adapter.rb +0 -134
  90. data/lib/kaal/backend/sqlite_adapter.rb +0 -116
  91. data/lib/kaal/railtie.rb +0 -183
  92. data/lib/kaal/rake_tasks.rb +0 -184
  93. data/lib/kaal/scheduler_file_loader.rb +0 -321
  94. data/lib/kaal/scheduler_hash_transform.rb +0 -45
data/lib/kaal.rb CHANGED
@@ -4,224 +4,100 @@
4
4
  #
5
5
  # This source code is licensed under the MIT license found in the
6
6
  # LICENSE file in the root directory of this source tree.
7
-
8
7
  require 'kaal/version'
9
- require 'kaal/configuration'
8
+ require 'kaal/config'
10
9
  require 'kaal/registry'
11
10
  require 'kaal/dispatch/registry'
12
11
  require 'kaal/dispatch/memory_engine'
13
12
  require 'kaal/dispatch/redis_engine'
14
- require 'kaal/dispatch/database_engine'
15
13
  require 'kaal/definition/registry'
16
14
  require 'kaal/definition/memory_engine'
17
15
  require 'kaal/definition/redis_engine'
18
- require 'kaal/definition/database_engine'
19
16
  require 'kaal/backend/adapter'
20
17
  require 'kaal/backend/memory_adapter'
21
18
  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'
19
+ require 'kaal/backend/sqlite'
20
+ require 'kaal/backend/postgres'
21
+ require 'kaal/backend/mysql'
22
+ require 'kaal/backend/dispatch_registry_accessor'
23
+ require 'kaal/backend/dispatch_attempt_logger'
24
+ require 'kaal/persistence/migration_templates'
25
+ require 'kaal/sequel_support'
26
+ require 'kaal/active_record_support'
27
+ require 'kaal/utils'
30
28
  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
- # )
29
+ require 'kaal/definitions/registry_accessor'
30
+ require 'kaal/definitions/registration_service'
31
+ require 'kaal/runtime'
32
+ require 'kaal/scheduler_file'
33
+ require 'kaal/core'
34
+ require 'kaal/support/hash_tools'
35
+
36
+ # Plain-Ruby scheduler surface with configurable backends, registries, and CLI helpers.
51
37
  module Kaal
52
38
  class << self
53
39
  include RegisterConflictSupport
54
40
 
55
- ##
56
- # Get the current configuration instance.
57
- #
58
- # @return [Configuration] the global configuration object
59
41
  def configuration
60
42
  @configuration ||= Configuration.new
61
43
  end
62
44
 
63
- ##
64
- # Get the current registry instance.
65
- #
66
- # @return [Registry] the global registry object
67
45
  def registry
68
46
  @registry ||= Registry.new
69
47
  end
70
48
 
71
- ##
72
- # Get the coordinator instance.
73
- #
74
- # @return [Coordinator] the global coordinator object
75
49
  def coordinator
76
50
  @coordinator ||= Coordinator.new(configuration: configuration, registry: registry)
77
51
  end
78
52
 
79
- ##
80
- # Reset configuration to defaults. Primarily used in tests.
81
- #
82
- # @return [Configuration] a fresh configuration object
83
53
  def reset_configuration!
84
54
  @configuration = Configuration.new
85
- @coordinator = nil # Invalidate coordinator so it rebuilds with new config
55
+ @coordinator = nil
86
56
  @definition_registry = nil
57
+ @definitions_registry_accessor = nil
58
+ @dispatch_registry_accessor = nil
87
59
  end
88
60
 
89
- ##
90
- # Reset registry to empty state. Primarily used in tests.
91
- #
92
- # @return [Registry] a fresh registry object
93
61
  def reset_registry!
94
62
  @registry = Registry.new
95
- @definition_registry&.clear
96
- @coordinator = nil # Invalidate coordinator so it rebuilds with new registry
63
+ definition_registry = @definition_registry
64
+ definition_registry.clear if definition_registry.respond_to?(:clear)
65
+ @coordinator = nil
97
66
  end
98
67
 
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
68
  def reset_coordinator!
107
- # Stop the existing coordinator if it's running
108
69
  if @coordinator&.running?
109
70
  stopped = @coordinator.stop!
110
71
  raise 'Failed to stop coordinator thread within timeout' unless stopped
111
72
  end
112
73
 
113
- # Create and return a fresh coordinator
114
74
  @coordinator = nil
115
75
  coordinator
116
76
  end
117
77
 
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
78
  def configure
131
79
  yield(configuration) if block_given?
132
80
  end
133
81
 
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
82
  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
83
+ registration_service.call(key:, cron:, enqueue:)
176
84
  end
177
85
 
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(
86
+ def load_scheduler_file!(runtime_context: RuntimeContext.default)
87
+ SchedulerFileLoader.new(
185
88
  configuration: configuration,
186
89
  definition_registry: definition_registry,
187
90
  registry: registry,
188
- logger: configuration.logger
189
- )
190
- loader.load
91
+ logger: configuration.logger,
92
+ runtime_context: runtime_context
93
+ ).load
191
94
  end
192
95
 
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
96
  def unregister(key:)
199
97
  definition_registry.remove_definition(key)
200
98
  registry.remove(key)
201
99
  end
202
100
 
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
101
  def registered
226
102
  definition_registry.all_definitions.map do |definition|
227
103
  key = definition[:key]
@@ -230,297 +106,97 @@ module Kaal
230
106
  end
231
107
  end
232
108
 
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
109
  def registered?(key:)
242
- definition_registry.find_definition(key).present?
110
+ !!definition_registry.find_definition(key)
243
111
  end
244
112
 
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
113
  def enable(key:)
251
114
  definition_registry.enable_definition(key)
252
115
  end
253
116
 
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
117
  def disable(key:)
260
118
  definition_registry.disable_definition(key)
261
119
  end
262
120
 
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
121
  def start!
274
122
  coordinator.start!
275
123
  end
276
124
 
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
125
  def stop!(timeout: 30)
291
126
  coordinator.stop!(timeout: timeout)
292
127
  end
293
128
 
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
129
  def running?
304
130
  coordinator.running?
305
131
  end
306
132
 
307
- ##
308
- # Restart the scheduler (stop then start).
309
- #
310
- # @return [Thread] the started thread
311
- #
312
- # @example
313
- # Kaal.restart!
314
133
  def restart!
315
134
  coordinator.restart!
316
135
  end
317
136
 
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
137
  def tick!
329
138
  coordinator.tick!
330
139
  end
331
140
 
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
141
  def with_idempotency(key, fire_time)
350
142
  raise ArgumentError, 'block required' unless block_given?
351
143
 
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?
144
+ yield(IdempotencyKeyGenerator.call(key, fire_time, configuration: configuration))
145
+ end
434
146
 
435
- registry
436
- rescue StandardError => e
437
- configuration.logger&.warn("Error accessing dispatch registry: #{e.message}")
438
- nil
147
+ def dispatched?(key, fire_time)
148
+ dispatch_registry_accessor.dispatched?(key, fire_time)
439
149
  end
440
150
 
441
- ##
442
- # Configuration accessors for convenience.
443
- def tick_interval
444
- configuration.tick_interval
151
+ def dispatch_log_registry
152
+ dispatch_registry_accessor.registry
445
153
  end
446
154
 
155
+ def tick_interval = configuration.tick_interval
156
+ def window_lookback = configuration.window_lookback
157
+ def window_lookahead = configuration.window_lookahead
158
+ def lease_ttl = configuration.lease_ttl
159
+ def namespace = configuration.namespace
160
+ def backend = configuration.backend
161
+ def logger = configuration.logger
162
+ def time_zone = configuration.time_zone
163
+
447
164
  def tick_interval=(value)
448
165
  configuration.tick_interval = value
449
166
  end
450
167
 
451
- def window_lookback
452
- configuration.window_lookback
453
- end
454
-
455
168
  def window_lookback=(value)
456
169
  configuration.window_lookback = value
457
170
  end
458
171
 
459
- def window_lookahead
460
- configuration.window_lookahead
461
- end
462
-
463
172
  def window_lookahead=(value)
464
173
  configuration.window_lookahead = value
465
174
  end
466
175
 
467
- def lease_ttl
468
- configuration.lease_ttl
469
- end
470
-
471
176
  def lease_ttl=(value)
472
177
  configuration.lease_ttl = value
473
178
  end
474
179
 
475
- def namespace
476
- configuration.namespace
477
- end
478
-
479
180
  def namespace=(value)
480
181
  configuration.namespace = value
481
182
  end
482
183
 
483
- def backend
484
- configuration.backend
485
- end
486
-
487
184
  def backend=(value)
488
185
  configuration.backend = value
489
186
  end
490
187
 
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
188
  def logger=(value)
513
189
  configuration.logger = value
514
190
  end
515
191
 
516
- def time_zone
517
- configuration.time_zone
518
- end
519
-
520
192
  def time_zone=(value)
521
193
  configuration.time_zone = value
522
194
  end
523
195
 
196
+ def definition_registry
197
+ definitions_registry_accessor.call
198
+ end
199
+
524
200
  def validate
525
201
  configuration.validate
526
202
  end
@@ -529,43 +205,53 @@ module Kaal
529
205
  configuration.validate!
530
206
  end
531
207
 
532
- ##
533
- # Validate a cron expression.
534
- #
535
- # @param expression [String] cron expression
536
- # @return [Boolean] true if valid, false otherwise
537
208
  def valid?(expression)
538
209
  CronUtils.valid?(expression)
539
210
  end
540
211
 
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
212
  def simplify(expression)
548
213
  CronUtils.simplify(expression)
549
214
  end
550
215
 
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
216
  def lint(expression)
557
217
  CronUtils.lint(expression)
558
218
  end
559
219
 
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
220
  def to_human(expression, locale: nil)
568
221
  CronHumanizer.to_human(expression, locale: locale)
569
222
  end
223
+
224
+ private
225
+
226
+ def rollback_registered_definition(key, existing_definition)
227
+ if existing_definition
228
+ definition_registry.upsert_definition(
229
+ **Definition::AttributeHelpers.definition_attributes(existing_definition), enabled: existing_definition[:enabled]
230
+ )
231
+ elsif !registry.registered?(key)
232
+ definition_registry.remove_definition(key)
233
+ end
234
+ end
235
+
236
+ def registration_service
237
+ @registration_service ||= Definitions::RegistrationService.new(
238
+ configuration: configuration,
239
+ definition_registry: definition_registry,
240
+ registry: registry
241
+ )
242
+ end
243
+
244
+ def definitions_registry_accessor
245
+ @definitions_registry_accessor ||= Definitions::RegistryAccessor.new(
246
+ configuration: configuration,
247
+ fallback_registry_provider: lambda {
248
+ @definition_registry ||= Definition::MemoryEngine.new
249
+ }
250
+ )
251
+ end
252
+
253
+ def dispatch_registry_accessor
254
+ @dispatch_registry_accessor ||= Backend::DispatchRegistryAccessor.new(configuration: configuration)
255
+ end
570
256
  end
571
257
  end