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/core.rb ADDED
@@ -0,0 +1,18 @@
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
+ require 'kaal/core/coordinator'
8
+ require 'kaal/core/occurrence_finder'
9
+ require 'kaal/core/enabled_entry_enumerator'
10
+
11
+ module Kaal
12
+ # Core scheduling orchestration types.
13
+ module Core
14
+ Coordinator = ::Kaal::Coordinator
15
+ OccurrenceFinder = ::Kaal::OccurrenceFinder
16
+ EnabledEntryEnumerator = ::Kaal::EnabledEntryEnumerator
17
+ end
18
+ end
@@ -4,14 +4,16 @@
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
- require 'active_support/core_ext/object/deep_dup'
9
7
  require_relative 'registry'
8
+ require_relative 'persistence_helpers'
9
+ require 'kaal/support/hash_tools'
10
10
 
11
11
  module Kaal
12
12
  module Definition
13
13
  # In-memory definition registry used when no persistent backend is configured.
14
14
  class MemoryEngine < Registry
15
+ include Kaal::Support::HashTools
16
+
15
17
  def initialize
16
18
  super
17
19
  @definitions = {}
@@ -20,42 +22,33 @@ module Kaal
20
22
 
21
23
  def upsert_definition(key:, cron:, enabled: true, source: 'code', metadata: {})
22
24
  @mutex.synchronize do
23
- now = Time.current
25
+ now = Time.now.utc
24
26
  existing = @definitions[key]
25
- stored_metadata = (metadata || {}).deep_dup
26
- disabled_at = if enabled
27
- nil
28
- elsif existing && existing[:enabled] == false
29
- existing[:disabled_at]
30
- else
31
- now
32
- end
33
27
  definition = {
34
28
  key: key,
35
29
  cron: cron,
36
30
  enabled: enabled,
37
31
  source: source,
38
- metadata: stored_metadata,
32
+ metadata: deep_dup(metadata || {}),
39
33
  created_at: existing ? existing[:created_at] : now,
40
34
  updated_at: now,
41
- disabled_at:
35
+ disabled_at: PersistenceHelpers.disabled_at_for(existing, enabled, now)
42
36
  }
43
37
  @definitions[key] = definition
44
-
45
- definition.deep_dup
38
+ deep_dup(definition)
46
39
  end
47
40
  end
48
41
 
49
42
  def remove_definition(key)
50
- @mutex.synchronize { @definitions.delete(key)&.deep_dup }
43
+ @mutex.synchronize { deep_dup(@definitions.delete(key)) }
51
44
  end
52
45
 
53
46
  def find_definition(key)
54
- @mutex.synchronize { @definitions[key]&.deep_dup }
47
+ @mutex.synchronize { deep_dup(@definitions[key]) }
55
48
  end
56
49
 
57
50
  def all_definitions
58
- @mutex.synchronize { @definitions.values.map(&:deep_dup) }
51
+ @mutex.synchronize { @definitions.values.sort_by { |definition| definition[:key] }.map { |definition| deep_dup(definition) } }
59
52
  end
60
53
 
61
54
  def clear
@@ -0,0 +1,31 @@
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
+ require 'json'
8
+
9
+ module Kaal
10
+ module Definition
11
+ # Shared pure helpers for persisted definition rows and metadata.
12
+ module PersistenceHelpers
13
+ module_function
14
+
15
+ def disabled_at_for(existing, enabled, now)
16
+ return nil if enabled
17
+ return existing[:disabled_at] if existing && existing[:enabled] == false
18
+
19
+ now
20
+ end
21
+
22
+ def parse_metadata(value)
23
+ return {} if value.to_s.empty?
24
+
25
+ JSON.parse(value, symbolize_names: true)
26
+ rescue JSON::ParserError
27
+ {}
28
+ end
29
+ end
30
+ end
31
+ end
@@ -4,15 +4,18 @@
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 'json'
9
8
  require 'time'
