shopify_api-graphql-tiny 0.0.2 → 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 +9 -0
- data/README.md +130 -3
- data/lib/shopify_api/graphql/tiny/version.rb +3 -1
- data/lib/shopify_api/graphql/tiny.rb +207 -14
- data/shopify_api-graphql-tiny.gemspec +7 -1
- metadata +9 -5
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
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
2023-02-12 v0.1.1
|
|
2
|
+
--------------------
|
|
3
|
+
* Add pagination support
|
|
4
|
+
|
|
5
|
+
2022-06-16 v0.1.0
|
|
6
|
+
--------------------
|
|
7
|
+
* Make RateLimitError a subclass of GraphQLError
|
|
8
|
+
* Set HTTP user-agent header
|
|
9
|
+
|
|
1
10
|
2022-06-08 v0.0.2
|
|
2
11
|
--------------------
|
|
3
12
|
* Add GraphQLError#response to return the parsed GQL response body
|
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
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
|
|
|
7
7
|
## Usage
|
|
8
8
|
|
|
@@ -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
|
|
@@ -24,6 +24,7 @@ module ShopifyAPI
|
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
RateLimitError = Class.new(GraphQLError)
|
|
27
28
|
|
|
28
29
|
class HTTPError < Error
|
|
29
30
|
attr_reader :code
|
|
@@ -34,14 +35,7 @@ module ShopifyAPI
|
|
|
34
35
|
end
|
|
35
36
|
end
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
attr_reader :response
|
|
39
|
-
|
|
40
|
-
def initialize(message, response)
|
|
41
|
-
super(message)
|
|
42
|
-
@response = response
|
|
43
|
-
end
|
|
44
|
-
end
|
|
38
|
+
USER_AGENT = "ShopifyAPI::GraphQL::Tiny v#{VERSION} (Ruby v#{RUBY_VERSION})"
|
|
45
39
|
|
|
46
40
|
SHOPIFY_DOMAIN = ".myshopify.com"
|
|
47
41
|
|
|
@@ -49,6 +43,8 @@ module ShopifyAPI
|
|
|
49
43
|
QUERY_COST_HEADER = "X-GraphQL-Cost-Include-Fields"
|
|
50
44
|
|
|
51
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
|
|
52
48
|
DEFAULT_RETRY_OPTIONS = {
|
|
53
49
|
ConnectionError => { :wait => 3, :tries => 20 },
|
|
54
50
|
GraphQLError => { :wait => 3, :tries => 20 },
|
|
@@ -71,7 +67,7 @@ module ShopifyAPI
|
|
|
71
67
|
#
|
|
72
68
|
# [:retry (Boolean|Hash)] +Hash+ can be retry config options. For the format see {ShopifyAPIRetry}[https://github.com/ScreenStaring/shopify_api_retry/#usage]. Defaults to +true+
|
|
73
69
|
# [:version (String)] Shopify API version to use. Defaults to the latest version.
|
|
74
|
-
# [:debug (Boolean)] Output the HTTP request/response to +STDERR+. Defaults to +false+.
|
|
70
|
+
# [:debug (Boolean|IO)] Output the HTTP request/response to +STDERR+ or to its value if it's an +IO+. Defaults to +false+.
|
|
75
71
|
#
|
|
76
72
|
# === Errors
|
|
77
73
|
#
|
|
@@ -102,21 +98,71 @@ module ShopifyAPI
|
|
|
102
98
|
#
|
|
103
99
|
# === Errors
|
|
104
100
|
#
|
|
105
|
-
# ConnectionError, HTTPError, RateLimitError, GraphQLError
|
|
101
|
+
# ArgumentError, ConnectionError, HTTPError, RateLimitError, GraphQLError
|
|
106
102
|
#
|
|
107
|
-
# * An HTTPError is raised of the response does not have 200 status code
|
|
108
|
-
# * A RateLimitError is raised if rate-limited and retries are disabled or if still
|
|
109
|
-
#
|
|
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
|
|
110
108
|
#
|
|
111
109
|
# === Returns
|
|
112
110
|
#
|
|
113
111
|
# [Hash] The GraphQL response. Unmodified.
|
|
114
112
|
|
|
115
113
|
def execute(q, variables = nil)
|
|
114
|
+
raise ArgumentError, "query required" if q.nil? || q.to_s.strip.empty?
|
|
115
|
+
|
|
116
116
|
config = retry? ? @options[:retry] || DEFAULT_RETRY_OPTIONS : {}
|
|
117
117
|
ShopifyAPIRetry::GraphQL.retry(config) { post(q, variables) }
|
|
118
118
|
end
|
|
119
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
|
+
|
|
120
166
|
private
|
|
121
167
|
|
|
122
168
|
def retry?
|
|
@@ -138,12 +184,18 @@ module ShopifyAPI
|
|
|
138
184
|
|
|
139
185
|
post = Net::HTTP::Post.new(@endpoint.path)
|
|
140
186
|
post.body = params.to_json
|
|
187
|
+
post["User-Agent"] = USER_AGENT
|
|
141
188
|
|
|
142
189
|
@headers.each { |k,v| post[k] = v }
|
|
143
190
|
|
|
144
191
|
request = Net::HTTP.new(@endpoint.host, @endpoint.port)
|
|
145
192
|
request.use_ssl = true
|
|
146
|
-
|
|
193
|
+
|
|
194
|
+
if @options[:debug]
|
|
195
|
+
request.set_debug_output(
|
|
196
|
+
@options[:debug].is_a?(IO) ? @options[:debug] : $stderr
|
|
197
|
+
)
|
|
198
|
+
end
|
|
147
199
|
|
|
148
200
|
response = request.start { |http| http.request(post) }
|
|
149
201
|
rescue => e
|
|
@@ -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.
|
|
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:
|
|
@@ -123,6 +127,6 @@ requirements: []
|
|
|
123
127
|
rubygems_version: 3.1.4
|
|
124
128
|
signing_key:
|
|
125
129
|
specification_version: 4
|
|
126
|
-
summary: Lightweight, no-nonsense, Shopify Admin API
|
|
127
|
-
retry
|
|
130
|
+
summary: Lightweight, no-nonsense, Shopify GraphQL Admin API client with built-in
|
|
131
|
+
pagination and retry
|
|
128
132
|
test_files: []
|