kaal 0.4.0 → 0.5.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +36 -1
  3. data/lib/kaal/active_record_support.rb +2 -2
  4. data/lib/kaal/backend/adapter.rb +4 -0
  5. data/lib/kaal/backend/memory_adapter.rb +5 -0
  6. data/lib/kaal/backend/mysql.rb +25 -3
  7. data/lib/kaal/backend/postgres.rb +6 -2
  8. data/lib/kaal/backend/redis_adapter.rb +5 -0
  9. data/lib/kaal/backend/sqlite.rb +4 -0
  10. data/lib/kaal/cli.rb +1 -0
  11. data/lib/kaal/config/configuration.rb +33 -2
  12. data/lib/kaal/config/delayed_job_security_policy.rb +60 -0
  13. data/lib/kaal/config.rb +1 -0
  14. data/lib/kaal/core/coordinator.rb +68 -19
  15. data/lib/kaal/delayed_job/database_engine.rb +116 -0
  16. data/lib/kaal/delayed_job/dispatch_failure_logger.rb +31 -0
  17. data/lib/kaal/delayed_job/memory_engine.rb +79 -0
  18. data/lib/kaal/delayed_job/mysql_version_support.rb +43 -0
  19. data/lib/kaal/delayed_job/redis_engine.rb +119 -0
  20. data/lib/kaal/delayed_job/registry.rb +39 -0
  21. data/lib/kaal/internal/active_record/database_backend.rb +5 -0
  22. data/lib/kaal/internal/active_record/delayed_job_record.rb +16 -0
  23. data/lib/kaal/internal/active_record/delayed_job_registry.rb +119 -0
  24. data/lib/kaal/internal/active_record/migration_templates.rb +33 -3
  25. data/lib/kaal/internal/active_record/mysql_backend.rb +23 -5
  26. data/lib/kaal/internal/active_record/postgres_backend.rb +4 -0
  27. data/lib/kaal/internal/active_record.rb +2 -0
  28. data/lib/kaal/internal/sequel/database_backend.rb +5 -0
  29. data/lib/kaal/internal/sequel/mysql_backend.rb +15 -1
  30. data/lib/kaal/internal/sequel/postgres_backend.rb +4 -0
  31. data/lib/kaal/internal/sequel.rb +1 -0
  32. data/lib/kaal/job_dispatcher.rb +108 -0
  33. data/lib/kaal/persistence/database.rb +4 -0
  34. data/lib/kaal/persistence/migration_templates.rb +35 -3
  35. data/lib/kaal/runtime/scheduler_boot_loader.rb +2 -0
  36. data/lib/kaal/scheduler_file/job_applier.rb +28 -53
  37. data/lib/kaal/sequel_support.rb +2 -2
  38. data/lib/kaal/version.rb +1 -1
  39. data/lib/kaal.rb +111 -0
  40. metadata +11 -1
