coil 1.4.0 → 1.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.
- checksums.yaml +4 -4
- data/README.md +12 -3
- data/app/jobs/coil/inbox/messages_cleanup_job.rb +18 -0
- data/app/jobs/coil/outbox/messages_cleanup_job.rb +18 -0
- data/app/jobs/coil/transactional_messages_cleanup_job.rb +106 -0
- data/db/migrate/20250101182458_add_foreign_key_to_inbox_completions_on_delete_cascade.rb +32 -0
- data/db/migrate/20250102031753_validate_foreign_key_inbox_completions.rb +19 -0
- data/db/migrate/20250102040148_remove_old_foreign_key_from_inbox_completions.rb +24 -0
- data/db/migrate/20250102040649_add_foreign_key_to_outbox_completions_on_delete_cascade.rb +32 -0
- data/db/migrate/20250102040950_validate_foreign_key_outbox_completions.rb +19 -0
- data/db/migrate/20250102041225_remove_old_foreign_key_from_outbox_completions.rb +24 -0
- data/lib/coil/version.rb +1 -1
- data/lib/coil.rb +2 -0
- data/rbi/coil.rbi +59 -0
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb9891f8607d9006f5b72a07e88725a18e57dd6ea8a22c7c610eb9e490557983
|
4
|
+
data.tar.gz: eac76e2c7a1a1c3f3697613a637fa939b11ecb588e65dd828cb4ddd2f7b1137f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0c23e1a776d61ce557f1ace11aac2dc29fc71ee87a4dfd8037bb50266bdbb1254d87d5e1a3870f1622f9aa7837719e0608a89f0162da0f6c55e64a7adaa21341
|
7
|
+
data.tar.gz: 961ef82b7218976a207cf06af86ca2e7339fc31d255a6d1aaf8d80bf762436afe88e43af3dc0f27e69950fe0d5cb4abdfd5350bc17596aa05000d85b5878cc83
|
data/README.md
CHANGED
@@ -36,10 +36,11 @@ gem "schema_version_cache"
|
|
36
36
|
|
37
37
|
Install engine and migrations:
|
38
38
|
```console
|
39
|
-
$ bundle
|
40
|
-
$ bundle exec rails coil:install:migrations
|
41
|
-
$ bundle exec rails db:migrate
|
39
|
+
$ bundle install
|
40
|
+
$ bundle exec rails coil:install:migrations db:migrate
|
42
41
|
```
|
42
|
+
(_NOTE_: Also run the above commands when upgrading, as newer versions may
|
43
|
+
introduce additional migrations.)
|
43
44
|
|
44
45
|
Register periodic jobs:
|
45
46
|
```ruby
|
@@ -49,9 +50,15 @@ Sidekiq.configure_server do |config|
|
|
49
50
|
config.periodic do |mgr|
|
50
51
|
mgr.register("*/10 * * * *", "Coil::Inbox::MessagesPeriodicJob")
|
51
52
|
mgr.register("5-59/10 * * * *", "Coil::Outbox::MessagesPeriodicJob")
|
53
|
+
|
54
|
+
mgr.register("7-59/20 * * * *", "Coil::Inbox::MessagesCleanupJob")
|
55
|
+
mgr.register("12-59/20 * * * *", "Coil::Outbox::MessagesCleanupJob")
|
52
56
|
end
|
53
57
|
end
|
54
58
|
```
|
59
|
+
(_NOTE_: The cleanup jobs delete already-processed messages once their retention
|
60
|
+
period has passed. Retention periods can be [configured](#configuration) using
|
61
|
+
`Coil.inbox_retention_period` and `Coil.outbox_retention_period`.)
|
55
62
|
|
56
63
|
Filter retryable errors out of alerting, e.g. airbrake:
|
57
64
|
```ruby
|
@@ -242,6 +249,8 @@ initializer at `config/initializers/coil.rb` with the following content, then
|
|
242
249
|
uncomment and adjust the settings you wish to change:
|
243
250
|
```ruby
|
244
251
|
# Coil.sidekiq_queue = "default"
|
252
|
+
# Coil.inbox_retention_period = 12.weeks
|
253
|
+
# Coil.outbox_retention_period = 12.weeks
|
245
254
|
```
|
246
255
|
|
247
256
|
## Development
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
# This job deletes processed inbox messages whose retention period has passed.
|
4
|
+
module Coil
|
5
|
+
module Inbox
|
6
|
+
class MessagesCleanupJob < TransactionalMessagesCleanupJob
|
7
|
+
private
|
8
|
+
|
9
|
+
def message_parent_class
|
10
|
+
::Coil::Inbox::Message
|
11
|
+
end
|
12
|
+
|
13
|
+
def retention_period
|
14
|
+
::Coil.inbox_retention_period
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
# This job deletes processed outbox messages whose retention period has passed.
|
4
|
+
module Coil
|
5
|
+
module Outbox
|
6
|
+
class MessagesCleanupJob < TransactionalMessagesCleanupJob
|
7
|
+
private
|
8
|
+
|
9
|
+
def message_parent_class
|
10
|
+
::Coil::Outbox::Message
|
11
|
+
end
|
12
|
+
|
13
|
+
def retention_period
|
14
|
+
::Coil.outbox_retention_period
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# A cleanup job deletes processed messages whose retention period has passed.
|
5
|
+
module Coil
|
6
|
+
class TransactionalMessagesCleanupJob < ApplicationJob
|
7
|
+
DuplicateJobError = Class.new(StandardError)
|
8
|
+
|
9
|
+
# Sidekiq is not designed for long-running jobs, so we place an upper bound
|
10
|
+
# on job duration. When a job exceeds this bound, we'll enqueue a subsequent
|
11
|
+
# job to pick up where we left off.
|
12
|
+
MAX_DURATION = 5.minutes
|
13
|
+
|
14
|
+
sidekiq_options retry: 4, dead: false
|
15
|
+
|
16
|
+
def perform(batch_size = 1000)
|
17
|
+
result = delete_messages(batch_size)
|
18
|
+
total_deletions = result.deletions.values.sum
|
19
|
+
deletions_json = result.deletions.to_json
|
20
|
+
|
21
|
+
case result
|
22
|
+
when Finished
|
23
|
+
Rails.logger.info(<<~INFO.squish)
|
24
|
+
#{self.class} finished after deleting #{total_deletions} messages
|
25
|
+
(#{deletions_json}).
|
26
|
+
INFO
|
27
|
+
when ExceededDeadline
|
28
|
+
Rails.logger.info(<<~INFO.squish)
|
29
|
+
#{self.class} exceeded deadline after deleting #{total_deletions}
|
30
|
+
messages (#{deletions_json}). Enqueuing subsequent job.
|
31
|
+
INFO
|
32
|
+
self.class.perform_async(batch_size)
|
33
|
+
end
|
34
|
+
rescue DuplicateJobError
|
35
|
+
# A duplicate job is in the midst of its message-deletion loop. We'll call
|
36
|
+
# this job done and allow the other one to continue.
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def delete_messages(batch_size)
|
42
|
+
ApplicationRecord.uncached do
|
43
|
+
locking do
|
44
|
+
_delete_messages(batch_size)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
Finished = Struct.new(:deletions)
|
50
|
+
ExceededDeadline = Struct.new(:deletions)
|
51
|
+
|
52
|
+
def _delete_messages(batch_size)
|
53
|
+
now = Time.current
|
54
|
+
created_before = now - retention_period
|
55
|
+
deadline = now + MAX_DURATION
|
56
|
+
deletions = Hash.new(0)
|
57
|
+
|
58
|
+
# Identify distinct message types, their associated job types, and the
|
59
|
+
# messages that can safely be deleted. Delete in batches until finished
|
60
|
+
# or the deadline is exceeded.
|
61
|
+
message_parent_class.select(:type).distinct.pluck(:type).each do |type|
|
62
|
+
message_class = message_class_for(type)
|
63
|
+
messages =
|
64
|
+
if message_class.present?
|
65
|
+
message_class.processed(processor_name: message_class.new.job_class.name)
|
66
|
+
else
|
67
|
+
message_parent_class.where(type:)
|
68
|
+
end
|
69
|
+
|
70
|
+
messages
|
71
|
+
.where(created_at: nil...created_before)
|
72
|
+
.in_batches(of: batch_size) do |batch|
|
73
|
+
return ExceededDeadline.new(deletions) if Time.current > deadline
|
74
|
+
deletions[type] += batch.distinct(false).delete_all
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
Finished.new(deletions)
|
79
|
+
end
|
80
|
+
|
81
|
+
QUEUE_TYPE = "CLEANUP_QUEUE"
|
82
|
+
|
83
|
+
def locking(&blk)
|
84
|
+
QueueLocking.locking(
|
85
|
+
queue_type: QUEUE_TYPE,
|
86
|
+
message_type: message_parent_class.to_s,
|
87
|
+
message_keys: [self.class.to_s],
|
88
|
+
wait: false,
|
89
|
+
&blk
|
90
|
+
)
|
91
|
+
rescue QueueLocking::LockWaitTimeout
|
92
|
+
raise DuplicateJobError
|
93
|
+
end
|
94
|
+
|
95
|
+
def message_class_for(type)
|
96
|
+
message_parent_class.sti_class_for(type)
|
97
|
+
rescue ActiveRecord::SubclassNotFound
|
98
|
+
end
|
99
|
+
|
100
|
+
def message_parent_class
|
101
|
+
end
|
102
|
+
|
103
|
+
def retention_period
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
class AddForeignKeyToInboxCompletionsOnDeleteCascade < ActiveRecord::Migration[6.0]
|
4
|
+
def change
|
5
|
+
from_table = :coil_inbox_completions
|
6
|
+
to_table = :coil_inbox_messages
|
7
|
+
column = :last_completed_message_id
|
8
|
+
old_key = find_foreign_key(from_table, to_table:, column:, on_delete: nil)
|
9
|
+
|
10
|
+
# NOTE: To minimize the impact on read/write availability while adding this
|
11
|
+
# foreign key, we specify `validate: false`, skipping the table scan that
|
12
|
+
# would normally be performed to validate that all existing rows satisfy the
|
13
|
+
# new constraint. We can then validate it in a separate transaction (see the
|
14
|
+
# next migration) without blocking reads/writes to the tables involved.
|
15
|
+
# https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-DESC-ADD-TABLE-CONSTRAINT
|
16
|
+
add_foreign_key(
|
17
|
+
from_table,
|
18
|
+
to_table,
|
19
|
+
column:,
|
20
|
+
name: "#{old_key.name}_on_delete_cascade",
|
21
|
+
on_delete: :cascade,
|
22
|
+
validate: false
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def find_foreign_key(from_table, **options)
|
29
|
+
foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) } ||
|
30
|
+
raise("No foreign key found from table '#{from_table}' for #{options}")
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
class ValidateForeignKeyInboxCompletions < ActiveRecord::Migration[6.0]
|
4
|
+
def change
|
5
|
+
from_table = :coil_inbox_completions
|
6
|
+
to_table = :coil_inbox_messages
|
7
|
+
column = :last_completed_message_id
|
8
|
+
key = find_foreign_key(from_table, to_table:, column:, on_delete: :cascade)
|
9
|
+
|
10
|
+
validate_foreign_key from_table, to_table, name: key.name
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def find_foreign_key(from_table, **options)
|
16
|
+
foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) } ||
|
17
|
+
raise("No foreign key found from table '#{from_table}' for #{options}")
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
class RemoveOldForeignKeyFromInboxCompletions < ActiveRecord::Migration[6.0]
|
4
|
+
def change
|
5
|
+
from_table = :coil_inbox_completions
|
6
|
+
to_table = :coil_inbox_messages
|
7
|
+
column = :last_completed_message_id
|
8
|
+
new_key = find_foreign_key(from_table, to_table:, column:, on_delete: :cascade)
|
9
|
+
|
10
|
+
remove_foreign_key(
|
11
|
+
from_table,
|
12
|
+
to_table,
|
13
|
+
column:,
|
14
|
+
name: new_key.name.delete_suffix("_on_delete_cascade")
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def find_foreign_key(from_table, **options)
|
21
|
+
foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) } ||
|
22
|
+
raise("No foreign key found from table '#{from_table}' for #{options}")
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
class AddForeignKeyToOutboxCompletionsOnDeleteCascade < ActiveRecord::Migration[6.0]
|
4
|
+
def change
|
5
|
+
from_table = :coil_outbox_completions
|
6
|
+
to_table = :coil_outbox_messages
|
7
|
+
column = :last_completed_message_id
|
8
|
+
old_key = find_foreign_key(from_table, to_table:, column:, on_delete: nil)
|
9
|
+
|
10
|
+
# NOTE: To minimize the impact on read/write availability while adding this
|
11
|
+
# foreign key, we specify `validate: false`, skipping the table scan that
|
12
|
+
# would normally be performed to validate that all existing rows satisfy the
|
13
|
+
# new constraint. We can then validate it in a separate transaction (see the
|
14
|
+
# next migration) without blocking reads/writes to the tables involved.
|
15
|
+
# https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-DESC-ADD-TABLE-CONSTRAINT
|
16
|
+
add_foreign_key(
|
17
|
+
from_table,
|
18
|
+
to_table,
|
19
|
+
column:,
|
20
|
+
name: "#{old_key.name}_on_delete_cascade",
|
21
|
+
on_delete: :cascade,
|
22
|
+
validate: false
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def find_foreign_key(from_table, **options)
|
29
|
+
foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) } ||
|
30
|
+
raise("No foreign key found from table '#{from_table}' for #{options}")
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
class ValidateForeignKeyOutboxCompletions < ActiveRecord::Migration[6.0]
|
4
|
+
def change
|
5
|
+
from_table = :coil_outbox_completions
|
6
|
+
to_table = :coil_outbox_messages
|
7
|
+
column = :last_completed_message_id
|
8
|
+
key = find_foreign_key(from_table, to_table:, column:, on_delete: :cascade)
|
9
|
+
|
10
|
+
validate_foreign_key from_table, to_table, name: key.name
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def find_foreign_key(from_table, **options)
|
16
|
+
foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) } ||
|
17
|
+
raise("No foreign key found from table '#{from_table}' for #{options}")
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
class RemoveOldForeignKeyFromOutboxCompletions < ActiveRecord::Migration[6.0]
|
4
|
+
def change
|
5
|
+
from_table = :coil_outbox_completions
|
6
|
+
to_table = :coil_outbox_messages
|
7
|
+
column = :last_completed_message_id
|
8
|
+
new_key = find_foreign_key(from_table, to_table:, column:, on_delete: :cascade)
|
9
|
+
|
10
|
+
remove_foreign_key(
|
11
|
+
from_table,
|
12
|
+
to_table,
|
13
|
+
column:,
|
14
|
+
name: new_key.name.delete_suffix("_on_delete_cascade")
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def find_foreign_key(from_table, **options)
|
21
|
+
foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) } ||
|
22
|
+
raise("No foreign key found from table '#{from_table}' for #{options}")
|
23
|
+
end
|
24
|
+
end
|
data/lib/coil/version.rb
CHANGED
data/lib/coil.rb
CHANGED
data/rbi/coil.rbi
CHANGED
@@ -79,6 +79,16 @@ Coil::Inbox::Message::COMPLETION = Coil::Inbox::Completion
|
|
79
79
|
Coil::Inbox::Message::PERSISTENCE_QUEUE = T.let(T.unsafe(nil), String)
|
80
80
|
Coil::Inbox::Message::PROCESS_QUEUE = T.let(T.unsafe(nil), String)
|
81
81
|
|
82
|
+
class Coil::Inbox::MessagesCleanupJob < ::Coil::TransactionalMessagesCleanupJob
|
83
|
+
private
|
84
|
+
|
85
|
+
sig { override.returns(T.class_of(::Coil::Inbox::Message)) }
|
86
|
+
def message_parent_class; end
|
87
|
+
|
88
|
+
sig { override.returns(ActiveSupport::Duration) }
|
89
|
+
def retention_period; end
|
90
|
+
end
|
91
|
+
|
82
92
|
class Coil::Inbox::MessagesPeriodicJob < ::Coil::TransactionalMessagesPeriodicJob
|
83
93
|
private
|
84
94
|
|
@@ -140,6 +150,16 @@ Coil::Outbox::Message::COMPLETION = Coil::Outbox::Completion
|
|
140
150
|
Coil::Outbox::Message::PERSISTENCE_QUEUE = T.let(T.unsafe(nil), String)
|
141
151
|
Coil::Outbox::Message::PROCESS_QUEUE = T.let(T.unsafe(nil), String)
|
142
152
|
|
153
|
+
class Coil::Outbox::MessagesCleanupJob < ::Coil::TransactionalMessagesCleanupJob
|
154
|
+
private
|
155
|
+
|
156
|
+
sig { override.returns(T.class_of(::Coil::Outbox::Message)) }
|
157
|
+
def message_parent_class; end
|
158
|
+
|
159
|
+
sig { override.returns(ActiveSupport::Duration) }
|
160
|
+
def retention_period; end
|
161
|
+
end
|
162
|
+
|
143
163
|
class Coil::Outbox::MessagesPeriodicJob < ::Coil::TransactionalMessagesPeriodicJob
|
144
164
|
private
|
145
165
|
|
@@ -311,6 +331,45 @@ class Coil::TransactionalMessagesJob::DuplicateJobError < ::StandardError; end
|
|
311
331
|
Coil::TransactionalMessagesJob::MAX_DURATION = T.let(T.unsafe(nil), ActiveSupport::Duration)
|
312
332
|
class Coil::TransactionalMessagesJob::RetryableError < ::StandardError; end
|
313
333
|
|
334
|
+
class Coil::TransactionalMessagesCleanupJob < ::Coil::ApplicationJob
|
335
|
+
abstract!
|
336
|
+
|
337
|
+
class DuplicateJobError < ::StandardError; end
|
338
|
+
|
339
|
+
MAX_DURATION = T.let(T.unsafe(nil), ActiveSupport::Duration)
|
340
|
+
|
341
|
+
sig { params(batch_size: Integer).void }
|
342
|
+
def perform(batch_size = 1000); end
|
343
|
+
|
344
|
+
private
|
345
|
+
|
346
|
+
Result = T.type_alias { T.any(Finished, ExceededDeadline) }
|
347
|
+
|
348
|
+
sig { params(batch_size: Integer).returns(Result) }
|
349
|
+
def delete_messages(batch_size); end
|
350
|
+
|
351
|
+
sig { params(batch_size: Integer).returns(Result) }
|
352
|
+
def _delete_messages(batch_size); end
|
353
|
+
|
354
|
+
QUEUE_TYPE = T.let(T.unsafe(nil), String)
|
355
|
+
|
356
|
+
sig {
|
357
|
+
type_parameters(:P)
|
358
|
+
.params(blk: T.proc.returns(T.type_parameter(:P)))
|
359
|
+
.returns(T.type_parameter(:P))
|
360
|
+
}
|
361
|
+
def locking(&blk); end
|
362
|
+
|
363
|
+
sig { params(type: String).returns(T.nilable(::Coil::AnyMessageClass)) }
|
364
|
+
def message_class_for(type); end
|
365
|
+
|
366
|
+
sig { abstract.returns(::Coil::AnyMessageClass) }
|
367
|
+
def message_parent_class; end
|
368
|
+
|
369
|
+
sig { abstract.returns(ActiveSupport::Duration) }
|
370
|
+
def retention_period; end
|
371
|
+
end
|
372
|
+
|
314
373
|
class Coil::TransactionalMessagesPeriodicJob < ::Coil::ApplicationJob
|
315
374
|
abstract!
|
316
375
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: coil
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Brennan
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2025-01-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -253,8 +253,11 @@ files:
|
|
253
253
|
- README.md
|
254
254
|
- Rakefile
|
255
255
|
- app/jobs/coil/application_job.rb
|
256
|
+
- app/jobs/coil/inbox/messages_cleanup_job.rb
|
256
257
|
- app/jobs/coil/inbox/messages_periodic_job.rb
|
258
|
+
- app/jobs/coil/outbox/messages_cleanup_job.rb
|
257
259
|
- app/jobs/coil/outbox/messages_periodic_job.rb
|
260
|
+
- app/jobs/coil/transactional_messages_cleanup_job.rb
|
258
261
|
- app/jobs/coil/transactional_messages_job.rb
|
259
262
|
- app/jobs/coil/transactional_messages_periodic_job.rb
|
260
263
|
- app/models/coil/application_record.rb
|
@@ -268,6 +271,12 @@ files:
|
|
268
271
|
- app/models/concerns/coil/transactional_message.rb
|
269
272
|
- config/routes.rb
|
270
273
|
- db/migrate/20240604163650_create_coil_tables.rb
|
274
|
+
- db/migrate/20250101182458_add_foreign_key_to_inbox_completions_on_delete_cascade.rb
|
275
|
+
- db/migrate/20250102031753_validate_foreign_key_inbox_completions.rb
|
276
|
+
- db/migrate/20250102040148_remove_old_foreign_key_from_inbox_completions.rb
|
277
|
+
- db/migrate/20250102040649_add_foreign_key_to_outbox_completions_on_delete_cascade.rb
|
278
|
+
- db/migrate/20250102040950_validate_foreign_key_outbox_completions.rb
|
279
|
+
- db/migrate/20250102041225_remove_old_foreign_key_from_outbox_completions.rb
|
271
280
|
- lib/coil.rb
|
272
281
|
- lib/coil/engine.rb
|
273
282
|
- lib/coil/queue_locking.rb
|