libsql-activerecord2 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6c9161b429a7ede7bc2e3786e30e97df71a47a939fa927ac322866b13f8ec3fe
4
+ data.tar.gz: 9261dfb43874b4b505cb01f601e6d953c352ac667e319bcad38fd74eab2e09a5
5
+ SHA512:
6
+ metadata.gz: c24e01dcb01d607411d7bf87938585da1ee2e2294d48a4cf3368ccda337834684459c0acc1873c2fde2619620ec2bc9d0eb450d34a534f26e4d142f2989afd59
7
+ data.tar.gz: 6d8d747de0df0609763adb9e6e8af65a9ed34ef87259ef319a91b32188a763fd051f68e6b074d0fd3b06e8c826c3ae4a3d760b5f380bce8ab761fce6c550659d
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Speria
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # libsql-activerecord2
2
+
3
+ ActiveRecord adapter for [libSQL](https://libsql.org/) / [Turso](https://turso.tech/), built on the [libsql2](https://github.com/speria-jp/libsql-ruby2) gem.
4
+
5
+ ## Installation
6
+
7
+ Add to your Gemfile:
8
+
9
+ ```ruby
10
+ gem "libsql-activerecord2"
11
+ ```
12
+
13
+ Then run:
14
+
15
+ ```bash
16
+ bundle install
17
+ ```
18
+
19
+ ## Configuration
20
+
21
+ Configure the `libsql` adapter in `config/database.yml` (Rails) or via `ActiveRecord::Base.establish_connection`.
22
+
23
+ ### Local File
24
+
25
+ ```yaml
26
+ development:
27
+ adapter: libsql
28
+ database: db/development.sqlite3
29
+ ```
30
+
31
+ ### In-Memory
32
+
33
+ ```yaml
34
+ test:
35
+ adapter: libsql
36
+ database: ":memory:"
37
+ ```
38
+
39
+ ### Remote (Turso)
40
+
41
+ ```yaml
42
+ production:
43
+ adapter: libsql
44
+ database: libsql://your-db.turso.io
45
+ token: <%= ENV["TURSO_AUTH_TOKEN"] %>
46
+ ```
47
+
48
+ ### Embedded Replica
49
+
50
+ Keeps a local replica that syncs with the remote database — ideal for read-heavy workloads with low latency.
51
+
52
+ ```yaml
53
+ production:
54
+ adapter: libsql
55
+ database: libsql://your-db.turso.io
56
+ token: <%= ENV["TURSO_AUTH_TOKEN"] %>
57
+ replica_path: db/local_replica.sqlite3
58
+ read_your_writes: true # default: true
59
+ ```
60
+
61
+ To manually trigger a sync:
62
+
63
+ ```ruby
64
+ ActiveRecord::Base.connection.sync
65
+ ```
66
+
67
+ ## Usage
68
+
69
+ Once configured, use ActiveRecord as usual:
70
+
71
+ ```ruby
72
+ class User < ApplicationRecord
73
+ end
74
+
75
+ User.create!(name: "Alice", email: "alice@example.com")
76
+ User.where(name: "Alice").first
77
+ ```
78
+
79
+ ### Without Rails
80
+
81
+ ```ruby
82
+ require "libsql_activerecord"
83
+
84
+ ActiveRecord::Base.establish_connection(
85
+ adapter: "libsql",
86
+ database: ":memory:"
87
+ )
88
+ ```
89
+
90
+ ## Supported Features
91
+
92
+ | Feature | Status |
93
+ |---------|--------|
94
+ | Migrations | Yes |
95
+ | Primary keys | Yes |
96
+ | Savepoints (nested transactions) | Yes |
97
+ | Foreign keys | Yes |
98
+ | JSON columns | Yes |
99
+ | DDL transactions | No (planned) |
100
+ | EXPLAIN | No (planned) |
101
+ | Prepared statement cache | No (planned) |
102
+ | INSERT RETURNING | No (planned) |
103
+
104
+ ### Connection Modes
105
+
106
+ | Mode | Supported |
107
+ |------|-----------|
108
+ | Local file | Yes |
109
+ | In-memory | Yes |
110
+ | Remote (Turso) | Yes |
111
+ | Embedded replica | Yes |
112
+
113
+ ### Type Mapping
114
+
115
+ | ActiveRecord Type | SQL Type | SQLite Affinity |
116
+ |-------------------|----------|-----------------|
117
+ | `:primary_key` | `INTEGER PRIMARY KEY AUTOINCREMENT` | INTEGER |
118
+ | `:string` / `:text` | `TEXT` | TEXT |
119
+ | `:integer` | `INTEGER` | INTEGER |
120
+ | `:float` / `:decimal` | `REAL` | REAL |
121
+ | `:datetime` / `:timestamp` | `DATETIME` | NUMERIC |
122
+ | `:date` | `DATE` | NUMERIC |
123
+ | `:time` | `TIME` | NUMERIC |
124
+ | `:binary` | `BLOB` | BLOB |
125
+ | `:boolean` | `BOOLEAN` | NUMERIC |
126
+ | `:json` | `TEXT` | TEXT |
127
+
128
+ ### Exception Mapping
129
+
130
+ | libSQL Error | ActiveRecord Exception |
131
+ |-------------|----------------------|
132
+ | `NOT NULL constraint failed` | `ActiveRecord::NotNullViolation` |
133
+ | `UNIQUE constraint failed` | `ActiveRecord::RecordNotUnique` |
134
+ | `FOREIGN KEY constraint failed` | `ActiveRecord::InvalidForeignKey` |
135
+ | Other `Libsql::Error` | `ActiveRecord::StatementInvalid` |
136
+
137
+ ## Fork Safety
138
+
139
+ The adapter supports forking web servers (Puma, Unicorn). The `discard!` method cleans up the Rust runtime safely, and new connections are established in child processes automatically.
140
+
141
+ ## Requirements
142
+
143
+ - Ruby >= 3.4
144
+ - ActiveRecord >= 8.0
145
+ - [libsql2](https://github.com/speria-jp/libsql-ruby2) >= 0.1.5
146
+
147
+ ## Development
148
+
149
+ ```bash
150
+ bundle install
151
+ bundle exec rspec # Unit tests
152
+
153
+ # Integration tests (per AR version)
154
+ cd integration_tests/8.0 && bundle install && bundle exec rspec
155
+ cd integration_tests/8.1 && bundle install && bundle exec rspec
156
+ ```
157
+
158
+ ## License
159
+
160
+ Released under the [MIT License](LICENSE).
@@ -0,0 +1,409 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
4
+ require "active_record/connection_adapters/abstract_adapter"
5
+ require "libsql2"
6
+
7
+ module ActiveRecord
8
+ module ConnectionAdapters
9
+ class LibsqlAdapter < AbstractAdapter
10
+ ADAPTER_NAME = "Libsql"
11
+
12
+ # --- Read query detection (PRAGMA treated as read) ---
13
+
14
+ READ_QUERY = AbstractAdapter.build_read_query_regexp(:pragma)
15
+ private_constant :READ_QUERY
16
+
17
+ # --- FOR UPDATE / FOR SHARE stripping ---
18
+
19
+ FOR_UPDATE_PATTERN = %r{\s+FOR\s+(?:UPDATE|SHARE)(?:\s+SKIP\s+LOCKED)?(?:\s+OF\s+\S+)?\s*(?=/\*|$)}i
20
+ private_constant :FOR_UPDATE_PATTERN
21
+
22
+ # --- Native database types ---
23
+
24
+ NATIVE_DATABASE_TYPES = {
25
+ primary_key: "INTEGER PRIMARY KEY AUTOINCREMENT",
26
+ string: { name: "TEXT" },
27
+ text: { name: "TEXT" },
28
+ integer: { name: "INTEGER" },
29
+ float: { name: "REAL" },
30
+ decimal: { name: "REAL" },
31
+ datetime: { name: "DATETIME" },
32
+ timestamp: { name: "DATETIME" },
33
+ time: { name: "TIME" },
34
+ date: { name: "DATE" },
35
+ binary: { name: "BLOB" },
36
+ boolean: { name: "BOOLEAN" },
37
+ json: { name: "TEXT" }
38
+ }.freeze
39
+
40
+ # --- Column.new branching for AR 8.0 / 8.1 ---
41
+
42
+ COLUMN_BUILDER =
43
+ if ActiveRecord::VERSION::MAJOR > 8 ||
44
+ (ActiveRecord::VERSION::MAJOR == 8 && ActiveRecord::VERSION::MINOR >= 1)
45
+ # AR 8.1+: Column.new(name, cast_type, default, sql_type_metadata, null, default_function, **kwargs)
46
+ lambda { |name, cast_type, default, sql_type_md, null, default_function, **kwargs|
47
+ Column.new(name, cast_type, default, sql_type_md, null, default_function, **kwargs)
48
+ }
49
+ else
50
+ # AR 8.0: Column.new(name, default, sql_type_metadata, null, default_function, **kwargs)
51
+ lambda { |name, _cast_type, default, sql_type_md, null, default_function, **kwargs|
52
+ Column.new(name, default, sql_type_md, null, default_function, **kwargs)
53
+ }
54
+ end
55
+ private_constant :COLUMN_BUILDER
56
+
57
+ # --- AR 8.1 detection ---
58
+
59
+ AR_8_1_OR_LATER = ActiveRecord::VERSION::MAJOR > 8 ||
60
+ (ActiveRecord::VERSION::MAJOR == 8 && ActiveRecord::VERSION::MINOR >= 1)
61
+ private_constant :AR_8_1_OR_LATER
62
+
63
+ # --- Quoting (class methods required by AR 8.0+) ---
64
+
65
+ module Quoting
66
+ module ClassMethods
67
+ def quote_column_name(name)
68
+ @quoted_column_names ||= {}
69
+ @quoted_column_names[name] ||= %("#{name.to_s.gsub('"', '""')}").freeze
70
+ end
71
+ end
72
+ end
73
+
74
+ include Quoting
75
+ extend Quoting::ClassMethods
76
+
77
+ def self.quote_table_name(name)
78
+ @quoted_table_names ||= {}
79
+ @quoted_table_names[name] ||= %("#{name.to_s.gsub('"', '""').gsub('.', '"."')}").freeze
80
+ end
81
+
82
+ def quote_table_name(name)
83
+ self.class.quote_table_name(name)
84
+ end
85
+
86
+ def quoted_true = "1"
87
+ def quoted_false = "0"
88
+ def unquoted_true = 1
89
+ def unquoted_false = 0
90
+
91
+ # --- Feature flags ---
92
+
93
+ def supports_migrations? = true
94
+ def supports_primary_key? = true
95
+ def supports_savepoints? = true
96
+ def supports_foreign_keys? = true
97
+ def supports_json? = true
98
+ def supports_ddl_transactions? = false
99
+ def supports_explain? = false
100
+ def supports_lazy_transactions? = false
101
+ def supports_insert_returning? = false
102
+
103
+ def native_database_types
104
+ NATIVE_DATABASE_TYPES
105
+ end
106
+
107
+ # --- Connection lifecycle ---
108
+
109
+ def initialize(...)
110
+ super
111
+ @raw_database = nil
112
+ end
113
+
114
+ def connect
115
+ @raw_database, @raw_connection = build_libsql_connection
116
+ end
117
+
118
+ def active?
119
+ return false unless @raw_connection
120
+
121
+ @raw_connection.query("SELECT 1")
122
+ true
123
+ rescue ::Libsql::Error, ::Libsql::ClosedError
124
+ false
125
+ end
126
+
127
+ def connected?
128
+ !@raw_connection.nil?
129
+ end
130
+
131
+ def disconnect!
132
+ super
133
+ @raw_connection&.close
134
+ @raw_database&.close
135
+ @raw_connection = nil
136
+ @raw_database = nil
137
+ end
138
+
139
+ # Clean up Rust runtime after fork (Puma / Unicorn safety).
140
+ def discard!
141
+ @raw_database&.discard!
142
+ @raw_connection = nil
143
+ @raw_database = nil
144
+ end
145
+
146
+ # Sync embedded replica with remote.
147
+ def sync
148
+ @raw_database&.sync
149
+ end
150
+
151
+ # --- Query execution pipeline ---
152
+
153
+ def write_query?(sql)
154
+ !READ_QUERY.match?(sql)
155
+ rescue ArgumentError
156
+ !READ_QUERY.match?(sql.b)
157
+ end
158
+
159
+ def perform_query(raw_connection, sql, _binds, type_casted_binds,
160
+ prepare:, notification_payload:, batch: false)
161
+ sanitized = sanitize_for_update(sql)
162
+ params = type_casted_binds || []
163
+
164
+ result =
165
+ if batch
166
+ raw_connection.execute_batch(sanitized)
167
+ build_empty_result(affected: 0)
168
+ elsif write_query?(sanitized)
169
+ affected = raw_connection.execute(sanitized, params)
170
+ @last_inserted_id = raw_connection.last_insert_rowid
171
+ build_empty_result(affected: affected)
172
+ else
173
+ rows_obj = raw_connection.query(sanitized, params)
174
+ build_read_result(rows_obj, raw_connection)
175
+ end
176
+
177
+ notification_payload[:row_count] = result&.length || 0
178
+ notification_payload[:affected_rows] = affected_rows(result) if AR_8_1_OR_LATER
179
+
180
+ result
181
+ end
182
+
183
+ def cast_result(result)
184
+ result
185
+ end
186
+
187
+ def affected_rows(result)
188
+ if result.respond_to?(:affected_rows)
189
+ result.affected_rows
190
+ else
191
+ @last_affected_rows
192
+ end
193
+ end
194
+
195
+ def last_inserted_id(_result)
196
+ @last_inserted_id
197
+ end
198
+
199
+ # --- Transactions ---
200
+
201
+ def begin_db_transaction
202
+ @raw_connection.execute("BEGIN")
203
+ end
204
+
205
+ def commit_db_transaction
206
+ @raw_connection.execute("COMMIT")
207
+ end
208
+
209
+ def exec_rollback_db_transaction
210
+ @raw_connection.execute("ROLLBACK")
211
+ end
212
+
213
+ def create_savepoint(name = current_savepoint_name)
214
+ @raw_connection.execute("SAVEPOINT #{quote_column_name(name)}")
215
+ end
216
+
217
+ def release_savepoint(name = current_savepoint_name)
218
+ @raw_connection.execute("RELEASE SAVEPOINT #{quote_column_name(name)}")
219
+ end
220
+
221
+ def exec_rollback_to_savepoint(name = current_savepoint_name)
222
+ @raw_connection.execute("ROLLBACK TO SAVEPOINT #{quote_column_name(name)}")
223
+ end
224
+
225
+ # --- Schema introspection ---
226
+
227
+ def tables
228
+ query = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name"
229
+ result = @raw_connection.query(query)
230
+ result.to_a.map { |row| row["name"] }
231
+ end
232
+
233
+ def table_exists?(table_name)
234
+ tables.include?(table_name.to_s)
235
+ end
236
+
237
+ def views
238
+ query = "SELECT name FROM sqlite_master WHERE type='view' AND name NOT LIKE 'sqlite_%' ORDER BY name"
239
+ result = @raw_connection.query(query)
240
+ result.to_a.map { |row| row["name"] }
241
+ end
242
+
243
+ def column_definitions(table_name)
244
+ result = @raw_connection.query("PRAGMA table_info(#{quote_table_name(table_name)})")
245
+ result.to_a
246
+ end
247
+
248
+ def new_column_from_field(_table_name, field, _definitions)
249
+ default_value = field["dflt_value"]
250
+
251
+ # Unquote string defaults
252
+ default_value = default_value[1..-2] if default_value&.start_with?("'") && default_value.end_with?("'")
253
+
254
+ type = field["type"]
255
+ type_metadata = fetch_type_metadata(type)
256
+ cast_type = lookup_cast_type(type)
257
+ null = field["notnull"].to_i.zero?
258
+ default_function = nil
259
+
260
+ COLUMN_BUILDER.call(
261
+ field["name"], cast_type, default_value, type_metadata, null, default_function
262
+ )
263
+ end
264
+
265
+ def primary_keys(table_name)
266
+ result = @raw_connection.query("PRAGMA table_info(#{quote_table_name(table_name)})")
267
+ result.to_a
268
+ .select { |row| row["pk"].to_i.positive? }
269
+ .sort_by { |row| row["pk"].to_i }
270
+ .map { |row| row["name"] }
271
+ end
272
+
273
+ def indexes(table_name)
274
+ index_list = @raw_connection.query("PRAGMA index_list(#{quote_table_name(table_name)})").to_a
275
+
276
+ index_list.filter_map do |idx|
277
+ next if idx["name"].start_with?("sqlite_")
278
+
279
+ columns = @raw_connection.query("PRAGMA index_info(#{quote_table_name(idx['name'])})").to_a
280
+ column_names = columns.sort_by { |c| c["seqno"].to_i }.map { |c| c["name"] }
281
+
282
+ IndexDefinition.new(
283
+ table_name,
284
+ idx["name"],
285
+ idx["unique"].to_i != 0,
286
+ column_names
287
+ )
288
+ end
289
+ end
290
+
291
+ # --- Schema DDL ---
292
+
293
+ def rename_table(table_name, new_name, **)
294
+ execute("ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}")
295
+ end
296
+
297
+ def rename_column(table_name, column_name, new_column_name, **)
298
+ execute(
299
+ "ALTER TABLE #{quote_table_name(table_name)} " \
300
+ "RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
301
+ )
302
+ end
303
+
304
+ # --- Exception translation ---
305
+
306
+ def translate_exception(exception, message:, sql:, binds:)
307
+ # Libsql::Error inherits from RuntimeError, so the default
308
+ # translate_exception would return it as-is. We must handle
309
+ # it explicitly to convert to AR exception classes.
310
+ msg = exception.message
311
+ if /NOT NULL constraint failed/i.match?(msg)
312
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
313
+ elsif /UNIQUE constraint failed/i.match?(msg)
314
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
315
+ elsif /FOREIGN KEY constraint failed/i.match?(msg)
316
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
317
+ elsif exception.is_a?(::Libsql::Error)
318
+ StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
319
+ else
320
+ super
321
+ end
322
+ end
323
+
324
+ # --- Type map (class method, required by AR 8.0+) ---
325
+
326
+ class << self
327
+ private
328
+
329
+ def initialize_type_map(m)
330
+ super
331
+ # SQLite INTEGER is always 64-bit signed; override AR default (limit: 4)
332
+ m.register_type(/^integer/i, Type::Integer.new(limit: 8))
333
+ # REAL is not registered by AbstractAdapter
334
+ m.register_type(/^real/i, Type::Float.new)
335
+ end
336
+ end
337
+
338
+ TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
339
+ EXTENDED_TYPE_MAPS = Concurrent::Map.new
340
+
341
+ private
342
+
343
+ # --- Connection building ---
344
+
345
+ def build_libsql_connection
346
+ database_url = @config[:database] || raise(ArgumentError, "libsql adapter requires :database")
347
+ token = @config[:token] || ""
348
+ replica_path = @config[:replica_path]
349
+
350
+ db = if replica_path
351
+ ::Libsql::Database.open(
352
+ database_url.to_s,
353
+ auth_token: token.to_s,
354
+ local_path: replica_path.to_s,
355
+ read_your_writes: @config.fetch(:read_your_writes, true)
356
+ )
357
+ elsif database_url.to_s.start_with?("libsql://", "https://")
358
+ ::Libsql::Database.open(database_url.to_s, auth_token: token.to_s)
359
+ else
360
+ ::Libsql::Database.open(database_url.to_s)
361
+ end
362
+
363
+ [db, db.connect]
364
+ end
365
+
366
+ # Reconnect implementation called by AbstractAdapter#reconnect!
367
+ def reconnect
368
+ disconnect!
369
+ @raw_database, @raw_connection = build_libsql_connection
370
+ end
371
+
372
+ # --- Query helpers ---
373
+
374
+ def sanitize_for_update(sql)
375
+ sql.gsub(FOR_UPDATE_PATTERN, " ")
376
+ end
377
+
378
+ def build_read_result(rows_obj, raw_connection)
379
+ columns = rows_obj.columns
380
+ rows = rows_obj.to_a.map { |row| columns.map { |c| row[c] } }
381
+ affected = raw_connection.changes
382
+
383
+ if AR_8_1_OR_LATER
384
+ ActiveRecord::Result.new(columns, rows, affected_rows: affected)
385
+ else
386
+ @last_affected_rows = affected
387
+ ActiveRecord::Result.new(columns, rows)
388
+ end
389
+ end
390
+
391
+ def build_empty_result(affected:)
392
+ if AR_8_1_OR_LATER
393
+ ActiveRecord::Result.empty(affected_rows: affected)
394
+ else
395
+ @last_affected_rows = affected
396
+ ActiveRecord::Result.empty
397
+ end
398
+ end
399
+ end
400
+ end
401
+ end
402
+
403
+ # Register adapter directly (works with and without Rails).
404
+ # When loaded via Railtie the duplicate register is harmless.
405
+ ActiveRecord::ConnectionAdapters.register(
406
+ "libsql",
407
+ "ActiveRecord::ConnectionAdapters::LibsqlAdapter",
408
+ "active_record/connection_adapters/libsql_adapter"
409
+ )
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/railtie"
4
+
5
+ module LibsqlActiverecord
6
+ class Railtie < Rails::Railtie
7
+ initializer "libsql_activerecord.register_adapter" do
8
+ ActiveSupport.on_load(:active_record) do
9
+ ActiveRecord::ConnectionAdapters.register(
10
+ "libsql",
11
+ "ActiveRecord::ConnectionAdapters::LibsqlAdapter",
12
+ "active_record/connection_adapters/libsql_adapter"
13
+ )
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LibsqlActiverecord
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "libsql_activerecord/version"
4
+ require_relative "libsql_activerecord/railtie" if defined?(Rails::Railtie)
5
+
6
+ require "active_record/connection_adapters/libsql_adapter"
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: libsql-activerecord2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Speria
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activerecord
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '8.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '8.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: libsql2
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.1.5
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 0.1.5
40
+ description: ActiveRecord connection adapter for libSQL databases (local, remote,
41
+ and embedded replica modes), built on the libsql2 gem.
42
+ executables: []
43
+ extensions: []
44
+ extra_rdoc_files: []
45
+ files:
46
+ - LICENSE
47
+ - README.md
48
+ - lib/active_record/connection_adapters/libsql_adapter.rb
49
+ - lib/libsql_activerecord.rb
50
+ - lib/libsql_activerecord/railtie.rb
51
+ - lib/libsql_activerecord/version.rb
52
+ homepage: https://github.com/speria-jp/libsql-activerecord2
53
+ licenses:
54
+ - MIT
55
+ metadata:
56
+ rubygems_mfa_required: 'true'
57
+ rdoc_options: []
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '3.4'
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubygems_version: 4.0.3
72
+ specification_version: 4
73
+ summary: ActiveRecord adapter for libSQL / Turso
74
+ test_files: []