lutaml-store 0.1.1
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 +7 -0
- data/.github/workflows/main.yml +27 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +10 -0
- data/.rubocop_todo.yml +450 -0
- data/CLAUDE.md +57 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/CORRECTED_HTTP_CACHE_IMPLEMENTATION.md +209 -0
- data/CORRECTED_HTTP_CACHE_PLAN.md +164 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +220 -0
- data/README.adoc +1430 -0
- data/Rakefile +12 -0
- data/TODO.impl/0-lutaml-store-self-quality.md +112 -0
- data/TODO.impl/1-lutaml-hal-migration.md +60 -0
- data/TODO.impl/2-glossarist-migration.md +359 -0
- data/TODO.impl/3-lutaml-jsonschema-migration.md +273 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/demo/Gemfile +15 -0
- data/demo/Gemfile.lock +61 -0
- data/demo/README.adoc +301 -0
- data/demo/data/vcards/co/contact_10_thompson.data +1 -0
- data/demo/data/vcards/co/contact_10_thompson.meta +1 -0
- data/demo/data/vcards/co/contact_1_doe.data +1 -0
- data/demo/data/vcards/co/contact_1_doe.meta +1 -0
- data/demo/data/vcards/co/contact_2_smith.data +1 -0
- data/demo/data/vcards/co/contact_2_smith.meta +1 -0
- data/demo/data/vcards/co/contact_3_johnson.data +1 -0
- data/demo/data/vcards/co/contact_3_johnson.meta +1 -0
- data/demo/data/vcards/co/contact_4_garcia.data +1 -0
- data/demo/data/vcards/co/contact_4_garcia.meta +1 -0
- data/demo/data/vcards/co/contact_5_wilson.data +1 -0
- data/demo/data/vcards/co/contact_5_wilson.meta +1 -0
- data/demo/data/vcards/co/contact_6_brown.data +1 -0
- data/demo/data/vcards/co/contact_6_brown.meta +1 -0
- data/demo/data/vcards/co/contact_7_davis.data +1 -0
- data/demo/data/vcards/co/contact_7_davis.meta +1 -0
- data/demo/data/vcards/co/contact_8_anderson.data +1 -0
- data/demo/data/vcards/co/contact_8_anderson.meta +1 -0
- data/demo/data/vcards/co/contact_9_taylor.data +1 -0
- data/demo/data/vcards/co/contact_9_taylor.meta +1 -0
- data/demo/data/vcards.db +0 -0
- data/demo/pottery_class_demo.rb +164 -0
- data/demo/vcard_models.rb +140 -0
- data/demo/vcard_store_demo.rb +526 -0
- data/lib/lutaml/store/adapter/base.rb +65 -0
- data/lib/lutaml/store/adapter/filesystem.rb +288 -0
- data/lib/lutaml/store/adapter/memory.rb +225 -0
- data/lib/lutaml/store/adapter/sqlite.rb +193 -0
- data/lib/lutaml/store/adapter.rb +12 -0
- data/lib/lutaml/store/attribute_updater.rb +198 -0
- data/lib/lutaml/store/basic_store.rb +190 -0
- data/lib/lutaml/store/cache.rb +108 -0
- data/lib/lutaml/store/cache_store.rb +282 -0
- data/lib/lutaml/store/composite_model_handler.rb +169 -0
- data/lib/lutaml/store/compression.rb +137 -0
- data/lib/lutaml/store/config.rb +178 -0
- data/lib/lutaml/store/database_store.rb +425 -0
- data/lib/lutaml/store/events.rb +92 -0
- data/lib/lutaml/store/format/base.rb +33 -0
- data/lib/lutaml/store/format/json.rb +25 -0
- data/lib/lutaml/store/format/jsonl.rb +37 -0
- data/lib/lutaml/store/format/marshal_format.rb +37 -0
- data/lib/lutaml/store/format/yaml.rb +29 -0
- data/lib/lutaml/store/format/yamls.rb +35 -0
- data/lib/lutaml/store/format.rb +33 -0
- data/lib/lutaml/store/http_cache.rb +279 -0
- data/lib/lutaml/store/http_cache_config.rb +53 -0
- data/lib/lutaml/store/http_cache_entry.rb +69 -0
- data/lib/lutaml/store/http_header_processor.rb +175 -0
- data/lib/lutaml/store/integrity.rb +102 -0
- data/lib/lutaml/store/model_registration.rb +75 -0
- data/lib/lutaml/store/model_registry.rb +123 -0
- data/lib/lutaml/store/model_serializer.rb +69 -0
- data/lib/lutaml/store/monitor.rb +192 -0
- data/lib/lutaml/store/storage_key.rb +40 -0
- data/lib/lutaml/store/version.rb +7 -0
- data/lib/lutaml/store.rb +41 -0
- data/lutaml-store.gemspec +35 -0
- data/plan.adoc +606 -0
- data/sig/lutaml/store.rbs +6 -0
- data/spec/lutaml/store/adapter_interface_spec.rb +89 -0
- data/spec/lutaml/store/anti_pattern_guard_spec.rb +35 -0
- data/spec/lutaml/store/anti_pattern_spec.rb +78 -0
- data/spec/lutaml/store/autoload_spec.rb +34 -0
- data/spec/lutaml/store/cache_store_spec.rb +271 -0
- data/spec/lutaml/store/compression_spec.rb +78 -0
- data/spec/lutaml/store/config_enhanced_spec.rb +158 -0
- data/spec/lutaml/store/corrected_http_cache_integration_spec.rb +336 -0
- data/spec/lutaml/store/custom_serializer_spec.rb +108 -0
- data/spec/lutaml/store/database_store_spec.rb +279 -0
- data/spec/lutaml/store/file_io_spec.rb +219 -0
- data/spec/lutaml/store/format_round_trip_spec.rb +110 -0
- data/spec/lutaml/store/format_spec.rb +70 -0
- data/spec/lutaml/store/http_cache_entry_spec.rb +203 -0
- data/spec/lutaml/store/http_cache_hal_integration_spec.rb +404 -0
- data/spec/lutaml/store/http_cache_spec.rb +422 -0
- data/spec/lutaml/store/http_header_processor_spec.rb +290 -0
- data/spec/lutaml/store/import_spec.rb +90 -0
- data/spec/lutaml/store/integrity_spec.rb +157 -0
- data/spec/lutaml/store/key_collision_serializer_spec.rb +98 -0
- data/spec/lutaml/store/load_save_spec.rb +107 -0
- data/spec/lutaml/store/lutaml_model_integration_spec.rb +291 -0
- data/spec/lutaml/store/model_serializer_spec.rb +140 -0
- data/spec/lutaml/store/store_spec.rb +182 -0
- data/spec/lutaml/store_spec.rb +21 -0
- data/spec/spec_helper.rb +16 -0
- metadata +166 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Store
|
|
5
|
+
class Monitor
|
|
6
|
+
def initialize
|
|
7
|
+
@stats = {
|
|
8
|
+
operations: Hash.new(0),
|
|
9
|
+
errors: Hash.new(0),
|
|
10
|
+
access_times: {},
|
|
11
|
+
total_access_count: 0
|
|
12
|
+
}
|
|
13
|
+
@mutex = Mutex.new
|
|
14
|
+
@start_time = Time.now
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Record an operation
|
|
18
|
+
# @param operation [Symbol] the operation type (:get, :set, :delete, :clear, etc.)
|
|
19
|
+
# @param duration [Float] operation duration in seconds
|
|
20
|
+
# @param success [Boolean] whether the operation succeeded
|
|
21
|
+
def record_operation(operation, duration: nil, success: true)
|
|
22
|
+
@mutex.synchronize do
|
|
23
|
+
@stats[:operations][operation] += 1
|
|
24
|
+
@stats[:total_access_count] += 1
|
|
25
|
+
|
|
26
|
+
@stats[:errors][operation] += 1 unless success
|
|
27
|
+
|
|
28
|
+
if duration
|
|
29
|
+
@stats[:access_times][operation] ||= []
|
|
30
|
+
@stats[:access_times][operation] << duration
|
|
31
|
+
|
|
32
|
+
# Keep only last 1000 measurements to prevent memory bloat
|
|
33
|
+
@stats[:access_times][operation].shift if @stats[:access_times][operation].size > 1000
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Record an error
|
|
39
|
+
# @param operation [Symbol] the operation that failed
|
|
40
|
+
# @param error [Exception] the error that occurred
|
|
41
|
+
def record_error(operation, _error)
|
|
42
|
+
@mutex.synchronize do
|
|
43
|
+
@stats[:errors][operation] += 1
|
|
44
|
+
@stats[:errors][:total] += 1
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Get current statistics
|
|
49
|
+
# @return [Hash] current monitoring statistics
|
|
50
|
+
def stats
|
|
51
|
+
@mutex.synchronize do
|
|
52
|
+
{
|
|
53
|
+
uptime: Time.now - @start_time,
|
|
54
|
+
total_operations: @stats[:total_access_count],
|
|
55
|
+
operations: @stats[:operations].dup,
|
|
56
|
+
errors: @stats[:errors].dup,
|
|
57
|
+
performance: calculate_performance_stats,
|
|
58
|
+
error_rate: calculate_error_rate
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Get statistics for a specific operation
|
|
64
|
+
# @param operation [Symbol] the operation to get stats for
|
|
65
|
+
# @return [Hash] operation-specific statistics
|
|
66
|
+
def operation_stats(operation)
|
|
67
|
+
@mutex.synchronize do
|
|
68
|
+
{
|
|
69
|
+
count: @stats[:operations][operation],
|
|
70
|
+
errors: @stats[:errors][operation],
|
|
71
|
+
error_rate: calculate_operation_error_rate(operation),
|
|
72
|
+
performance: calculate_operation_performance(operation)
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Reset all statistics
|
|
78
|
+
def reset
|
|
79
|
+
@mutex.synchronize do
|
|
80
|
+
@stats[:operations].clear
|
|
81
|
+
@stats[:errors].clear
|
|
82
|
+
@stats[:access_times].clear
|
|
83
|
+
@stats[:total_access_count] = 0
|
|
84
|
+
@start_time = Time.now
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Get a summary report
|
|
89
|
+
# @return [String] formatted statistics report
|
|
90
|
+
def report
|
|
91
|
+
current_stats = stats
|
|
92
|
+
|
|
93
|
+
<<~REPORT
|
|
94
|
+
Lutaml::Store Monitor Report
|
|
95
|
+
============================
|
|
96
|
+
|
|
97
|
+
Uptime: #{format_duration(current_stats[:uptime])}
|
|
98
|
+
Total Operations: #{current_stats[:total_operations]}
|
|
99
|
+
Overall Error Rate: #{(current_stats[:error_rate] * 100).round(2)}%
|
|
100
|
+
|
|
101
|
+
Operations:
|
|
102
|
+
#{format_operations(current_stats[:operations])}
|
|
103
|
+
|
|
104
|
+
Errors:
|
|
105
|
+
#{format_errors(current_stats[:errors])}
|
|
106
|
+
|
|
107
|
+
Performance:
|
|
108
|
+
#{format_performance(current_stats[:performance])}
|
|
109
|
+
REPORT
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def calculate_performance_stats
|
|
115
|
+
performance = {}
|
|
116
|
+
|
|
117
|
+
@stats[:access_times].each do |operation, times|
|
|
118
|
+
next if times.empty?
|
|
119
|
+
|
|
120
|
+
performance[operation] = {
|
|
121
|
+
avg: times.sum / times.size,
|
|
122
|
+
min: times.min,
|
|
123
|
+
max: times.max,
|
|
124
|
+
count: times.size
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
performance
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def calculate_error_rate
|
|
132
|
+
total_ops = @stats[:total_access_count]
|
|
133
|
+
total_errors = @stats[:errors].values.sum
|
|
134
|
+
|
|
135
|
+
return 0.0 if total_ops.zero?
|
|
136
|
+
|
|
137
|
+
total_errors.to_f / total_ops
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def calculate_operation_error_rate(operation)
|
|
141
|
+
total_ops = @stats[:operations][operation]
|
|
142
|
+
errors = @stats[:errors][operation]
|
|
143
|
+
|
|
144
|
+
return 0.0 if total_ops.zero?
|
|
145
|
+
|
|
146
|
+
errors.to_f / total_ops
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def calculate_operation_performance(operation)
|
|
150
|
+
times = @stats[:access_times][operation]
|
|
151
|
+
return nil unless times && !times.empty?
|
|
152
|
+
|
|
153
|
+
{
|
|
154
|
+
avg: times.sum / times.size,
|
|
155
|
+
min: times.min,
|
|
156
|
+
max: times.max,
|
|
157
|
+
count: times.size
|
|
158
|
+
}
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def format_duration(seconds)
|
|
162
|
+
if seconds < 60
|
|
163
|
+
"#{seconds.round(1)}s"
|
|
164
|
+
elsif seconds < 3600
|
|
165
|
+
"#{(seconds / 60).round(1)}m"
|
|
166
|
+
else
|
|
167
|
+
"#{(seconds / 3600).round(1)}h"
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def format_operations(operations)
|
|
172
|
+
operations.map { |op, count| " #{op}: #{count}" }.join("\n")
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def format_errors(errors)
|
|
176
|
+
return " None" if errors.empty?
|
|
177
|
+
|
|
178
|
+
errors.map { |op, count| " #{op}: #{count}" }.join("\n")
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def format_performance(performance)
|
|
182
|
+
return " No timing data available" if performance.empty?
|
|
183
|
+
|
|
184
|
+
performance.map do |op, stats|
|
|
185
|
+
" #{op}: avg=#{(stats[:avg] * 1000).round(2)}ms, " \
|
|
186
|
+
"min=#{(stats[:min] * 1000).round(2)}ms, " \
|
|
187
|
+
"max=#{(stats[:max] * 1000).round(2)}ms"
|
|
188
|
+
end.join("\n")
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Store
|
|
5
|
+
class StorageKey
|
|
6
|
+
attr_reader :class_name, :key_value
|
|
7
|
+
|
|
8
|
+
def initialize(class_name, key_value)
|
|
9
|
+
@class_name = class_name.to_s
|
|
10
|
+
@key_value = key_value.to_s
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_s
|
|
14
|
+
"#{@class_name}:#{@key_value}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
alias to_str to_s
|
|
18
|
+
|
|
19
|
+
def self.parse(string)
|
|
20
|
+
str = string.to_s
|
|
21
|
+
sep = str.rindex(/(?<!:):(?!:)/)
|
|
22
|
+
return new("", str) unless sep
|
|
23
|
+
|
|
24
|
+
new(str[0...sep], str[sep + 1..])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def eql?(other)
|
|
28
|
+
other.is_a?(StorageKey) && to_s == other.to_s
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def hash
|
|
32
|
+
to_s.hash
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def ==(other)
|
|
36
|
+
to_s == other.to_s
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/lutaml/store.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Store
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
class ConfigurationError < Error; end
|
|
7
|
+
class BackendError < Error; end
|
|
8
|
+
class ModelNotRegisteredError < Error; end
|
|
9
|
+
class InvalidKeyError < Error; end
|
|
10
|
+
class PolymorphicUpdateError < Error; end
|
|
11
|
+
class CompositeModelError < Error; end
|
|
12
|
+
|
|
13
|
+
autoload :VERSION, "lutaml/store/version"
|
|
14
|
+
autoload :Config, "lutaml/store/config"
|
|
15
|
+
autoload :Cache, "lutaml/store/cache"
|
|
16
|
+
autoload :Monitor, "lutaml/store/monitor"
|
|
17
|
+
autoload :Events, "lutaml/store/events"
|
|
18
|
+
autoload :Integrity, "lutaml/store/integrity"
|
|
19
|
+
autoload :Compression, "lutaml/store/compression"
|
|
20
|
+
autoload :BasicStore, "lutaml/store/basic_store"
|
|
21
|
+
autoload :CacheStore, "lutaml/store/cache_store"
|
|
22
|
+
autoload :ModelSerializer, "lutaml/store/model_serializer"
|
|
23
|
+
autoload :ModelRegistration, "lutaml/store/model_registration"
|
|
24
|
+
autoload :ModelRegistry, "lutaml/store/model_registry"
|
|
25
|
+
autoload :CompositeModelHandler, "lutaml/store/composite_model_handler"
|
|
26
|
+
autoload :AttributeUpdater, "lutaml/store/attribute_updater"
|
|
27
|
+
autoload :DatabaseStore, "lutaml/store/database_store"
|
|
28
|
+
autoload :Adapter, "lutaml/store/adapter"
|
|
29
|
+
autoload :StorageKey, "lutaml/store/storage_key"
|
|
30
|
+
autoload :Format, "lutaml/store/format"
|
|
31
|
+
|
|
32
|
+
autoload :HttpCache, "lutaml/store/http_cache"
|
|
33
|
+
autoload :HttpCacheConfig, "lutaml/store/http_cache_config"
|
|
34
|
+
autoload :HttpCacheEntry, "lutaml/store/http_cache_entry"
|
|
35
|
+
autoload :HttpHeaderProcessor, "lutaml/store/http_header_processor"
|
|
36
|
+
|
|
37
|
+
def self.new(adapter:, models: [], **options)
|
|
38
|
+
DatabaseStore.new(adapter: adapter, models: models, **options)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/lutaml/store/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "lutaml-store"
|
|
7
|
+
spec.version = Lutaml::Store::VERSION
|
|
8
|
+
spec.authors = ["Ronald Tse"]
|
|
9
|
+
spec.email = ["ronald.tse@ribose.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Store-centric database-style API for Lutaml::Model with multi-backend persistence."
|
|
12
|
+
spec.description = <<~DESCRIPTION
|
|
13
|
+
Provides a unified store interface for Lutaml::Model objects with model registry,
|
|
14
|
+
polymorphic support, composite relationships, and multiple storage backends
|
|
15
|
+
(memory, filesystem, SQLite).
|
|
16
|
+
DESCRIPTION
|
|
17
|
+
|
|
18
|
+
spec.homepage = "https://github.com/lutaml/lutaml-store"
|
|
19
|
+
spec.license = "BSD-2-Clause"
|
|
20
|
+
|
|
21
|
+
spec.bindir = "exe"
|
|
22
|
+
spec.require_paths = ["lib"]
|
|
23
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
|
|
24
|
+
|
|
25
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
26
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
27
|
+
f.match(%r{^(test|features)/})
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
31
|
+
|
|
32
|
+
spec.add_dependency "lutaml-model", "~> 0.8.15"
|
|
33
|
+
|
|
34
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
35
|
+
end
|