@@ -0,0 +1,108 @@
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
+ # Shared job-class resolution and dispatch rules used by recurring and delayed jobs.
9
+ module JobDispatcher
10
+ module_function
11
+
12
+ def resolve_job_class(job_class_name:, key:, queue: nil, apply_delayed_job_allow_list: true)
13
+ job_class = normalize_job_class(job_class_name, key, apply_delayed_job_allow_list:)
14
+ validate_dispatch_interface(job_class, key, queue)
15
+ end
16
+
17
+ def normalized_job_class_name(job_class_name:, key:, apply_delayed_job_allow_list: true)
18
+ normalized_job_class_name = normalize_job_class_name(job_class_name)
19
+ raise SchedulerConfigError, "Job class cannot be blank for key '#{key}'" if normalized_job_class_name.empty?
20
+
21
+ return normalized_job_class_name unless apply_delayed_job_allow_list
22
+
23
+ validate_allowed_job_class_name!(job_class_name: normalized_job_class_name, key:)
24
+ normalized_job_class_name
25
+ end
26
+
27
+ def dispatch(job_class:, queue:, args:, key: nil)
28
+ job_class_name = job_class.name
29
+ scheduler_context = key ? " for scheduler job '#{key}'" : ''
30
+
31
+ if queue && !job_class.respond_to?(:set)
32
+ raise SchedulerConfigError,
33
+ "job_class '#{job_class_name}' must respond to .set to use queue #{queue.inspect}#{scheduler_context}"
34
+ end
35
+
36
+ if queue
37
+ job_class.set(queue: queue).perform_later(*args)
38
+ elsif job_class.respond_to?(:perform_later)
39
+ job_class.perform_later(*args)
40
+ elsif job_class.respond_to?(:perform)
41
+ job_class.perform(*args)
42
+ else
43
+ raise SchedulerConfigError,
44
+ "job_class '#{job_class_name}' must respond to .perform, .perform_later, or .set(...).perform_later#{scheduler_context}"
45
+ end
46
+ end
47
+
48
+ def active_job_dispatch?(job_class, queue)
49
+ (queue && job_class.respond_to?(:set)) || job_class.respond_to?(:perform_later)
50
+ end
51
+
52
+ def normalize_job_class_name(job_class)
53
+ case job_class
54
+ when Module
55
+ job_class.name.to_s.strip
56
+ else
57
+ job_class.to_s.strip
58
+ end
59
+ end
60
+
61
+ def normalize_job_class(job_class_name, key, apply_delayed_job_allow_list: true)
62
+ normalized_job_class_name = normalized_job_class_name(
63
+ job_class_name:,
64
+ key:,
65
+ apply_delayed_job_allow_list:
66
+ )
67
+
68
+ return job_class_name if job_class_name.is_a?(Module)
69
+
70
+ job_class = begin
71
+ Kaal::Support::HashTools.constantize(normalized_job_class_name)
72
+ rescue NameError
73
+ nil
74
+ end
75
+
76
+ return job_class if job_class
77
+
78
+ raise SchedulerConfigError, "Unknown job_class #{normalized_job_class_name.inspect} for key '#{key}'"
79
+ end
80
+ private_class_method :normalize_job_class
81
+
82
+ def validate_allowed_job_class_name!(job_class_name:, key:)
83
+ allowed_prefixes = Array(Kaal.configuration.delayed_job_allowed_class_prefixes)
84
+ return if allowed_prefixes.empty?
85
+ return if allowed_prefixes.any? { |prefix| job_class_name.start_with?(prefix) }
86
+
87
+ raise SchedulerConfigError,
88
+ "job_class '#{job_class_name}' for key '#{key}' is not allowed by delayed_job_allowed_class_prefixes"
89
+ end
90
+ private_class_method :validate_allowed_job_class_name!
91
+
92
+ def validate_dispatch_interface(job_class, key, queue)
93
+ queue_present = !queue.nil?
94
+ no_queue = !queue_present
95
+ supports_set = job_class.respond_to?(:set)
96
+ supports_perform_later = job_class.respond_to?(:perform_later)
97
+ supports_perform = job_class.respond_to?(:perform)
98
+
99
+ return job_class if queue_present && supports_set
100
+ return job_class if no_queue && supports_perform_later
101
+ return job_class if no_queue && supports_perform
102
+
103
+ raise SchedulerConfigError,
104
+ "job_class '#{job_class.name}' for key '#{key}' must respond to .perform, .perform_later, or .set(...).perform_later"
105
+ end
106
+ private_class_method :validate_dispatch_interface
107
+ end
108
+ end
@@ -30,6 +30,10 @@ module Kaal
30
30
  def locks_dataset
31
31
  connection[:kaal_locks]
32
32
  end
33
+
34
+ def delayed_jobs_dataset
35
+ connection[:kaal_delayed_jobs]
36
+ end
33
37
  end
34
38
  end
35
39
  end
@@ -11,17 +11,21 @@ module Kaal
11
11
  module_function
12
12
 
13
13
  def for_backend(backend)
14
- case backend.to_s
14
+ backend_name = backend.to_s
15
+
16
+ case backend_name
15
17
  when 'sqlite'
16
18
  {
17
19
  '001_create_kaal_dispatches.rb' => dispatches_template,
18
20
  '002_create_kaal_locks.rb' => locks_template,
19
- '003_create_kaal_definitions.rb' => definitions_template
21
+ '003_create_kaal_definitions.rb' => definitions_template,
22
+ '004_create_kaal_delayed_jobs.rb' => delayed_jobs_template('sqlite')
20
23
  }
21
24
  when 'postgres', 'mysql'
