shopify_api-graphql-tiny 0.1.0 → 0.1.1
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 +4 -4
- data/.env.template +5 -0
- data/.github/workflows/ci.yml +2 -1
- data/Changes +4 -0
- data/README.md +129 -2
- data/lib/shopify_api/graphql/tiny/version.rb +3 -1
- data/lib/shopify_api/graphql/tiny.rb +197 -4
- data/shopify_api-graphql-tiny.gemspec +7 -1
- metadata +10 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 125818846e86b77345ee59d8e6d03c4600b2aaa56f18e33ec1ae23ecfa90938e
|
4
|
+
data.tar.gz: 4b4443e43ff6d37fdbbad202222b6f5b3dff11f8f0a377260898dab06d2209ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5418a19e0a24b56d7e587c9a1bde22bf4d1c830639393fa30b371216dc6e8f9824cf1b31f477e6834ae17ec92c0594803a8b0593920a1b7db22655e5fc8439dd
|
7
|
+
data.tar.gz: b2ae36edf66dcb67395950e39231cbba3d13f0873b381a78b1cd8404f3e40fd3c7dbc157030c6665cc029ea6800611455cfb19da8fc5dfcd7fc08c3d45304631
|
data/.env.template
CHANGED
data/.github/workflows/ci.yml
CHANGED
@@ -11,10 +11,11 @@ jobs:
|
|
11
11
|
SHOPIFY_DOMAIN: "${{ secrets.SHOPIFY_DOMAIN }}"
|
12
12
|
SHOPIFY_TOKEN: "${{ secrets.SHOPIFY_TOKEN }}"
|
13
13
|
SHOPIFY_CUSTOMER_ID: "${{ secrets.SHOPIFY_CUSTOMER_ID }}"
|
14
|
+
SHOPIFY_PRODUCT_ID: "${{ secrets.SHOPIFY_PRODUCT_ID }}"
|
14
15
|
|
15
16
|
strategy:
|
16
17
|
matrix:
|
17
|
-
ruby: ["3.1", "3.0", "2.7.2", "2.6.6", "2.5.8", "2.4.10"]
|
18
|
+
ruby: ["3.2", "3.1", "3.0", "2.7.2", "2.6.6", "2.5.8", "2.4.10"]
|
18
19
|
|
19
20
|
steps:
|
20
21
|
- uses: actions/checkout@v2
|
data/Changes
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# ShopifyAPI::GraphQL::Tiny
|
2
2
|
|
3
|
-
Lightweight, no-nonsense, Shopify GraphQL Admin API client with built-in retry.
|
3
|
+
Lightweight, no-nonsense, Shopify GraphQL Admin API client with built-in pagination and retry.
|
4
4
|
|
5
5
|
[](https://github.com/ScreenStaring/shopify_api-graphql-tiny/actions)
|
6
6
|
|
@@ -10,6 +10,8 @@ Lightweight, no-nonsense, Shopify GraphQL Admin API client with built-in retry.
|
|
10
10
|
require "shopify_api/graphql/tiny"
|
11
11
|
|
12
12
|
gql = ShopifyAPI::GraphQL::Tiny.new("my-shop", token)
|
13
|
+
|
14
|
+
# Automatically retried
|
13
15
|
result = gql.execute(<<-GQL, :id => "gid://shopify/Customer/1283599123")
|
14
16
|
query findCustomer($id: ID!) {
|
15
17
|
customer(id: $id) {
|
@@ -33,6 +35,8 @@ p customer["tags"]
|
|
33
35
|
p customer.dig("metafields", "edges", 0, "node")["value"]
|
34
36
|
|
35
37
|
updates = { :id => customer["id"], :tags => customer["tags"] + %w[foo bar] }
|
38
|
+
|
39
|
+
# Automatically retried as well
|
36
40
|
result = gql.execute(<<-GQL, :input => updates)
|
37
41
|
mutation customerUpdate($input: CustomerInput!) {
|
38
42
|
customerUpdate(input: $input) {
|
@@ -50,7 +54,129 @@ GQL
|
|
50
54
|
p result.dig("data", "customerUpdate", "userErrors")
|
51
55
|
```
|
52
56
|
|
53
|
-
|
57
|
+
### Pagination
|
58
|
+
|
59
|
+
In addition to built-in request retry `ShopifyAPI::GraphQL::Tiny` also builds in support for pagination.
|
60
|
+
|
61
|
+
Using pagination requires you to include [the Shopify `PageInfo` object](https://shopify.dev/api/admin-graphql/2022-10/objects/PageInfo)
|
62
|
+
in your queries and wrap them in a function that accepts a page/cursor argument.
|
63
|
+
|
64
|
+
The pager's `#execute` is like the non-paginated `#execute` method and accepts additional, non-pagination query arguments:
|
65
|
+
|
66
|
+
```rb
|
67
|
+
pager.execute(query, :foo => 123)
|
68
|
+
```
|
69
|
+
|
70
|
+
And it accepts a block which will be passed each page returned by the query:
|
71
|
+
|
72
|
+
```rb
|
73
|
+
pager.execute(query, :foo => 123) do |page|
|
74
|
+
# do something with each page
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
#### `after` Pagination
|
79
|
+
|
80
|
+
To use `after` pagination, i.e., to paginate forward, your query must:
|
81
|
+
|
82
|
+
- Make the page/cursor argument optional
|
83
|
+
- Include `PageInfo`'s `hasNextPage` and `endCursor` fields
|
84
|
+
|
85
|
+
For example:
|
86
|
+
|
87
|
+
```rb
|
88
|
+
FIND_ORDERS = <<-GQL
|
89
|
+
query findOrders($after: String) {
|
90
|
+
orders(first: 10 after: $after) {
|
91
|
+
pageInfo {
|
92
|
+
hasNextPage
|
93
|
+
endCursor
|
94
|
+
}
|
95
|
+
edges {
|
96
|
+
node {
|
97
|
+
id
|
98
|
+
email
|
99
|
+
}
|
100
|
+
}
|
101
|
+
}
|
102
|
+
}
|
103
|
+
GQL
|
104
|
+
|
105
|
+
pager = gql.paginate # This is the same as gql.paginate(:after)
|
106
|
+
pager.execute(FIND_ORDERS) do |page|
|
107
|
+
orders = page.dig("data", "orders", "edges")
|
108
|
+
orders.each do |order|
|
109
|
+
# ...
|
110
|
+
end
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
By default it is assumed your GraphQL query uses a variable named `$after`. You can specify a different name using the `:variable`
|
115
|
+
option:
|
116
|
+
|
117
|
+
```rb
|
118
|
+
pager = gql.paginate(:after, :variable => "yourVariable")
|
119
|
+
```
|
120
|
+
|
121
|
+
#### `before` Pagination
|
122
|
+
|
123
|
+
To use `before` pagination, i.e. to paginate backward, your query must:
|
124
|
+
|
125
|
+
- Make the page/cursor argument **required**
|
126
|
+
- Include the `PageInfo`'s `hasPreviousPage` and `startCursor` fields
|
127
|
+
- Specify the `:before` argument to `#paginate`
|
128
|
+
|
129
|
+
For example:
|
130
|
+
|
131
|
+
```rb
|
132
|
+
FIND_ORDERS = <<-GQL
|
133
|
+
query findOrders($before: String) {
|
134
|
+
orders(last: 10 before: $before) {
|
135
|
+
pageInfo {
|
136
|
+
hasPreviousPage
|
137
|
+
startCursor
|
138
|
+
}
|
139
|
+
edges {
|
140
|
+
node {
|
141
|
+
id
|
142
|
+
email
|
143
|
+
}
|
144
|
+
}
|
145
|
+
}
|
146
|
+
}
|
147
|
+
GQL
|
148
|
+
|
149
|
+
pager = gql.paginate(:before)
|
150
|
+
pager.execute(FIND_ORDERS) do |page|
|
151
|
+
# ...
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
By default it is assumed your GraphQL query uses a variable named `$before`. You can specify a different name using the `:variable`
|
156
|
+
option:
|
157
|
+
|
158
|
+
```rb
|
159
|
+
pager = gql.paginate(:before, :variable => "yourVariable")
|
160
|
+
```
|
161
|
+
|
162
|
+
#### Response Pagination Data
|
163
|
+
|
164
|
+
By default `ShopifyAPI::GraphQL::Tiny` will use the first `pageInfo` block with a next or previous page it finds
|
165
|
+
in the GraphQL response. If necessary you can specify an explicit location for the `pageInfo` block:
|
166
|
+
|
167
|
+
```rb
|
168
|
+
pager = gql.paginate(:after => %w[some path to it])
|
169
|
+
pager.execute(query) { |page| }
|
170
|
+
|
171
|
+
pager = gql.paginate(:after => ->(data) { data.dig("some", "path", "to", "it") })
|
172
|
+
pager.execute(query) { |page| }
|
173
|
+
```
|
174
|
+
|
175
|
+
The `"data"` and `"pageInfo"` keys are automatically added if not provided.
|
176
|
+
|
177
|
+
### Automatically Retrying Failed Requests
|
178
|
+
|
179
|
+
See [the docs](https://rubydoc.info/gems/shopify_api-graphql-tiny) for more information.
|
54
180
|
|
55
181
|
## Testing
|
56
182
|
|
@@ -59,6 +185,7 @@ See [the docs](https://rdoc.info/gems/shopify_api-graphql-tiny) for complete doc
|
|
59
185
|
## See Also
|
60
186
|
|
61
187
|
- [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
|
188
|
+
- [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
|
62
189
|
- [ShopifyAPIRetry](https://github.com/ScreenStaring/shopify_api_retry) - Retry a ShopifyAPI request if rate-limited or other errors occur (REST and GraphQL APIs)
|
63
190
|
|
64
191
|
## License
|
@@ -43,6 +43,8 @@ module ShopifyAPI
|
|
43
43
|
QUERY_COST_HEADER = "X-GraphQL-Cost-Include-Fields"
|
44
44
|
|
45
45
|
DEFAULT_HEADERS = { "Content-Type" => "application/json" }.freeze
|
46
|
+
|
47
|
+
# Retry rules to be used for all instances if no rules are specified via the +:retry+ option when creating an instance
|
46
48
|
DEFAULT_RETRY_OPTIONS = {
|
47
49
|
ConnectionError => { :wait => 3, :tries => 20 },
|
48
50
|
GraphQLError => { :wait => 3, :tries => 20 },
|
@@ -96,21 +98,71 @@ module ShopifyAPI
|
|
96
98
|
#
|
97
99
|
# === Errors
|
98
100
|
#
|
99
|
-
# ConnectionError, HTTPError, RateLimitError, GraphQLError
|
101
|
+
# ArgumentError, ConnectionError, HTTPError, RateLimitError, GraphQLError
|
100
102
|
#
|
101
|
-
# * An HTTPError is raised of the response does not have 200 status code
|
102
|
-
# * A RateLimitError is raised if rate-limited and retries are disabled or if still
|
103
|
-
#
|
103
|
+
# * An ShopifyAPI::GraphQL::Tiny::HTTPError is raised of the response does not have 200 status code
|
104
|
+
# * A ShopifyAPI::GraphQL::Tiny::RateLimitError is raised if rate-limited and retries are disabled or if still
|
105
|
+
# rate-limited after the configured number of retry attempts
|
106
|
+
# * A ShopifyAPI::GraphQL::Tiny::GraphQLError is raised if the response contains an +errors+ property that is
|
107
|
+
# not a rate-limit error
|
104
108
|
#
|
105
109
|
# === Returns
|
106
110
|
#
|
107
111
|
# [Hash] The GraphQL response. Unmodified.
|
108
112
|
|
109
113
|
def execute(q, variables = nil)
|
114
|
+
raise ArgumentError, "query required" if q.nil? || q.to_s.strip.empty?
|
115
|
+
|
110
116
|
config = retry? ? @options[:retry] || DEFAULT_RETRY_OPTIONS : {}
|
111
117
|
ShopifyAPIRetry::GraphQL.retry(config) { post(q, variables) }
|
112
118
|
end
|
113
119
|
|
120
|
+
##
|
121
|
+
# Create a pager to execute a paginated query:
|
122
|
+
#
|
123
|
+
# pager = gql.paginate # This is the same as gql.paginate(:after)
|
124
|
+
# pager.execute(query, :id => id) do |page|
|
125
|
+
# page.dig("data", "product", "title")
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
# The block is called for each page.
|
129
|
+
#
|
130
|
+
# Using pagination requires you to include the
|
131
|
+
# {PageInfo}[https://shopify.dev/api/admin-graphql/2022-10/objects/PageInfo]
|
132
|
+
# object in your queries and wrap them in a function that accepts a page/cursor argument.
|
133
|
+
# See the README for more information.
|
134
|
+
#
|
135
|
+
# === Arguments
|
136
|
+
#
|
137
|
+
# [direction (Symbol)] The direction to paginate, either +:after+ or +:before+. Optional, defaults to +:after:+
|
138
|
+
# [options (Hash)] Pagination options. Optional.
|
139
|
+
#
|
140
|
+
# === Options
|
141
|
+
#
|
142
|
+
# [:after (Array|Proc)] The location of {PageInfo}[https://shopify.dev/api/admin-graphql/2022-10/objects/PageInfo]
|
143
|
+
# block.
|
144
|
+
#
|
145
|
+
# An +Array+ will be passed directly to <code>Hash#dig</code>. A +TypeError+ resulting
|
146
|
+
# from the +#dig+ call will be raised as an +ArgumentError+.
|
147
|
+
#
|
148
|
+
# The <code>"data"</code> and <code>"pageInfo"</code> keys are automatically added if not provided.
|
149
|
+
#
|
150
|
+
# A +Proc+ must accept the GraphQL response +Hash+ as its argument and must return the
|
151
|
+
# +pageInfo+ block to use for pagination.
|
152
|
+
#
|
153
|
+
# [:before (Array|Proc)] See the +:after+ option
|
154
|
+
# [:variable (String)] Name of the GraphQL variable to use as the "page" argument.
|
155
|
+
# Defaults to <code>"before"</code> or <code>"after"</code>, depending on the pagination
|
156
|
+
# direction.
|
157
|
+
#
|
158
|
+
# === Errors
|
159
|
+
#
|
160
|
+
# ArgumentError
|
161
|
+
|
162
|
+
def paginate(*options)
|
163
|
+
Pager.new(self, options)
|
164
|
+
end
|
165
|
+
|
114
166
|
private
|
115
167
|
|
116
168
|
def retry?
|
@@ -166,6 +218,147 @@ module ShopifyAPI
|
|
166
218
|
raise GraphQLError.new(prefix + errors, json)
|
167
219
|
end
|
168
220
|
end
|
221
|
+
|
222
|
+
class Pager # :nodoc:
|
223
|
+
NEXT_PAGE_KEYS = {
|
224
|
+
:before => %w[hasPreviousPage startCursor].freeze,
|
225
|
+
:after => %w[hasNextPage endCursor].freeze
|
226
|
+
}.freeze
|
227
|
+
|
228
|
+
def initialize(gql, *options)
|
229
|
+
@gql = gql
|
230
|
+
@options = normalize_options(options)
|
231
|
+
end
|
232
|
+
|
233
|
+
def execute(q, variables = nil)
|
234
|
+
unless pagination_variable_exists?(q)
|
235
|
+
raise ArgumentError, "query does not contain the pagination variable '#{@options[:variable]}'"
|
236
|
+
end
|
237
|
+
|
238
|
+
variables ||= {}
|
239
|
+
pagination_finder = @options[@options[:direction]]
|
240
|
+
|
241
|
+
loop do
|
242
|
+
page = @gql.execute(q, variables)
|
243
|
+
|
244
|
+
yield page
|
245
|
+
|
246
|
+
cursor = pagination_finder[page]
|
247
|
+
break unless cursor
|
248
|
+
|
249
|
+
next_page_variables = variables.dup
|
250
|
+
next_page_variables[@options[:variable]] = cursor
|
251
|
+
#break unless next_page_variables != variables
|
252
|
+
|
253
|
+
variables = next_page_variables
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
private
|
258
|
+
|
259
|
+
def normalize_options(options)
|
260
|
+
normalized = {}
|
261
|
+
|
262
|
+
options.flatten!
|
263
|
+
options.each do |option|
|
264
|
+
case option
|
265
|
+
when Hash
|
266
|
+
normalized.merge!(normalize_hash_option(option))
|
267
|
+
when *NEXT_PAGE_KEYS.keys
|
268
|
+
normalized[:direction] = option
|
269
|
+
else
|
270
|
+
raise ArgumentError, "invalid pagination option #{option}"
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
normalized[:direction] ||= :after
|
275
|
+
normalized[normalized[:direction]] ||= method(:default_pagination_finder)
|
276
|
+
|
277
|
+
normalized[:variable] ||= normalized[:direction].to_s
|
278
|
+
normalized[:variable] = normalized[:variable].sub(%r{\A\$}, "")
|
279
|
+
|
280
|
+
normalized
|
281
|
+
end
|
282
|
+
|
283
|
+
def normalize_hash_option(option)
|
284
|
+
normalized = option.dup
|
285
|
+
|
286
|
+
NEXT_PAGE_KEYS.each do |key, _|
|
287
|
+
next unless option.include?(key)
|
288
|
+
|
289
|
+
normalized[:direction] = key
|
290
|
+
|
291
|
+
case option[key]
|
292
|
+
when Proc
|
293
|
+
normalized[key] = ->(data) { extract_cursor(option[key][data]) }
|
294
|
+
when Array
|
295
|
+
path = pagination_path(option[key])
|
296
|
+
normalized[key] = ->(data) do
|
297
|
+
begin
|
298
|
+
extract_cursor(data.dig(*path))
|
299
|
+
rescue TypeError => e
|
300
|
+
# Use original path in error as not to confuse
|
301
|
+
raise ArgumentError, "invalid pagination path #{option[key]}: #{e}"
|
302
|
+
end
|
303
|
+
end
|
304
|
+
else
|
305
|
+
raise ArgumentError, "invalid pagination locator #{option[key]}"
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
normalized
|
310
|
+
end
|
311
|
+
|
312
|
+
def pagination_path(user_path)
|
313
|
+
path = user_path.dup
|
314
|
+
|
315
|
+
# No need for this, we check for this key ourselves
|
316
|
+
path.pop if path[-1] == "pageInfo"
|
317
|
+
|
318
|
+
# Must always include this (sigh)
|
319
|
+
path.unshift("data") if path[0] != "data"
|
320
|
+
|
321
|
+
path
|
322
|
+
end
|
323
|
+
|
324
|
+
def pagination_variable_exists?(query)
|
325
|
+
name = Regexp.quote(@options[:variable])
|
326
|
+
query.match?(%r{\$#{name}\s*:})
|
327
|
+
end
|
328
|
+
|
329
|
+
def extract_cursor(data)
|
330
|
+
return unless data.is_a?(Hash)
|
331
|
+
|
332
|
+
has_next, next_cursor = NEXT_PAGE_KEYS[@options[:direction]]
|
333
|
+
|
334
|
+
pi = data["pageInfo"]
|
335
|
+
return unless pi && pi[has_next]
|
336
|
+
|
337
|
+
pi[next_cursor]
|
338
|
+
end
|
339
|
+
|
340
|
+
def default_pagination_finder(data)
|
341
|
+
cursor = nil
|
342
|
+
|
343
|
+
case data
|
344
|
+
when Hash
|
345
|
+
cursor = extract_cursor(data)
|
346
|
+
return cursor if cursor
|
347
|
+
|
348
|
+
data.values.each do |v|
|
349
|
+
cursor = default_pagination_finder(v)
|
350
|
+
break if cursor
|
351
|
+
end
|
352
|
+
when Array
|
353
|
+
data.each do |v|
|
354
|
+
cursor = default_pagination_finder(v)
|
355
|
+
break if cursor
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
cursor
|
360
|
+
end
|
361
|
+
end
|
169
362
|
end
|
170
363
|
|
171
364
|
GQL = GraphQL
|
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["Skye Shaw"]
|
10
10
|
spec.email = ["skye.shaw@gmail.com"]
|
11
11
|
|
12
|
-
spec.summary = %q{Lightweight, no-nonsense, Shopify Admin API
|
12
|
+
spec.summary = %q{Lightweight, no-nonsense, Shopify GraphQL Admin API client with built-in pagination and retry}
|
13
13
|
spec.homepage = "https://github.com/ScreenStaring/shopify_api-graphql-tiny"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
@@ -21,6 +21,12 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.bindir = "exe"
|
22
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
23
|
spec.require_paths = ["lib"]
|
24
|
+
spec.metadata = {
|
25
|
+
"bug_tracker_uri" => "https://github.com/ScreenStaring/shopify_api-graphql-tiny/issues",
|
26
|
+
"changelog_uri" => "https://github.com/ScreenStaring/shopify_api-graphql-tiny/blob/master/Changes",
|
27
|
+
"documentation_uri" => "https://rubydoc.info/gems/shopify_api-graphql-tiny",
|
28
|
+
"source_code_uri" => "https://github.com/ScreenStaring/shopify_api-graphql-tiny",
|
29
|
+
}
|
24
30
|
|
25
31
|
spec.add_dependency "shopify_api_retry", "~> 0.2"
|
26
32
|
spec.add_development_dependency "webmock", "~> 3.0"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shopify_api-graphql-tiny
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Skye Shaw
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-02-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: shopify_api_retry
|
@@ -104,7 +104,11 @@ files:
|
|
104
104
|
homepage: https://github.com/ScreenStaring/shopify_api-graphql-tiny
|
105
105
|
licenses:
|
106
106
|
- MIT
|
107
|
-
metadata:
|
107
|
+
metadata:
|
108
|
+
bug_tracker_uri: https://github.com/ScreenStaring/shopify_api-graphql-tiny/issues
|
109
|
+
changelog_uri: https://github.com/ScreenStaring/shopify_api-graphql-tiny/blob/master/Changes
|
110
|
+
documentation_uri: https://rubydoc.info/gems/shopify_api-graphql-tiny
|
111
|
+
source_code_uri: https://github.com/ScreenStaring/shopify_api-graphql-tiny
|
108
112
|
post_install_message:
|
109
113
|
rdoc_options: []
|
110
114
|
require_paths:
|
@@ -120,10 +124,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
120
124
|
- !ruby/object:Gem::Version
|
121
125
|
version: '0'
|
122
126
|
requirements: []
|
123
|
-
|
124
|
-
rubygems_version: 2.7.6
|
127
|
+
rubygems_version: 3.1.4
|
125
128
|
signing_key:
|
126
129
|
specification_version: 4
|
127
|
-
summary: Lightweight, no-nonsense, Shopify Admin API
|
128
|
-
retry
|
130
|
+
summary: Lightweight, no-nonsense, Shopify GraphQL Admin API client with built-in
|
131
|
+
pagination and retry
|
129
132
|
test_files: []
|