shopify_api-graphql-tiny 1.0.0 → 1.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c827a0b468aeb34cfb05760a7a1a25e641c61f21f6d2d15c4ed8a05b29bd383b
4
- data.tar.gz: 23b357593431fb8beda6a29689b6b90e4101fde3cac2eaaad38c7a2cb48f1437
3
+ metadata.gz: 18495ff517670050b05536c1ef5fda702e689a4ffab229eba78fd2aba055dce5
4
+ data.tar.gz: c374cf2ae48b8ed7de70b5ec8501841593535ad21ef2d5591deb17c7dc96fdef
5
5
  SHA512:
6
- metadata.gz: 7fe6d5f27b0936703ce4f7f45875406ab11069d41f8e83e9d57a873ce5dd63d1093e88219cd8b5b69eed2bdd3b39bb233292360aa8bb4534f5a799690cceab5f
7
- data.tar.gz: d7231b2be188d9bedbfa92f6de69a0ee4c082b8801633db73c570c7cef6c6a349afc81e942e2f36b685edf26a02d9e74817595d7b4988298f42092182a49a718
6
+ metadata.gz: 3388c846e2190e5996f780bf847d28f66c9336e0a96d53a8b6eed051fe8e38ffb18f3b32cc0abb15dfb15373d9f254eb6c8afbd2fbc050b3c19aea9c690db4de
7
+ data.tar.gz: e29a9dd41cd963bd96e8025ee31c5c4ec5b6b2726164655707c214fa73062f4e30e72545ea2df0c1b2ce54c90816c564776f509e4b9e3a51ef672290d35d2aec
data/Changes CHANGED
@@ -1,3 +1,11 @@
1
+ 2026-06-23 v1.0.2
2
+ --------------------
3
+ * Add :raise_on_warnings option to raise a WarningError when a response contains warnings
4
+
5
+ 2026-01-24 v1.0.1
6
+ --------------------
7
+ * Add support for calling #paginate without a block
8
+
1
9
  2026-01-20 v1.0.0
2
10
  --------------------
3
11
  * Add support for exponential backoff and remove ShopifyAPIRetry
data/README.md CHANGED
@@ -97,7 +97,7 @@ These can be overridden globally (by assigning to the constant) or per instance:
97
97
  gql = ShopifyAPI::GraphQL::Tiny.new(shop, token, :max_attempts => 20, :max_delay => 90)
98
98
  ```
99
99
 
100
- `ShopifyAPI::GraphQL::Tiny::DEFAULT_RETRY_ERRORS` determines what is retried. It contains and HTTP statuses codes, Shopify GraphQL errors codes, and exceptions.
100
+ `ShopifyAPI::GraphQL::Tiny::DEFAULT_RETRY_ERRORS` determines what is retried. It contains HTTP status codes, Shopify GraphQL errors codes, and exception classes.
101
101
  By default it contains:
102
102
 
103
103
  * `"5XX"` - Any HTTP 5XX status
@@ -160,6 +160,13 @@ pager.execute(query, :foo => 123) do |page|
160
160
  end
161
161
  ```
162
162
 
163
+ If a block is not given an `Enumerator::Lazy` instance is returned that will fetch the next page upon each iteration:
164
+
165
+ ```rb
166
+ results = pager.execute(query, :foo => 123)
167
+ results.each { |page| ... }
168
+ ```
169
+
163
170
  #### `after` Pagination
164
171
 
165
172
  To use `after` pagination, i.e., to paginate forward, your query must:
@@ -259,6 +266,37 @@ pager.execute(query) { |page| }
259
266
 
260
267
  The `"data"` and `"pageInfo"` keys are automatically added if not provided.
261
268
 