22
25
  {
23
26
  '001_create_kaal_dispatches.rb' => dispatches_template,
24
- '002_create_kaal_definitions.rb' => definitions_template
27
+ '002_create_kaal_definitions.rb' => definitions_template,
28
+ '003_create_kaal_delayed_jobs.rb' => delayed_jobs_template(backend_name)
25
29
  }
26
30
  else
27
31
  {}
@@ -92,6 +96,34 @@ module Kaal
92
96
  end
93
97
  RUBY
94
98
  end
99
+
100
+ def delayed_jobs_template(backend)
101
+ args_definition =
102
+ if backend == 'mysql'
103
+ 'String :args, text: true, null: false'
104
+ else
105
+ "String :args, text: true, null: false, default: '[]'"
106
+ end
107
+
108
+ <<~RUBY
109
+ Sequel.migration do
110
+ change do
111
+ create_table?(:kaal_delayed_jobs) do
112
+ primary_key :id
113
+ String :job_id, null: false
114
+ Time :run_at, null: false
115
+ String :job_class, null: false
116
+ #{args_definition}
117
+ String :queue
118
+ Time :created_at, null: false
119
+ end
120
+
121
+ add_index :kaal_delayed_jobs, :job_id, unique: true
122
+ add_index :kaal_delayed_jobs, :run_at
123
+ end
124
+ end
125
+ RUBY
126
+ end
95
127
  end
96
128
  end
97
129
  end
@@ -22,6 +22,8 @@ module Kaal
22
22
  configuration = fetch_configuration
23
23
  return unless configuration
24
24
 
25
+ Kaal.warn_on_risky_configuration!(configuration:, logger: @logger)
26
+
25
27
  return load_scheduler_file if configuration.scheduler_missing_file_policy == :error
26
28
 
27
29
  scheduler_path = configuration.scheduler_config_path.to_s.strip
@@ -77,7 +77,12 @@ module Kaal
77
77
  end
78
78
 
79
79
  def resolved_job_class(job_class_name:, key:, queue: nil)
80
- resolve_job_class(job_class_name:, key:, queue:)
80
+ Kaal::JobDispatcher.resolve_job_class(
81
+ job_class_name:,
82
+ key:,
83
+ queue:,
84
+ apply_delayed_job_allow_list: false
85
+ )
81
86
  end
82
87
 
83
88
  def conflict?(key:, existing_definition:)
@@ -157,7 +162,7 @@ module Kaal
157
162
  validate_keyword_keys(raw_kwargs, key)
158
163
 
159
164
  resolved_kwargs = raw_kwargs.transform_keys(&:to_sym)
160
- dispatch_job(job_class, queue, resolved_args, resolved_kwargs)
165
+ dispatch_job(job_class, queue, resolved_args, resolved_kwargs, key)
161
166
  end
162
167
  end
163
168
 
@@ -178,64 +183,34 @@ module Kaal
178
183
  nil
179
184
  end
180
185
 
181
- def resolve_job_class(job_class_name:, key:, queue: nil)
182
- normalized_job_class_name = job_class_name.to_s.strip
183
- raise SchedulerConfigError, "Job class cannot be blank for key '#{key}'" if normalized_job_class_name.empty?
184
-
185
- error_message = "Unknown job_class #{normalized_job_class_name.inspect} for key '#{key}'"
186
- job_class = begin
187
- Kaal::Support::HashTools.constantize(normalized_job_class_name)
188
- rescue NameError
189
- nil
190
- end
191
-
192
- return validate_dispatch_interface(job_class, key, queue) if job_class
193
-
194
- raise_unknown_job_class(error_message)
195
- end
196
-
197
- private :build_callback, :resolve_job_class
198
-
199
- def dispatch_job(job_class, queue, args, kwargs)
200
- job_class_name = job_class.name
201
-
202
- if queue && !job_class.respond_to?(:set)
203
- raise SchedulerConfigError,
204
- "job_class '#{job_class_name}' must respond to .set to use queue #{queue.inspect}"
205
- end
186
+ private :build_callback
206
187
 
