Module: BetterAuth::Telemetry::Detectors::Database

Defined in:
lib/better_auth/telemetry/detectors/database.rb

Overview

Database detector. Returns a small hash describing the database backend the host application is using (or ‘nil` when no signal is available).

This is the Ruby-specific replacement for upstream’s ‘detect-database.ts`, which only walked the Node `package.json` for known SQL/ORM packages. The Ruby port adds two earlier precedence rules (a caller-supplied context override and a `BetterAuth::Configuration` adapter check) so an application with a configured Better Auth adapter does not fall through to the generic gem fallback.

## Precedence chain (Requirement 10)

  1. **Context override** — when the caller supplied a non-empty ‘context.database` string, return it verbatim with `version: nil`. This is the upstream `context.database` seam.

  2. **Configuration adapter** — when ‘options` is a Configuration (or a hash with a `:database` key) and the value is a known adapter symbol (ADAPTER_SYMBOLS) or a `BetterAuth::Adapters::*` instance (ADAPTER_CLASS_MAP), return its short identifier with `version: nil`.

  3. **Gem fallback** — when neither rule above matches, walk ‘Gem.loaded_specs` in GEM_FALLBACKS order and return the first match as `<gem_name>, version: <spec.version.to_s>`.

  4. Otherwise — ‘nil`.

## Failure handling

The whole call is wrapped in ‘rescue StandardError; nil` so a surprise from any branch (an exotic `context` shape, a `Configuration` reader that raises on a partially constructed instance, a `Gem.loaded_specs` mutation, …) degrades to `nil` rather than escaping out of the init payload composition in BetterAuth::Telemetry.create.

Examples:

Context override

ctx = BetterAuth::Telemetry::NormalizedContext.from(database: "postgresql")
BetterAuth::Telemetry::Detectors::Database.call(nil, ctx)
# => {name: "postgresql", version: nil}

Configuration symbol

config = BetterAuth::Configuration.new(secret: "...", database: :memory)
BetterAuth::Telemetry::Detectors::Database.call(config, nil)
# => {name: "memory", version: nil}

Constant Summary collapse

ADAPTER_CLASS_MAP =

Map from ‘BetterAuth::Adapters::*` class name to the short identifier reported in the init event. Class names are matched as strings so loading the telemetry gem does not autoload every adapter constant.

{
  "BetterAuth::Adapters::Postgres" => "postgres",
  "BetterAuth::Adapters::MySQL" => "mysql",
  "BetterAuth::Adapters::SQLite" => "sqlite",
  "BetterAuth::Adapters::MSSQL" => "mssql",
  "BetterAuth::Adapters::Memory" => "memory"
}.freeze
ADAPTER_SYMBOLS =

Map from a known Configuration#database symbol value to the short identifier reported in the init event.

{
  postgres: "postgres",
  mysql: "mysql",
  sqlite: "sqlite",
  mssql: "mssql",
  memory: "memory"
}.freeze
GEM_FALLBACKS =

Gems to probe in ‘Gem.loaded_specs`, in upstream-spec order. First match wins.

%w[sequel pg mysql2 sqlite3 activerecord mongoid mongo rom-sql].freeze

Class Method Summary collapse

Class Method Details

.call(options, context) ⇒ Hash{Symbol => String, nil}?

Resolve the database signal for the host application.

Parameters:

  • options (BetterAuth::Configuration, Hash, nil)

    the options passed to BetterAuth::Telemetry.create. May be a Configuration (production path), a raw hash with a ‘:database` key, or `nil`.

  • context (BetterAuth::Telemetry::NormalizedContext, Hash, nil)

    the optional context. When it responds to ‘:database` (or carries a `:database` / `“database”` key) and the value is a non-empty string, that string short-circuits the chain.

Returns:

  • (Hash{Symbol => String, nil}, nil)

    either ‘String, version: String|nil` or `nil` when nothing matches.



97
98
99
100
101
102
103
104
105
106
107
# File 'lib/better_auth/telemetry/detectors/database.rb', line 97

def call(options, context)
  override = context_override(context)
  return {name: override, version: nil} if override

  identifier = identify_from_options(options)
  return {name: identifier, version: nil} if identifier

  detect_from_gems
rescue
  nil
end

.configuration_database(options) ⇒ Object?

Read ‘database` from a Configuration or a raw hash. Returns `nil` for any other input shape.

Parameters:

  • options (BetterAuth::Configuration, Hash, nil)

Returns:

  • (Object, nil)


150
151
152
153
154
155
156
157
158
159
160
# File 'lib/better_auth/telemetry/detectors/database.rb', line 150

def configuration_database(options)
  return nil if options.nil?

  if defined?(::BetterAuth::Configuration) && options.is_a?(::BetterAuth::Configuration)
    return options.database
  end

  return options[:database] || options["database"] if options.is_a?(Hash)

  nil
end

.context_override(context) ⇒ String?

Read ‘database` from a NormalizedContext-like or hash-like context. Returns the raw string when present and non-empty, otherwise `nil`. Non-string values (e.g. a symbol set accidentally) are ignored to keep the wire shape stable.

Parameters:

  • context (#database, Hash, nil)

Returns:

  • (String, nil)


116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/better_auth/telemetry/detectors/database.rb', line 116

def context_override(context)
  return nil if context.nil?

  value =
    if context.respond_to?(:database)
      context.database
    elsif context.respond_to?(:[])
      context[:database] || context["database"]
    end

  return nil unless value.is_a?(String)
  return nil if value.empty?

  value
end

.detect_from_gemsHash{Symbol => String}?

Walk GEM_FALLBACKS in order and return the first ‘Gem.loaded_specs` match as `version:`. Returns `nil` when no listed gem is loaded.

Returns:

  • (Hash{Symbol => String}, nil)


181
182
183
184
185
186
187
188
189
190
# File 'lib/better_auth/telemetry/detectors/database.rb', line 181

def detect_from_gems
  GEM_FALLBACKS.each do |name|
    spec = ::Gem.loaded_specs[name]
    next if spec.nil?

    version = spec.respond_to?(:version) ? spec.version : nil
    return {name: name, version: version&.to_s}
  end
  nil
end

.identify_adapter(value) ⇒ String?

Map a known adapter symbol or a ‘BetterAuth::Adapters::*` instance to its short identifier. Returns `nil` when the value is neither a known symbol nor a known adapter class.

Parameters:

  • value (Symbol, BetterAuth::Adapters::Base, Object)

Returns:

  • (String, nil)


168
169
170
171
172
173
174
# File 'lib/better_auth/telemetry/detectors/database.rb', line 168

def identify_adapter(value)
  if value.is_a?(Symbol)
    return ADAPTER_SYMBOLS[value]
  end

  ADAPTER_CLASS_MAP[value.class.name]
end

.identify_from_options(options) ⇒ String?

Translate the configuration’s ‘database` value into a short identifier when it matches a known adapter symbol or a known `BetterAuth::Adapters::*` class.

Parameters:

  • options (BetterAuth::Configuration, Hash, nil)

Returns:

  • (String, nil)


138
139
140
141
142
143
# File 'lib/better_auth/telemetry/detectors/database.rb', line 138

def identify_from_options(options)
  database = configuration_database(options)
  return nil if database.nil?

  identify_adapter(database)
end