10
9
  require_relative 'registry'
10
+ require 'kaal/support/hash_tools'
11
+ require 'kaal/definition/persistence_helpers'
11
12
 
12
13
  module Kaal
13
14
  module Definition
14
15
  # Redis-backed definition registry shared across processes.
15
16
  class RedisEngine < Registry
17
+ include Kaal::Support::HashTools
18
+
16
19
  def initialize(redis, namespace: 'kaal')
17
20
  super()
18
21
  @redis = redis
@@ -20,21 +23,21 @@ module Kaal
20
23
  end
21
24
 
22
25
  def upsert_definition(key:, cron:, enabled: true, source: 'code', metadata: {})
23
- now = Time.current
26
+ now = Time.now.utc
24
27
  existing = find_definition(key)
25
28
  payload = {
26
29
  key: key,
27
30
  cron: cron,
28
31
  enabled: enabled,
29
32
  source: source,
30
- metadata: metadata,
33
+ metadata: deep_dup(metadata || {}),
31
34
  created_at: existing ? existing[:created_at] : now,
32
35
  updated_at: now,
33
- disabled_at: enabled ? nil : now
36
+ disabled_at: PersistenceHelpers.disabled_at_for(existing, enabled, now)
34
37
  }
35
38
 
36
39
  @redis.hset(storage_key, key, JSON.generate(self.class.serialize_payload(payload)))
37
- payload
40
+ deep_dup(payload)
38
41
  end
39
42
 
40
43
  def remove_definition(key)
@@ -49,7 +52,7 @@ module Kaal
49
52
  end
50
53
 
51
54
  def all_definitions
52
- @redis.hvals(storage_key).filter_map { |raw| self.class.deserialize_payload(raw) }
55
+ @redis.hvals(storage_key).filter_map { |raw| self.class.deserialize_payload(raw) }.sort_by { |definition| definition[:key] }
53
56
  end
54
57
 
55
58
  private
@@ -4,9 +4,31 @@
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
  module Kaal
9
8
  module Definition
9
+ # Pure helpers for extracting persisted definition attributes without ActiveSupport.
10
+ module AttributeHelpers
11
+ module_function
12
+
13
+ def definition_attributes(definition)
14
+ {
15
+ key: definition[:key],
16
+ cron: definition[:cron],
17
+ source: definition[:source],
18
+ metadata: definition[:metadata]
19
+ }
20
+ end
21
+
22
+ def persisted_definition_attributes(definition)
23
+ return {} unless definition
24
+
25
+ {
26
+ enabled: definition[:enabled],
27
+ metadata: definition[:metadata]
28
+ }
29
+ end
30
+ end
31
+
10
32
  # Base abstraction for cron definition storage.
11
33
  class Registry
12
34
  def upsert_definition(**)
@@ -43,7 +65,7 @@ module Kaal
43
65
  definition = find_definition(key)
44
66
  return nil unless definition
45
67
 
46
- attributes = definition.slice(:key, :cron, :source, :metadata).merge(enabled: enabled)
68
+ attributes = AttributeHelpers.definition_attributes(definition).merge(enabled: enabled)
47
69
  upsert_definition(**attributes)
48
70
  end
49
71
  end