207
- if queue
208
- job_class.set(queue: queue).perform_later(*args, **kwargs)
209
- elsif job_class.respond_to?(:perform_later)
210
- job_class.perform_later(*args, **kwargs)
211
- elsif job_class.respond_to?(:perform)
212
- job_class.perform(*args, **kwargs)
188
+ def dispatch_job(job_class, queue, args, kwargs, key)
189
+ if kwargs.empty?
190
+ Kaal::JobDispatcher.dispatch(job_class:, queue:, args:, key:)
213
191
  else
214
- raise SchedulerConfigError,
215
- "job_class '#{job_class_name}' must respond to .perform, .perform_later, or .set(...).perform_later"
216
- end
217
- end
218
-
219
- def raise_unknown_job_class(error_message)
220
- raise SchedulerConfigError, error_message
221
- end
222
-
223
- def validate_dispatch_interface(job_class, key, queue)
224
- queue_present = !queue.nil?
225
- supports_set = job_class.respond_to?(:set)
226
- supports_perform_later = job_class.respond_to?(:perform_later)
227
- supports_perform = job_class.respond_to?(:perform)
192
+ job_class_name = job_class.name
228
193
 
229
- return job_class if queue_present && supports_set
230
- return job_class if !queue_present && supports_perform_later
231
- return job_class if !queue_present && supports_perform
194
+ if queue && !job_class.respond_to?(:set)
195
+ raise SchedulerConfigError,
196
+ "job_class '#{job_class_name}' must respond to .set to use queue #{queue.inspect} for scheduler job '#{key}'"
197
+ end
232
198
 
233
- raise SchedulerConfigError,
234
- "job_class '#{job_class.name}' for key '#{key}' must respond to .perform, .perform_later, or .set(...).perform_later"
199
+ if queue
200
+ job_class.set(queue: queue).perform_later(*args, **kwargs)
201
+ elsif job_class.respond_to?(:perform_later)
202
+ job_class.perform_later(*args, **kwargs)
203
+ elsif job_class.respond_to?(:perform)
204
+ job_class.perform(*args, **kwargs)
205
+ else
206
+ raise SchedulerConfigError,
207
+ "job_class '#{job_class_name}' must respond to .perform, .perform_later, or .set(...).perform_later for scheduler job '#{key}'"
208
+ end
209
+ end
235
210
  end
236
211
 
237
212
  def active_job_dispatch?(job_class, queue)
238
- (queue && job_class.respond_to?(:set)) || job_class.respond_to?(:perform_later)
213
+ Kaal::JobDispatcher.active_job_dispatch?(job_class, queue)
239
214
  end
240
215
  end
241
216
  end
@@ -62,9 +62,9 @@ module Kaal
62
62
  end
63
63
 
64
64
  def migration_suffixes_for(backend)
65
- return %w[dispatches locks definitions] if backend.to_s == 'sqlite'
65
+ return %w[dispatches locks definitions delayed_jobs] if backend.to_s == 'sqlite'
66
66
 
67
- %w[dispatches definitions]
67
+ %w[dispatches definitions delayed_jobs]
68
68
  end
69
69
 
70
70
  def timestamp(offset = 0)
data/lib/kaal/version.rb CHANGED
@@ -5,5 +5,5 @@
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
7
  module Kaal
8
- VERSION = '0.4.0'
8
+ VERSION = '0.5.0'
9
9
  end
data/lib/kaal.rb CHANGED
@@ -10,6 +10,11 @@ require 'kaal/registry'
10
10
  require 'kaal/dispatch/registry'
11
11
  require 'kaal/dispatch/memory_engine'
12
12
  require 'kaal/dispatch/redis_engine'
13
+ require 'kaal/delayed_job/registry'
14
+ require 'kaal/delayed_job/memory_engine'
15
+ require 'kaal/delayed_job/redis_engine'
16
+ require 'kaal/delayed_job/dispatch_failure_logger'
17
+ require 'kaal/delayed_job/mysql_version_support'
13
18
  require 'kaal/definition/registry'
14
19
  require 'kaal/definition/memory_engine'
15
20
  require 'kaal/definition/redis_engine'
@@ -25,6 +30,7 @@ require 'kaal/persistence/migration_templates'
25
30
  require 'kaal/sequel_support'
26
31
  require 'kaal/active_record_support'
27
32
  require 'kaal/utils'
33
+ require 'kaal/job_dispatcher'
28
34
  require 'kaal/register_conflict_support'