269
+ ### Raising on Warnings
270
+
271
+ A successful GraphQL response can still contain warnings (for example an invalid search field reported under
272
+ `extensions.search`). By default these are ignored. Set `:raise_on_warnings` to `true` to raise a `WarningError`
273
+ (a subclass of `GraphQLError`) whenever the response contains warnings:
274
+
275
+ ```rb
276
+ gql = ShopifyAPI::GraphQL::Tiny.new(shop, token, :raise_on_warnings => true)
277
+
278
+ begin
279
+ gql.execute(query)
280
+ rescue ShopifyAPI::GraphQL::Tiny::WarningError => e
281
+ warn e.message # The warnings, formatted
282
+ e.response # The full GraphQL response Hash
283
+ end
284
+ ```
285
+
286
+ ## Why Use This Instead of Shopify's API Client?
287
+
288
+ - Easy-to-use
289
+ - Built-in retry
290
+ - Built-in pagination
291
+ - Lightweight
292
+
293
+ Overall, Shopify's API client is bloated trash that will give you development headaches and long-term maintenance nightmares.
294
+
295
+ We used to use it, staring way back in 2015, but eventually had to pivot away from their Ruby libraries due to developer
296
+ frustration and high maintenance cost (and don't get us started on the ShopifyApp gem!@#).
297
+
298
+ For more information see: https://github.com/Shopify/shopify-api-ruby/issues/1181
299
+
262
300
  ## Testing
263
301
 
264
302
  `cp env.template .env` and fill-in `.env` with the missing values. This requires a Shopify store.
@@ -271,6 +309,7 @@ bundle exec rake rate_limit SHOPIFY_DOMAIN=your-domain SHOPIFY_TOKEN=your-token
271
309
 
272
310
  ## See Also
273
311
 
312
+ - [`ShopifyAPI::GraphQL::Request`](https://github.com/ScreenStaring/shopify_api-graphql-request) - A higher-level wrapper around this class with improved exception handling and `:snake_case` hash key conversion
274
313
  - [Shopify Dev Tools](https://github.com/ScreenStaring/shopify-dev-tools) - Command-line program to assist with the development and/or maintenance of Shopify apps and stores
275
314
  - [Shopify ID Export](https://github.com/ScreenStaring/shopify_id_export/) - Dump Shopify product and variant IDs —along with other identifiers— to a CSV or JSON file
276
315
  - [`TinyGID`](https://github.com/sshaw/tiny_gid/) - Build Global ID (gid://) URI strings from scalar values
@@ -3,7 +3,7 @@
3
3
  module ShopifyAPI
4
4
  module GraphQL
5
5
  class Tiny
6
- VERSION = "1.0.0"
6
+ VERSION = "1.0.2"
7
7
  end
8
8
  end
9
9
  end
@@ -29,6 +29,7 @@ module ShopifyAPI
29
29
  end
30
30
 
31
31
  RateLimitError = Class.new(GraphQLError)
32
+ WarningError = Class.new(GraphQLError)
32
33
 
33
34
  class HTTPError < Error
34
35
  attr_reader :code
@@ -84,6 +85,7 @@ module ShopifyAPI
84
85
  # [:jitter (Boolean)] Exponential backoff jitter (random delay added to backoff). Defaults to +true+
85
86
  # [:multiplier (Float)] Exponential backoff multiplier. Defaults to +2.0+
86
87
  # [:debug (Boolean|IO)] Output the HTTP request/response to +STDERR+ or to its value if it's an +IO+. Defaults to +false+.
88
+ # [:raise_on_warnings (Boolean)] If +true+ raise a WarningError when the GraphQL response contains warnings. Defaults to +false+.
87
89
  #
88
90
  # === Errors
89
91
  #
@@ -96,6 +98,7 @@ module ShopifyAPI
96
98
 
97
99
  @domain = shopify_domain(shop)
98
100
  @options = options || {}
101
+ @raise_on_warnings = @options[:raise_on_warnings]
99
102
 
100
103
  @headers = DEFAULT_HEADERS.dup
101
104
  @headers[ACCESS_TOKEN_HEADER] = token
@@ -137,6 +140,8 @@ module ShopifyAPI
137
140
  # rate-limited after the configured number of retry attempts
138
141
  # * A ShopifyAPI::GraphQL::Tiny::GraphQLError is raised if the response contains an +errors+ property that is
139
142
  # not a rate-limit error
143
+ # * A ShopifyAPI::GraphQL::Tiny::WarningError is raised if the +:raise_on_warnings+ option is +true+ and the
144
+ # response contains warnings
140
145
  #
141
146
  # === Returns
142
147
  #
@@ -158,7 +163,8 @@ module ShopifyAPI
158
163
  # page.dig("data", "product", "title")
159
164
  # end
160
165
  #
161
- # The block is called for each page.
166
+ # The block is called for each page. If a block is not provided returns an instance of Enumerator::Lazy that will fetch the next
167
+ # page on each iteration.
162
168
  #
163
169
  # Using pagination requires you to include the
164
170
  # {PageInfo}[https://shopify.dev/api/admin-graphql/2022-10/objects/PageInfo]
@@ -188,9 +194,13 @@ module ShopifyAPI
188
194
  # Defaults to <code>"before"</code> or <code>"after"</code>, depending on the pagination
189
195
  # direction.
190
196
  #
197
+ # === Returns
198
+ #
199
+ # +nil+ or `Enumerator::Lazy` if no block was provided
200
+ #
191
201
  # === Errors
192
202
  #
193
- # ArgumentError
203
+ # See #execute
194
204
 
195
205
  def paginate(*options)
196
206
  Pager.new(self, options)
@@ -216,12 +226,20 @@ module ShopifyAPI
216
226
  end
217
227
 
218
228
  json = parse_json(response.body)
219
- return json unless json.include?("errors")
220
229
 
221
- return make_request(query, variables) if handle_graphql_error(json)
230
+ if json.include?("errors")
231
+ return make_request(query, variables) if handle_graphql_error(json)
232
+
233
+ message = error_message(json["errors"])
234
+ raise GraphQLError.new("failed to execute query for #@domain: #{message}", json)
235
+ end
236
+
237
+ if @raise_on_warnings
238
+ warnings = find_warnings(json)
239
+ raise WarningError.new("query for #@domain returned warnings: #{warning_message(warnings)}", json) unless warnings.empty?
240
+ end
222
241
 
223
- message = error_message(json["errors"])
224
- raise GraphQLError.new("failed to execute query for #@domain: #{message}", json)
242
+ json
225
243
  end
226
244
 
227
245
  def post(query, variables = nil)
@@ -318,6 +336,34 @@ module ShopifyAPI
318
336
  end.join(", ")
319
337
  end
320
338
 
339
+ def find_warnings(data, prop = nil)
340
+ case data
341
+ when Hash
342
+ data.flat_map do |key, value|
343
+ if key == "warnings" && value.is_a?(Array)
344
+ value.map { |w| [prop, w] }
345
+ else
346
+ find_warnings(value, key)
347
+ end
348
+ end
349
+ when Array
350
+ data.flat_map { |value| find_warnings(value, prop) }
351
+ else
352
+ []
353
+ end
354
+ end
355
+
356
+ def warning_message(warnings)
357
+ warnings.map do |prop, warning|
358
+ next warning.to_s unless warning.is_a?(Hash)
359
+
360
+ field = Array(warning["field"]).join(".")
361
+ parts = [prop]
362
+ parts << field unless field.empty?
363
+ "#{parts.join(" ")}: #{warning["message"]}"
364
+ end.join("\n")
365
+ end
366
+
321
367
  def request_attempts_remain?
322
368
  @request_attempts < @backoff_options[:max_attempts]
323
369
  end
@@ -348,19 +394,22 @@ module ShopifyAPI
348
394
  variables ||= {}
349
395
  pagination_finder = @options[@options[:direction]]
350
396
 
351
- loop do
352
- page = @gql.execute(q, variables)
353
-
354
- yield page
397
+ enumerator = Enumerator.new do |y|
398
+ loop do
399
+ page = @gql.execute(q, variables)
400
+ y << page
355
401
 
356
- cursor = pagination_finder[page]
357
- break unless cursor
402
+ cursor = pagination_finder[page]
403
+ break unless cursor
358
404
 
359
- next_page_variables = variables.dup
360
- next_page_variables[@options[:variable]] = cursor
361
- #break unless next_page_variables != variables
405
+ variables[@options[:variable]] = cursor
406
+ end
407
+ end
362
408
 
363
- variables = next_page_variables
409
+ if block_given?
410
+ enumerator.each { |page| yield page }
411
+ else
412
+ enumerator.lazy
364
413
  end
365
414
  end
366
415
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shopify_api-graphql-tiny
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Skye Shaw
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-01-20 00:00:00.000000000 Z
10
+ date: 2026-06-23 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: net_http_timeout_errors