whodunit-chronicles 0.1.0.pre → 0.2.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/.codeclimate.yml +50 -0
- data/.rubocop.yml +2 -1
- data/.yardopts +7 -5
- data/CHANGELOG.md +76 -1
- data/README.md +408 -22
- data/examples/images/campaign-performance-analytics.png +0 -0
- data/examples/images/candidate-journey-analytics.png +0 -0
- data/examples/images/recruitment-funnel-analytics.png +0 -0
- data/lib/whodunit/chronicles/adapters/mysql.rb +261 -0
- data/lib/whodunit/chronicles/configuration.rb +23 -12
- data/lib/whodunit/chronicles/connection.rb +88 -0
- data/lib/whodunit/chronicles/persistence.rb +129 -0
- data/lib/whodunit/chronicles/processor.rb +127 -0
- data/lib/whodunit/chronicles/service.rb +23 -21
- data/lib/whodunit/chronicles/table.rb +120 -0
- data/lib/whodunit/chronicles/version.rb +1 -1
- data/lib/whodunit/chronicles.rb +11 -1
- data/whodunit-chronicles.gemspec +6 -2
- metadata +68 -4
- data/lib/whodunit/chronicles/audit_processor.rb +0 -270
@@ -4,10 +4,10 @@ require 'concurrent-ruby'
|
|
4
4
|
|
5
5
|
module Whodunit
|
6
6
|
module Chronicles
|
7
|
-
# Main service orchestrator for
|
7
|
+
# Main service orchestrator for chronicle streaming
|
8
8
|
#
|
9
|
-
# Coordinates the stream adapter and
|
10
|
-
# a complete
|
9
|
+
# Coordinates the stream adapter and processor to provide
|
10
|
+
# a complete chronicle streaming solution with error handling and monitoring.
|
11
11
|
class Service
|
12
12
|
attr_reader :adapter, :processor, :logger, :executor
|
13
13
|
|
@@ -17,7 +17,7 @@ module Whodunit
|
|
17
17
|
logger: Chronicles.logger
|
18
18
|
)
|
19
19
|
@adapter = adapter || build_adapter
|
20
|
-
@processor = processor ||
|
20
|
+
@processor = processor || Processor.new(logger: logger)
|
21
21
|
@logger = logger
|
22
22
|
@executor = Concurrent::ThreadPoolExecutor.new(
|
23
23
|
min_threads: 1,
|
@@ -29,13 +29,13 @@ module Whodunit
|
|
29
29
|
@retry_count = 0
|
30
30
|
end
|
31
31
|
|
32
|
-
# Start the
|
32
|
+
# Start the chronicle streaming service
|
33
33
|
#
|
34
34
|
# @return [self]
|
35
35
|
def start
|
36
36
|
return self if running?
|
37
37
|
|
38
|
-
log(:info, 'Starting Chronicles
|
38
|
+
log(:info, 'Starting Chronicles streaming service')
|
39
39
|
|
40
40
|
validate_setup!
|
41
41
|
test_connections!
|
@@ -45,7 +45,7 @@ module Whodunit
|
|
45
45
|
|
46
46
|
start_streaming_with_retry
|
47
47
|
|
48
|
-
log(:info, 'Chronicles
|
48
|
+
log(:info, 'Chronicles streaming service started successfully')
|
49
49
|
self
|
50
50
|
rescue StandardError => e
|
51
51
|
log(:error, 'Failed to start service', error: e.message)
|
@@ -53,13 +53,13 @@ module Whodunit
|
|
53
53
|
raise
|
54
54
|
end
|
55
55
|
|
56
|
-
# Stop the
|
56
|
+
# Stop the chronicle streaming service
|
57
57
|
#
|
58
58
|
# @return [void]
|
59
59
|
def stop
|
60
60
|
return unless running?
|
61
61
|
|
62
|
-
log(:info, 'Stopping Chronicles
|
62
|
+
log(:info, 'Stopping Chronicles streaming service')
|
63
63
|
@running = false
|
64
64
|
|
65
65
|
adapter.stop_streaming if adapter.streaming?
|
@@ -67,7 +67,7 @@ module Whodunit
|
|
67
67
|
@executor.wait_for_termination(timeout: 30)
|
68
68
|
|
69
69
|
processor.close
|
70
|
-
log(:info, 'Chronicles
|
70
|
+
log(:info, 'Chronicles streaming service stopped')
|
71
71
|
end
|
72
72
|
|
73
73
|
# Check if service is running
|
@@ -94,23 +94,23 @@ module Whodunit
|
|
94
94
|
}
|
95
95
|
end
|
96
96
|
|
97
|
-
# Set up the
|
97
|
+
# Set up the chronicle streaming infrastructure
|
98
98
|
#
|
99
99
|
# @return [void]
|
100
100
|
def setup!
|
101
|
-
log(:info, 'Setting up
|
101
|
+
log(:info, 'Setting up chronicle streaming infrastructure')
|
102
102
|
adapter.setup
|
103
|
-
log(:info, '
|
103
|
+
log(:info, 'Chronicle streaming infrastructure setup completed')
|
104
104
|
end
|
105
105
|
|
106
|
-
# Tear down the
|
106
|
+
# Tear down the chronicle streaming infrastructure
|
107
107
|
#
|
108
108
|
# @return [void]
|
109
109
|
def teardown!
|
110
|
-
log(:info, 'Tearing down
|
110
|
+
log(:info, 'Tearing down chronicle streaming infrastructure')
|
111
111
|
stop if running?
|
112
112
|
adapter.teardown
|
113
|
-
log(:info, '
|
113
|
+
log(:info, 'Chronicle streaming infrastructure teardown completed')
|
114
114
|
end
|
115
115
|
|
116
116
|
private
|
@@ -119,6 +119,8 @@ module Whodunit
|
|
119
119
|
case Chronicles.config.adapter
|
120
120
|
when :postgresql
|
121
121
|
Adapters::PostgreSQL.new(logger: logger)
|
122
|
+
when :mysql
|
123
|
+
Adapters::MySQL.new(logger: logger)
|
122
124
|
else
|
123
125
|
raise ConfigurationError, "Unsupported adapter: #{Chronicles.config.adapter}"
|
124
126
|
end
|
@@ -134,8 +136,8 @@ module Whodunit
|
|
134
136
|
|
135
137
|
def test_connections!
|
136
138
|
adapter.test_connection
|
137
|
-
# Test
|
138
|
-
processor.send(:
|
139
|
+
# Test processor connection by creating a dummy connection
|
140
|
+
processor.send(:ensure_connection)
|
139
141
|
rescue StandardError => e
|
140
142
|
raise AdapterError, "Connection test failed: #{e.message}"
|
141
143
|
end
|
@@ -159,7 +161,7 @@ module Whodunit
|
|
159
161
|
|
160
162
|
def process_change_event(change_event)
|
161
163
|
return unless change_event
|
162
|
-
return unless
|
164
|
+
return unless should_chronicle_table?(change_event)
|
163
165
|
|
164
166
|
log(:debug, 'Processing change event',
|
165
167
|
table: change_event.qualified_table_name,
|
@@ -174,8 +176,8 @@ module Whodunit
|
|
174
176
|
)
|
175
177
|
end
|
176
178
|
|
177
|
-
def
|
178
|
-
Chronicles.config.
|
179
|
+
def should_chronicle_table?(change_event)
|
180
|
+
Chronicles.config.chronicle_table?(
|
179
181
|
change_event.table_name,
|
180
182
|
change_event.schema_name,
|
181
183
|
)
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Whodunit
|
4
|
+
module Chronicles
|
5
|
+
# Handles table creation for different database adapters
|
6
|
+
#
|
7
|
+
# Provides adapter-specific SQL for creating chronicles tables
|
8
|
+
module Table
|
9
|
+
private
|
10
|
+
|
11
|
+
def ensure_table_exists
|
12
|
+
db_type = detect_database_type(@audit_database_url || Chronicles.config.database_url)
|
13
|
+
|
14
|
+
case db_type
|
15
|
+
when :postgresql
|
16
|
+
create_postgresql_table
|
17
|
+
when :mysql
|
18
|
+
create_mysql_table
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_postgresql_table
|
23
|
+
create_sql = <<~SQL
|
24
|
+
CREATE TABLE IF NOT EXISTS whodunit_chronicles_audits (
|
25
|
+
id BIGSERIAL PRIMARY KEY,
|
26
|
+
table_name TEXT NOT NULL,
|
27
|
+
schema_name TEXT NOT NULL DEFAULT 'public',
|
28
|
+
record_id JSONB,
|
29
|
+
action TEXT NOT NULL CHECK (action IN ('INSERT', 'UPDATE', 'DELETE')),
|
30
|
+
old_data JSONB,
|
31
|
+
new_data JSONB,
|
32
|
+
changes JSONB,
|
33
|
+
user_id BIGINT,
|
34
|
+
user_type TEXT,
|
35
|
+
transaction_id TEXT,
|
36
|
+
sequence_number INTEGER,
|
37
|
+
occurred_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
38
|
+
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
39
|
+
metadata JSONB DEFAULT '{}'::jsonb,
|
40
|
+
#{' '}
|
41
|
+
-- Indexes for performance
|
42
|
+
CONSTRAINT valid_data_for_action CHECK (
|
43
|
+
(action = 'INSERT' AND old_data IS NULL AND new_data IS NOT NULL) OR
|
44
|
+
(action = 'UPDATE' AND old_data IS NOT NULL AND new_data IS NOT NULL) OR#{' '}
|
45
|
+
(action = 'DELETE' AND old_data IS NOT NULL AND new_data IS NULL)
|
46
|
+
)
|
47
|
+
);
|
48
|
+
|
49
|
+
-- Performance indexes
|
50
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_chronicles_audits_table_record#{' '}
|
51
|
+
ON whodunit_chronicles_audits (table_name, (record_id->>'id'));
|
52
|
+
|
53
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_chronicles_audits_occurred_at#{' '}
|
54
|
+
ON whodunit_chronicles_audits (occurred_at DESC);
|
55
|
+
|
56
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_chronicles_audits_user#{' '}
|
57
|
+
ON whodunit_chronicles_audits (user_id, user_type);
|
58
|
+
|
59
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_chronicles_audits_action#{' '}
|
60
|
+
ON whodunit_chronicles_audits (action);
|
61
|
+
|
62
|
+
-- GIN index for JSONB columns
|
63
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_chronicles_audits_record_id_gin#{' '}
|
64
|
+
ON whodunit_chronicles_audits USING GIN (record_id);
|
65
|
+
|
66
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_chronicles_audits_changes_gin#{' '}
|
67
|
+
ON whodunit_chronicles_audits USING GIN (changes);
|
68
|
+
SQL
|
69
|
+
|
70
|
+
@connection.exec(create_sql)
|
71
|
+
rescue PG::Error => e
|
72
|
+
# Ignore "already exists" errors from CONCURRENTLY
|
73
|
+
raise unless e.message.include?('already exists')
|
74
|
+
end
|
75
|
+
|
76
|
+
def create_mysql_table
|
77
|
+
create_sql = <<~SQL
|
78
|
+
CREATE TABLE IF NOT EXISTS whodunit_chronicles_audits (
|
79
|
+
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
80
|
+
table_name TEXT NOT NULL,
|
81
|
+
schema_name TEXT NOT NULL DEFAULT 'public',
|
82
|
+
record_id JSON,
|
83
|
+
action TEXT NOT NULL CHECK (action IN ('INSERT', 'UPDATE', 'DELETE')),
|
84
|
+
old_data JSON,
|
85
|
+
new_data JSON,
|
86
|
+
changes JSON,
|
87
|
+
user_id BIGINT,
|
88
|
+
user_type TEXT,
|
89
|
+
transaction_id TEXT,
|
90
|
+
sequence_number INTEGER,
|
91
|
+
occurred_at TIMESTAMP NOT NULL,
|
92
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
93
|
+
metadata JSON DEFAULT (JSON_OBJECT()),
|
94
|
+
#{' '}
|
95
|
+
-- Constraint for data integrity
|
96
|
+
CONSTRAINT valid_data_for_action CHECK (
|
97
|
+
(action = 'INSERT' AND old_data IS NULL AND new_data IS NOT NULL) OR
|
98
|
+
(action = 'UPDATE' AND old_data IS NOT NULL AND new_data IS NOT NULL) OR
|
99
|
+
(action = 'DELETE' AND old_data IS NOT NULL AND new_data IS NULL)
|
100
|
+
),
|
101
|
+
#{' '}
|
102
|
+
-- Performance indexes
|
103
|
+
INDEX idx_chronicles_audits_table_record (table_name(255), (JSON_UNQUOTE(JSON_EXTRACT(record_id, '$.id')))),
|
104
|
+
INDEX idx_chronicles_audits_occurred_at (occurred_at DESC),
|
105
|
+
INDEX idx_chronicles_audits_user (user_id, user_type(255)),
|
106
|
+
INDEX idx_chronicles_audits_action (action(50))
|
107
|
+
);
|
108
|
+
SQL
|
109
|
+
|
110
|
+
@connection.query(create_sql)
|
111
|
+
rescue StandardError => e
|
112
|
+
# Ignore "already exists" errors
|
113
|
+
unless e.message.include?('already exists') ||
|
114
|
+
(e.message.include?('Table') && e.message.include?('already exists'))
|
115
|
+
raise
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
data/lib/whodunit/chronicles.rb
CHANGED
@@ -8,11 +8,15 @@ require_relative 'chronicles/version'
|
|
8
8
|
require_relative 'chronicles/configuration'
|
9
9
|
require_relative 'chronicles/change_event'
|
10
10
|
require_relative 'chronicles/stream_adapter'
|
11
|
-
require_relative 'chronicles/
|
11
|
+
require_relative 'chronicles/connection'
|
12
|
+
require_relative 'chronicles/table'
|
13
|
+
require_relative 'chronicles/persistence'
|
14
|
+
require_relative 'chronicles/processor'
|
12
15
|
require_relative 'chronicles/service'
|
13
16
|
|
14
17
|
# Adapters
|
15
18
|
require_relative 'chronicles/adapters/postgresql'
|
19
|
+
require_relative 'chronicles/adapters/mysql'
|
16
20
|
|
17
21
|
module Whodunit
|
18
22
|
# Chronicles - The complete historical record of `whodunit did what?` data
|
@@ -28,8 +32,11 @@ module Whodunit
|
|
28
32
|
setting :database_url, default: ENV.fetch('DATABASE_URL', nil)
|
29
33
|
setting :audit_database_url, default: ENV.fetch('AUDIT_DATABASE_URL', nil)
|
30
34
|
setting :adapter, default: :postgresql
|
35
|
+
# PostgreSQL-specific settings
|
31
36
|
setting :publication_name, default: 'whodunit_audit'
|
32
37
|
setting :replication_slot_name, default: 'whodunit_audit_slot'
|
38
|
+
# MySQL-specific settings
|
39
|
+
setting :mysql_server_id, default: 1001
|
33
40
|
setting :batch_size, default: 100
|
34
41
|
setting :max_retry_attempts, default: 3
|
35
42
|
setting :retry_delay, default: 5
|
@@ -46,6 +53,9 @@ module Whodunit
|
|
46
53
|
# config.database_url = "postgresql://localhost/myapp"
|
47
54
|
# config.audit_database_url = "postgresql://localhost/myapp_audit"
|
48
55
|
# config.adapter = :postgresql
|
56
|
+
# # OR for MySQL:
|
57
|
+
# config.adapter = :mysql
|
58
|
+
# config.mysql_server_id = 1001
|
49
59
|
# end
|
50
60
|
def self.configure
|
51
61
|
yield(config) if block_given?
|
data/whodunit-chronicles.gemspec
CHANGED
@@ -41,7 +41,9 @@ Gem::Specification.new do |spec|
|
|
41
41
|
# Database dependencies
|
42
42
|
spec.add_dependency 'pg', '~> 1.5'
|
43
43
|
# Driver for MySQL-compatible database
|
44
|
-
|
44
|
+
spec.add_dependency 'trilogy', '~> 2.9'
|
45
|
+
# Required for Ruby 3.4+ compatibility (trilogy dependency)
|
46
|
+
spec.add_dependency 'bigdecimal', '~> 3.1'
|
45
47
|
|
46
48
|
# Development dependencies
|
47
49
|
spec.add_development_dependency 'kramdown', '~> 2.5'
|
@@ -49,13 +51,15 @@ Gem::Specification.new do |spec|
|
|
49
51
|
spec.add_development_dependency 'mocha', '~> 2.1'
|
50
52
|
spec.add_development_dependency 'pry', '~> 0.14'
|
51
53
|
spec.add_development_dependency 'rake', '~> 13.0'
|
54
|
+
spec.add_development_dependency 'rspec_junit_formatter', '~> 0.6.0'
|
52
55
|
spec.add_development_dependency 'rubocop', '~> 1.60'
|
53
56
|
spec.add_development_dependency 'rubocop-minitest', '~> 0.34'
|
54
57
|
spec.add_development_dependency 'rubocop-performance', '~> 1.19'
|
55
58
|
spec.add_development_dependency 'simplecov', '~> 0.22'
|
59
|
+
spec.add_development_dependency 'simplecov-cobertura', '~> 3.0'
|
56
60
|
spec.add_development_dependency 'yard', '~> 0.9'
|
57
61
|
|
58
62
|
# Security scanning dependencies
|
59
|
-
spec.add_development_dependency 'brakeman', '~>
|
63
|
+
spec.add_development_dependency 'brakeman', '~> 7.1'
|
60
64
|
spec.add_development_dependency 'bundler-audit', '~> 0.9'
|
61
65
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: whodunit-chronicles
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ken C. Demanawa
|
@@ -66,6 +66,34 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1.5'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: trilogy
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.9'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.9'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bigdecimal
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.1'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.1'
|
69
97
|
- !ruby/object:Gem::Dependency
|
70
98
|
name: kramdown
|
71
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,6 +164,20 @@ dependencies:
|
|
136
164
|
- - "~>"
|
137
165
|
- !ruby/object:Gem::Version
|
138
166
|
version: '13.0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rspec_junit_formatter
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 0.6.0
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: 0.6.0
|
139
181
|
- !ruby/object:Gem::Dependency
|
140
182
|
name: rubocop
|
141
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -192,6 +234,20 @@ dependencies:
|
|
192
234
|
- - "~>"
|
193
235
|
- !ruby/object:Gem::Version
|
194
236
|
version: '0.22'
|
237
|
+
- !ruby/object:Gem::Dependency
|
238
|
+
name: simplecov-cobertura
|
239
|
+
requirement: !ruby/object:Gem::Requirement
|
240
|
+
requirements:
|
241
|
+
- - "~>"
|
242
|
+
- !ruby/object:Gem::Version
|
243
|
+
version: '3.0'
|
244
|
+
type: :development
|
245
|
+
prerelease: false
|
246
|
+
version_requirements: !ruby/object:Gem::Requirement
|
247
|
+
requirements:
|
248
|
+
- - "~>"
|
249
|
+
- !ruby/object:Gem::Version
|
250
|
+
version: '3.0'
|
195
251
|
- !ruby/object:Gem::Dependency
|
196
252
|
name: yard
|
197
253
|
requirement: !ruby/object:Gem::Requirement
|
@@ -212,14 +268,14 @@ dependencies:
|
|
212
268
|
requirements:
|
213
269
|
- - "~>"
|
214
270
|
- !ruby/object:Gem::Version
|
215
|
-
version: '
|
271
|
+
version: '7.1'
|
216
272
|
type: :development
|
217
273
|
prerelease: false
|
218
274
|
version_requirements: !ruby/object:Gem::Requirement
|
219
275
|
requirements:
|
220
276
|
- - "~>"
|
221
277
|
- !ruby/object:Gem::Version
|
222
|
-
version: '
|
278
|
+
version: '7.1'
|
223
279
|
- !ruby/object:Gem::Dependency
|
224
280
|
name: bundler-audit
|
225
281
|
requirement: !ruby/object:Gem::Requirement
|
@@ -243,6 +299,7 @@ executables: []
|
|
243
299
|
extensions: []
|
244
300
|
extra_rdoc_files: []
|
245
301
|
files:
|
302
|
+
- ".codeclimate.yml"
|
246
303
|
- ".rubocop.yml"
|
247
304
|
- ".yardopts"
|
248
305
|
- CHANGELOG.md
|
@@ -250,15 +307,22 @@ files:
|
|
250
307
|
- LICENSE
|
251
308
|
- README.md
|
252
309
|
- Rakefile
|
310
|
+
- examples/images/campaign-performance-analytics.png
|
311
|
+
- examples/images/candidate-journey-analytics.png
|
312
|
+
- examples/images/recruitment-funnel-analytics.png
|
253
313
|
- lib/.gitkeep
|
254
314
|
- lib/whodunit-chronicles.rb
|
255
315
|
- lib/whodunit/chronicles.rb
|
316
|
+
- lib/whodunit/chronicles/adapters/mysql.rb
|
256
317
|
- lib/whodunit/chronicles/adapters/postgresql.rb
|
257
|
-
- lib/whodunit/chronicles/audit_processor.rb
|
258
318
|
- lib/whodunit/chronicles/change_event.rb
|
259
319
|
- lib/whodunit/chronicles/configuration.rb
|
320
|
+
- lib/whodunit/chronicles/connection.rb
|
321
|
+
- lib/whodunit/chronicles/persistence.rb
|
322
|
+
- lib/whodunit/chronicles/processor.rb
|
260
323
|
- lib/whodunit/chronicles/service.rb
|
261
324
|
- lib/whodunit/chronicles/stream_adapter.rb
|
325
|
+
- lib/whodunit/chronicles/table.rb
|
262
326
|
- lib/whodunit/chronicles/version.rb
|
263
327
|
- whodunit-chronicles.gemspec
|
264
328
|
homepage: https://github.com/whodunit-gem/whodunit-chronicles
|