better_auth-mongodb 0.8.0 → 0.10.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 +8 -1
- data/README.md +18 -4
- data/lib/better_auth/mongodb/version.rb +1 -1
- data/lib/better_auth/mongodb.rb +117 -14
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7f38f577d0bab0d49bd3fd44384f0869e6323abcbe1ecaf6096e1bfdab3ed45a
|
|
4
|
+
data.tar.gz: 7b4074915658cc13c7e0ae92f545a8a98fb50764647c153c168ed0bc9675218d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1acce3252242ec0a1470496cb819e6a9e6027ddff9c03df2ecf38a106293c28322cbaeeeb05bcb80e81867e7e75966bb3fc275a821d2c20f9b8a7aab1380d94f
|
|
7
|
+
data.tar.gz: 1a85cb29c777b43dbf086339e087d2062153de6a945a1f5750abb95130bf826a9734d8206060f04e93cad6c51cce08430704226976e5e1438c3b00a5ae3aa5b0
|
data/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.10.0 - 2026-05-21
|
|
6
|
+
|
|
5
7
|
- Rename the canonical Ruby gem to `better_auth-mongodb` while keeping
|
|
6
8
|
`better_auth-mongo-adapter` as a deprecated compatibility package.
|
|
9
|
+
- Fixed `use_plural: true` so configured schema `model_name` values such as
|
|
10
|
+
`people` and `api_keys` are used directly instead of being pluralized again.
|
|
11
|
+
- Clarified MongoDB filter docs: `in` requires array values, while `not_in`
|
|
12
|
+
accepts scalar values as a Ruby adapter-family compatibility behavior.
|
|
13
|
+
- Improved MongoDB owner counting, nullable unique indexes, and adapter parity coverage.
|
|
7
14
|
|
|
8
15
|
## 0.7.0 - 2026-05-05
|
|
9
16
|
|
|
@@ -12,7 +19,7 @@
|
|
|
12
19
|
- Consolidated Mongo fake test support and strengthened transaction rollback coverage for staged mutations.
|
|
13
20
|
- Apply `advanced[:database][:default_find_many_limit]` to uncapped `find_many` calls and one-to-many Mongo `$lookup` joins, defaulting to 100 when omitted.
|
|
14
21
|
- Match upstream Mongo where-clause semantics for mixed connectors by bucketing multi-clause filters into `$and` and `$or` arrays instead of left-fold nesting.
|
|
15
|
-
- Allow scalar values for `
|
|
22
|
+
- Allow scalar values for `not_in` filters as an intentional Ruby adapter-family adaptation while keeping `in` aligned with the shared adapter array contract.
|
|
16
23
|
|
|
17
24
|
## 0.1.1 - 2026-04-30
|
|
18
25
|
|
data/README.md
CHANGED
|
@@ -94,12 +94,26 @@ auth = BetterAuth.auth(
|
|
|
94
94
|
|
|
95
95
|
The same default applies to one-to-many join lookups when the join config does not set `limit:`. Passing an explicit `limit:` to `find_many` or to the join config overrides the default.
|
|
96
96
|
|
|
97
|
+
Explicit `limit:` values must be positive integers. Explicit `offset:` values
|
|
98
|
+
must be zero or positive integers. Invalid configured defaults, including
|
|
99
|
+
non-positive `default_find_many_limit` values, fall back to the built-in cap of
|
|
100
|
+
100 records.
|
|
101
|
+
|
|
97
102
|
One-to-one joins ignore one-to-many limits. They are returned as a single object or `nil`.
|
|
98
103
|
|
|
99
|
-
Ruby's
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
Ruby's MongoDB adapter matches upstream's adapter factory by requiring array
|
|
105
|
+
values for the `in` filter operator. The Ruby adapter still accepts scalar
|
|
106
|
+
values for `not_in` and coerces them to a one-element list, matching the Ruby
|
|
107
|
+
adapter-family behavior.
|
|
108
|
+
|
|
109
|
+
Update calls intentionally strip logical `id` / Mongo `_id` from `$set` payloads
|
|
110
|
+
so callers cannot mutate immutable Mongo identifiers. If an update contains no
|
|
111
|
+
caller-supplied schema fields after id and unknown fields are ignored, the
|
|
112
|
+
adapter raises `BAD_REQUEST` before calling MongoDB.
|
|
113
|
+
|
|
114
|
+
Default storage field names use Ruby's snake_case convention. For example, an
|
|
115
|
+
additional or plugin field named `camelCaseField` is stored as
|
|
116
|
+
`camel_case_field` unless the schema config provides an explicit `fieldName`.
|
|
103
117
|
|
|
104
118
|
## Compatibility
|
|
105
119
|
|
data/lib/better_auth/mongodb.rb
CHANGED
|
@@ -46,12 +46,12 @@ module BetterAuth
|
|
|
46
46
|
def find_many(model:, where: [], sort_by: nil, limit: nil, offset: nil, select: nil, join: nil)
|
|
47
47
|
model = model.to_s
|
|
48
48
|
pipeline = [{"$match" => mongo_filter(model, where || [])}]
|
|
49
|
+
pipeline << {"$sort" => {sort_field(model, sort_by) => sort_direction(sort_by)}} if sort_by
|
|
50
|
+
pipeline << {"$skip" => non_negative_integer!(offset, "offset")} unless offset.nil?
|
|
51
|
+
effective_limit = limit.nil? ? default_find_many_limit : positive_integer!(limit, "limit")
|
|
52
|
+
pipeline << {"$limit" => effective_limit}
|
|
49
53
|
pipeline.concat(join_stages(model, join)) if join
|
|
50
54
|
pipeline << {"$project" => projection_for(model, select, join)} if select && !select.empty?
|
|
51
|
-
pipeline << {"$sort" => {sort_field(model, sort_by) => sort_direction(sort_by)}} if sort_by
|
|
52
|
-
pipeline << {"$skip" => offset.to_i} if offset
|
|
53
|
-
effective_limit = limit.nil? ? default_find_many_limit : limit.to_i
|
|
54
|
-
pipeline << {"$limit" => effective_limit} if effective_limit.positive?
|
|
55
55
|
|
|
56
56
|
collection_for(model)
|
|
57
57
|
.aggregate(pipeline, session_options)
|
|
@@ -61,9 +61,11 @@ module BetterAuth
|
|
|
61
61
|
|
|
62
62
|
def update(model:, where:, update:)
|
|
63
63
|
model = model.to_s
|
|
64
|
+
ensure_update_input_has_fields!(model, update)
|
|
64
65
|
data = transform_input(model, update, "update", true)
|
|
65
66
|
document = to_document(model, data)
|
|
66
67
|
document.delete("_id")
|
|
68
|
+
ensure_update_document!(document)
|
|
67
69
|
result = collection_for(model).find_one_and_update(
|
|
68
70
|
mongo_filter(model, where || []),
|
|
69
71
|
{"$set" => document},
|
|
@@ -75,9 +77,11 @@ module BetterAuth
|
|
|
75
77
|
|
|
76
78
|
def update_many(model:, where:, update:)
|
|
77
79
|
model = model.to_s
|
|
80
|
+
ensure_update_input_has_fields!(model, update)
|
|
78
81
|
data = transform_input(model, update, "update", true)
|
|
79
82
|
document = to_document(model, data)
|
|
80
83
|
document.delete("_id")
|
|
84
|
+
ensure_update_document!(document)
|
|
81
85
|
result = collection_for(model).update_many(
|
|
82
86
|
mongo_filter(model, where || []),
|
|
83
87
|
{"$set" => document},
|
|
@@ -115,8 +119,8 @@ module BetterAuth
|
|
|
115
119
|
|
|
116
120
|
collection = collection_for(model)
|
|
117
121
|
key = storage_field(model, field)
|
|
118
|
-
index_options = attributes
|
|
119
|
-
collection
|
|
122
|
+
index_options = index_options_for(attributes)
|
|
123
|
+
create_index!(collection, {key => 1}, index_options)
|
|
120
124
|
{
|
|
121
125
|
collection: collection_name(model),
|
|
122
126
|
field: field,
|
|
@@ -147,6 +151,34 @@ module BetterAuth
|
|
|
147
151
|
|
|
148
152
|
private
|
|
149
153
|
|
|
154
|
+
def index_options_for(attributes)
|
|
155
|
+
return {} unless attributes[:unique]
|
|
156
|
+
|
|
157
|
+
options = {unique: true}
|
|
158
|
+
options[:sparse] = true unless attributes[:required]
|
|
159
|
+
options
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def create_index!(collection, keys, options)
|
|
163
|
+
collection.indexes.create_one(keys, options)
|
|
164
|
+
rescue Mongo::Error::OperationFailure => error
|
|
165
|
+
raise unless index_options_conflict?(error) && collection.indexes.respond_to?(:drop_one)
|
|
166
|
+
|
|
167
|
+
collection.indexes.drop_one(default_index_name(keys))
|
|
168
|
+
collection.indexes.create_one(keys, options)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def index_options_conflict?(error)
|
|
172
|
+
error.message.include?("IndexOptionsConflict") ||
|
|
173
|
+
error.message.include?("IndexKeySpecsConflict") ||
|
|
174
|
+
error.message.include?("already exists with different options") ||
|
|
175
|
+
error.message.include?("same name as the requested index")
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def default_index_name(keys)
|
|
179
|
+
keys.map { |field, direction| "#{field}_#{direction}" }.join("_")
|
|
180
|
+
end
|
|
181
|
+
|
|
150
182
|
def transform_input(model, data, action, force_allow_id)
|
|
151
183
|
fields = fields_for(model)
|
|
152
184
|
input = stringify_keys(data)
|
|
@@ -180,7 +212,7 @@ module BetterAuth
|
|
|
180
212
|
end
|
|
181
213
|
|
|
182
214
|
def mongo_filter(model, where)
|
|
183
|
-
clauses =
|
|
215
|
+
clauses = validate_where!(where)
|
|
184
216
|
return {} if clauses.empty?
|
|
185
217
|
|
|
186
218
|
conditions = clauses.map do |clause|
|
|
@@ -205,7 +237,8 @@ module BetterAuth
|
|
|
205
237
|
value = options.advanced.dig(:database, :default_find_many_limit)
|
|
206
238
|
return 100 if value.nil?
|
|
207
239
|
|
|
208
|
-
Integer(value)
|
|
240
|
+
parsed = Integer(value)
|
|
241
|
+
parsed.positive? ? parsed : 100
|
|
209
242
|
rescue ArgumentError, TypeError
|
|
210
243
|
100
|
|
211
244
|
end
|
|
@@ -218,7 +251,10 @@ module BetterAuth
|
|
|
218
251
|
operator = (fetch_key(clause, :operator) || "eq").to_s.downcase
|
|
219
252
|
value = fetch_key(clause, :value)
|
|
220
253
|
|
|
221
|
-
|
|
254
|
+
requested_field = fetch_key(clause, :field)
|
|
255
|
+
bad_request!("where field is required") if requested_field.nil? || requested_field.to_s.empty?
|
|
256
|
+
|
|
257
|
+
field = resolve_field(model, requested_field)
|
|
222
258
|
attributes = fields_for(model).fetch(field)
|
|
223
259
|
key = (field == "id") ? "_id" : storage_field(model, field)
|
|
224
260
|
mode = (fetch_key(clause, :mode) || "sensitive").to_s
|
|
@@ -230,6 +266,8 @@ module BetterAuth
|
|
|
230
266
|
when "eq"
|
|
231
267
|
(insensitive && value.is_a?(String)) ? regex_condition(key, value, :eq, insensitive: true) : {key => store_value(field, value, attributes, strict_id: true)}
|
|
232
268
|
when "in"
|
|
269
|
+
bad_request!("where value must be an array for in operator") unless value.is_a?(Array)
|
|
270
|
+
|
|
233
271
|
(insensitive && value.is_a?(Array)) ? insensitive_in_condition(key, value) : {key => {"$in" => array_operator_values(value).map { |entry| store_value(field, entry, attributes, strict_id: true) }}}
|
|
234
272
|
when "not_in"
|
|
235
273
|
(insensitive && value.is_a?(Array)) ? insensitive_not_in_condition(key, value) : {key => {"$nin" => array_operator_values(value).map { |entry| store_value(field, entry, attributes, strict_id: true) }}}
|
|
@@ -281,9 +319,9 @@ module BetterAuth
|
|
|
281
319
|
foreign_field = storage_field_for_join(join_model, config.fetch(:to))
|
|
282
320
|
relation = config[:relation]
|
|
283
321
|
limit = config.key?(:limit) ? config[:limit] : nil
|
|
284
|
-
effective_limit = limit.nil? ? default_find_many_limit : limit
|
|
322
|
+
effective_limit = limit.nil? ? default_find_many_limit : positive_integer!(limit, "join limit")
|
|
285
323
|
unique = relation == "one-to-one" || config[:unique]
|
|
286
|
-
should_limit = !unique
|
|
324
|
+
should_limit = !unique
|
|
287
325
|
|
|
288
326
|
lookup = if should_limit
|
|
289
327
|
{
|
|
@@ -313,19 +351,30 @@ module BetterAuth
|
|
|
313
351
|
end
|
|
314
352
|
|
|
315
353
|
def normalized_join(model, join)
|
|
354
|
+
bad_request!("join must be a hash") unless join.is_a?(Hash)
|
|
355
|
+
|
|
316
356
|
join.each_with_object({}) do |(join_model, config), result|
|
|
317
357
|
join_model = join_model.to_s
|
|
358
|
+
bad_request!("join model is required") if join_model.empty?
|
|
359
|
+
|
|
318
360
|
result[join_model] = normalize_join_config(model, join_model, config)
|
|
319
361
|
end
|
|
320
362
|
end
|
|
321
363
|
|
|
322
364
|
def normalize_join_config(model, join_model, config)
|
|
365
|
+
bad_request!("join config must be true or a hash") unless config == true || config.is_a?(Hash)
|
|
366
|
+
|
|
323
367
|
if config.is_a?(Hash) && (config.key?(:on) || config.key?("on"))
|
|
324
368
|
on = config[:on] || config["on"]
|
|
369
|
+
bad_request!("join on must be a hash") unless on.is_a?(Hash)
|
|
370
|
+
|
|
325
371
|
relation = config[:relation] || config["relation"]
|
|
326
372
|
limit = config[:limit] || config["limit"]
|
|
327
373
|
from = fetch_key(on, :from)
|
|
328
374
|
to = fetch_key(on, :to)
|
|
375
|
+
bad_request!("join on.from is required") if from.nil? || from.to_s.empty?
|
|
376
|
+
bad_request!("join on.to is required") if to.nil? || to.to_s.empty?
|
|
377
|
+
|
|
329
378
|
return {from: Schema.storage_key(from), to: Schema.storage_key(to), relation: relation, limit: limit, unique: unique_join_field?(join_model, to)}
|
|
330
379
|
end
|
|
331
380
|
|
|
@@ -414,9 +463,8 @@ module BetterAuth
|
|
|
414
463
|
def collection_name(model)
|
|
415
464
|
model = default_model_name(model)
|
|
416
465
|
configured = configured_model_name(model)
|
|
417
|
-
return "#{configured}s" if configured && use_plural
|
|
418
|
-
return configured if configured
|
|
419
466
|
return schema_for(model).fetch(:model_name) if use_plural
|
|
467
|
+
return configured if configured
|
|
420
468
|
|
|
421
469
|
model.to_s
|
|
422
470
|
end
|
|
@@ -548,7 +596,7 @@ module BetterAuth
|
|
|
548
596
|
|
|
549
597
|
def coerce_value(value, attributes)
|
|
550
598
|
return value if value.nil?
|
|
551
|
-
return
|
|
599
|
+
return parse_date_value(value) if attributes[:type] == "date" && value.is_a?(String)
|
|
552
600
|
|
|
553
601
|
value
|
|
554
602
|
end
|
|
@@ -611,6 +659,61 @@ module BetterAuth
|
|
|
611
659
|
nil
|
|
612
660
|
end
|
|
613
661
|
|
|
662
|
+
def validate_where!(where)
|
|
663
|
+
bad_request!("where must be an array") unless where.is_a?(Array)
|
|
664
|
+
|
|
665
|
+
where.each do |clause|
|
|
666
|
+
bad_request!("where entries must be hashes") unless clause.is_a?(Hash)
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
where
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
def positive_integer!(value, name)
|
|
673
|
+
parsed = Integer(value)
|
|
674
|
+
bad_request!("#{name} must be a positive integer") unless parsed.positive?
|
|
675
|
+
|
|
676
|
+
parsed
|
|
677
|
+
rescue ArgumentError, TypeError
|
|
678
|
+
bad_request!("#{name} must be a positive integer")
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
def non_negative_integer!(value, name)
|
|
682
|
+
parsed = Integer(value)
|
|
683
|
+
bad_request!("#{name} must be zero or a positive integer") if parsed.negative?
|
|
684
|
+
|
|
685
|
+
parsed
|
|
686
|
+
rescue ArgumentError, TypeError
|
|
687
|
+
bad_request!("#{name} must be zero or a positive integer")
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
def ensure_update_document!(document)
|
|
691
|
+
bad_request!("No fields to update") if document.empty?
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
def ensure_update_input_has_fields!(model, update)
|
|
695
|
+
bad_request!("update must be a hash") unless update.is_a?(Hash)
|
|
696
|
+
|
|
697
|
+
fields = fields_for(model)
|
|
698
|
+
input = stringify_keys(update)
|
|
699
|
+
has_updatable_field = input.any? do |field_key, _value|
|
|
700
|
+
next false if field_key == "id" || field_key == "_id"
|
|
701
|
+
|
|
702
|
+
fields.key?(field_key) || fields.any? { |_field, attributes| attributes[:field_name].to_s == field_key }
|
|
703
|
+
end
|
|
704
|
+
bad_request!("No fields to update") unless has_updatable_field
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
def parse_date_value(value)
|
|
708
|
+
Time.parse(value)
|
|
709
|
+
rescue ArgumentError
|
|
710
|
+
bad_request!("Invalid date value")
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
def bad_request!(message)
|
|
714
|
+
raise APIError.new("BAD_REQUEST", message: message)
|
|
715
|
+
end
|
|
716
|
+
|
|
614
717
|
def schema_for(model)
|
|
615
718
|
Schema.auth_tables(options).fetch(default_model_name(model))
|
|
616
719
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: better_auth-mongodb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sebastian Sala
|
|
@@ -161,14 +161,14 @@ files:
|
|
|
161
161
|
- lib/better_auth/mongo_adapter.rb
|
|
162
162
|
- lib/better_auth/mongodb.rb
|
|
163
163
|
- lib/better_auth/mongodb/version.rb
|
|
164
|
-
homepage: https://github.com/sebasxsala/better-auth
|
|
164
|
+
homepage: https://github.com/sebasxsala/better-auth-rb
|
|
165
165
|
licenses:
|
|
166
166
|
- MIT
|
|
167
167
|
metadata:
|
|
168
|
-
homepage_uri: https://github.com/sebasxsala/better-auth
|
|
169
|
-
source_code_uri: https://github.com/sebasxsala/better-auth
|
|
170
|
-
changelog_uri: https://github.com/sebasxsala/better-auth/blob/main/packages/better_auth-mongodb/CHANGELOG.md
|
|
171
|
-
bug_tracker_uri: https://github.com/sebasxsala/better-auth/issues
|
|
168
|
+
homepage_uri: https://github.com/sebasxsala/better-auth-rb
|
|
169
|
+
source_code_uri: https://github.com/sebasxsala/better-auth-rb
|
|
170
|
+
changelog_uri: https://github.com/sebasxsala/better-auth-rb/blob/main/packages/better_auth-mongodb/CHANGELOG.md
|
|
171
|
+
bug_tracker_uri: https://github.com/sebasxsala/better-auth-rb/issues
|
|
172
172
|
rdoc_options: []
|
|
173
173
|
require_paths:
|
|
174
174
|
- lib
|