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