whodunit-chronicles 0.3.0 → 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/CHANGELOG.md +22 -226
- data/LICENSE +1 -1
- data/README.md +96 -599
- data/exe/whodunit-chronicles +6 -0
- data/lib/whodunit/chronicles/chronicler.rb +62 -0
- data/lib/whodunit/chronicles/cli.rb +131 -0
- data/lib/whodunit/chronicles/errors.rb +7 -33
- data/lib/whodunit/chronicles/ledger.rb +69 -0
- data/lib/whodunit/chronicles/ledger_entry.rb +143 -0
- data/lib/whodunit/chronicles/ledger_factory.rb +66 -0
- data/lib/whodunit/chronicles/ledgers/file_ledger.rb +56 -0
- data/lib/whodunit/chronicles/ledgers/memory_ledger.rb +29 -0
- data/lib/whodunit/chronicles/ledgers/sqlite_ledger.rb +172 -0
- data/lib/whodunit/chronicles/version.rb +2 -1
- data/lib/whodunit/chronicles.rb +12 -65
- data/lib/whodunit-chronicles.rb +0 -1
- data/sig/whodunit/chronicles/chronicler.rbs +14 -0
- data/sig/whodunit/chronicles/cli.rbs +17 -0
- data/sig/whodunit/chronicles/errors.rbs +15 -0
- data/sig/whodunit/chronicles/ledger.rbs +13 -0
- data/sig/whodunit/chronicles/ledger_entry.rbs +62 -0
- data/sig/whodunit/chronicles/ledger_factory.rbs +14 -0
- data/sig/whodunit/chronicles/ledgers/file_ledger.rbs +14 -0
- data/sig/whodunit/chronicles/ledgers/memory_ledger.rbs +12 -0
- data/sig/whodunit/chronicles/ledgers/sqlite_ledger.rbs +30 -0
- data/sig/whodunit/chronicles.rbs +5 -0
- metadata +40 -326
- data/.codeclimate.yml +0 -50
- data/.rubocop.yml +0 -93
- data/.yardopts +0 -14
- data/CODE_OF_CONDUCT.md +0 -132
- data/CONTRIBUTING.md +0 -186
- data/Rakefile +0 -18
- data/docker/mysql/init.sql +0 -33
- data/docker/postgres/init.sql +0 -40
- data/docker-compose.yml +0 -138
- 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/.gitkeep +0 -0
- data/lib/whodunit/chronicles/adapter_loader.rb +0 -69
- data/lib/whodunit/chronicles/adapters/mysql.rb +0 -261
- data/lib/whodunit/chronicles/adapters/postgresql.rb +0 -278
- data/lib/whodunit/chronicles/change_event.rb +0 -201
- data/lib/whodunit/chronicles/composite_processor.rb +0 -86
- data/lib/whodunit/chronicles/configuration.rb +0 -112
- data/lib/whodunit/chronicles/connection.rb +0 -88
- data/lib/whodunit/chronicles/persistence.rb +0 -129
- data/lib/whodunit/chronicles/processor.rb +0 -127
- data/lib/whodunit/chronicles/service.rb +0 -207
- data/lib/whodunit/chronicles/stream_adapter.rb +0 -91
- data/lib/whodunit/chronicles/table.rb +0 -120
- data/whodunit-chronicles.gemspec +0 -79
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'time'
|
|
5
|
+
require_relative '../errors'
|
|
6
|
+
require_relative '../ledger'
|
|
7
|
+
|
|
8
|
+
module Whodunit
|
|
9
|
+
module Chronicles
|
|
10
|
+
module Ledgers
|
|
11
|
+
# SQLite-backed embedded durable ledger.
|
|
12
|
+
#
|
|
13
|
+
# SQLiteLedger is the default solid local book. It can create its table,
|
|
14
|
+
# create indexes, report status, and append immutable ledger entries. The
|
|
15
|
+
# sqlite3 gem is loaded lazily only when a connection is not injected.
|
|
16
|
+
class SQLiteLedger < Ledger
|
|
17
|
+
# Default SQLite table for entries.
|
|
18
|
+
DEFAULT_TABLE = 'whodunit_chronicles_entries'
|
|
19
|
+
|
|
20
|
+
# @return [String] path to the SQLite database file
|
|
21
|
+
attr_reader :path
|
|
22
|
+
|
|
23
|
+
# @return [String] table receiving entries
|
|
24
|
+
attr_reader :table_name
|
|
25
|
+
|
|
26
|
+
# Create a SQLite-backed ledger.
|
|
27
|
+
#
|
|
28
|
+
# @param path [String] path to the SQLite database file
|
|
29
|
+
# @param table_name [String] table receiving entries
|
|
30
|
+
# @param connection [Object, nil] optional SQLite-compatible connection
|
|
31
|
+
def initialize(path:, table_name: DEFAULT_TABLE, connection: nil)
|
|
32
|
+
@path = path.to_s
|
|
33
|
+
@table_name = table_name.to_s
|
|
34
|
+
@connection = connection
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Create the entries table if needed.
|
|
38
|
+
#
|
|
39
|
+
# @return [SQLiteLedger] this ledger
|
|
40
|
+
def prepare!
|
|
41
|
+
connection.execute(<<~SQL)
|
|
42
|
+
CREATE TABLE IF NOT EXISTS #{quoted_table_name} (
|
|
43
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
44
|
+
event_id TEXT NOT NULL,
|
|
45
|
+
occurred_at TEXT NOT NULL,
|
|
46
|
+
recorded_at TEXT NOT NULL,
|
|
47
|
+
namespace TEXT,
|
|
48
|
+
entity TEXT,
|
|
49
|
+
identity TEXT,
|
|
50
|
+
operation TEXT NOT NULL,
|
|
51
|
+
actor TEXT,
|
|
52
|
+
changes TEXT,
|
|
53
|
+
metadata TEXT,
|
|
54
|
+
payload TEXT NOT NULL
|
|
55
|
+
)
|
|
56
|
+
SQL
|
|
57
|
+
self
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Create indexes useful for audit lookup and de-duplication.
|
|
61
|
+
#
|
|
62
|
+
# @return [SQLiteLedger] this ledger
|
|
63
|
+
def ensure_indexes!
|
|
64
|
+
connection.execute("CREATE UNIQUE INDEX IF NOT EXISTS #{index_name(:event_id)} ON #{quoted_table_name} (event_id)")
|
|
65
|
+
connection.execute("CREATE INDEX IF NOT EXISTS #{index_name(:entity)} ON #{quoted_table_name} (namespace, entity)")
|
|
66
|
+
connection.execute("CREATE INDEX IF NOT EXISTS #{index_name(:occurred_at)} ON #{quoted_table_name} (occurred_at)")
|
|
67
|
+
self
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Append one ledger entry.
|
|
71
|
+
#
|
|
72
|
+
# @param entry [LedgerEntry] entry to append
|
|
73
|
+
# @raise [AppendError] when SQLite rejects a duplicate event_id
|
|
74
|
+
# @return [LedgerEntry] appended entry
|
|
75
|
+
def append(entry)
|
|
76
|
+
connection.execute(<<~SQL, bind_values(entry))
|
|
77
|
+
INSERT INTO #{quoted_table_name} (
|
|
78
|
+
event_id, occurred_at, recorded_at, namespace, entity,
|
|
79
|
+
identity, operation, actor, changes, metadata, payload
|
|
80
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
81
|
+
SQL
|
|
82
|
+
entry
|
|
83
|
+
rescue StandardError => e
|
|
84
|
+
raise unless sqlite_constraint_error?(e)
|
|
85
|
+
|
|
86
|
+
raise AppendError, "duplicate ledger event_id: #{entry.event_id}"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Return lightweight operational status for this ledger.
|
|
90
|
+
#
|
|
91
|
+
# @return [Hash<Symbol, Object>] ledger status
|
|
92
|
+
def status
|
|
93
|
+
{
|
|
94
|
+
adapter: 'sqlite',
|
|
95
|
+
path: path,
|
|
96
|
+
table_name: table_name,
|
|
97
|
+
prepared: prepared?,
|
|
98
|
+
entries: prepared? ? count_entries : nil
|
|
99
|
+
}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
# Return the SQLite connection, creating it lazily when required.
|
|
105
|
+
def connection
|
|
106
|
+
@connection ||= begin
|
|
107
|
+
require 'sqlite3'
|
|
108
|
+
SQLite3::Database.new(path)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Build bind values for one entry.
|
|
113
|
+
def bind_values(entry)
|
|
114
|
+
[
|
|
115
|
+
entry.event_id,
|
|
116
|
+
serialize_time(entry.occurred_at),
|
|
117
|
+
serialize_time(entry.recorded_at),
|
|
118
|
+
entry.namespace,
|
|
119
|
+
entry.entity,
|
|
120
|
+
JSON.generate(entry.identity),
|
|
121
|
+
entry.operation.to_s,
|
|
122
|
+
JSON.generate(entry.actor),
|
|
123
|
+
JSON.generate(entry.changes),
|
|
124
|
+
JSON.generate(entry.metadata),
|
|
125
|
+
JSON.generate(entry.payload)
|
|
126
|
+
]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Serialize time-like objects consistently.
|
|
130
|
+
def serialize_time(value)
|
|
131
|
+
return value.iso8601 if value.respond_to?(:iso8601)
|
|
132
|
+
|
|
133
|
+
value.to_s
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Determine whether the entries table exists.
|
|
137
|
+
def prepared?
|
|
138
|
+
rows = connection.execute(
|
|
139
|
+
"SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?",
|
|
140
|
+
[table_name]
|
|
141
|
+
)
|
|
142
|
+
!rows.empty?
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Count persisted entries.
|
|
146
|
+
def count_entries
|
|
147
|
+
connection.execute("SELECT COUNT(*) FROM #{quoted_table_name}").first.first
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Return whether an error is SQLite's unique constraint failure.
|
|
151
|
+
def sqlite_constraint_error?(error)
|
|
152
|
+
!!(defined?(SQLite3::ConstraintException) && error.is_a?(SQLite3::ConstraintException))
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Quote a SQLite identifier.
|
|
156
|
+
def quote_identifier(identifier)
|
|
157
|
+
%("#{identifier.to_s.gsub('"', '""')}")
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Quote the configured table name.
|
|
161
|
+
def quoted_table_name
|
|
162
|
+
@quoted_table_name ||= table_name.split('.').map { |part| quote_identifier(part) }.join('.')
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Build a safe index name from the table and suffix.
|
|
166
|
+
def index_name(suffix)
|
|
167
|
+
quote_identifier("#{table_name.gsub(/\W+/, '_')}_#{suffix}_idx")
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
data/lib/whodunit/chronicles.rb
CHANGED
|
@@ -1,74 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
4
|
-
require 'dry/configurable'
|
|
5
|
-
require 'dry/logger'
|
|
3
|
+
require 'cdc_core'
|
|
6
4
|
|
|
7
5
|
require_relative 'chronicles/version'
|
|
8
|
-
require_relative 'chronicles/configuration'
|
|
9
|
-
require_relative 'chronicles/change_event'
|
|
10
|
-
require_relative 'chronicles/stream_adapter'
|
|
11
|
-
require_relative 'chronicles/connection'
|
|
12
|
-
require_relative 'chronicles/table'
|
|
13
|
-
require_relative 'chronicles/persistence'
|
|
14
|
-
require_relative 'chronicles/processor'
|
|
15
|
-
require_relative 'chronicles/service'
|
|
16
|
-
|
|
17
6
|
require_relative 'chronicles/errors'
|
|
18
|
-
require_relative 'chronicles/
|
|
19
|
-
require_relative 'chronicles/
|
|
20
|
-
|
|
7
|
+
require_relative 'chronicles/ledger'
|
|
8
|
+
require_relative 'chronicles/ledger_entry'
|
|
9
|
+
require_relative 'chronicles/chronicler'
|
|
10
|
+
require_relative 'chronicles/ledgers/memory_ledger'
|
|
11
|
+
require_relative 'chronicles/ledgers/file_ledger'
|
|
12
|
+
require_relative 'chronicles/ledgers/sqlite_ledger'
|
|
13
|
+
require_relative 'chronicles/ledger_factory'
|
|
14
|
+
require_relative 'chronicles/cli'
|
|
15
|
+
|
|
16
|
+
# Namespace for lightweight attribution and audit ecosystem libraries.
|
|
21
17
|
module Whodunit
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# While Whodunit tracks who made changes, Chronicles captures what changed
|
|
25
|
-
# by streaming database events into comprehensive audit trails with zero
|
|
26
|
-
# Rails application overhead.
|
|
18
|
+
# Canonical CDC audit sink namespace.
|
|
27
19
|
module Chronicles
|
|
28
|
-
extend Dry::Configurable
|
|
29
|
-
|
|
30
|
-
# Configuration settings
|
|
31
|
-
setting :logger, default: Dry::Logger.new
|
|
32
|
-
setting :database_url, default: ENV.fetch('DATABASE_URL', nil)
|
|
33
|
-
setting :audit_database_url, default: ENV.fetch('AUDIT_DATABASE_URL', nil)
|
|
34
|
-
setting :adapter, default: :postgresql
|
|
35
|
-
# PostgreSQL-specific settings
|
|
36
|
-
setting :publication_name, default: 'whodunit_audit'
|
|
37
|
-
setting :replication_slot_name, default: 'whodunit_audit_slot'
|
|
38
|
-
# MySQL-specific settings
|
|
39
|
-
setting :mysql_server_id, default: 1001
|
|
40
|
-
setting :batch_size, default: 100
|
|
41
|
-
setting :max_retry_attempts, default: 3
|
|
42
|
-
setting :retry_delay, default: 5
|
|
43
|
-
|
|
44
|
-
# Configure Chronicles
|
|
45
|
-
#
|
|
46
|
-
# @example
|
|
47
|
-
# Whodunit::Chronicles.configure do |config|
|
|
48
|
-
# config.database_url = "postgresql://localhost/myapp"
|
|
49
|
-
# config.audit_database_url = "postgresql://localhost/myapp_audit"
|
|
50
|
-
# config.adapter = :postgresql
|
|
51
|
-
# # OR for MySQL:
|
|
52
|
-
# config.adapter = :mysql
|
|
53
|
-
# config.mysql_server_id = 1001
|
|
54
|
-
# end
|
|
55
|
-
def self.configure
|
|
56
|
-
yield(config) if block_given?
|
|
57
|
-
config
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# Get the configured logger
|
|
61
|
-
#
|
|
62
|
-
# @return [Dry::Logger]
|
|
63
|
-
def self.logger
|
|
64
|
-
config.logger
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# Start the audit streaming service
|
|
68
|
-
#
|
|
69
|
-
# @return [Service]
|
|
70
|
-
def self.start
|
|
71
|
-
Service.new.start
|
|
72
|
-
end
|
|
73
20
|
end
|
|
74
21
|
end
|
data/lib/whodunit-chronicles.rb
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Whodunit
|
|
2
|
+
module Chronicles
|
|
3
|
+
class Chronicler < ::CDC::Core::Processor
|
|
4
|
+
attr_reader ledger: untyped
|
|
5
|
+
|
|
6
|
+
def initialize: (ledger: untyped, ?prepare: bool, ?ensure_indexes: bool, ?clock: untyped) -> void
|
|
7
|
+
def process: (::CDC::Core::ChangeEvent event) -> ::CDC::Core::ProcessorResult
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def partition_for: (LedgerEntry entry) -> untyped
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Whodunit
|
|
2
|
+
module Chronicles
|
|
3
|
+
class CLI
|
|
4
|
+
def self.run: (Array[String] argv, ?out: untyped, ?err: untyped) -> Integer
|
|
5
|
+
def initialize: (argv: Array[String], out: untyped, err: untyped) -> void
|
|
6
|
+
def run: () -> Integer
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def execute_ledger_command: (untyped ledger, String command, Array[String] options) -> Integer
|
|
11
|
+
def load_config: (String path) -> Hash[untyped, untyped]
|
|
12
|
+
def format_status: (Hash[untyped, untyped] status, Array[String] options) -> String
|
|
13
|
+
def ensure_supported!: (untyped ledger, Symbol method_name) -> true
|
|
14
|
+
def usage: (Integer code) -> Integer
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Whodunit
|
|
2
|
+
module Chronicles
|
|
3
|
+
class Ledger
|
|
4
|
+
def prepare!: () -> self
|
|
5
|
+
def ensure_indexes!: () -> self
|
|
6
|
+
def migrate!: () -> self
|
|
7
|
+
def verify: () -> bool
|
|
8
|
+
def status: () -> Hash[Symbol, untyped]
|
|
9
|
+
def partition_for: (LedgerEntry entry) -> Ledger
|
|
10
|
+
def append: (LedgerEntry entry) -> untyped
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Whodunit
|
|
2
|
+
module Chronicles
|
|
3
|
+
class LedgerEntry < Data
|
|
4
|
+
attr_reader event_id: String
|
|
5
|
+
attr_reader occurred_at: untyped
|
|
6
|
+
attr_reader recorded_at: untyped
|
|
7
|
+
attr_reader namespace: String
|
|
8
|
+
attr_reader entity: String
|
|
9
|
+
attr_reader identity: untyped
|
|
10
|
+
attr_reader operation: Symbol
|
|
11
|
+
attr_reader actor: untyped
|
|
12
|
+
attr_reader changes: untyped
|
|
13
|
+
attr_reader metadata: Hash[untyped, untyped]
|
|
14
|
+
attr_reader payload: Hash[untyped, untyped]
|
|
15
|
+
|
|
16
|
+
def initialize: (
|
|
17
|
+
event_id: String,
|
|
18
|
+
occurred_at: untyped,
|
|
19
|
+
recorded_at: untyped,
|
|
20
|
+
namespace: String?,
|
|
21
|
+
entity: String?,
|
|
22
|
+
identity: untyped,
|
|
23
|
+
operation: Symbol | String,
|
|
24
|
+
actor: untyped,
|
|
25
|
+
changes: untyped,
|
|
26
|
+
metadata: Hash[untyped, untyped],
|
|
27
|
+
payload: Hash[untyped, untyped]
|
|
28
|
+
) -> void
|
|
29
|
+
|
|
30
|
+
def self.new: (
|
|
31
|
+
event_id: String,
|
|
32
|
+
occurred_at: untyped,
|
|
33
|
+
recorded_at: untyped,
|
|
34
|
+
namespace: String?,
|
|
35
|
+
entity: String?,
|
|
36
|
+
identity: untyped,
|
|
37
|
+
operation: Symbol | String,
|
|
38
|
+
actor: untyped,
|
|
39
|
+
changes: untyped,
|
|
40
|
+
metadata: Hash[untyped, untyped],
|
|
41
|
+
payload: Hash[untyped, untyped]
|
|
42
|
+
) -> LedgerEntry
|
|
43
|
+
|
|
44
|
+
def self.from_change_event: (::CDC::Core::ChangeEvent event, ?clock: untyped) -> LedgerEntry
|
|
45
|
+
def self.from_event: (::CDC::Core::ChangeEvent event, ?clock: untyped) -> LedgerEntry
|
|
46
|
+
def to_h: () -> Hash[Symbol, untyped]
|
|
47
|
+
def ordering_identity: () -> String
|
|
48
|
+
def members: () -> Array[Symbol]
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def self.validate_change_event!: (::Object event) -> true
|
|
53
|
+
def self.event_id_for: (::CDC::Core::ChangeEvent event) -> String
|
|
54
|
+
def self.namespace_for: (::CDC::Core::ChangeEvent event) -> String?
|
|
55
|
+
def self.entity_for: (::CDC::Core::ChangeEvent event) -> String?
|
|
56
|
+
def self.actor_for: (::CDC::Core::ChangeEvent event) -> untyped
|
|
57
|
+
def self.changes_for: (::CDC::Core::ChangeEvent event) -> untyped
|
|
58
|
+
def self.metadata_for: (::CDC::Core::ChangeEvent event) -> Hash[untyped, untyped]
|
|
59
|
+
def self.source_position_for: (::CDC::Core::ChangeEvent event) -> untyped
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Whodunit
|
|
2
|
+
module Chronicles
|
|
3
|
+
class LedgerFactory
|
|
4
|
+
def self.build: (Hash[untyped, untyped] config) -> Ledger
|
|
5
|
+
def initialize: (Hash[untyped, untyped] config) -> void
|
|
6
|
+
def build: () -> Ledger
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def stringify_keys: (untyped value) -> untyped
|
|
11
|
+
def required_value: (Hash[String, untyped] config, String key) -> untyped
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Whodunit
|
|
2
|
+
module Chronicles
|
|
3
|
+
module Ledgers
|
|
4
|
+
class FileLedger < Ledger
|
|
5
|
+
attr_reader path: String
|
|
6
|
+
|
|
7
|
+
def initialize: (path: String) -> void
|
|
8
|
+
def prepare!: () -> FileLedger
|
|
9
|
+
def append: (LedgerEntry entry) -> LedgerEntry
|
|
10
|
+
def entries: () -> Array[Hash[untyped, untyped]]
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Whodunit
|
|
2
|
+
module Chronicles
|
|
3
|
+
module Ledgers
|
|
4
|
+
class SQLiteLedger < Ledger
|
|
5
|
+
DEFAULT_TABLE: String
|
|
6
|
+
|
|
7
|
+
attr_reader path: String
|
|
8
|
+
attr_reader table_name: String
|
|
9
|
+
|
|
10
|
+
def initialize: (path: String, ?table_name: String, ?connection: untyped) -> void
|
|
11
|
+
def prepare!: () -> SQLiteLedger
|
|
12
|
+
def ensure_indexes!: () -> SQLiteLedger
|
|
13
|
+
def append: (LedgerEntry entry) -> LedgerEntry
|
|
14
|
+
def status: () -> Hash[Symbol, untyped]
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def connection: () -> untyped
|
|
19
|
+
def bind_values: (LedgerEntry entry) -> Array[untyped]
|
|
20
|
+
def serialize_time: (untyped value) -> String
|
|
21
|
+
def prepared?: () -> bool
|
|
22
|
+
def count_entries: () -> Integer
|
|
23
|
+
def sqlite_constraint_error?: (StandardError error) -> bool
|
|
24
|
+
def quote_identifier: (untyped identifier) -> String
|
|
25
|
+
def quoted_table_name: () -> String
|
|
26
|
+
def index_name: (Symbol suffix) -> String
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|