printavo-ruby 0.3.0 → 0.4.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 +4 -4
- data/README.md +38 -3
- data/docs/CHANGELOG.md +9 -0
- data/docs/FUTURE.md +1 -16
- data/lib/printavo/graphql_client.rb +84 -4
- data/lib/printavo/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4bdc16c358a3dafbdae1842e0ddb8eebbe8775053b1b95efb2308cd33056e2ea
|
|
4
|
+
data.tar.gz: 7399f57922f9244a02900c458bf9d2b9830aa0d63a5b4ff07952ca3ade6254fd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fa26e26063d2ed94011d20f22c4ae6c9fa964c62d2fa07b31343b97f2d1e9dbe623114960dab489cca029fe0d1f036964c7748b27a96e1c2eb7d63d33d3d285f
|
|
7
|
+
data.tar.gz: 0427ed5bc1601858080cb1f057547cb4143cc51d32cbd7189fa4fc8546260045c11603204814e28656b26a97f00e286634b24f8b536c879d14e5f8dd44f8313c
|
data/README.md
CHANGED
|
@@ -196,6 +196,42 @@ result = client.graphql.query(
|
|
|
196
196
|
)
|
|
197
197
|
```
|
|
198
198
|
|
|
199
|
+
### Mutations
|
|
200
|
+
|
|
201
|
+
Use `mutate` for GraphQL write operations:
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
result = client.graphql.mutate(
|
|
205
|
+
<<~GQL,
|
|
206
|
+
mutation UpdateOrder($id: ID!, $nickname: String!) {
|
|
207
|
+
updateOrder(id: $id, input: { nickname: $nickname }) {
|
|
208
|
+
order { id nickname }
|
|
209
|
+
errors
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
GQL
|
|
213
|
+
variables: { id: "99", nickname: "Rush Job" }
|
|
214
|
+
)
|
|
215
|
+
result["updateOrder"]["order"]["nickname"] # => "Rush Job"
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Raw GraphQL Pagination
|
|
219
|
+
|
|
220
|
+
Paginate any custom query without a resource wrapper using `paginate`.
|
|
221
|
+
The `path:` option is dot-separated and maps to the connection in the response:
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
client.graphql.paginate(MY_QUERY, path: "orders", first: 50) do |nodes|
|
|
225
|
+
nodes.each { |n| puts n["nickname"] }
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Nested connection (e.g. order.lineItems)
|
|
229
|
+
client.graphql.paginate(JOBS_QUERY, path: "order.lineItems",
|
|
230
|
+
variables: { orderId: "99" }) do |nodes|
|
|
231
|
+
nodes.each { |j| puts j["name"] }
|
|
232
|
+
end
|
|
233
|
+
```
|
|
234
|
+
|
|
199
235
|
## Webhooks
|
|
200
236
|
|
|
201
237
|
`Printavo::Webhooks.verify` provides Rack-compatible HMAC-SHA256 signature verification.
|
|
@@ -257,12 +293,11 @@ end
|
|
|
257
293
|
| 0.1.0 | Auth + Customers + Orders + Jobs + Webhooks (Rack-compatible) ✅ |
|
|
258
294
|
| 0.2.0 | Status registry + Inquiries ✅ |
|
|
259
295
|
| 0.3.0 | Pagination helpers (`each_page`, `all_pages`) + `bin/lint` ✅ |
|
|
260
|
-
| 0.4.0 | Expanded GraphQL DSL |
|
|
296
|
+
| 0.4.0 | Expanded GraphQL DSL (`mutate`, `paginate`) ✅ |
|
|
261
297
|
| 0.5.0 | Mutations (create/update) |
|
|
262
298
|
| 0.6.0 | Analytics / Reporting queries |
|
|
263
299
|
| 0.7.0 | Community burn-in / API stabilization |
|
|
264
|
-
| 0.8.0 |
|
|
265
|
-
| 0.9.0 | Retry/backoff + rate limit awareness |
|
|
300
|
+
| 0.8.0 | Retry/backoff + rate limit awareness |
|
|
266
301
|
| 1.0.0 | Stable public SDK |
|
|
267
302
|
|
|
268
303
|
**Rules**: `PATCH` = bug fix · `MINOR` = new backward-compatible feature · `MAJOR` = breaking change
|
data/docs/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.4.0] - 2026-03-30
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- `GraphqlClient#mutate` — semantic method for GraphQL mutations; identical transport to `query` but signals write intent at the call site; lays the foundation for 0.5.0 resource-level mutations
|
|
15
|
+
- `GraphqlClient#paginate` — cursor-following pagination for raw GraphQL queries; accepts a dot-separated `path:` to resolve nested connections (e.g. `"order.lineItems"`); yields each page's `nodes` array
|
|
16
|
+
- `GraphqlClient#execute` (private) — extracted shared POST logic; `query` and `mutate` both delegate to it
|
|
17
|
+
- `GraphqlClient#dig_path` (private) — resolves dot-separated key paths against nested response hashes
|
|
18
|
+
- Roadmap: removed stale Pagination entry from 0.8.0 (shipped in 0.3.0); Retry/backoff renumbered to 0.8.0
|
|
19
|
+
|
|
11
20
|
## [0.3.0] - 2026-03-29
|
|
12
21
|
|
|
13
22
|
### Added
|
data/docs/FUTURE.md
CHANGED
|
@@ -20,21 +20,6 @@ printavo sync orders --to crm
|
|
|
20
20
|
|
|
21
21
|
Planned version: `0.7.0`
|
|
22
22
|
|
|
23
|
-
### Pagination Abstraction
|
|
24
|
-
|
|
25
|
-
A lazy-enumerator-style helper to automatically page through all results:
|
|
26
|
-
|
|
27
|
-
```ruby
|
|
28
|
-
client.customers.each_page do |page|
|
|
29
|
-
page.each { |c| process(c) }
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Or collect all:
|
|
33
|
-
all_orders = client.orders.all_pages
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Planned version: `0.8.0`
|
|
37
|
-
|
|
38
23
|
### Retry/Backoff
|
|
39
24
|
|
|
40
25
|
Intelligent rate limit handling with exponential backoff:
|
|
@@ -48,7 +33,7 @@ client = Printavo::Client.new(
|
|
|
48
33
|
)
|
|
49
34
|
```
|
|
50
35
|
|
|
51
|
-
Planned version: `0.
|
|
36
|
+
Planned version: `0.8.0`
|
|
52
37
|
|
|
53
38
|
### Analytics / Reporting Expansion
|
|
54
39
|
|
|
@@ -7,16 +7,90 @@ module Printavo
|
|
|
7
7
|
@connection = connection
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
# Executes a GraphQL query and returns the parsed `data` hash.
|
|
11
|
+
#
|
|
12
|
+
# @param query_string [String] the GraphQL query document
|
|
13
|
+
# @param variables [Hash] optional input variables
|
|
14
|
+
# @return [Hash]
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# client.graphql.query("{ customers { nodes { id } } }")
|
|
18
|
+
# client.graphql.query(
|
|
19
|
+
# "query Customer($id: ID!) { customer(id: $id) { id email } }",
|
|
20
|
+
# variables: { id: "42" }
|
|
21
|
+
# )
|
|
10
22
|
def query(query_string, variables: {})
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
end
|
|
23
|
+
execute(query_string, variables: variables)
|
|
24
|
+
end
|
|
14
25
|
|
|
15
|
-
|
|
26
|
+
# Executes a GraphQL mutation and returns the parsed `data` hash.
|
|
27
|
+
# Semantically equivalent to `query` — both POST to the same endpoint —
|
|
28
|
+
# but distinguishes write intent at the call site.
|
|
29
|
+
#
|
|
30
|
+
# @param mutation_string [String] the GraphQL mutation document
|
|
31
|
+
# @param variables [Hash] optional input variables
|
|
32
|
+
# @return [Hash]
|
|
33
|
+
#
|
|
34
|
+
# @example
|
|
35
|
+
# client.graphql.mutate(
|
|
36
|
+
# <<~GQL,
|
|
37
|
+
# mutation UpdateOrder($id: ID!, $input: OrderInput!) {
|
|
38
|
+
# updateOrder(id: $id, input: $input) {
|
|
39
|
+
# order { id nickname }
|
|
40
|
+
# errors
|
|
41
|
+
# }
|
|
42
|
+
# }
|
|
43
|
+
# GQL
|
|
44
|
+
# variables: { id: "99", input: { nickname: "Rush Job" } }
|
|
45
|
+
# )
|
|
46
|
+
def mutate(mutation_string, variables: {})
|
|
47
|
+
execute(mutation_string, variables: variables)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Iterates all pages of a paginated GraphQL query, yielding each page's
|
|
51
|
+
# nodes array. The query must accept `$first: Int` and `$after: String`
|
|
52
|
+
# variables, and the target connection must expose `nodes` and `pageInfo`.
|
|
53
|
+
#
|
|
54
|
+
# @param query_string [String] the GraphQL query document
|
|
55
|
+
# @param path [String] dot-separated key path to the connection in the response
|
|
56
|
+
# e.g. "orders" or "customer.orders"
|
|
57
|
+
# @param variables [Hash] additional variables merged with `first` and `after`
|
|
58
|
+
# @param first [Integer] page size (default 25)
|
|
59
|
+
# @yieldparam nodes [Array<Hash>] one page of raw node hashes
|
|
60
|
+
#
|
|
61
|
+
# @example
|
|
62
|
+
# client.graphql.paginate(ORDERS_QUERY, path: "orders") do |nodes|
|
|
63
|
+
# nodes.each { |n| puts n["nickname"] }
|
|
64
|
+
# end
|
|
65
|
+
#
|
|
66
|
+
# @example With extra variables
|
|
67
|
+
# client.graphql.paginate(JOBS_QUERY, path: "order.lineItems",
|
|
68
|
+
# variables: { orderId: "99" }, first: 50) do |nodes|
|
|
69
|
+
# nodes.each { |j| puts j["name"] }
|
|
70
|
+
# end
|
|
71
|
+
def paginate(query_string, path:, variables: {}, first: 25)
|
|
72
|
+
after = nil
|
|
73
|
+
loop do
|
|
74
|
+
data = execute(query_string, variables: variables.merge(first: first, after: after))
|
|
75
|
+
conn = dig_path(data, path)
|
|
76
|
+
nodes = conn&.fetch('nodes', []) || []
|
|
77
|
+
yield nodes
|
|
78
|
+
page_info = conn&.fetch('pageInfo', {}) || {}
|
|
79
|
+
break unless page_info['hasNextPage']
|
|
80
|
+
|
|
81
|
+
after = page_info['endCursor']
|
|
82
|
+
end
|
|
16
83
|
end
|
|
17
84
|
|
|
18
85
|
private
|
|
19
86
|
|
|
87
|
+
def execute(document, variables: {})
|
|
88
|
+
response = @connection.post('') do |req|
|
|
89
|
+
req.body = JSON.generate(query: document, variables: variables)
|
|
90
|
+
end
|
|
91
|
+
handle_response(response)
|
|
92
|
+
end
|
|
93
|
+
|
|
20
94
|
def handle_response(response)
|
|
21
95
|
body = response.body
|
|
22
96
|
|
|
@@ -34,5 +108,11 @@ module Printavo
|
|
|
34
108
|
|
|
35
109
|
body.is_a?(Hash) ? body['data'] : body
|
|
36
110
|
end
|
|
111
|
+
|
|
112
|
+
# Resolves a dot-separated path against a nested hash.
|
|
113
|
+
# e.g. dig_path(data, "customer.orders") => data["customer"]["orders"]
|
|
114
|
+
def dig_path(data, path)
|
|
115
|
+
path.split('.').reduce(data) { |obj, key| obj.is_a?(Hash) ? obj[key] : nil }
|
|
116
|
+
end
|
|
37
117
|
end
|
|
38
118
|
end
|
data/lib/printavo/version.rb
CHANGED