instant-ruby 0.2.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: b27a065c0c17c64d247486df6f8e7c7ad3ea865d9c93beca708b0bcd4e517c36
4
+ data.tar.gz: 595d188d47e68d4539f9fe984c76197b9062dd16a0cb9e4e4a5a7f80bf5bb500
5
+ SHA512:
6
+ metadata.gz: ee348223c741d24422e53af75f2d7ca0a8be3673c71e1d7d2cee36cd78bfc74f73b91df56abd7b789cad71afe5233f0cea7371545b5a65f5625fc9f013c10fcf
7
+ data.tar.gz: f5b51e79f92ce3e6c9e8801c945089b96979dae8d2e041484c68aeb53fb4ad86cb123ad70c81a5d2aea36cd1b97d379988021439561e7bd41a679c52792d4560
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.0
3
+
4
+ Style/StringLiterals:
5
+ EnforcedStyle: double_quotes
6
+
7
+ Style/StringLiteralsInInterpolation:
8
+ EnforcedStyle: double_quotes
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-12-21
4
+
5
+ - Initial release
@@ -0,0 +1,132 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the overall
26
+ community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or advances of
31
+ any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email address,
35
+ without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official email address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement at
63
+ [INSERT CONTACT METHOD].
64
+ All complaints will be reviewed and investigated promptly and fairly.
65
+
66
+ All community leaders are obligated to respect the privacy and security of the
67
+ reporter of any incident.
68
+
69
+ ## Enforcement Guidelines
70
+
71
+ Community leaders will follow these Community Impact Guidelines in determining
72
+ the consequences for any action they deem in violation of this Code of Conduct:
73
+
74
+ ### 1. Correction
75
+
76
+ **Community Impact**: Use of inappropriate language or other behavior deemed
77
+ unprofessional or unwelcome in the community.
78
+
79
+ **Consequence**: A private, written warning from community leaders, providing
80
+ clarity around the nature of the violation and an explanation of why the
81
+ behavior was inappropriate. A public apology may be requested.
82
+
83
+ ### 2. Warning
84
+
85
+ **Community Impact**: A violation through a single incident or series of
86
+ actions.
87
+
88
+ **Consequence**: A warning with consequences for continued behavior. No
89
+ interaction with the people involved, including unsolicited interaction with
90
+ those enforcing the Code of Conduct, for a specified period of time. This
91
+ includes avoiding interactions in community spaces as well as external channels
92
+ like social media. Violating these terms may lead to a temporary or permanent
93
+ ban.
94
+
95
+ ### 3. Temporary Ban
96
+
97
+ **Community Impact**: A serious violation of community standards, including
98
+ sustained inappropriate behavior.
99
+
100
+ **Consequence**: A temporary ban from any sort of interaction or public
101
+ communication with the community for a specified period of time. No public or
102
+ private interaction with the people involved, including unsolicited interaction
103
+ with those enforcing the Code of Conduct, is allowed during this period.
104
+ Violating these terms may lead to a permanent ban.
105
+
106
+ ### 4. Permanent Ban
107
+
108
+ **Community Impact**: Demonstrating a pattern of violation of community
109
+ standards, including sustained inappropriate behavior, harassment of an
110
+ individual, or aggression toward or disparagement of classes of individuals.
111
+
112
+ **Consequence**: A permanent ban from any sort of public interaction within the
113
+ community.
114
+
115
+ ## Attribution
116
+
117
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118
+ version 2.1, available at
119
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120
+
121
+ Community Impact Guidelines were inspired by
122
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123
+
124
+ For answers to common questions about this code of conduct, see the FAQ at
125
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126
+ [https://www.contributor-covenant.org/translations][translations].
127
+
128
+ [homepage]: https://www.contributor-covenant.org
129
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130
+ [Mozilla CoC]: https://github.com/mozilla/diversity
131
+ [FAQ]: https://www.contributor-covenant.org/faq
132
+ [translations]: https://www.contributor-covenant.org/translations
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 John Paul
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # Instant Ruby
2
+
3
+ A lightweight, idiomatic Ruby client for [InstantDB](https://instantdb.com).
4
+
5
+ This gem provides a clean interface for InstantDB's Admin API, allowing you to query data, run transactions, and manage authentication from your Ruby backend.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'instant-ruby'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ ```bash
18
+ $ bundle install
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ```ruby
24
+ require 'instant-ruby'
25
+
26
+ # 1. Configure
27
+ Instant::Client.configure do |config|
28
+ config.app_id = ENV['INSTANT_APP_ID']
29
+ config.admin_token = ENV['INSTANT_ADMIN_TOKEN']
30
+ end
31
+
32
+ # 2. Write data (Transaction)
33
+ user_id = Instant::Client.id
34
+ Instant::Client.transact do |tx|
35
+ tx.users[user_id].update(name: "Alice", email: "alice@example.com")
36
+ end
37
+
38
+ # 3. Query data
39
+ users = Instant::Client.query({ users: { where: { email: "alice@example.com" } } })
40
+ ```
41
+
42
+ ## Configuration
43
+
44
+ In a Rails application, place this in `config/initializers/instant.rb`.
45
+
46
+ ```ruby
47
+ Instant::Client.configure do |config|
48
+ config.app_id = ENV['INSTANT_APP_ID']
49
+ config.admin_token = ENV['INSTANT_ADMIN_TOKEN']
50
+ end
51
+ ```
52
+
53
+ ## Querying
54
+
55
+ The query syntax mirrors the InstantDB client SDK. The gem automatically handles the `$` operator for modifiers (like `where`, `order`, `limit`), so you can write clean Ruby hashes.
56
+
57
+ ### Basic Queries
58
+
59
+ ```ruby
60
+ # Fetch all tasks
61
+ data = Instant::Client.query({ tasks: {} })
62
+
63
+ # With modifiers (automatically normalized)
64
+ data = Instant::Client.query({
65
+ tasks: {
66
+ where: { completed: false },
67
+ order: { created_at: "desc" },
68
+ limit: 10
69
+ }
70
+ })
71
+
72
+ # Nested relations
73
+ data = Instant::Client.query({
74
+ users: {
75
+ posts: {
76
+ comments: { limit: 5 }
77
+ }
78
+ }
79
+ })
80
+ ```
81
+
82
+ ### Helper Methods
83
+
84
+ Convenience methods are available for common patterns:
85
+
86
+ ```ruby
87
+ # Query all items in a namespace
88
+ Instant::Client.query_all(:users)
89
+
90
+ # Query with a simple where clause
91
+ Instant::Client.query_where(:users, { email: "alice@example.com" })
92
+
93
+ # Query by specific ID
94
+ Instant::Client.query_by_id(:users, "user-id-123")
95
+ ```
96
+
97
+ ## Transactions
98
+
99
+ Transactions allow you to write, update, and delete data atomically.
100
+
101
+ ### Block Syntax (Recommended)
102
+
103
+ The block syntax allows for readable, chainable operations.
104
+
105
+ ```ruby
106
+ user_id = Instant::Client.id
107
+ team_id = "team-123"
108
+
109
+ Instant::Client.transact do |tx|
110
+ # Create/Update a user and link to a team
111
+ tx.users[user_id].update(
112
+ name: "Alice",
113
+ role: "admin"
114
+ ).link(teams: team_id)
115
+
116
+ # Update the team in the same transaction
117
+ tx.teams[team_id].update(active: true)
118
+ end
119
+ ```
120
+
121
+ ### Operations
122
+
123
+ Available operations on an entity:
124
+
125
+ - `update(attrs)`: Updates fields (merges).
126
+ - `link(relation_name: id)`: Creates a link.
127
+ - `unlink(relation_name: id)`: Removes a link.
128
+ - `delete`: Deletes the entity.
129
+
130
+ ### Array Syntax
131
+
132
+ For dynamic or programmatic transactions, you can pass an array of steps directly.
133
+
134
+ ```ruby
135
+ steps = []
136
+ steps << Instant::Client.tx.users["user-1"].delete
137
+ steps << Instant::Client.tx.logs[Instant::Client.id].update(action: "deleted_user")
138
+
139
+ Instant::Client.transact(steps)
140
+ ```
141
+
142
+ ## Authentication & Permissions
143
+
144
+ ### Managing Tokens
145
+
146
+ ```ruby
147
+ # Create a refresh token for a specific email
148
+ token = Instant::Client.create_token("alice@example.com")
149
+
150
+ # Create a guest user (returns token and user ID)
151
+ guest = Instant::Client.create_guest_user
152
+ ```
153
+
154
+ ### Impersonation
155
+
156
+ You can run queries or transactions "as" a specific user to test permissions or perform actions on their behalf.
157
+
158
+ ```ruby
159
+ # Fetch data as 'alice@example.com'
160
+ Instant::Client.query(
161
+ { secrets: {} },
162
+ as_email: "alice@example.com"
163
+ )
164
+ ```
165
+
166
+ ## Contributing
167
+
168
+ Bug reports and pull requests are welcome on GitHub at https://github.com/dqnamo/instant-ruby.
169
+
170
+ ## License
171
+
172
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[test rubocop]
Binary file
@@ -0,0 +1,32 @@
1
+ # instant-ruby.gemspec
2
+ require_relative "lib/instant/version"
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "instant-ruby"
6
+ spec.version = Instant::VERSION
7
+ spec.authors = ["Your Name"]
8
+ spec.email = ["your@email.com"]
9
+
10
+ spec.summary = "A Ruby client for InstantDB"
11
+ spec.description = "A Ruby client for InstantDB with support for queries and transactions."
12
+ spec.homepage = "https://github.com/dqnamo/instant-ruby"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = ">= 3.0.0"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = spec.homepage
18
+
19
+ # Include only files in the git repo
20
+ spec.files = Dir.chdir(__dir__) do
21
+ `git ls-files -z`.split("\x0").reject do |f|
22
+ (File.expand_path(f) == __FILE__) ||
23
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
24
+ end
25
+ end
26
+
27
+ spec.bindir = "exe"
28
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+
31
+ # No external dependencies needed!
32
+ end
@@ -0,0 +1,335 @@
1
+ # lib/instant/client.rb
2
+ require "net/http"
3
+ require "uri"
4
+ require "json"
5
+ require "securerandom"
6
+ require "forwardable"
7
+
8
+ module Instant
9
+ class Client
10
+ class Error < StandardError; end
11
+ class AuthenticationError < Error; end
12
+ class RequestError < Error; end
13
+
14
+ # Keys that should be moved into the "$" object automatically
15
+ MODIFIERS = [:where, :order, :limit, :offset, :first, :last, :after, :before].freeze
16
+
17
+ class Configuration
18
+ attr_accessor :app_id, :admin_token, :base_url
19
+
20
+ def initialize
21
+ @base_url = "https://api.instantdb.com"
22
+ end
23
+ end
24
+
25
+ class << self
26
+ extend Forwardable
27
+
28
+ # Expose instance methods as class methods for convenience
29
+ def_delegators :instance, :query, :query_all, :query_where, :query_by_id,
30
+ :create_token, :create_guest_user, :verify_token,
31
+ :transact
32
+
33
+ attr_writer :configuration
34
+
35
+ def configuration
36
+ @configuration ||= Configuration.new
37
+ end
38
+
39
+ def configure
40
+ yield(configuration)
41
+ end
42
+
43
+ def instance
44
+ @instance ||= new
45
+ end
46
+
47
+ def id
48
+ SecureRandom.uuid
49
+ end
50
+
51
+ # SDK-style transaction helper
52
+ def tx
53
+ TransactionProxy.new(instance)
54
+ end
55
+ end
56
+
57
+ def initialize(app_id: nil, admin_token: nil, base_url: nil)
58
+ @app_id = app_id || self.class.configuration.app_id
59
+ @admin_token = admin_token || self.class.configuration.admin_token
60
+ @base_url = base_url || self.class.configuration.base_url
61
+ end
62
+
63
+ # --- Query API ---
64
+
65
+ def query(query_obj, options = {})
66
+ # Apply magic to remove need for "$"
67
+ normalized = normalize_query(query_obj)
68
+
69
+ endpoint = "/admin/query"
70
+ body = { query: normalized }
71
+ make_request(:post, endpoint, body, options)
72
+ end
73
+
74
+ def query_all(namespace, options = {})
75
+ query({ namespace => {} }, options)
76
+ end
77
+
78
+ def query_where(namespace, where_clause, options = {})
79
+ query({ namespace => { where: where_clause } }, options)
80
+ end
81
+
82
+ def query_by_id(namespace, id, options = {})
83
+ query_where(namespace, { id: id }, options)
84
+ end
85
+
86
+ # --- Transaction API ---
87
+
88
+ def transact(steps_or_options = {}, options = {}, &block)
89
+ steps = []
90
+
91
+ if block_given?
92
+ # Block syntax: Instant::Client.transact { |tx| ... }
93
+ # steps_or_options is actually 'options' here
94
+ builder = TransactionBuilder.new(self)
95
+ yield(builder)
96
+ steps = builder.all_steps
97
+ options = steps_or_options
98
+ elsif steps_or_options.is_a?(Array)
99
+ # Array syntax: Instant::Client.transact([step1, step2])
100
+ # flatten handles nested arrays of steps
101
+ steps = steps_or_options.flat_map { |s| s.respond_to?(:steps) ? s.steps : s }
102
+ else
103
+ # No steps provided or incorrect usage
104
+ steps = []
105
+ options = steps_or_options
106
+ end
107
+
108
+ endpoint = "/admin/transact"
109
+ body = { steps: steps }
110
+ make_request(:post, endpoint, body, options)
111
+ end
112
+
113
+ # --- Auth API ---
114
+
115
+ def create_token(email)
116
+ endpoint = "/admin/refresh_tokens"
117
+ body = { email: email }
118
+ make_request(:post, endpoint, body)
119
+ end
120
+
121
+ def create_guest_user
122
+ endpoint = "/runtime/auth/sign_in_guest"
123
+ body = { "app-id" => @app_id }
124
+
125
+ # We need a custom request here because this endpoint is public/runtime
126
+ uri = URI.parse("#{@base_url}#{endpoint}")
127
+ http = Net::HTTP.new(uri.host, uri.port)
128
+ http.use_ssl = true
129
+
130
+ request = Net::HTTP::Post.new(uri.path)
131
+ request["Content-Type"] = "application/json"
132
+ request.body = body.to_json
133
+
134
+ response = http.request(request)
135
+ result = handle_response(response)
136
+
137
+ # Symbolize keys manually for result
138
+ {
139
+ user_id: result.dig("user", "id"),
140
+ refresh_token: result["token"],
141
+ user: result["user"]
142
+ }
143
+ end
144
+
145
+ # --- Step Helpers ---
146
+
147
+ def update_step(namespace, id, attrs)
148
+ [ "update", namespace, id, attrs ]
149
+ end
150
+
151
+ def merge_step(namespace, id, attrs)
152
+ [ "merge", namespace, id, attrs ]
153
+ end
154
+
155
+ def link_step(namespace, id, links)
156
+ [ "link", namespace, id, links ]
157
+ end
158
+
159
+ def unlink_step(namespace, id, links)
160
+ [ "unlink", namespace, id, links ]
161
+ end
162
+
163
+ def delete_step(namespace, id)
164
+ [ "delete", namespace, id ]
165
+ end
166
+
167
+ private
168
+
169
+ # Magic method to move specific keys into "$"
170
+ def normalize_query(obj)
171
+ return obj unless obj.is_a?(Hash)
172
+
173
+ # Recursively process values first
174
+ processed = obj.transform_values { |v| normalize_query(v) }
175
+
176
+ modifiers = {}
177
+ fields = {}
178
+
179
+ processed.each do |k, v|
180
+ key_sym = k.to_s.to_sym rescue nil
181
+ if key_sym && MODIFIERS.include?(key_sym)
182
+ modifiers[k] = v
183
+ else
184
+ fields[k] = v
185
+ end
186
+ end
187
+
188
+ if modifiers.any?
189
+ # Use string "$" key for JSON compatibility
190
+ existing_dollar = fields["$"] || fields[:"$"] || {}
191
+ fields["$"] = existing_dollar.merge(modifiers)
192
+ end
193
+
194
+ fields
195
+ end
196
+
197
+ def make_request(method, endpoint, body = nil, options = {})
198
+ uri = URI.parse("#{@base_url}#{endpoint}")
199
+ http = Net::HTTP.new(uri.host, uri.port)
200
+ http.use_ssl = true
201
+
202
+ request = case method
203
+ when :post then Net::HTTP::Post.new(uri.path)
204
+ when :get then Net::HTTP::Get.new(uri.path)
205
+ else raise ArgumentError, "Unsupported HTTP method: #{method}"
206
+ end
207
+
208
+ request["Content-Type"] = "application/json"
209
+ request["Authorization"] = "Bearer #{@admin_token}"
210
+ request["App-Id"] = @app_id
211
+
212
+ request["as-email"] = options[:as_email] if options[:as_email]
213
+ request["as-token"] = options[:as_token] if options[:as_token]
214
+ request["as-guest"] = options[:as_guest].to_s if options.key?(:as_guest)
215
+
216
+ request.body = body.to_json if body
217
+
218
+ response = http.request(request)
219
+ handle_response(response)
220
+ end
221
+
222
+ def handle_response(response)
223
+ case response.code.to_i
224
+ when 200..299
225
+ return {} if response.body.nil? || response.body.empty?
226
+ JSON.parse(response.body, symbolize_names: true)
227
+ when 401
228
+ raise AuthenticationError, "Authentication failed: #{response.body}"
229
+ when 400..499
230
+ raise RequestError, "Request failed (#{response.code}): #{response.body}"
231
+ when 500..599
232
+ raise Error, "Server error (#{response.code}): #{response.body}"
233
+ else
234
+ raise Error, "Unexpected response (#{response.code}): #{response.body}"
235
+ end
236
+ rescue JSON::ParserError => e
237
+ raise Error, "Failed to parse response: #{e.message}"
238
+ end
239
+
240
+ # --- Builder Classes ---
241
+
242
+ class TransactionBuilder
243
+ def initialize(client)
244
+ @client = client
245
+ @entity_builders = []
246
+ end
247
+
248
+ def method_missing(namespace, *args, &block)
249
+ if args.length == 1 && !block
250
+ # tx.users(id)
251
+ builder = EntityBuilder.new(@client, namespace.to_s, args.first)
252
+ @entity_builders << builder
253
+ builder
254
+ elsif args.empty?
255
+ # tx.users[id] via proxy
256
+ NamespaceProxy.new(@client, namespace.to_s, @entity_builders)
257
+ else
258
+ super
259
+ end
260
+ end
261
+
262
+ def respond_to_missing?(method_name, include_private = false)
263
+ true
264
+ end
265
+
266
+ def all_steps
267
+ @entity_builders.flat_map(&:steps)
268
+ end
269
+ end
270
+
271
+ class TransactionProxy
272
+ def initialize(client)
273
+ @client = client
274
+ end
275
+
276
+ def method_missing(namespace, *args, &block)
277
+ NamespaceProxy.new(@client, namespace.to_s, nil)
278
+ end
279
+
280
+ def respond_to_missing?(method_name, include_private = false)
281
+ true
282
+ end
283
+ end
284
+
285
+ class NamespaceProxy
286
+ def initialize(client, namespace, entity_builders_list = nil)
287
+ @client = client
288
+ @namespace = namespace
289
+ @entity_builders_list = entity_builders_list
290
+ end
291
+
292
+ def [](id)
293
+ builder = EntityBuilder.new(@client, @namespace, id)
294
+ @entity_builders_list << builder if @entity_builders_list
295
+ builder
296
+ end
297
+ end
298
+
299
+ class EntityBuilder
300
+ attr_reader :steps
301
+
302
+ def initialize(client, namespace, id)
303
+ @client = client
304
+ @namespace = namespace
305
+ @id = id
306
+ @steps = []
307
+ end
308
+
309
+ def update(attrs)
310
+ @steps << @client.update_step(@namespace, @id, attrs)
311
+ self
312
+ end
313
+
314
+ def merge(attrs)
315
+ @steps << @client.merge_step(@namespace, @id, attrs)
316
+ self
317
+ end
318
+
319
+ def link(links)
320
+ @steps << @client.link_step(@namespace, @id, links)
321
+ self
322
+ end
323
+
324
+ def unlink(links)
325
+ @steps << @client.unlink_step(@namespace, @id, links)
326
+ self
327
+ end
328
+
329
+ def delete
330
+ @steps << @client.delete_step(@namespace, @id)
331
+ self
332
+ end
333
+ end
334
+ end
335
+ end
@@ -0,0 +1,4 @@
1
+ # lib/instant/version.rb
2
+ module Instant
3
+ VERSION = "0.2.0"
4
+ end
@@ -0,0 +1,10 @@
1
+ # lib/instant-ruby.rb
2
+ require "instant/version"
3
+ require "instant/client"
4
+
5
+ module Instant
6
+ class Error < StandardError; end
7
+
8
+ # Delegate handy methods to the Client class for easy access
9
+ # e.g. Instant.config, Instant.connect, etc if desired
10
+ end
@@ -0,0 +1,6 @@
1
+ module Instant
2
+ module Ruby
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: instant-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Your Name
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-12-21 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A Ruby client for InstantDB with support for queries and transactions.
14
+ email:
15
+ - your@email.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".rubocop.yml"
21
+ - CHANGELOG.md
22
+ - CODE_OF_CONDUCT.md
23
+ - LICENSE.txt
24
+ - README.md
25
+ - Rakefile
26
+ - instant-ruby-0.1.0.gem
27
+ - instant-ruby.gemspec
28
+ - lib/instant-ruby.rb
29
+ - lib/instant/client.rb
30
+ - lib/instant/version.rb
31
+ - sig/instant/ruby.rbs
32
+ homepage: https://github.com/dqnamo/instant-ruby
33
+ licenses:
34
+ - MIT
35
+ metadata:
36
+ homepage_uri: https://github.com/dqnamo/instant-ruby
37
+ source_code_uri: https://github.com/dqnamo/instant-ruby
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 3.0.0
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubygems_version: 3.5.22
54
+ signing_key:
55
+ specification_version: 4
56
+ summary: A Ruby client for InstantDB
57
+ test_files: []