@@ -0,0 +1,62 @@
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
+ module Kaal
8
+ module Definitions
9
+ # Registers code-defined jobs while preserving persisted definition state.
10
+ class RegistrationService
11
+ include RegisterConflictSupport
12
+
13
+ attr_reader :configuration, :definition_registry, :registry
14
+
15
+ def initialize(configuration:, definition_registry:, registry:)
16
+ @configuration = configuration
17
+ @definition_registry = definition_registry
18
+ @registry = registry
19
+ end
20
+
21
+ def call(key:, cron:, enqueue:)
22
+ existing_definition = @definition_registry.find_definition(key)
23
+ existing_entry = @registry.find(key)
24
+ if existing_entry
25
+ conflict_result = resolve_register_conflict(
26
+ key: key,
27
+ cron: cron,
28
+ enqueue: enqueue,
29
+ existing_definition: existing_definition,
30
+ existing_entry: existing_entry
31
+ )
32
+
33
+ return conflict_result if conflict_result
34
+
35
+ raise RegistryError, "Key '#{key}' is already registered"
36
+ end
37
+
38
+ persisted_attributes = {
39
+ enabled: true,
40
+ source: 'code',
41
+ metadata: {}
42
+ }.merge(Definition::AttributeHelpers.persisted_definition_attributes(existing_definition))
43
+ @definition_registry.upsert_definition(key: key, cron: cron, **persisted_attributes)
44
+ with_registered_definition_rollback(key, existing_definition) do
45
+ @registry.add(key: key, cron: cron, enqueue: enqueue)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def rollback_registered_definition(key, existing_definition)
52
+ if existing_definition
53
+ @definition_registry.upsert_definition(
54
+ **Definition::AttributeHelpers.definition_attributes(existing_definition), enabled: existing_definition[:enabled]
55
+ )
56
+ elsif !@registry.registered?(key)
57
+ @definition_registry.remove_definition(key)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,33 @@
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
+ module Kaal
8
+ module Definitions
9
+ # Resolves the active definition registry with an in-memory fallback.
10
+ class RegistryAccessor
11
+ def initialize(configuration:, fallback_registry_provider:)
12
+ @configuration = configuration
13
+ @fallback_registry_provider = fallback_registry_provider
14
+ end
15
+
16
+ def call
17
+ configured_backend = @configuration.backend
18
+ registry = configured_backend&.definition_registry
19
+ return registry if registry
20
+
21
+ fallback_registry
22
+ rescue NoMethodError
23
+ fallback_registry
24
+ end
25
+
26
+ private
27
+
28
+ def fallback_registry
29
+ @fallback_registry_provider.call
30
+ end
31
+ end
32
+ end
33
+ end
@@ -4,7 +4,6 @@
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_relative 'registry'
9
8
 
10
9
  module Kaal
@@ -17,8 +16,8 @@ module Kaal
17
16
  #
18
17
  # @example Usage
19
18
  # registry = Kaal::Dispatch::MemoryEngine.new
20
- # registry.log_dispatch('daily_report', Time.current, 'node-1')
21
- # registry.dispatched?('daily_report', Time.current) # => true
19
+ # registry.log_dispatch('daily_report', Time.now.utc, 'node-1')
20
+ # registry.dispatched?('daily_report', Time.now.utc) # => true
22
21
  class MemoryEngine < Registry
23
22
  ##
24
23
  # Initialize a new in-memory registry.
@@ -42,7 +41,7 @@ module Kaal
42
41
  @dispatches[storage_key] = {
43
42
  key: key,
44
43
  fire_time: fire_time,
45
- dispatched_at: Time.current,
44
+ dispatched_at: Time.now.utc,
46
45
  node_id: node_id,
47
46
  status: status
48
47
  }
@@ -4,7 +4,6 @@
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 'json'
9
8
  require_relative 'registry'
10
9
 
@@ -19,7 +18,7 @@ module Kaal
19
18
  # @example Usage
20
19
  # redis = Redis.new(url: ENV['REDIS_URL'])
21
20
  # registry = Kaal::Dispatch::RedisEngine.new(redis, namespace: 'myapp')
22
- # registry.log_dispatch('daily_report', Time.current, 'node-1')
21
+ # registry.log_dispatch('daily_report', Time.now.utc, 'node-1')
23
22
  class RedisEngine < Registry
24
23
  # Default TTL for dispatch records (7 days in seconds)
25
24
  DEFAULT_TTL = 7 * 24 * 60 * 60
