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.
Files changed (110) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/main.yml +27 -0
  3. data/.gitignore +12 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +10 -0
  6. data/.rubocop_todo.yml +450 -0
  7. data/CLAUDE.md +57 -0
  8. data/CODE_OF_CONDUCT.md +132 -0
  9. data/CORRECTED_HTTP_CACHE_IMPLEMENTATION.md +209 -0
  10. data/CORRECTED_HTTP_CACHE_PLAN.md +164 -0
  11. data/Gemfile +15 -0
  12. data/Gemfile.lock +220 -0
  13. data/README.adoc +1430 -0
  14. data/Rakefile +12 -0
  15. data/TODO.impl/0-lutaml-store-self-quality.md +112 -0
  16. data/TODO.impl/1-lutaml-hal-migration.md +60 -0
  17. data/TODO.impl/2-glossarist-migration.md +359 -0
  18. data/TODO.impl/3-lutaml-jsonschema-migration.md +273 -0
  19. data/bin/console +11 -0
  20. data/bin/setup +8 -0
  21. data/demo/Gemfile +15 -0
  22. data/demo/Gemfile.lock +61 -0
  23. data/demo/README.adoc +301 -0
  24. data/demo/data/vcards/co/contact_10_thompson.data +1 -0
  25. data/demo/data/vcards/co/contact_10_thompson.meta +1 -0
  26. data/demo/data/vcards/co/contact_1_doe.data +1 -0
  27. data/demo/data/vcards/co/contact_1_doe.meta +1 -0
  28. data/demo/data/vcards/co/contact_2_smith.data +1 -0
  29. data/demo/data/vcards/co/contact_2_smith.meta +1 -0
  30. data/demo/data/vcards/co/contact_3_johnson.data +1 -0
  31. data/demo/data/vcards/co/contact_3_johnson.meta +1 -0
  32. data/demo/data/vcards/co/contact_4_garcia.data +1 -0
  33. data/demo/data/vcards/co/contact_4_garcia.meta +1 -0
  34. data/demo/data/vcards/co/contact_5_wilson.data +1 -0
  35. data/demo/data/vcards/co/contact_5_wilson.meta +1 -0
  36. data/demo/data/vcards/co/contact_6_brown.data +1 -0
  37. data/demo/data/vcards/co/contact_6_brown.meta +1 -0
  38. data/demo/data/vcards/co/contact_7_davis.data +1 -0
  39. data/demo/data/vcards/co/contact_7_davis.meta +1 -0
  40. data/demo/data/vcards/co/contact_8_anderson.data +1 -0
  41. data/demo/data/vcards/co/contact_8_anderson.meta +1 -0
  42. data/demo/data/vcards/co/contact_9_taylor.data +1 -0
  43. data/demo/data/vcards/co/contact_9_taylor.meta +1 -0
  44. data/demo/data/vcards.db +0 -0
  45. data/demo/pottery_class_demo.rb +164 -0
  46. data/demo/vcard_models.rb +140 -0
  47. data/demo/vcard_store_demo.rb +526 -0
  48. data/lib/lutaml/store/adapter/base.rb +65 -0
  49. data/lib/lutaml/store/adapter/filesystem.rb +288 -0
  50. data/lib/lutaml/store/adapter/memory.rb +225 -0
  51. data/lib/lutaml/store/adapter/sqlite.rb +193 -0
  52. data/lib/lutaml/store/adapter.rb +12 -0
  53. data/lib/lutaml/store/attribute_updater.rb +198 -0
  54. data/lib/lutaml/store/basic_store.rb +190 -0
  55. data/lib/lutaml/store/cache.rb +108 -0
  56. data/lib/lutaml/store/cache_store.rb +282 -0
  57. data/lib/lutaml/store/composite_model_handler.rb +169 -0
  58. data/lib/lutaml/store/compression.rb +137 -0
  59. data/lib/lutaml/store/config.rb +178 -0
  60. data/lib/lutaml/store/database_store.rb +425 -0
  61. data/lib/lutaml/store/events.rb +92 -0
  62. data/lib/lutaml/store/format/base.rb +33 -0
  63. data/lib/lutaml/store/format/json.rb +25 -0
  64. data/lib/lutaml/store/format/jsonl.rb +37 -0
  65. data/lib/lutaml/store/format/marshal_format.rb +37 -0
  66. data/lib/lutaml/store/format/yaml.rb +29 -0
  67. data/lib/lutaml/store/format/yamls.rb +35 -0
  68. data/lib/lutaml/store/format.rb +33 -0
  69. data/lib/lutaml/store/http_cache.rb +279 -0
  70. data/lib/lutaml/store/http_cache_config.rb +53 -0
  71. data/lib/lutaml/store/http_cache_entry.rb +69 -0
  72. data/lib/lutaml/store/http_header_processor.rb +175 -0
  73. data/lib/lutaml/store/integrity.rb +102 -0
  74. data/lib/lutaml/store/model_registration.rb +75 -0
  75. data/lib/lutaml/store/model_registry.rb +123 -0
  76. data/lib/lutaml/store/model_serializer.rb +69 -0
  77. data/lib/lutaml/store/monitor.rb +192 -0
  78. data/lib/lutaml/store/storage_key.rb +40 -0
  79. data/lib/lutaml/store/version.rb +7 -0
  80. data/lib/lutaml/store.rb +41 -0
  81. data/lutaml-store.gemspec +35 -0
  82. data/plan.adoc +606 -0
  83. data/sig/lutaml/store.rbs +6 -0
  84. data/spec/lutaml/store/adapter_interface_spec.rb +89 -0
  85. data/spec/lutaml/store/anti_pattern_guard_spec.rb +35 -0
  86. data/spec/lutaml/store/anti_pattern_spec.rb +78 -0
  87. data/spec/lutaml/store/autoload_spec.rb +34 -0
  88. data/spec/lutaml/store/cache_store_spec.rb +271 -0
  89. data/spec/lutaml/store/compression_spec.rb +78 -0
  90. data/spec/lutaml/store/config_enhanced_spec.rb +158 -0
  91. data/spec/lutaml/store/corrected_http_cache_integration_spec.rb +336 -0
  92. data/spec/lutaml/store/custom_serializer_spec.rb +108 -0
  93. data/spec/lutaml/store/database_store_spec.rb +279 -0
  94. data/spec/lutaml/store/file_io_spec.rb +219 -0
  95. data/spec/lutaml/store/format_round_trip_spec.rb +110 -0
  96. data/spec/lutaml/store/format_spec.rb +70 -0
  97. data/spec/lutaml/store/http_cache_entry_spec.rb +203 -0
  98. data/spec/lutaml/store/http_cache_hal_integration_spec.rb +404 -0
  99. data/spec/lutaml/store/http_cache_spec.rb +422 -0
  100. data/spec/lutaml/store/http_header_processor_spec.rb +290 -0
  101. data/spec/lutaml/store/import_spec.rb +90 -0
  102. data/spec/lutaml/store/integrity_spec.rb +157 -0
  103. data/spec/lutaml/store/key_collision_serializer_spec.rb +98 -0
  104. data/spec/lutaml/store/load_save_spec.rb +107 -0
  105. data/spec/lutaml/store/lutaml_model_integration_spec.rb +291 -0
  106. data/spec/lutaml/store/model_serializer_spec.rb +140 -0
  107. data/spec/lutaml/store/store_spec.rb +182 -0
  108. data/spec/lutaml/store_spec.rb +21 -0
  109. data/spec/spec_helper.rb +16 -0
  110. 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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Store
5
+ VERSION = "0.1.1"
6
+ end
7
+ end
@@ -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