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.
- checksums.yaml +4 -4
- data/README.md +79 -287
- data/Rakefile +4 -2
- data/config/kaal.rb +15 -0
- data/config/scheduler.yml +12 -0
- data/{lib/tasks/kaal_tasks.rake → exe/kaal} +5 -3
- data/lib/kaal/active_record_support.rb +82 -0
- data/lib/kaal/backend/adapter.rb +0 -1
- data/lib/kaal/backend/dispatch_attempt_logger.rb +33 -0
- data/lib/kaal/backend/dispatch_logging.rb +36 -23
- data/lib/kaal/backend/dispatch_registry_accessor.rb +43 -0
- data/lib/kaal/backend/memory_adapter.rb +7 -5
- data/lib/kaal/backend/mysql.rb +41 -0
- data/lib/kaal/backend/postgres.rb +41 -0
- data/lib/kaal/backend/redis_adapter.rb +6 -6
- data/lib/kaal/backend/sqlite.rb +41 -0
- data/lib/kaal/cli.rb +230 -0
- data/lib/kaal/{configuration.rb → config/configuration.rb} +0 -1
- data/lib/kaal/{scheduler_config_error.rb → config/scheduler_config_error.rb} +0 -1
- data/lib/kaal/config/scheduler_time_zone_resolver.rb +50 -0
- data/lib/kaal/config.rb +19 -0
- data/lib/kaal/{coordinator.rb → core/coordinator.rb} +42 -62
- data/lib/kaal/core/enabled_entry_enumerator.rb +51 -0
- data/lib/kaal/core/occurrence_finder.rb +38 -0
- data/lib/kaal/core.rb +18 -0
- data/lib/kaal/definition/database_engine.rb +54 -16
- data/lib/kaal/definition/memory_engine.rb +11 -18
- data/lib/kaal/definition/persistence_helpers.rb +31 -0
- data/lib/kaal/definition/redis_engine.rb +9 -6
- data/lib/kaal/definition/registry.rb +24 -2
- data/lib/kaal/definitions/registration_service.rb +62 -0
- data/lib/kaal/definitions/registry_accessor.rb +33 -0
- data/lib/kaal/dispatch/database_engine.rb +87 -61
- data/lib/kaal/dispatch/memory_engine.rb +3 -4
- data/lib/kaal/dispatch/redis_engine.rb +2 -3
- data/lib/kaal/dispatch/registry.rb +0 -1
- data/lib/kaal/internal/active_record/base_record.rb +16 -0
- data/lib/kaal/internal/active_record/connection_support.rb +96 -0
- data/lib/kaal/internal/active_record/database_backend.rb +73 -0
- data/lib/kaal/internal/active_record/definition_record.rb +16 -0
- data/lib/kaal/internal/active_record/definition_registry.rb +81 -0
- data/lib/kaal/internal/active_record/dispatch_record.rb +16 -0
- data/lib/kaal/internal/active_record/dispatch_registry.rb +100 -0
- data/lib/kaal/internal/active_record/lock_record.rb +16 -0
- data/lib/kaal/internal/active_record/migration_templates.rb +108 -0
- data/lib/kaal/internal/active_record/mysql_backend.rb +71 -0
- data/lib/kaal/internal/active_record/postgres_backend.rb +69 -0
- data/lib/kaal/internal/active_record.rb +17 -0
- data/lib/kaal/internal/sequel/database_backend.rb +74 -0
- data/lib/kaal/internal/sequel/mysql_backend.rb +69 -0
- data/lib/kaal/internal/sequel/postgres_backend.rb +67 -0
- data/lib/kaal/internal/sequel.rb +12 -0
- data/lib/kaal/persistence/database.rb +35 -0
- data/lib/kaal/persistence/migration_templates.rb +97 -0
- data/lib/kaal/register_conflict_support.rb +0 -1
- data/lib/kaal/registry.rb +0 -3
- data/lib/kaal/runtime/runtime_context.rb +41 -0
- data/lib/kaal/runtime/scheduler_boot_loader.rb +52 -0
- data/lib/kaal/runtime/signal_handler_chain.rb +42 -0
- data/lib/kaal/runtime/signal_handler_installer.rb +39 -0
- data/lib/kaal/runtime.rb +20 -0
- data/lib/kaal/scheduler_file/hash_transform.rb +22 -0
- data/lib/kaal/scheduler_file/helper_bundle.rb +28 -0
- data/lib/kaal/scheduler_file/job_applier.rb +242 -0
- data/lib/kaal/scheduler_file/job_normalizer.rb +90 -0
- data/lib/kaal/scheduler_file/loader.rb +152 -0
- data/lib/kaal/scheduler_file/payload_loader.rb +95 -0
- data/lib/kaal/{scheduler_placeholder_support.rb → scheduler_file/placeholder_support.rb} +0 -1
- data/lib/kaal/scheduler_file.rb +18 -0
- data/lib/kaal/sequel_support.rb +82 -0
- data/lib/kaal/support/hash_tools.rb +93 -0
- data/lib/kaal/{cron_humanizer.rb → utils/cron_humanizer.rb} +19 -1
- data/lib/kaal/{cron_utils.rb → utils/cron_utils.rb} +0 -1
- data/lib/kaal/{idempotency_key_generator.rb → utils/idempotency_key_generator.rb} +3 -3
- data/lib/kaal/utils.rb +18 -0
- data/lib/kaal/version.rb +1 -2
- data/lib/kaal.rb +83 -397
- metadata +87 -42
- data/app/models/kaal/cron_definition.rb +0 -76
- data/app/models/kaal/cron_dispatch.rb +0 -50
- data/app/models/kaal/cron_lock.rb +0 -38
- data/lib/generators/kaal/install/install_generator.rb +0 -72
- data/lib/generators/kaal/install/templates/create_kaal_definitions.rb.tt +0 -21
- data/lib/generators/kaal/install/templates/create_kaal_dispatches.rb.tt +0 -20
- data/lib/generators/kaal/install/templates/create_kaal_locks.rb.tt +0 -17
- data/lib/generators/kaal/install/templates/kaal.rb.tt +0 -31
- data/lib/generators/kaal/install/templates/scheduler.yml.tt +0 -22
- data/lib/kaal/backend/mysql_adapter.rb +0 -170
- data/lib/kaal/backend/postgres_adapter.rb +0 -134
- data/lib/kaal/backend/sqlite_adapter.rb +0 -116
- data/lib/kaal/railtie.rb +0 -183
- data/lib/kaal/rake_tasks.rb +0 -184
- data/lib/kaal/scheduler_file_loader.rb +0 -321
- data/lib/kaal/scheduler_hash_transform.rb +0 -45
|
@@ -0,0 +1,95 @@
|
|
|
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 'erb'
|
|
8
|
+
require 'yaml'
|
|
9
|
+
|
|
10
|
+
module Kaal
|
|
11
|
+
class SchedulerFileLoader
|
|
12
|
+
# Loads and validates scheduler YAML payloads from disk.
|
|
13
|
+
class PayloadLoader
|
|
14
|
+
def initialize(configuration:, runtime_context:, logger:, hash_transform:)
|
|
15
|
+
@configuration = configuration
|
|
16
|
+
@runtime_context = runtime_context
|
|
17
|
+
@logger = logger
|
|
18
|
+
@hash_transform = hash_transform
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def load
|
|
22
|
+
path = scheduler_file_path
|
|
23
|
+
return [path, nil] unless File.exist?(path)
|
|
24
|
+
|
|
25
|
+
[path, parse_yaml(path)]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def handle_missing_file(path)
|
|
29
|
+
message = "Scheduler file not found at #{path}"
|
|
30
|
+
raise SchedulerConfigError, message if @configuration.scheduler_missing_file_policy == :error
|
|
31
|
+
|
|
32
|
+
@logger&.warn(message)
|
|
33
|
+
[]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def extract_jobs(payload)
|
|
37
|
+
environment_name = @runtime_context.environment_name
|
|
38
|
+
defaults = fetch_hash(payload, 'defaults')
|
|
39
|
+
env_payload = fetch_hash(payload, environment_name)
|
|
40
|
+
default_jobs = defaults.fetch('jobs', [])
|
|
41
|
+
env_jobs = env_payload.fetch('jobs', [])
|
|
42
|
+
raise SchedulerConfigError, "Expected 'defaults.jobs' to be an array" unless default_jobs.is_a?(Array)
|
|
43
|
+
raise SchedulerConfigError, "Expected '#{environment_name}.jobs' to be an array" unless env_jobs.is_a?(Array)
|
|
44
|
+
|
|
45
|
+
default_jobs + env_jobs
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def validate_unique_keys(jobs)
|
|
49
|
+
keys = jobs.map do |job_payload|
|
|
50
|
+
raise SchedulerConfigError, "Each jobs entry must be a mapping, got #{job_payload.class}" unless job_payload.is_a?(Hash)
|
|
51
|
+
|
|
52
|
+
@hash_transform.stringify_keys(job_payload)['key'].to_s.strip
|
|
53
|
+
end
|
|
54
|
+
duplicates = keys.group_by(&:itself).select { |key, arr| !key.empty? && arr.size > 1 }.keys
|
|
55
|
+
return if duplicates.empty?
|
|
56
|
+
|
|
57
|
+
raise SchedulerConfigError, "Duplicate job keys in scheduler file: #{duplicates.join(', ')}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def scheduler_file_path
|
|
63
|
+
configured_path = @configuration.scheduler_config_path.to_s.strip
|
|
64
|
+
raise SchedulerConfigError, 'scheduler_config_path cannot be blank' if configured_path.empty?
|
|
65
|
+
|
|
66
|
+
@runtime_context.resolve_path(configured_path)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def parse_yaml(path)
|
|
70
|
+
rendered = render_yaml_erb(path)
|
|
71
|
+
parsed = YAML.safe_load(rendered) || {}
|
|
72
|
+
raise SchedulerConfigError, "Expected scheduler YAML root to be a mapping in #{path}" unless parsed.is_a?(Hash)
|
|
73
|
+
|
|
74
|
+
@hash_transform.stringify_keys(parsed)
|
|
75
|
+
rescue Psych::Exception => e
|
|
76
|
+
raise SchedulerConfigError, "Failed to parse scheduler YAML at #{path}: #{e.message}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def render_yaml_erb(path)
|
|
80
|
+
ERB.new(File.read(path), trim_mode: '-').result
|
|
81
|
+
rescue StandardError, SyntaxError => e
|
|
82
|
+
raise SchedulerConfigError, "Failed to evaluate scheduler ERB at #{path}: #{e.message}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def fetch_hash(payload, key)
|
|
86
|
+
section = payload.fetch(key)
|
|
87
|
+
raise SchedulerConfigError, "Expected '#{key}' section to be a mapping" unless section.is_a?(Hash)
|
|
88
|
+
|
|
89
|
+
section
|
|
90
|
+
rescue KeyError
|
|
91
|
+
{}
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -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/scheduler_file/loader'
|
|
8
|
+
require 'kaal/scheduler_file/hash_transform'
|
|
9
|
+
require 'kaal/scheduler_file/placeholder_support'
|
|
10
|
+
|
|
11
|
+
module Kaal
|
|
12
|
+
# Scheduler file loading and payload helpers.
|
|
13
|
+
module SchedulerFile
|
|
14
|
+
Loader = ::Kaal::SchedulerFileLoader
|
|
15
|
+
HashTransform = ::Kaal::SchedulerHashTransform
|
|
16
|
+
PlaceholderSupport = ::Kaal::SchedulerPlaceholderSupport
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
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 'fileutils'
|
|
8
|
+
|
|
9
|
+
module Kaal
|
|
10
|
+
# Sequel migration/install support for SQL-backed Kaal backends.
|
|
11
|
+
module Sequel
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def install_postgres_migration(target_dir:, migration_name: 'create_kaal_postgres_backend')
|
|
15
|
+
install_migrations(target_dir:, backend: 'postgres', migration_name:)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def install_mysql_migration(target_dir:, migration_name: 'create_kaal_mysql_backend')
|
|
19
|
+
install_migrations(target_dir:, backend: 'mysql', migration_name:)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def install_sqlite_migration(target_dir:, migration_name: 'create_kaal_sqlite_backend')
|
|
23
|
+
install_migrations(target_dir:, backend: 'sqlite', migration_name:)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def install_migrations(target_dir:, backend:, migration_name: nil)
|
|
27
|
+
require_sequel!
|
|
28
|
+
|
|
29
|
+
normalized_name = normalize_migration_name(migration_name, fallback: default_migration_name_for(backend))
|
|
30
|
+
base_path = File.expand_path(target_dir)
|
|
31
|
+
FileUtils.mkdir_p(base_path)
|
|
32
|
+
|
|
33
|
+
Kaal::Persistence::MigrationTemplates.for_backend(backend).map.with_index do |(_name, contents), index|
|
|
34
|
+
suffix = migration_suffixes_for(backend).fetch(index)
|
|
35
|
+
path = File.expand_path("#{timestamp(index)}_#{normalized_name}_#{suffix}.rb", base_path)
|
|
36
|
+
File.write(path, contents)
|
|
37
|
+
path
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def require_sequel!
|
|
42
|
+
require 'sequel'
|
|
43
|
+
rescue LoadError => e
|
|
44
|
+
raise LoadError,
|
|
45
|
+
"#{e.message}. Add `gem 'sequel'` to your Gemfile to use Sequel-backed Kaal SQL support.",
|
|
46
|
+
cause: e
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def normalize_migration_name(name, fallback:)
|
|
50
|
+
normalized = name.to_s.each_char.with_object(+'') do |char, buffer|
|
|
51
|
+
if letter?(char) || digit?(char)
|
|
52
|
+
buffer << char.downcase
|
|
53
|
+
elsif !buffer.empty? && !buffer.end_with?('_')
|
|
54
|
+
buffer << '_'
|
|
55
|
+
end
|
|
56
|
+
end.delete_suffix('_')
|
|
57
|
+
normalized.empty? ? fallback : normalized
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def default_migration_name_for(backend)
|
|
61
|
+
"create_kaal_#{backend}_backend"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def migration_suffixes_for(backend)
|
|
65
|
+
return %w[dispatches locks definitions] if backend.to_s == 'sqlite'
|
|
66
|
+
|
|
67
|
+
%w[dispatches definitions]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def timestamp(offset = 0)
|
|
71
|
+
(Time.now.utc + offset).strftime('%Y%m%d%H%M%S')
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def letter?(char)
|
|
75
|
+
char.between?('a', 'z') || char.between?('A', 'Z')
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def digit?(char)
|
|
79
|
+
char.between?('0', '9')
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
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 Support
|
|
9
|
+
# Small deep-copy and key-normalization helpers used across config and scheduler loading.
|
|
10
|
+
module HashTools
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def deep_dup(value)
|
|
14
|
+
case value
|
|
15
|
+
when Hash
|
|
16
|
+
value.each_with_object({}) do |(key, child), memo|
|
|
17
|
+
duplicated_pair = [deep_dup(key), deep_dup(child)]
|
|
18
|
+
memo[duplicated_pair[0]] = duplicated_pair[1]
|
|
19
|
+
end
|
|
20
|
+
when Array
|
|
21
|
+
value.map { |child| duplicate_child(child) }
|
|
22
|
+
else
|
|
23
|
+
duplicable?(value) ? value.dup : value
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def stringify_keys(value)
|
|
28
|
+
transform_keys(value, &:to_s)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def symbolize_keys(value)
|
|
32
|
+
transform_keys(value) { |key| key.to_s.to_sym }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def deep_merge(left, right)
|
|
36
|
+
left.merge(right) do |_key, left_value, right_value|
|
|
37
|
+
if left_value.is_a?(Hash) && right_value.is_a?(Hash)
|
|
38
|
+
deep_merge(left_value, right_value)
|
|
39
|
+
else
|
|
40
|
+
deep_dup(right_value)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def constantize(name)
|
|
46
|
+
name.to_s.split('::').reject(&:empty?).reduce(Object) { |scope, part| scope.const_get(part) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def duplicable?(value)
|
|
50
|
+
!value.is_a?(NilClass) &&
|
|
51
|
+
!value.is_a?(FalseClass) &&
|
|
52
|
+
!value.is_a?(TrueClass) &&
|
|
53
|
+
!value.is_a?(Symbol) &&
|
|
54
|
+
!value.is_a?(Numeric) &&
|
|
55
|
+
!value.is_a?(Method) &&
|
|
56
|
+
!value.is_a?(Proc)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def transform_keys(value, &)
|
|
60
|
+
case value
|
|
61
|
+
when Hash
|
|
62
|
+
transform_hash_keys(value, &)
|
|
63
|
+
when Array
|
|
64
|
+
transform_array_keys(value, &)
|
|
65
|
+
else
|
|
66
|
+
value
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def duplicate_child(child)
|
|
71
|
+
deep_dup(child)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def transform_child_keys(child, &)
|
|
75
|
+
transform_keys(child, &)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def transform_hash_keys(value, &)
|
|
79
|
+
value.each_with_object({}) do |(key, child), memo|
|
|
80
|
+
transformed_pair = [yield(key), transform_child_keys(child, &)]
|
|
81
|
+
memo[transformed_pair[0]] = transformed_pair[1]
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def transform_array_keys(value, &)
|
|
86
|
+
value.map { |child| transform_child_keys(child, &) }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private_class_method :duplicate_child, :transform_child_keys, :transform_hash_keys, :transform_array_keys
|
|
90
|
+
private_class_method :transform_keys
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -4,14 +4,15 @@
|
|
|
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 'fugit'
|
|
9
8
|
require 'i18n'
|
|
9
|
+
require 'yaml'
|
|
10
10
|
|
|
11
11
|
module Kaal
|
|
12
12
|
##
|
|
13
13
|
# Human-friendly cron phrase generation with i18n support.
|
|
14
14
|
module CronHumanizer
|
|
15
|
+
I18N_LOAD_MUTEX = Mutex.new
|
|
15
16
|
MACRO_PHRASES = {
|
|
16
17
|
'@yearly' => 'phrases.yearly',
|
|
17
18
|
'@monthly' => 'phrases.monthly',
|
|
@@ -23,6 +24,8 @@ module Kaal
|
|
|
23
24
|
module_function
|
|
24
25
|
|
|
25
26
|
def to_human(expression, locale: nil)
|
|
27
|
+
ensure_i18n_loaded!
|
|
28
|
+
|
|
26
29
|
normalized = CronUtils.safe_normalize_expression(expression)
|
|
27
30
|
raise ArgumentError, CronUtils.invalid_expression_error_message('') unless normalized
|
|
28
31
|
raise ArgumentError, CronUtils.invalid_expression_error_message(normalized) if normalized.empty?
|
|
@@ -178,5 +181,20 @@ module Kaal
|
|
|
178
181
|
I18n.t("kaal.#{key}", **)
|
|
179
182
|
end
|
|
180
183
|
private_class_method :translate_phrase
|
|
184
|
+
|
|
185
|
+
def ensure_i18n_loaded!
|
|
186
|
+
locale_file = File.expand_path('../../../config/locales/en.yml', __dir__)
|
|
187
|
+
return if I18n.load_path.include?(locale_file)
|
|
188
|
+
|
|
189
|
+
I18N_LOAD_MUTEX.synchronize do
|
|
190
|
+
return if I18n.load_path.include?(locale_file)
|
|
191
|
+
|
|
192
|
+
I18n.load_path << locale_file
|
|
193
|
+
locales = YAML.safe_load_file(locale_file, aliases: true) || {}
|
|
194
|
+
I18n.available_locales = Array(I18n.available_locales) | locales.keys.map(&:to_sym)
|
|
195
|
+
I18n.backend.load_translations
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
private_class_method :ensure_i18n_loaded!
|
|
181
199
|
end
|
|
182
200
|
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
|
module Kaal
|
|
9
8
|
##
|
|
10
9
|
# Utility class for generating idempotency keys.
|
|
@@ -13,7 +12,7 @@ module Kaal
|
|
|
13
12
|
# Format: {namespace}-{cron_key}-{fire_time_unix}
|
|
14
13
|
#
|
|
15
14
|
# @example Generate a key
|
|
16
|
-
# key = Kaal::IdempotencyKeyGenerator.call('reports:daily', Time.
|
|
15
|
+
# key = Kaal::IdempotencyKeyGenerator.call('reports:daily', Time.now.utc, configuration: config)
|
|
17
16
|
# # => "kaal-reports:daily-1708283400"
|
|
18
17
|
class IdempotencyKeyGenerator
|
|
19
18
|
##
|
|
@@ -24,7 +23,8 @@ module Kaal
|
|
|
24
23
|
# @param configuration [Configuration] the Kaal configuration instance
|
|
25
24
|
# @return [String] the formatted idempotency key
|
|
26
25
|
def self.call(cron_key, fire_time, configuration:)
|
|
27
|
-
namespace = configuration.namespace
|
|
26
|
+
namespace = configuration.namespace.to_s.strip
|
|
27
|
+
namespace = 'kaal' if namespace.empty?
|
|
28
28
|
"#{namespace}-#{cron_key}-#{fire_time.to_i}"
|
|
29
29
|
end
|
|
30
30
|
end
|
data/lib/kaal/utils.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/utils/cron_utils'
|
|
8
|
+
require 'kaal/utils/cron_humanizer'
|
|
9
|
+
require 'kaal/utils/idempotency_key_generator'
|
|
10
|
+
|
|
11
|
+
module Kaal
|
|
12
|
+
# Utility functions and pure helpers.
|
|
13
|
+
module Utils
|
|
14
|
+
CronUtils = ::Kaal::CronUtils
|
|
15
|
+
CronHumanizer = ::Kaal::CronHumanizer
|
|
16
|
+
IdempotencyKeyGenerator = ::Kaal::IdempotencyKeyGenerator
|
|
17
|
+
end
|
|
18
|
+
end
|