@@ -50,7 +49,7 @@ module Kaal
50
49
  record = {
51
50
  key: key,
52
51
  fire_time: fire_time.to_i,
53
- dispatched_at: Time.current.to_i,
52
+ dispatched_at: Time.now.utc.to_i,
54
53
  node_id: node_id,
55
54
  status: status
56
55
  }
@@ -4,7 +4,6 @@
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
  module Kaal
9
8
  module Dispatch
10
9
  ##
@@ -4,7 +4,6 @@
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
  module Kaal
9
8
  # Register conflict handling helpers shared by Kaal singleton methods.
10
9
  module RegisterConflictSupport
data/lib/kaal/registry.rb CHANGED
@@ -4,7 +4,6 @@
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
  module Kaal
9
8
  ##
10
9
  # Thread-safe registry for storing and managing registered cron jobs.
@@ -0,0 +1,41 @@
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
+ require 'pathname'
8
+
9
+ module Kaal
10
+ # Resolves environment and path information for plain-Ruby runtime loading.
11
+ class RuntimeContext
12
+ DEFAULT_ENVIRONMENT_NAME = 'development'
13
+ ENVIRONMENT_KEYS = %w[KAAL_ENV RAILS_ENV APP_ENV RACK_ENV].freeze
14
+
15
+ attr_reader :environment_name, :root_path
16
+
17
+ def self.default(env: ENV, root_path: Dir.pwd)
18
+ new(root_path: root_path, environment_name: environment_name_from(env))
19
+ end
20
+
21
+ def self.environment_name_from(env)
22
+ ENVIRONMENT_KEYS.each do |key|
23
+ value = env[key].to_s.strip
24
+ return value unless value.empty?
25
+ end
26
+
27
+ DEFAULT_ENVIRONMENT_NAME
28
+ end
29
+
30
+ def initialize(root_path:, environment_name:)
31
+ @root_path = Pathname.new(root_path)
32
+ @environment_name = environment_name.to_s
33
+ end
34
+
35
+ def resolve_path(path)
36
+ return path.to_s if Pathname.new(path).absolute?
37
+
38
+ root_path.join(path).to_s
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,52 @@
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
+ module Kaal
8
+ # Loads scheduler.yml at framework boot time while respecting missing-file policy.
9
+ class SchedulerBootLoader
10
+ def initialize(configuration_provider:, logger:, runtime_context:, load_scheduler_file:)
11
+ @configuration_provider = configuration_provider
12
+ @logger = logger
13
+ @runtime_context = runtime_context
14
+ @load_scheduler_file = load_scheduler_file
15
+ end
16
+
17
+ def load_on_boot
18
+ load_on_boot!
19
+ end
20
+
21
+ def load_on_boot!
22
+ configuration = fetch_configuration
23
+ return unless configuration
24
+
25
+ return load_scheduler_file if configuration.scheduler_missing_file_policy == :error
26
+
27
+ scheduler_path = configuration.scheduler_config_path.to_s.strip
28
+ return if scheduler_path.empty?
29
+
30
+ absolute_path = @runtime_context.resolve_path(scheduler_path)
31
+ unless File.exist?(absolute_path)
32
+ @logger&.warn("Scheduler file not found at #{absolute_path}")
33
+ return
34
+ end
35
+
36
+ load_scheduler_file
37
+ end
38
+
39
+ private
40
+
41
+ def load_scheduler_file
42
+ @load_scheduler_file.call
43
+ end
44
+
45
+ def fetch_configuration
46
+ @configuration_provider.call
47
+ rescue NameError => e
48
+ @logger&.debug("Skipping scheduler file boot load due to configuration error: #{e.message}")
49
+ nil
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,42 @@
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
+ module Kaal
8
+ # Invokes a previously registered signal handler when it is safe to do so.
9
+ class SignalHandlerChain
10
+ RESERVED_COMMAND_HANDLERS = %w[DEFAULT IGNORE].freeze
11
+
12
+ def initialize(signal:, previous_handler:, logger:)
13
+ @signal = signal
14
+ @previous_handler = previous_handler
15
+ @logger = logger
16
+ end
17
+
18
+ def call(...)
19
+ return unless @previous_handler
20
+
21
+ case @previous_handler
22
+ when Proc, Method
23
+ invoke_callable(...)
24
+ when String
25
+ return if RESERVED_COMMAND_HANDLERS.include?(@previous_handler)
26
+
27
+ @logger&.debug("Previous #{@signal} handler was a command: #{@previous_handler}")
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def invoke_callable(*args)
34
+ arity = @previous_handler.arity
35
+ return @previous_handler.call if arity.zero?
36
+
37
+ argument_length = args.length
38
+ argument_count = arity.negative? ? argument_length : [arity, argument_length].min
39
+ @previous_handler.call(*args.first(argument_count))
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,39 @@
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
+ module Kaal
8
+ # Installs signal handlers while preserving the previous handlers for chaining.
9
+ class SignalHandlerInstaller
10
+ SIGNALS = %w[TERM INT].freeze
11
+ IGNORE_HANDLER = 'IGNORE'
12
+
13
+ def initialize(signal_module: Signal)
14
+ @signal_module = signal_module
15
+ end
16
+
17
+ def install(signals: SIGNALS)
18
+ signals.each_with_object({}) do |signal, previous_handlers|
19
+ previous_handler = capture_previous_handler(signal)
20
+ @signal_module.trap(signal) { yield(signal, previous_handler) }
21
+ previous_handlers[signal] = previous_handler
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def capture_previous_handler(signal)
28
+ previous_handler = @signal_module.trap(signal, IGNORE_HANDLER)
29
+ restore_previous_handler(signal, previous_handler)
30
+ previous_handler
31
+ end
32
+
33
+ def restore_previous_handler(signal, previous_handler)
34
+ return unless previous_handler && previous_handler != IGNORE_HANDLER
35
+
36
+ @signal_module.trap(signal, previous_handler)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,20 @@
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
+ require 'kaal/runtime/runtime_context'
8
+ require 'kaal/runtime/scheduler_boot_loader'
9
+ require 'kaal/runtime/signal_handler_chain'
10
+ require 'kaal/runtime/signal_handler_installer'
11
+
12
+ module Kaal
13
+ # Runtime wiring and lifecycle helpers.
14
+ module Runtime
15
+ RuntimeContext = ::Kaal::RuntimeContext
16
+ SchedulerBootLoader = ::Kaal::SchedulerBootLoader
17
+ SignalHandlerChain = ::Kaal::SignalHandlerChain
18
+ SignalHandlerInstaller = ::Kaal::SignalHandlerInstaller
19
+ end
20
+ end
@@ -0,0 +1,22 @@
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
+ require 'kaal/support/hash_tools'
8
+
9
+ module Kaal
10
+ # Shared deep hash key transformation helpers for scheduler payloads.
11
+ module SchedulerHashTransform
12
+ private
13
+
14
+ def stringify_keys(object)
15
+ Kaal::Support::HashTools.stringify_keys(object)
16
+ end
17
+
18
+ def symbolize_keys_deep(object)
19
+ Kaal::Support::HashTools.symbolize_keys(object)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
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
+ module Kaal
8
+ class SchedulerFileLoader
9
+ # Exposes hash/placeholder helpers to extracted scheduler-file collaborators.
10
+ class HelperBundle
11
+ def initialize(loader:)
12
+ @loader = loader
13
+ end
14
+
15
+ def stringify_keys(payload)
16
+ @loader.send(:stringify_keys, payload)
17
+ end
18
+
19
+ def resolve_placeholders(value, context)
20
+ @loader.send(:resolve_placeholders, value, context)
21
+ end
22
+
23
+ def validate_placeholders(value, key:)
24
+ @loader.send(:validate_placeholders, value, key:)
25
+ end
26
+ end
27
+ end
28
+ end