lutaml-store 0.2.2 → 0.2.4
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/.rubocop_todo.yml +22 -13
- data/lib/lutaml/store/adapter/base.rb +28 -0
- data/lib/lutaml/store/adapter/memory.rb +213 -121
- data/lib/lutaml/store/adapter/sqlite.rb +181 -7
- data/lib/lutaml/store/database_store.rb +302 -49
- data/lib/lutaml/store/predicate.rb +248 -0
- data/lib/lutaml/store/query.rb +259 -0
- data/lib/lutaml/store/version.rb +1 -1
- data/lib/lutaml/store.rb +2 -0
- metadata +12 -4
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Store
|
|
5
|
+
module Predicate
|
|
6
|
+
class Base
|
|
7
|
+
attr_reader :field, :value
|
|
8
|
+
|
|
9
|
+
def initialize(field, value = nil)
|
|
10
|
+
@field = field.to_sym
|
|
11
|
+
@value = value
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def match?(_target)
|
|
15
|
+
raise NotImplementedError
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def negate
|
|
19
|
+
raise NotImplementedError
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def hash_evaluable?(hash_data)
|
|
23
|
+
hash_data.key?(@field.to_s)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def extract(target)
|
|
29
|
+
target.is_a?(Hash) ? target[@field.to_s] : target.public_send(@field)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class Equal < Base
|
|
34
|
+
def match?(target)
|
|
35
|
+
extract(target) == @value
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def negate
|
|
39
|
+
NotEqual.new(@field, @value)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class NotEqual < Base
|
|
44
|
+
def match?(target)
|
|
45
|
+
extract(target) != @value
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def negate
|
|
49
|
+
Equal.new(@field, @value)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class GreaterThan < Base
|
|
54
|
+
def match?(target)
|
|
55
|
+
val = extract(target)
|
|
56
|
+
return false if val.nil?
|
|
57
|
+
|
|
58
|
+
val > @value
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def negate
|
|
62
|
+
LessThanOrEqual.new(@field, @value)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class LessThan < Base
|
|
67
|
+
def match?(target)
|
|
68
|
+
val = extract(target)
|
|
69
|
+
return false if val.nil?
|
|
70
|
+
|
|
71
|
+
val < @value
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def negate
|
|
75
|
+
GreaterThanOrEqual.new(@field, @value)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class GreaterThanOrEqual < Base
|
|
80
|
+
def match?(target)
|
|
81
|
+
val = extract(target)
|
|
82
|
+
return false if val.nil?
|
|
83
|
+
|
|
84
|
+
val >= @value
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def negate
|
|
88
|
+
LessThan.new(@field, @value)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
class LessThanOrEqual < Base
|
|
93
|
+
def match?(target)
|
|
94
|
+
val = extract(target)
|
|
95
|
+
return false if val.nil?
|
|
96
|
+
|
|
97
|
+
val <= @value
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def negate
|
|
101
|
+
GreaterThan.new(@field, @value)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
class Between < Base
|
|
106
|
+
def match?(target)
|
|
107
|
+
val = extract(target)
|
|
108
|
+
return false if val.nil?
|
|
109
|
+
|
|
110
|
+
@value.cover?(val)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def negate
|
|
114
|
+
NotBetween.new(@field, @value)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
class NotBetween < Base
|
|
119
|
+
def match?(target)
|
|
120
|
+
val = extract(target)
|
|
121
|
+
return false if val.nil?
|
|
122
|
+
|
|
123
|
+
!@value.cover?(val)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def negate
|
|
127
|
+
Between.new(@field, @value)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
class In < Base
|
|
132
|
+
def match?(target)
|
|
133
|
+
val = extract(target)
|
|
134
|
+
return false if val.nil?
|
|
135
|
+
|
|
136
|
+
@value.include?(val)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def negate
|
|
140
|
+
NotIn.new(@field, @value)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
class NotIn < Base
|
|
145
|
+
def match?(target)
|
|
146
|
+
val = extract(target)
|
|
147
|
+
return false if val.nil?
|
|
148
|
+
|
|
149
|
+
!@value.include?(val)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def negate
|
|
153
|
+
In.new(@field, @value)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
class Matches < Base
|
|
158
|
+
def match?(target)
|
|
159
|
+
val = extract(target)
|
|
160
|
+
return false if val.nil?
|
|
161
|
+
|
|
162
|
+
@value.match?(val.to_s)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def negate
|
|
166
|
+
NotMatches.new(@field, @value)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
class NotMatches < Base
|
|
171
|
+
def match?(target)
|
|
172
|
+
val = extract(target)
|
|
173
|
+
return true if val.nil?
|
|
174
|
+
|
|
175
|
+
!@value.match?(val.to_s)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def negate
|
|
179
|
+
Matches.new(@field, @value)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
class Nil < Base
|
|
184
|
+
def match?(target)
|
|
185
|
+
extract(target).nil?
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def negate
|
|
189
|
+
NotNil.new(@field)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
class NotNil < Base
|
|
194
|
+
def match?(target)
|
|
195
|
+
!extract(target).nil?
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def negate
|
|
199
|
+
Nil.new(@field)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# ── Factory methods ──
|
|
204
|
+
|
|
205
|
+
def self.gt(field, value)
|
|
206
|
+
GreaterThan.new(field, value)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def self.lt(field, value)
|
|
210
|
+
LessThan.new(field, value)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def self.gte(field, value)
|
|
214
|
+
GreaterThanOrEqual.new(field, value)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def self.lte(field, value)
|
|
218
|
+
LessThanOrEqual.new(field, value)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def self.not(field, value)
|
|
222
|
+
NotEqual.new(field, value)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def self.matches(field, pattern)
|
|
226
|
+
Matches.new(field, pattern)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def self.nil(field)
|
|
230
|
+
Nil.new(field)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def self.not_nil(field)
|
|
234
|
+
NotNil.new(field)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def self.build_from_hash(conditions)
|
|
238
|
+
conditions.map do |field, value|
|
|
239
|
+
case value
|
|
240
|
+
when Range then Between.new(field, value)
|
|
241
|
+
when Array then In.new(field, value)
|
|
242
|
+
else Equal.new(field, value)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Store
|
|
5
|
+
# Lazy, chainable query object — inspired by ActiveRecord::Relation.
|
|
6
|
+
# Collects predicates, sort orders, limit, and offset. Nothing executes
|
|
7
|
+
# until a terminal method is called (to_a, each, first, count, etc.).
|
|
8
|
+
class Query
|
|
9
|
+
include Enumerable
|
|
10
|
+
|
|
11
|
+
attr_reader :model_class, :predicates, :orders, :limit_value, :offset_value
|
|
12
|
+
|
|
13
|
+
def initialize(store, model_class, predicates: [], orders: [],
|
|
14
|
+
limit_value: nil, offset_value: nil)
|
|
15
|
+
@store = store
|
|
16
|
+
@model_class = model_class
|
|
17
|
+
@predicates = predicates.dup.freeze
|
|
18
|
+
@orders = orders.dup.freeze
|
|
19
|
+
@limit_value = limit_value
|
|
20
|
+
@offset_value = offset_value
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# ── Chainable methods (return new Query) ──
|
|
24
|
+
|
|
25
|
+
def where(*predicates_or_conditions, **kwargs)
|
|
26
|
+
conditions = kwargs
|
|
27
|
+
new_predicates = []
|
|
28
|
+
|
|
29
|
+
predicates_or_conditions.each do |arg|
|
|
30
|
+
if arg.is_a?(Predicate::Base)
|
|
31
|
+
new_predicates << arg
|
|
32
|
+
elsif arg.is_a?(Hash)
|
|
33
|
+
conditions = conditions.merge(arg)
|
|
34
|
+
else
|
|
35
|
+
raise ArgumentError, "where accepts Predicate objects or Hash conditions, got #{arg.class}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
new_predicates.concat(Predicate.build_from_hash(conditions)) unless conditions.empty?
|
|
40
|
+
chain(predicates: @predicates + new_predicates)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def not(conditions = {}, **kwargs)
|
|
44
|
+
conditions = conditions.merge(kwargs)
|
|
45
|
+
negated = Predicate.build_from_hash(conditions).map(&:negate)
|
|
46
|
+
chain(predicates: @predicates + negated)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def order(*specs)
|
|
50
|
+
new_orders = parse_order_specs(specs)
|
|
51
|
+
chain(orders: @orders + new_orders)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def limit(count)
|
|
55
|
+
chain(limit_value: count)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def offset(count)
|
|
59
|
+
chain(offset_value: count)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def reverse_order
|
|
63
|
+
reversed = @orders.map { |o| Order.new(o.field, o.direction == :asc ? :desc : :asc) }
|
|
64
|
+
chain(orders: reversed)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# ── Terminal methods (execute the query) ──
|
|
68
|
+
|
|
69
|
+
def to_a
|
|
70
|
+
@to_a_result ||= @store.execute_query(self)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def each(&block)
|
|
74
|
+
to_a.each(&block)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def first
|
|
78
|
+
self.class.new(@store, @model_class, predicates: @predicates,
|
|
79
|
+
orders: @orders, limit_value: 1,
|
|
80
|
+
offset_value: @offset_value).to_a.first
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def last
|
|
84
|
+
reversed = @orders.map { |o| Order.new(o.field, o.direction == :asc ? :desc : :asc) }
|
|
85
|
+
self.class.new(@store, @model_class, predicates: @predicates,
|
|
86
|
+
orders: reversed, limit_value: 1,
|
|
87
|
+
offset_value: @offset_value).to_a.first
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def find_by(**conditions)
|
|
91
|
+
where(**conditions).first
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def find_by!(**conditions)
|
|
95
|
+
result = find_by(**conditions)
|
|
96
|
+
raise ModelNotRegisteredError, "No #{@model_class} found matching #{conditions}" unless result
|
|
97
|
+
|
|
98
|
+
result
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def count
|
|
102
|
+
return to_a.size if @limit_value || @offset_value
|
|
103
|
+
|
|
104
|
+
@store.count_query(self)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
alias size count
|
|
108
|
+
alias length count
|
|
109
|
+
|
|
110
|
+
def exists?
|
|
111
|
+
self.class.new(@store, @model_class, predicates: @predicates,
|
|
112
|
+
orders: [], limit_value: 1, offset_value: nil).to_a.any?
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def empty?
|
|
116
|
+
!exists?
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def any?
|
|
120
|
+
exists?
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def none?
|
|
124
|
+
!exists?
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def one?
|
|
128
|
+
self.class.new(@store, @model_class, predicates: @predicates,
|
|
129
|
+
orders: [], limit_value: 2, offset_value: nil).to_a.size == 1
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def many?
|
|
133
|
+
self.class.new(@store, @model_class, predicates: @predicates,
|
|
134
|
+
orders: [], limit_value: 2, offset_value: nil).to_a.size > 1
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# ── Calculation shortcuts ──
|
|
138
|
+
|
|
139
|
+
def pluck(*fields)
|
|
140
|
+
to_a.map do |model|
|
|
141
|
+
if fields.size == 1
|
|
142
|
+
model.public_send(fields.first)
|
|
143
|
+
else
|
|
144
|
+
fields.map { |f| model.public_send(f) }
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def distinct(field = nil)
|
|
150
|
+
if field
|
|
151
|
+
to_a.map { |m| m.public_send(field) }.uniq
|
|
152
|
+
else
|
|
153
|
+
to_a.uniq
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def sum(field)
|
|
158
|
+
to_a.sum { |m| m.public_send(field) }
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def average(field)
|
|
162
|
+
values = to_a.map { |m| m.public_send(field) }.compact
|
|
163
|
+
return 0.0 if values.empty?
|
|
164
|
+
|
|
165
|
+
values.sum.to_f / values.size
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def minimum(field)
|
|
169
|
+
to_a.min_by { |m| m.public_send(field) }
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def maximum(field)
|
|
173
|
+
to_a.max_by { |m| m.public_send(field) }
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# ── Batch processing ──
|
|
177
|
+
|
|
178
|
+
def find_each(batch_size: 1000, &block)
|
|
179
|
+
raise ArgumentError, "find_each does not support limit/offset" if @limit_value || @offset_value
|
|
180
|
+
|
|
181
|
+
cursor = nil
|
|
182
|
+
loop do
|
|
183
|
+
batch = @store.fetch_batch(self, after: cursor, limit: batch_size)
|
|
184
|
+
break if batch.empty?
|
|
185
|
+
|
|
186
|
+
batch.each(&block)
|
|
187
|
+
cursor = @store.last_storage_key_from(batch, @model_class)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def in_batches(of: 1000)
|
|
192
|
+
raise ArgumentError, "in_batches does not support limit/offset" if @limit_value || @offset_value
|
|
193
|
+
|
|
194
|
+
cursor = nil
|
|
195
|
+
loop do
|
|
196
|
+
batch = @store.fetch_batch(self, after: cursor, limit: of)
|
|
197
|
+
break if batch.empty?
|
|
198
|
+
|
|
199
|
+
yield batch
|
|
200
|
+
cursor = @store.last_storage_key_from(batch, @model_class)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# ── Scopes ──
|
|
205
|
+
|
|
206
|
+
def apply(scope_name, *args, **kwargs)
|
|
207
|
+
scope_body = @store.scope_for(scope_name)
|
|
208
|
+
instance_exec(*args, **kwargs, &scope_body)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def inspect
|
|
212
|
+
parts = [@model_class.to_s]
|
|
213
|
+
parts << "WHERE #{@predicates.map(&:inspect).join(" AND ")}" if @predicates.any?
|
|
214
|
+
parts << "ORDER BY #{@orders.map { |o| "#{o.field} #{o.direction}" }.join(", ")}" if @orders.any?
|
|
215
|
+
parts << "LIMIT #{@limit_value}" if @limit_value
|
|
216
|
+
parts << "OFFSET #{@offset_value}" if @offset_value
|
|
217
|
+
"#<Query #{parts.join(" ")}>"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
private
|
|
221
|
+
|
|
222
|
+
def chain(**overrides)
|
|
223
|
+
self.class.new(
|
|
224
|
+
@store,
|
|
225
|
+
@model_class,
|
|
226
|
+
predicates: overrides.fetch(:predicates, @predicates),
|
|
227
|
+
orders: overrides.fetch(:orders, @orders),
|
|
228
|
+
limit_value: overrides.fetch(:limit_value, @limit_value),
|
|
229
|
+
offset_value: overrides.fetch(:offset_value, @offset_value)
|
|
230
|
+
)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def parse_order_specs(specs)
|
|
234
|
+
orders = []
|
|
235
|
+
i = 0
|
|
236
|
+
while i < specs.size
|
|
237
|
+
item = specs[i]
|
|
238
|
+
case item
|
|
239
|
+
when Hash
|
|
240
|
+
item.each { |field, dir| orders << Order.new(field, dir) }
|
|
241
|
+
when Symbol, String
|
|
242
|
+
next_val = specs[i + 1]
|
|
243
|
+
if next_val.is_a?(Symbol) && %i[asc desc].include?(next_val)
|
|
244
|
+
orders << Order.new(item, next_val)
|
|
245
|
+
i += 1
|
|
246
|
+
else
|
|
247
|
+
orders << Order.new(item, :asc)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
i += 1
|
|
251
|
+
end
|
|
252
|
+
orders
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Sort specification value object
|
|
257
|
+
Order = Struct.new(:field, :direction)
|
|
258
|
+
end
|
|
259
|
+
end
|
data/lib/lutaml/store/version.rb
CHANGED
data/lib/lutaml/store.rb
CHANGED
|
@@ -32,6 +32,8 @@ module Lutaml
|
|
|
32
32
|
autoload :Adapter, "lutaml/store/adapter"
|
|
33
33
|
autoload :StorageKey, "lutaml/store/storage_key"
|
|
34
34
|
autoload :Format, "lutaml/store/format"
|
|
35
|
+
autoload :Query, "lutaml/store/query"
|
|
36
|
+
autoload :Predicate, "lutaml/store/predicate"
|
|
35
37
|
|
|
36
38
|
autoload :HttpCache, "lutaml/store/http_cache"
|
|
37
39
|
autoload :HttpCacheConfig, "lutaml/store/http_cache_config"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lutaml-store
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ronald Tse
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: lutaml-model
|
|
@@ -28,16 +28,22 @@ dependencies:
|
|
|
28
28
|
name: rubyzip
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
|
-
- - "
|
|
31
|
+
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
33
|
version: '2.3'
|
|
34
|
+
- - "<"
|
|
35
|
+
- !ruby/object:Gem::Version
|
|
36
|
+
version: '4'
|
|
34
37
|
type: :runtime
|
|
35
38
|
prerelease: false
|
|
36
39
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
40
|
requirements:
|
|
38
|
-
- - "
|
|
41
|
+
- - ">="
|
|
39
42
|
- !ruby/object:Gem::Version
|
|
40
43
|
version: '2.3'
|
|
44
|
+
- - "<"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '4'
|
|
41
47
|
description: |
|
|
42
48
|
Provides a unified store interface for Lutaml::Model objects with model registry,
|
|
43
49
|
polymorphic support, composite relationships, and multiple storage backends
|
|
@@ -94,6 +100,8 @@ files:
|
|
|
94
100
|
- lib/lutaml/store/package_transport/base.rb
|
|
95
101
|
- lib/lutaml/store/package_transport/directory_transport.rb
|
|
96
102
|
- lib/lutaml/store/package_transport/zip_transport.rb
|
|
103
|
+
- lib/lutaml/store/predicate.rb
|
|
104
|
+
- lib/lutaml/store/query.rb
|
|
97
105
|
- lib/lutaml/store/storage_key.rb
|
|
98
106
|
- lib/lutaml/store/version.rb
|
|
99
107
|
- sig/lutaml/store.rbs
|