better_auth-mongo-adapter 0.6.2 → 0.7.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 +9 -0
- data/README.md +74 -5
- data/lib/better_auth/mongo_adapter/version.rb +1 -1
- data/lib/better_auth/mongo_adapter.rb +54 -16
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8422b43a0fc6310519286bca03295aecb91752ae5c4cd5997ff5be64486af7c9
|
|
4
|
+
data.tar.gz: ed65ca011f7f4a5913ae03830c049b8fcc023200c47ad9da125e55f6569272ba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 285006c97e41014ba65eb450e520bd9ac82848a0d796894ba526f1427a605808d51e25d174cd35d4f11884e235b4cc9465017a91c874b6aed5b6e45b41c8550a
|
|
7
|
+
data.tar.gz: 16029924074e406bb142a50936b4b77a1a90367c5aaf8a390ecfb8723f051a1e2c98c770bea4bab1daa7784c7a9763946c46b5a55c9fc3d5a5620dfc5e830d16
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.7.0 - 2026-05-05
|
|
6
|
+
|
|
7
|
+
- Added explicit `ensure_indexes!` setup helper for Mongo indexes derived from Better Auth schema metadata.
|
|
8
|
+
- Updated MongoDB setup docs to use the lambda adapter form, clearer standalone/replica-set transaction guidance, and production index guidance.
|
|
9
|
+
- Consolidated Mongo fake test support and strengthened transaction rollback coverage for staged mutations.
|
|
10
|
+
- Apply `advanced[:database][:default_find_many_limit]` to uncapped `find_many` calls and one-to-many Mongo `$lookup` joins, defaulting to 100 when omitted.
|
|
11
|
+
- Match upstream Mongo where-clause semantics for mixed connectors by bucketing multi-clause filters into `$and` and `$or` arrays instead of left-fold nesting.
|
|
12
|
+
- Allow scalar values for `in` and `not_in` filters as an intentional Ruby adapter-family adaptation.
|
|
13
|
+
|
|
5
14
|
## 0.1.1 - 2026-04-30
|
|
6
15
|
|
|
7
16
|
- Fixed inferred limited joins so explicit relation and limit configuration is preserved.
|
data/README.md
CHANGED
|
@@ -18,16 +18,85 @@ mongo_client = Mongo::Client.new(ENV.fetch("BETTER_AUTH_MONGODB_URL"))
|
|
|
18
18
|
|
|
19
19
|
auth = BetterAuth.auth(
|
|
20
20
|
secret: ENV.fetch("BETTER_AUTH_SECRET"),
|
|
21
|
-
database:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
database: ->(options) {
|
|
22
|
+
BetterAuth::Adapters::MongoDB.new(
|
|
23
|
+
options,
|
|
24
|
+
database: mongo_client.database,
|
|
25
|
+
client: mongo_client,
|
|
26
|
+
transaction: false
|
|
27
|
+
)
|
|
28
|
+
}
|
|
26
29
|
)
|
|
27
30
|
```
|
|
28
31
|
|
|
32
|
+
The lambda form lets Better Auth pass the final configuration into the adapter,
|
|
33
|
+
including plugins, custom schemas, and advanced database options.
|
|
34
|
+
|
|
29
35
|
## Notes
|
|
30
36
|
|
|
31
37
|
This package depends on the official `mongo` gem. Keeping MongoDB support outside `better_auth` avoids installing MongoDB client dependencies for applications that only use SQL, Rails, Hanami, or in-memory storage.
|
|
32
38
|
|
|
33
39
|
The adapter stores Better Auth models in singular MongoDB collections by default, maps logical `id` values to Mongo `_id`, converts ObjectId-compatible ids through the Mongo driver, and supports the shared Better Auth database adapter contract.
|
|
40
|
+
|
|
41
|
+
Transactions are deployment-dependent. MongoDB multi-document transactions may
|
|
42
|
+
be unavailable on standalone servers and usually require a replica set plus
|
|
43
|
+
compatible driver/session settings. The setup example uses `transaction: false`;
|
|
44
|
+
enable transactions only when the MongoDB deployment supports them.
|
|
45
|
+
|
|
46
|
+
When using a replica set, remove `transaction: false` or pass
|
|
47
|
+
`transaction: true`. When using standalone local MongoDB, keep
|
|
48
|
+
`transaction: false`.
|
|
49
|
+
|
|
50
|
+
## Indexes
|
|
51
|
+
|
|
52
|
+
MongoDB does not run SQL-style migrations. The adapter can create recommended
|
|
53
|
+
indexes from Better Auth schema metadata, but this is an explicit setup step:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
adapter = BetterAuth::Adapters::MongoDB.new(
|
|
57
|
+
options,
|
|
58
|
+
database: mongo_client.database,
|
|
59
|
+
client: mongo_client,
|
|
60
|
+
transaction: false
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
adapter.ensure_indexes!
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`ensure_indexes!` creates indexes for schema fields marked `unique: true` or
|
|
67
|
+
`index: true`, including plugin schemas and custom model or field names. It
|
|
68
|
+
skips Mongo `_id` because MongoDB creates that index automatically. The method
|
|
69
|
+
returns a summary of requested indexes so deployment scripts can log what was
|
|
70
|
+
applied.
|
|
71
|
+
|
|
72
|
+
## Limits
|
|
73
|
+
|
|
74
|
+
By default, `find_many` calls without an explicit `limit:` are capped at 100 records. Configure the default with Better Auth's advanced database option:
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
auth = BetterAuth.auth(
|
|
78
|
+
secret: ENV.fetch("BETTER_AUTH_SECRET"),
|
|
79
|
+
advanced: {
|
|
80
|
+
database: {
|
|
81
|
+
default_find_many_limit: 250
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
database: ->(options) {
|
|
85
|
+
BetterAuth::Adapters::MongoDB.new(
|
|
86
|
+
options,
|
|
87
|
+
database: mongo_client.database,
|
|
88
|
+
client: mongo_client,
|
|
89
|
+
transaction: false
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
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
|
+
|
|
97
|
+
One-to-one joins ignore one-to-many limits. They are returned as a single object or `nil`.
|
|
98
|
+
|
|
99
|
+
Ruby's adapters accept scalar values for `in` and `not_in` filters and coerce
|
|
100
|
+
them to a one-element list. This is an intentional Ruby adapter-family behavior;
|
|
101
|
+
upstream's TypeScript adapter factory is stricter before the Mongo adapter sees
|
|
102
|
+
the query.
|
|
@@ -49,7 +49,8 @@ module BetterAuth
|
|
|
49
49
|
pipeline << {"$project" => projection_for(model, select, join)} if select && !select.empty?
|
|
50
50
|
pipeline << {"$sort" => {sort_field(model, sort_by) => sort_direction(sort_by)}} if sort_by
|
|
51
51
|
pipeline << {"$skip" => offset.to_i} if offset
|
|
52
|
-
|
|
52
|
+
effective_limit = limit.nil? ? default_find_many_limit : limit.to_i
|
|
53
|
+
pipeline << {"$limit" => effective_limit} if effective_limit.positive?
|
|
53
54
|
|
|
54
55
|
collection_for(model)
|
|
55
56
|
.aggregate(pipeline, session_options)
|
|
@@ -105,6 +106,26 @@ module BetterAuth
|
|
|
105
106
|
(row["total"] || row[:total] || 0).to_i
|
|
106
107
|
end
|
|
107
108
|
|
|
109
|
+
def ensure_indexes!
|
|
110
|
+
Schema.auth_tables(options).flat_map do |model, table|
|
|
111
|
+
table.fetch(:fields).filter_map do |field, attributes|
|
|
112
|
+
next if field == "id"
|
|
113
|
+
next unless attributes[:unique] || attributes[:index]
|
|
114
|
+
|
|
115
|
+
collection = collection_for(model)
|
|
116
|
+
key = storage_field(model, field)
|
|
117
|
+
index_options = attributes[:unique] ? {unique: true} : {}
|
|
118
|
+
collection.indexes.create_one({key => 1}, index_options)
|
|
119
|
+
{
|
|
120
|
+
collection: collection_name(model),
|
|
121
|
+
field: field,
|
|
122
|
+
keys: {key => 1},
|
|
123
|
+
unique: attributes[:unique] == true
|
|
124
|
+
}
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
108
129
|
def transaction
|
|
109
130
|
return yield self unless client && @transaction_enabled && client.respond_to?(:start_session)
|
|
110
131
|
|
|
@@ -161,24 +182,40 @@ module BetterAuth
|
|
|
161
182
|
clauses = Array(where)
|
|
162
183
|
return {} if clauses.empty?
|
|
163
184
|
|
|
164
|
-
clauses.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if fetch_key(clause, :connector).to_s.upcase == "OR"
|
|
169
|
-
{"$or" => [filter, condition]}
|
|
185
|
+
conditions = clauses.map do |clause|
|
|
186
|
+
connector = if fetch_key(clause, :connector).to_s.upcase == "OR"
|
|
187
|
+
"OR"
|
|
170
188
|
else
|
|
171
|
-
|
|
189
|
+
"AND"
|
|
172
190
|
end
|
|
191
|
+
{condition: condition_for(model, clause), connector: connector}
|
|
173
192
|
end
|
|
193
|
+
return conditions.first.fetch(:condition) if conditions.one?
|
|
194
|
+
|
|
195
|
+
result = {}
|
|
196
|
+
and_conditions = conditions.select { |entry| entry.fetch(:connector) == "AND" }.map { |entry| entry.fetch(:condition) }
|
|
197
|
+
or_conditions = conditions.select { |entry| entry.fetch(:connector) == "OR" }.map { |entry| entry.fetch(:condition) }
|
|
198
|
+
result["$and"] = and_conditions if and_conditions.any?
|
|
199
|
+
result["$or"] = or_conditions if or_conditions.any?
|
|
200
|
+
result
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def default_find_many_limit
|
|
204
|
+
value = options.advanced.dig(:database, :default_find_many_limit)
|
|
205
|
+
return 100 if value.nil?
|
|
206
|
+
|
|
207
|
+
Integer(value)
|
|
208
|
+
rescue ArgumentError, TypeError
|
|
209
|
+
100
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def array_operator_values(value)
|
|
213
|
+
value.is_a?(Array) ? value : [value]
|
|
174
214
|
end
|
|
175
215
|
|
|
176
216
|
def condition_for(model, clause)
|
|
177
217
|
operator = (fetch_key(clause, :operator) || "eq").to_s.downcase
|
|
178
218
|
value = fetch_key(clause, :value)
|
|
179
|
-
if operator == "in" && !value.is_a?(Array)
|
|
180
|
-
raise MongoAdapterError.new("UNSUPPORTED_OPERATOR", "Value must be an array")
|
|
181
|
-
end
|
|
182
219
|
|
|
183
220
|
field = resolve_field(model, fetch_key(clause, :field))
|
|
184
221
|
attributes = fields_for(model).fetch(field)
|
|
@@ -192,9 +229,9 @@ module BetterAuth
|
|
|
192
229
|
when "eq"
|
|
193
230
|
(insensitive && value.is_a?(String)) ? regex_condition(key, value, :eq, insensitive: true) : {key => store_value(field, value, attributes, strict_id: true)}
|
|
194
231
|
when "in"
|
|
195
|
-
(insensitive && value.is_a?(Array)) ? insensitive_in_condition(key, value) : {key => {"$in" =>
|
|
232
|
+
(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) }}}
|
|
196
233
|
when "not_in"
|
|
197
|
-
(insensitive && value.is_a?(Array)) ? insensitive_not_in_condition(key, value) : {key => {"$nin" =>
|
|
234
|
+
(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) }}}
|
|
198
235
|
when "ne"
|
|
199
236
|
(insensitive && value.is_a?(String)) ? {key => {"$not" => regex_for(value, :eq, insensitive: true)}} : {key => {"$ne" => store_value(field, value, attributes, strict_id: true)}}
|
|
200
237
|
when "gt", "gte", "lt", "lte"
|
|
@@ -242,9 +279,10 @@ module BetterAuth
|
|
|
242
279
|
local_field = storage_field_for_join(model, config.fetch(:from))
|
|
243
280
|
foreign_field = storage_field_for_join(join_model, config.fetch(:to))
|
|
244
281
|
relation = config[:relation]
|
|
245
|
-
limit = config[:limit]
|
|
282
|
+
limit = config.key?(:limit) ? config[:limit] : nil
|
|
283
|
+
effective_limit = limit.nil? ? default_find_many_limit : limit.to_i
|
|
246
284
|
unique = relation == "one-to-one" || config[:unique]
|
|
247
|
-
should_limit = !unique &&
|
|
285
|
+
should_limit = !unique && effective_limit.positive?
|
|
248
286
|
|
|
249
287
|
lookup = if should_limit
|
|
250
288
|
{
|
|
@@ -253,7 +291,7 @@ module BetterAuth
|
|
|
253
291
|
"let" => {"localFieldValue" => "$#{local_field}"},
|
|
254
292
|
"pipeline" => [
|
|
255
293
|
{"$match" => {"$expr" => {"$eq" => ["$#{foreign_field}", "$$localFieldValue"]}}},
|
|
256
|
-
{"$limit" =>
|
|
294
|
+
{"$limit" => effective_limit}
|
|
257
295
|
],
|
|
258
296
|
"as" => join_model
|
|
259
297
|
}
|