29
35
  require 'kaal/definitions/registry_accessor'
30
36
  require 'kaal/definitions/registration_service'
@@ -56,12 +62,15 @@ module Kaal
56
62
  @definition_registry = nil
57
63
  @definitions_registry_accessor = nil
58
64
  @dispatch_registry_accessor = nil
65
+ @risky_configuration_warnings_emitted = {}
59
66
  end
60
67
 
61
68
  def reset_registry!
62
69
  @registry = Registry.new
63
70
  definition_registry = @definition_registry
64
71
  definition_registry.clear if definition_registry.respond_to?(:clear)
72
+ delayed_store = configuration.backend&.delayed_store
73
+ delayed_store.clear if delayed_store.respond_to?(:clear)
65
74
  @coordinator = nil
66
75
  end
67
76
 
@@ -84,6 +93,7 @@ module Kaal
84
93
  end
85
94
 
86
95
  def load_scheduler_file!(runtime_context: RuntimeContext.default)
96
+ warn_on_risky_configuration!
87
97
  SchedulerFileLoader.new(
88
98
  configuration: configuration,
89
99
  definition_registry: definition_registry,
@@ -119,6 +129,7 @@ module Kaal
119
129
  end
120
130
 
121
131
  def start!
132
+ warn_on_risky_configuration!
122
133
  coordinator.start!
123
134
  end
124
135
 
@@ -138,6 +149,30 @@ module Kaal
138
149
  coordinator.tick!
139
150
  end
140
151
 
152
+ # Enqueue a one-off delayed job. Delivery is at-most-once after claim.
153
+ def enqueue_at(at:, job_class:, args:, job_id:, queue: nil, connection: nil)
154
+ delayed_store = delayed_store!
155
+ resolved_run_at = normalize_delayed_run_at(at)
156
+ resolved_args = normalize_delayed_args(args)
157
+ resolved_queue = normalize_delayed_queue(queue)
158
+ resolved_job_id = normalize_delayed_job_id(job_id)
159
+ resolved_job_class = JobDispatcher.resolve_job_class(
160
+ job_class_name: job_class,
161
+ key: resolved_job_id,
162
+ queue: resolved_queue
163
+ )
164
+ resolved_job_class_name = JobDispatcher.normalize_job_class_name(resolved_job_class)
165
+
166
+ delayed_store.enqueue(
167
+ job_id: resolved_job_id,
168
+ run_at: resolved_run_at,
169
+ job_class: resolved_job_class_name,
170
+ args: resolved_args,
171
+ queue: resolved_queue,
172
+ connection: connection
173
+ )
174
+ end
175
+
141
176
  def with_idempotency(key, fire_time)
142
177
  raise ArgumentError, 'block required' unless block_given?
143
178
 
@@ -193,6 +228,14 @@ module Kaal
193
228
  configuration.time_zone = value
194
229
  end
195
230
 
231
+ def delayed_job_allowed_class_prefixes
232
+ configuration.delayed_job_allowed_class_prefixes
233
+ end
234
+
235
+ def delayed_job_allowed_class_prefixes=(value)
236
+ configuration.delayed_job_allowed_class_prefixes = value
237
+ end
238
+
196
239
  def definition_registry
197
240
  definitions_registry_accessor.call
198
241
  end
@@ -205,6 +248,25 @@ module Kaal
205
248
  configuration.validate!
206
249
  end
207
250
 
251
+ def validation_warnings
252
+ configuration.validation_warnings
253
+ end
254
+
255
+ def warn_on_risky_configuration!(configuration: self.configuration, logger: configuration.logger)
256
+ warnings = configuration.validation_warnings
257
+ return [] if warnings.empty?
258
+
259
+ @risky_configuration_warnings_emitted ||= {}
260
+ warnings.each do |warning|
261
+ next if @risky_configuration_warnings_emitted[warning]
262
+
263
+ logger&.warn(warning)
264
+ @risky_configuration_warnings_emitted[warning] = true
265
+ end
266
+
267
+ warnings
268
+ end
269
+
208
270
  def valid?(expression)
209
271
  CronUtils.valid?(expression)
210
272
  end
@@ -253,5 +315,54 @@ module Kaal
253
315
  def dispatch_registry_accessor
254
316
  @dispatch_registry_accessor ||= Backend::DispatchRegistryAccessor.new(configuration: configuration)
255
317
  end
318
+
319
+ def delayed_store!
320
+ delayed_store = configuration.backend&.delayed_store
321
+ return delayed_store if delayed_store
322
+
323
+ raise ArgumentError, 'Configured backend does not support delayed jobs'
324
+ end
325
+
326
+ def normalize_delayed_run_at(at)
327
+ time = at.is_a?(Time) ? at : at&.to_time
328
+ ensure_delayed_time!(time)
329
+
330
+ time.utc
331
+ rescue NoMethodError
332
+ ensure_delayed_time!(nil)
333
+ end
334
+
335
+ def normalize_delayed_args(args)
336
+ raise ArgumentError, 'args must be an array' unless args.is_a?(Array)
337
+
338
+ args.dup
339
+ end
340
+
341
+ def normalize_delayed_queue(queue)
342
+ case queue
343
+ when nil
344
+ return nil
345
+ end
346
+
347
+ normalized_queue = queue.to_s.strip
348
+ raise ArgumentError, 'queue cannot be blank' if normalized_queue.empty?
349
+
350
+ normalized_queue
351
+ end
352
+
353
+ def normalize_delayed_job_id(job_id)
354
+ normalized_job_id = job_id.to_s.strip
355
+ raise ArgumentError, 'job_id cannot be blank' if normalized_job_id.empty?
356
+
357
+ normalized_job_id
358
+ end
359
+
360
+ def invalid_delayed_time_error
361
+ ArgumentError.new('at must be a Time or time-like value')
362
+ end
363
+
364
+ def ensure_delayed_time!(time)
365
+ raise invalid_delayed_time_error unless time
366
+ end
256
367
  end
257
368
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kaal
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nitesh Purohit
@@ -97,6 +97,7 @@ files:
97
97
  - lib/kaal/cli.rb
98
98
  - lib/kaal/config.rb
99
99
  - lib/kaal/config/configuration.rb
100
+ - lib/kaal/config/delayed_job_security_policy.rb
100
101
  - lib/kaal/config/scheduler_config_error.rb
101
102
  - lib/kaal/config/scheduler_time_zone_resolver.rb
102
103
  - lib/kaal/core.rb
@@ -110,6 +111,12 @@ files:
110
111
  - lib/kaal/definition/registry.rb
111
112
  - lib/kaal/definitions/registration_service.rb
112
113
  - lib/kaal/definitions/registry_accessor.rb
114
+ - lib/kaal/delayed_job/database_engine.rb
115
+ - lib/kaal/delayed_job/dispatch_failure_logger.rb
116
+ - lib/kaal/delayed_job/memory_engine.rb
117
+ - lib/kaal/delayed_job/mysql_version_support.rb
118
+ - lib/kaal/delayed_job/redis_engine.rb
119
+ - lib/kaal/delayed_job/registry.rb
113
120
  - lib/kaal/dispatch/database_engine.rb
114
121
  - lib/kaal/dispatch/memory_engine.rb
115
122
  - lib/kaal/dispatch/redis_engine.rb
@@ -120,6 +127,8 @@ files:
120
127
  - lib/kaal/internal/active_record/database_backend.rb
121
128
  - lib/kaal/internal/active_record/definition_record.rb
122
129
  - lib/kaal/internal/active_record/definition_registry.rb
130
+ - lib/kaal/internal/active_record/delayed_job_record.rb
131
+ - lib/kaal/internal/active_record/delayed_job_registry.rb
123
132
  - lib/kaal/internal/active_record/dispatch_record.rb
124
133
  - lib/kaal/internal/active_record/dispatch_registry.rb
125
134
  - lib/kaal/internal/active_record/lock_record.rb
@@ -130,6 +139,7 @@ files:
130
139
  - lib/kaal/internal/sequel/database_backend.rb
131
140
  - lib/kaal/internal/sequel/mysql_backend.rb
132
141
  - lib/kaal/internal/sequel/postgres_backend.rb
142
+ - lib/kaal/job_dispatcher.rb
133
143
  - lib/kaal/persistence/database.rb
134
144
  - lib/kaal/persistence/migration_templates.rb
135
145
  - lib/kaal/register_conflict_support.rb