carddb 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.
@@ -0,0 +1,239 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CardDB
4
+ module Resources
5
+ # Records resource for searching and fetching records
6
+ class Records < Base
7
+ include FilterOperators
8
+
9
+ # Search for records in a dataset
10
+ #
11
+ # @param dataset_key [String] Dataset key (required)
12
+ # @param publisher_slug [String, nil] Publisher slug (uses default if not provided)
13
+ # @param game_key [String, nil] Game key (uses default if not provided)
14
+ # @param filter [Hash, nil] Filter conditions
15
+ # @param search [String, nil] Full-text search
16
+ # @param order_by [String, nil] Field to order by (e.g., "name:asc")
17
+ # @param resolve_links [Array<String>, nil] Link fields to resolve
18
+ # @param first [Integer, nil] Maximum number of results
19
+ # @param after [String, nil] Cursor for pagination
20
+ # @param validate_schema [Boolean, nil] Whether to validate against schema
21
+ # @yield [FilterBuilder] Optional block for filter DSL
22
+ # @return [Collection<Record>] Collection of records
23
+ #
24
+ # @example Basic search
25
+ # records = client.records.search(dataset_key: "cards")
26
+ #
27
+ # @example With filter DSL
28
+ # records = client.records.search(dataset_key: "cards") do
29
+ # where(type: "creature")
30
+ # where(hp: gte(100))
31
+ # end
32
+ #
33
+ # @example With hash filter
34
+ # records = client.records.search(
35
+ # dataset_key: "cards",
36
+ # filter: { "type" => "creature" }
37
+ # )
38
+ # rubocop:disable Metrics/MethodLength
39
+ def search(
40
+ dataset_key:,
41
+ publisher_slug: nil,
42
+ game_key: nil,
43
+ filter: nil,
44
+ search: nil,
45
+ order_by: nil,
46
+ resolve_links: nil,
47
+ first: nil,
48
+ after: nil,
49
+ validate_schema: nil,
50
+ &block
51
+ )
52
+ resolved_publisher = resolve_publisher(publisher_slug)
53
+ resolved_game = resolve_game(game_key)
54
+
55
+ validate_access!(resolved_publisher, resolved_game)
56
+
57
+ # Build filter from DSL block if provided
58
+ final_filter = if block_given?
59
+ FilterBuilder.build(&block)
60
+ else
61
+ filter
62
+ end
63
+
64
+ query = QueryBuilder.search_records(
65
+ publisher_slug: resolved_publisher,
66
+ game_key: resolved_game,
67
+ dataset_key: dataset_key,
68
+ filter: final_filter,
69
+ search: search,
70
+ order_by: order_by,
71
+ resolve_links: resolve_links,
72
+ first: first,
73
+ after: after,
74
+ validate_schema: validate_schema
75
+ )
76
+
77
+ variables = build_variables(
78
+ publisherSlug: resolved_publisher,
79
+ gameKey: resolved_game,
80
+ datasetKey: dataset_key,
81
+ filter: final_filter,
82
+ search: search,
83
+ orderBy: order_by,
84
+ resolveLinks: resolve_links,
85
+ first: first,
86
+ after: after,
87
+ validateSchema: validate_schema
88
+ )
89
+
90
+ data = connection.execute(query, variables)
91
+
92
+ # Create next page loader that preserves the filter block
93
+ search_params = {
94
+ dataset_key: dataset_key,
95
+ publisher_slug: publisher_slug,
96
+ game_key: game_key,
97
+ filter: final_filter,
98
+ search: search,
99
+ order_by: order_by,
100
+ resolve_links: resolve_links,
101
+ first: first,
102
+ validate_schema: validate_schema
103
+ }
104
+
105
+ next_page_loader = lambda do |cursor|
106
+ search(**search_params, after: cursor)
107
+ end
108
+
109
+ Collection.new(
110
+ data['searchRecords'],
111
+ item_class: Record,
112
+ next_page_loader: next_page_loader,
113
+ client: client
114
+ )
115
+ end
116
+ # rubocop:enable Metrics/MethodLength
117
+
118
+ # Fetch a single record by ID
119
+ #
120
+ # @param id [String] The record UUID
121
+ # @param cache [Boolean, nil] Whether to cache (nil = use config setting)
122
+ # @return [Record, nil] The record or nil if not found
123
+ def fetch(id, cache: nil)
124
+ key = cache_key('records', 'fetch', id: id)
125
+ with_cache(key, resource: :records, cache: cache) do
126
+ query = QueryBuilder.fetch_record
127
+ data = connection.execute(query, { id: id })
128
+
129
+ return nil unless data['fetchRecord']
130
+
131
+ Record.new(data['fetchRecord'], client: client)
132
+ end
133
+ end
134
+
135
+ # Get a record by its identifier value
136
+ #
137
+ # Looks up the record using the dataset's designated identifier field.
138
+ # Returns nil if the dataset has no identifier field or if no matching record is found.
139
+ #
140
+ # @param identifier [String] The identifier value to look up
141
+ # @param dataset_key [String] Dataset key (required)
142
+ # @param publisher_slug [String, nil] Publisher slug (uses default if not provided)
143
+ # @param game_key [String, nil] Game key (uses default if not provided)
144
+ # @param cache [Boolean, nil] Whether to cache (nil = use config setting)
145
+ # @return [Record, nil] The record or nil if not found
146
+ #
147
+ # @example
148
+ # record = client.records.get(identifier: "xy1-1", dataset_key: "cards")
149
+ def get(identifier:, dataset_key:, publisher_slug: nil, game_key: nil, cache: nil)
150
+ resolved_publisher = resolve_publisher(publisher_slug)
151
+ resolved_game = resolve_game(game_key)
152
+
153
+ validate_access!(resolved_publisher, resolved_game)
154
+
155
+ key = cache_key('records', 'get', publisher_slug: resolved_publisher, game_key: resolved_game,
156
+ dataset_key: dataset_key, identifier: identifier)
157
+ with_cache(key, resource: :records, cache: cache) do
158
+ query = QueryBuilder.fetch_record_by_identifier
159
+ variables = {
160
+ publisherSlug: resolved_publisher,
161
+ gameKey: resolved_game,
162
+ datasetKey: dataset_key,
163
+ identifier: identifier
164
+ }
165
+
166
+ data = connection.execute(query, variables)
167
+
168
+ return nil unless data['fetchRecordByIdentifier']
169
+
170
+ Record.new(data['fetchRecordByIdentifier'], client: client)
171
+ end
172
+ end
173
+
174
+ # Get multiple records by their identifier values
175
+ #
176
+ # Looks up records using the dataset's designated identifier field.
177
+ # Missing identifiers are omitted from the result.
178
+ #
179
+ # @param identifiers [Array<String>] Identifier values to look up (max 1000)
180
+ # @param dataset_key [String] Dataset key (required)
181
+ # @param publisher_slug [String, nil] Publisher slug (uses default if not provided)
182
+ # @param game_key [String, nil] Game key (uses default if not provided)
183
+ # @param cache [Boolean, nil] Whether to cache (nil = use config setting)
184
+ # @return [Array<Record>] Matching records
185
+ # @raise [ArgumentError] If more than 1000 identifiers are provided
186
+ #
187
+ # @example
188
+ # records = client.records.get_many(
189
+ # identifiers: ["xy1-1", "xy1-2"],
190
+ # dataset_key: "cards"
191
+ # )
192
+ def get_many(identifiers:, dataset_key:, publisher_slug: nil, game_key: nil, cache: nil)
193
+ raise ArgumentError, 'Maximum 1000 identifiers allowed' if identifiers.length > 1000
194
+ return [] if identifiers.empty?
195
+
196
+ resolved_publisher = resolve_publisher(publisher_slug)
197
+ resolved_game = resolve_game(game_key)
198
+
199
+ validate_access!(resolved_publisher, resolved_game)
200
+
201
+ key = cache_key(
202
+ 'records',
203
+ 'get_many',
204
+ publisher_slug: resolved_publisher,
205
+ game_key: resolved_game,
206
+ dataset_key: dataset_key,
207
+ identifiers: identifiers
208
+ )
209
+ with_cache(key, resource: :records, cache: cache) do
210
+ query = QueryBuilder.fetch_records_by_identifier
211
+ variables = {
212
+ publisherSlug: resolved_publisher,
213
+ gameKey: resolved_game,
214
+ datasetKey: dataset_key,
215
+ identifiers: identifiers
216
+ }
217
+
218
+ data = connection.execute(query, variables)
219
+
220
+ (data['fetchRecordsByIdentifier'] || []).map { |record| Record.new(record, client: client) }
221
+ end
222
+ end
223
+
224
+ # Fetch multiple records by IDs
225
+ #
226
+ # @param ids [Array<String>] Array of record UUIDs (max 1000)
227
+ # @return [Array<Record>] Array of records
228
+ # @raise [ArgumentError] If more than 1000 IDs provided
229
+ def fetch_many(ids)
230
+ raise ArgumentError, 'Maximum 1000 IDs allowed' if ids.length > 1000
231
+
232
+ query = QueryBuilder.fetch_records
233
+ data = connection.execute(query, { ids: ids })
234
+
235
+ (data['fetchRecords'] || []).map { |r| Record.new(r, client: client) }
236
+ end
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CardDB
4
+ module Resources
5
+ # Rules resource for listing game rules and deck formats.
6
+ class Rules < Base
7
+ # List rule-related datasets for a game.
8
+ #
9
+ # @param publisher_slug [String, nil] Publisher slug (uses default if not provided)
10
+ # @param game_key [String, nil] Game key (uses default if not provided)
11
+ # @param search [String, nil] Search by dataset name
12
+ # @param first [Integer, nil] Maximum number of results
13
+ # @param after [String, nil] Cursor for pagination
14
+ # @return [Collection<Dataset>] Collection of rule datasets
15
+ def datasets(publisher_slug: nil, game_key: nil, search: nil, first: nil, after: nil)
16
+ client.datasets.search(
17
+ publisher_slug: publisher_slug,
18
+ game_key: game_key,
19
+ search: search,
20
+ purpose: 'RULES',
21
+ first: first,
22
+ after: after
23
+ )
24
+ end
25
+
26
+ # List rules from the game's rules dataset.
27
+ #
28
+ # @param dataset_key [String] Rules dataset key (defaults to "rules")
29
+ # @return [Collection<Record>] Collection of rule records
30
+ def list(dataset_key: 'rules', **options, &block)
31
+ search_records(dataset_key: dataset_key, **options, &block)
32
+ end
33
+
34
+ # List deck/game formats from the game's formats dataset.
35
+ #
36
+ # @param dataset_key [String] Formats dataset key (defaults to "formats")
37
+ # @return [Collection<Record>] Collection of format records
38
+ def formats(dataset_key: 'formats', **options, &block)
39
+ search_records(dataset_key: dataset_key, **options, &block)
40
+ end
41
+
42
+ private
43
+
44
+ def search_records(dataset_key:, **options, &block)
45
+ client.records.search(dataset_key: dataset_key, **options, &block)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CardDB
4
+ VERSION = '0.2.0'
5
+ end
data/lib/carddb.rb ADDED
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'carddb/version'
4
+ require_relative 'carddb/errors'
5
+ require_relative 'carddb/configuration'
6
+ require_relative 'carddb/connection'
7
+ require_relative 'carddb/query_builder'
8
+ require_relative 'carddb/filter_builder'
9
+ require_relative 'carddb/collection'
10
+ require_relative 'carddb/cache'
11
+ require_relative 'carddb/resources/base'
12
+ require_relative 'carddb/resources/publishers'
13
+ require_relative 'carddb/resources/games'
14
+ require_relative 'carddb/resources/datasets'
15
+ require_relative 'carddb/resources/records'
16
+ require_relative 'carddb/resources/decks'
17
+ require_relative 'carddb/resources/rules'
18
+ require_relative 'carddb/batch'
19
+ require_relative 'carddb/client'
20
+
21
+ # CardDB Ruby client library.
22
+ #
23
+ # A Ruby client for the CardDB GraphQL API, providing search and fetch
24
+ # operations for card game data.
25
+ #
26
+ # @example Configure globally
27
+ # CardDB.configure do |config|
28
+ # config.api_key = "carddb_xxx"
29
+ # config.default_publisher = "pokemon-company"
30
+ # config.default_game = "pokemon-tcg"
31
+ # end
32
+ #
33
+ # @example Use module-level convenience methods
34
+ # games = CardDB.games.search
35
+ # records = CardDB.records.search(dataset_key: "cards")
36
+ #
37
+ # @example Use a dedicated client instance
38
+ # client = CardDB::Client.new(api_key: "carddb_xxx")
39
+ # games = client.games.search
40
+ module CardDB
41
+ class << self
42
+ # @return [Configuration] Global configuration instance
43
+ attr_writer :configuration
44
+
45
+ # Get the global configuration.
46
+ #
47
+ # @return [Configuration]
48
+ def configuration
49
+ @configuration ||= Configuration.new
50
+ end
51
+
52
+ # Configure the CardDB client globally.
53
+ #
54
+ # @yield [Configuration] The configuration instance
55
+ # @return [Configuration]
56
+ #
57
+ # @example
58
+ # CardDB.configure do |config|
59
+ # config.api_key = "carddb_xxx"
60
+ # config.default_publisher = "pokemon-company"
61
+ # end
62
+ def configure
63
+ # Reset the cached client so it picks up new configuration
64
+ @default_client = nil
65
+ yield(configuration)
66
+ configuration
67
+ end
68
+
69
+ # Reset the global configuration to defaults.
70
+ #
71
+ # @return [Configuration]
72
+ def reset_configuration!
73
+ @configuration = Configuration.new
74
+ @default_client = nil
75
+ configuration
76
+ end
77
+
78
+ # Get the default client instance (uses global configuration).
79
+ #
80
+ # @return [Client]
81
+ def default_client
82
+ @default_client ||= Client.new(config: configuration)
83
+ end
84
+
85
+ # Access the Publishers resource via the default client.
86
+ #
87
+ # @return [Resources::Publishers]
88
+ def publishers
89
+ default_client.publishers
90
+ end
91
+
92
+ # Access the Games resource via the default client.
93
+ #
94
+ # @return [Resources::Games]
95
+ def games
96
+ default_client.games
97
+ end
98
+
99
+ # Access the Datasets resource via the default client.
100
+ #
101
+ # @return [Resources::Datasets]
102
+ def datasets
103
+ default_client.datasets
104
+ end
105
+
106
+ # Access the Records resource via the default client.
107
+ #
108
+ # @return [Resources::Records]
109
+ def records
110
+ default_client.records
111
+ end
112
+
113
+ # Access the Decks resource via the default client.
114
+ #
115
+ # @return [Resources::Decks]
116
+ def decks
117
+ default_client.decks
118
+ end
119
+
120
+ # Access the Rules resource via the default client.
121
+ #
122
+ # @return [Resources::Rules]
123
+ def rules
124
+ default_client.rules
125
+ end
126
+
127
+ # Get information about the current API key.
128
+ #
129
+ # @return [Hash, nil]
130
+ def me
131
+ default_client.me
132
+ end
133
+
134
+ # Get the last rate limit info from the most recent request.
135
+ #
136
+ # @return [Hash, nil] Rate limit info with :limit, :remaining, :reset keys
137
+ def rate_limit_info
138
+ Thread.current[:carddb_rate_limit]
139
+ end
140
+
141
+ # Execute multiple queries in a single batch request.
142
+ #
143
+ # @yield [Batch] The batch builder
144
+ # @return [Array] Results in the same order as operations were added
145
+ #
146
+ # @example
147
+ # results = CardDB.batch do |b|
148
+ # b.games.fetch("uuid-1")
149
+ # b.games.fetch("uuid-2")
150
+ # b.publishers.fetch(slug: "pokemon-company")
151
+ # end
152
+ #
153
+ # results[0] # => Game
154
+ # results[1] # => Game
155
+ # results[2] # => Publisher
156
+ def batch(&block)
157
+ default_client.batch(&block)
158
+ end
159
+ end
160
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: carddb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - CardDB Team
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: faraday
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: faraday-retry
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.0'
40
+ description: A Ruby client library for interacting with the CardDB GraphQL API. Search
41
+ and fetch card game data with an expressive filter DSL.
42
+ email:
43
+ - support@carddb.xtda.org
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".rspec"
49
+ - ".rspec_status"
50
+ - ".rubocop.yml"
51
+ - AGENTS.md
52
+ - CHANGELOG.md
53
+ - LICENSE.txt
54
+ - README.md
55
+ - Rakefile
56
+ - examples/basic_usage.rb
57
+ - examples/filtering.rb
58
+ - examples/pagination.rb
59
+ - lib/carddb.rb
60
+ - lib/carddb/batch.rb
61
+ - lib/carddb/cache.rb
62
+ - lib/carddb/client.rb
63
+ - lib/carddb/collection.rb
64
+ - lib/carddb/configuration.rb
65
+ - lib/carddb/connection.rb
66
+ - lib/carddb/errors.rb
67
+ - lib/carddb/filter_builder.rb
68
+ - lib/carddb/query_builder.rb
69
+ - lib/carddb/resources/base.rb
70
+ - lib/carddb/resources/datasets.rb
71
+ - lib/carddb/resources/decks.rb
72
+ - lib/carddb/resources/games.rb
73
+ - lib/carddb/resources/publishers.rb
74
+ - lib/carddb/resources/records.rb
75
+ - lib/carddb/resources/rules.rb
76
+ - lib/carddb/version.rb
77
+ homepage: https://github.com/xtda/carddb-ruby
78
+ licenses:
79
+ - MIT
80
+ metadata:
81
+ homepage_uri: https://github.com/xtda/carddb-ruby
82
+ source_code_uri: https://github.com/xtda/carddb-ruby
83
+ changelog_uri: https://github.com/xtda/carddb-ruby/blob/main/CHANGELOG.md
84
+ rubygems_mfa_required: 'true'
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 3.0.0
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubygems_version: 4.0.9
100
+ specification_version: 4
101
+ summary: Ruby client for the CardDB API
102
+ test_files: []