printavo-ruby 0.17.0 → 0.18.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/docs/CACHING.md +65 -10
- data/docs/FUTURE.md +21 -62
- data/docs/TODO.md +43 -11
- data/docs/VISUALIZATION.md +81 -0
- data/lib/printavo/client.rb +28 -11
- data/lib/printavo/graphql_client.rb +22 -3
- data/lib/printavo/memory_store.rb +76 -0
- data/lib/printavo/version.rb +1 -1
- data/lib/printavo.rb +1 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d2291442eb2c04c3ead1543773bcd188723de181093941cdc8519681b5b43e4b
|
|
4
|
+
data.tar.gz: 00274b8c115044888d4099465521acf974c0356261130eb6aacaa21f1d4aa5ce
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fdd286fc6a29e0313aed8f7ebbea302f7737e17f44144a524396eadcde292d70d9a87b610d22ec3bd692a9a93572d7bb844c861c71dd1e87435c59a3cd465ade
|
|
7
|
+
data.tar.gz: 29719257b52dfd20f4f9da862e3ccca5df84f8dc64dfb564421a73cbe1b88e46e2a5b36891066ed1e43607e30a3e9a9c6ceecdb3697ac85ce53c2eb1a17b9afa
|
data/docs/CACHING.md
CHANGED
|
@@ -205,26 +205,81 @@ how frequently staff update records.
|
|
|
205
205
|
|
|
206
206
|
---
|
|
207
207
|
|
|
208
|
-
##
|
|
208
|
+
## Built-In Cache Adapter
|
|
209
209
|
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
As of v0.18.0, `Printavo::Client` accepts an optional `cache:` argument — any
|
|
211
|
+
object responding to `fetch(key, expires_in:) { }` and `delete(key)`. This
|
|
212
|
+
matches the `Rails.cache` interface, so no adapter is needed in Rails apps.
|
|
213
|
+
|
|
214
|
+
### With Rails.cache
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
client = Printavo::Client.new(
|
|
218
|
+
email: ENV["PRINTAVO_EMAIL"],
|
|
219
|
+
token: ENV["PRINTAVO_TOKEN"],
|
|
220
|
+
cache: Rails.cache, # Memcache, Redis, Solid Cache — anything Rails supports
|
|
221
|
+
default_ttl: 300 # seconds; default is 300 (5 minutes)
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# All queries are automatically cached and deduped:
|
|
225
|
+
client.customers.all # fetches from API, stores result
|
|
226
|
+
client.customers.all # returns cached result — no HTTP request
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### With Printavo::MemoryStore (no Rails dependency)
|
|
212
230
|
|
|
213
231
|
```ruby
|
|
214
|
-
# Possible future API — not implemented in v0.x
|
|
215
232
|
client = Printavo::Client.new(
|
|
216
233
|
email: ENV["PRINTAVO_EMAIL"],
|
|
217
234
|
token: ENV["PRINTAVO_TOKEN"],
|
|
218
|
-
cache:
|
|
219
|
-
default_ttl:
|
|
235
|
+
cache: Printavo::MemoryStore.new,
|
|
236
|
+
default_ttl: 600
|
|
220
237
|
)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
`Printavo::MemoryStore` is a thread-safe, TTL-aware in-memory store included
|
|
241
|
+
in the gem. No configuration required.
|
|
221
242
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
243
|
+
### Without a cache (default)
|
|
244
|
+
|
|
245
|
+
```ruby
|
|
246
|
+
client = Printavo::Client.new(
|
|
247
|
+
email: ENV["PRINTAVO_EMAIL"],
|
|
248
|
+
token: ENV["PRINTAVO_TOKEN"]
|
|
249
|
+
)
|
|
250
|
+
# cache: nil — every query hits the API
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Cache key generation
|
|
254
|
+
|
|
255
|
+
Cache keys are generated from a SHA-256 digest of the normalized query document
|
|
256
|
+
and variables: `"printavo:gql:<16-hex-chars>"`. Whitespace differences in query
|
|
257
|
+
strings do not cause cache misses.
|
|
258
|
+
|
|
259
|
+
### Mutations are never cached
|
|
260
|
+
|
|
261
|
+
`client.graphql.mutate(...)` always bypasses the cache, regardless of the
|
|
262
|
+
`cache:` setting. Only `client.graphql.query(...)` (and resource methods that
|
|
263
|
+
call it) are cache-aware.
|
|
264
|
+
|
|
265
|
+
### Custom cache stores
|
|
266
|
+
|
|
267
|
+
Any object with this interface works:
|
|
268
|
+
|
|
269
|
+
```ruby
|
|
270
|
+
class MyCache
|
|
271
|
+
def fetch(key, expires_in: nil)
|
|
272
|
+
# return cached value, or call block and store the result
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def delete(key)
|
|
276
|
+
# remove key from cache
|
|
277
|
+
end
|
|
278
|
+
end
|
|
225
279
|
```
|
|
226
280
|
|
|
227
|
-
|
|
281
|
+
This is exactly the interface `ActiveSupport::Cache::Store` exposes, so
|
|
282
|
+
Solid Cache, Dalli (Memcache), and Redis Cache Store all work out of the box.
|
|
228
283
|
|
|
229
284
|
---
|
|
230
285
|
|
data/docs/FUTURE.md
CHANGED
|
@@ -6,81 +6,40 @@ initial `0.x` releases. Contributions and discussions welcome!
|
|
|
6
6
|
|
|
7
7
|
## Planned Features
|
|
8
8
|
|
|
9
|
-
###
|
|
9
|
+
### Client-Side Aggregation Helpers
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Printavo's V2 GraphQL API is a transactional API — it has no pre-aggregated
|
|
12
|
+
analytics or reporting endpoints. Any analytics must be computed by paging
|
|
13
|
+
through existing resources in Ruby.
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
printavo customers
|
|
15
|
-
printavo orders
|
|
16
|
-
printavo orders find 12345
|
|
17
|
-
printavo analytics revenue
|
|
18
|
-
printavo sync orders --to crm
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Planned version: `0.7.0`
|
|
22
|
-
|
|
23
|
-
### Retry/Backoff
|
|
24
|
-
|
|
25
|
-
Intelligent rate limit handling with exponential backoff:
|
|
15
|
+
Potential helpers that would add value:
|
|
26
16
|
|
|
27
17
|
```ruby
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
max_retries: 3,
|
|
32
|
-
retry_on_rate_limit: true
|
|
33
|
-
)
|
|
34
|
-
```
|
|
18
|
+
# Revenue across all invoices in a date range
|
|
19
|
+
client.invoices.revenue_summary(after: "2026-01-01")
|
|
20
|
+
# => { total: "142300.00", count: 87, average: "1636.78" }
|
|
35
21
|
|
|
36
|
-
|
|
22
|
+
# Order counts grouped by status
|
|
23
|
+
client.orders.status_breakdown
|
|
24
|
+
# => { in_production: 12, approved: 5, completed: 230, ... }
|
|
37
25
|
|
|
38
|
-
|
|
26
|
+
# Most active customers by order count
|
|
27
|
+
client.customers.top(limit: 10, by: :order_count)
|
|
39
28
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
Planned version: `0.6.0`
|
|
44
|
-
|
|
45
|
-
### Mutations (Create / Update)
|
|
46
|
-
|
|
47
|
-
Support for creating and updating resources:
|
|
48
|
-
|
|
49
|
-
```ruby
|
|
50
|
-
client.customers.create(first_name: "Jane", last_name: "Smith", email: "jane@example.com")
|
|
51
|
-
client.orders.update("99", nickname: "Rush Job")
|
|
29
|
+
# Average turnaround time (created_at → updated_at) per status
|
|
30
|
+
client.orders.avg_turnaround
|
|
52
31
|
```
|
|
53
32
|
|
|
54
|
-
|
|
33
|
+
These helpers would page all relevant records locally and compute aggregates
|
|
34
|
+
in Ruby. Because they require full pagination, using the built-in cache adapter
|
|
35
|
+
is strongly recommended before implementing these in production workflows.
|
|
55
36
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
Optional cache layer that plugs into any cache store:
|
|
59
|
-
|
|
60
|
-
```ruby
|
|
61
|
-
client = Printavo::Client.new(
|
|
62
|
-
email: ENV["PRINTAVO_EMAIL"],
|
|
63
|
-
token: ENV["PRINTAVO_TOKEN"],
|
|
64
|
-
cache: Rails.cache # or a Redis client, etc.
|
|
65
|
-
)
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
See [docs/CACHING.md](docs/CACHING.md) for current caching recommendations.
|
|
37
|
+
See [docs/CACHING.md](docs/CACHING.md) for caching options.
|
|
69
38
|
|
|
70
39
|
## Visualization
|
|
71
40
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
Generate a visual map of a shop's Printavo status workflow:
|
|
75
|
-
|
|
76
|
-
```ruby
|
|
77
|
-
client.workflow.diagram(format: :svg)
|
|
78
|
-
# => Outputs an SVG flowchart: Quote → Approved → In Production → Completed
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Implementation options:
|
|
82
|
-
- [ruby-graphviz](https://github.com/glejeune/Ruby-Graphviz) — DOT → SVG/PNG
|
|
83
|
-
- Pure Ruby → Mermaid output (copy-paste into docs or GitHub markdown)
|
|
41
|
+
See **[docs/VISUALIZATION.md](VISUALIZATION.md)** for workflow diagram generation —
|
|
42
|
+
Mermaid, DOT, ASCII, and SVG output via the standalone example script.
|
|
84
43
|
|
|
85
44
|
## Multi-Language SDK Family
|
|
86
45
|
|
data/docs/TODO.md
CHANGED
|
@@ -402,6 +402,30 @@ return values — they are exposed via model field accessors, not separate resou
|
|
|
402
402
|
- [x] `Printavo::Enums::TransactionCategory`
|
|
403
403
|
- [x] `Printavo::Enums::TransactionSource`
|
|
404
404
|
|
|
405
|
+
### v0.17.0 — GraphQL Interface Field Coverage ✅
|
|
406
|
+
|
|
407
|
+
- [x] `VisualIDed` interface — `visualId` on all applicable models
|
|
408
|
+
- [x] `Timestamps` interface — `createdAt` / `updatedAt` on all applicable models
|
|
409
|
+
- [x] `MailAddress` interface — address fields on all applicable models
|
|
410
|
+
|
|
411
|
+
### v0.18.0 — Built-In Cache Adapter ✅
|
|
412
|
+
|
|
413
|
+
- [x] Optional `cache:` kwarg on `Printavo::Client` (any `Rails.cache`-compatible store)
|
|
414
|
+
- [x] `Printavo::MemoryStore` — thread-safe TTL-aware in-memory store (no Rails dependency)
|
|
415
|
+
- [x] `GraphqlClient` caching: `#query` is cache-aware; `#mutate` always bypasses
|
|
416
|
+
- [x] Stable SHA-256 cache key from normalized query + variables
|
|
417
|
+
- [x] `default_ttl: 300` configurable per-client
|
|
418
|
+
- [x] `docs/CACHING.md` updated with built-in adapter usage examples
|
|
419
|
+
|
|
420
|
+
### v0.18.1 — Examples: Diagramming & Reporting
|
|
421
|
+
|
|
422
|
+
- [x] `examples/diagramming/workflow_diagram.rb` — Mermaid, DOT, ASCII, SVG (dot CLI + ruby-graphviz gem)
|
|
423
|
+
- [x] `examples/reporting/sales_report.rb` — today, week, month, quarter, YTD, last year, custom range
|
|
424
|
+
- [x] `examples/reporting/sales_tax_report.rb` — invoice-level estimates + fee-level taxable detail
|
|
425
|
+
- [x] `examples/reporting/customers_export.rb` — CSV, XLSX, XLS, vCard (.vcf), HubSpot, Salesforce, Mailchimp, Constant Contact, Beehiiv, Brevo, ActiveCampaign
|
|
426
|
+
- [x] `examples/reporting/outstanding_tasks.rb` — overdue, due today, this week, upcoming, no due date; by-assignee summary; HTML calendar export
|
|
427
|
+
- [x] `docs/FUTURE.md` — Workflow Diagram section replaced with examples reference and rationale
|
|
428
|
+
|
|
405
429
|
### v0.99.0 — API Freeze
|
|
406
430
|
|
|
407
431
|
- [ ] Community feedback integration
|
|
@@ -418,11 +442,26 @@ return values — they are exposed via model field accessors, not separate resou
|
|
|
418
442
|
|
|
419
443
|
## Future / Stretch Goals
|
|
420
444
|
|
|
421
|
-
###
|
|
445
|
+
### Client-Side Aggregation Helpers
|
|
422
446
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
447
|
+
> **Note:** Printavo's V2 GraphQL API exposes no pre-aggregated analytics or
|
|
448
|
+
> reporting endpoints. All "analytics" must be computed locally by paging
|
|
449
|
+
> through existing resources. A cache adapter (see below) is a prerequisite
|
|
450
|
+
> before these helpers would be practical in production.
|
|
451
|
+
|
|
452
|
+
- [ ] `Invoices#revenue_summary(after:, before:)` — page invoices, return total/count/average
|
|
453
|
+
- [ ] `Orders#status_breakdown` — group order count by status key
|
|
454
|
+
- [ ] `Customers#top(limit:, by:)` — rank customers by order count or revenue
|
|
455
|
+
- [ ] `Orders#avg_turnaround` — average time from `created_at` to most recent `updated_at`
|
|
456
|
+
|
|
457
|
+
### Built-In Cache Adapter ✅ (v0.18.0)
|
|
458
|
+
|
|
459
|
+
- [x] Optional `cache:` kwarg on `Printavo::Client`
|
|
460
|
+
- [x] Duck-typed adapter interface: `fetch(key, expires_in:) { }` / `delete(key)` — matches `Rails.cache`
|
|
461
|
+
- [x] `Printavo::MemoryStore` — thread-safe in-memory store for non-Rails usage
|
|
462
|
+
- [x] `GraphqlClient#query` is cache-aware; `#mutate` always bypasses cache
|
|
463
|
+
- [x] Stable SHA-256 cache key from normalized query + variables
|
|
464
|
+
- [x] `default_ttl: 300` configurable per-client
|
|
426
465
|
|
|
427
466
|
### Workflow Diagram Generation
|
|
428
467
|
|
|
@@ -430,13 +469,6 @@ return values — they are exposed via model field accessors, not separate resou
|
|
|
430
469
|
- [ ] `ruby-graphviz` backend (DOT → SVG/PNG)
|
|
431
470
|
- [ ] Mermaid output option (embed in GitHub markdown)
|
|
432
471
|
|
|
433
|
-
### Multi-Language SDK Family
|
|
434
|
-
|
|
435
|
-
- [ ] `printavo-python`
|
|
436
|
-
- [ ] `printavo-swift`
|
|
437
|
-
- [ ] `printavo-zig`
|
|
438
|
-
- [ ] `printavo-odin`
|
|
439
|
-
|
|
440
472
|
---
|
|
441
473
|
|
|
442
474
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<!-- docs/VISUALIZATION.md -->
|
|
2
|
+
# Visualization for [printavo-ruby](https://github.com/scarver2/printavo-ruby)
|
|
3
|
+
|
|
4
|
+
## Workflow Diagram Generation
|
|
5
|
+
|
|
6
|
+
See **[examples/diagramming/workflow_diagram.rb](../examples/diagramming/workflow_diagram.rb)**
|
|
7
|
+
for a complete, runnable example covering all four output formats.
|
|
8
|
+
|
|
9
|
+
Adding diagram generation to the gem itself would require either shipping
|
|
10
|
+
`ruby-graphviz` as a hard dependency or assuming a system `dot` binary —
|
|
11
|
+
both are burdensome for a Ruby API client. The standalone example gives
|
|
12
|
+
consumers full control over their output format and toolchain without
|
|
13
|
+
bloating the gem.
|
|
14
|
+
|
|
15
|
+
**Supported formats in the example:**
|
|
16
|
+
|
|
17
|
+
| Format | Dependency | Best for |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `:ascii` | none | Terminal output, quick sanity-check |
|
|
20
|
+
| `:mermaid` | none | GitHub README/issues/PRs, VS Code preview |
|
|
21
|
+
| `:dot` | none (text) | Piping to `dot -Tsvg` or any Graphviz renderer |
|
|
22
|
+
| `:svg` | `brew install graphviz` or `gem install ruby-graphviz` | Production-grade visual files |
|
|
23
|
+
|
|
24
|
+
Statuses are fetched via `client.statuses.all` and rendered as a linear
|
|
25
|
+
left-to-right flow in the order Printavo returns them. Printavo has no
|
|
26
|
+
explicit transition edges in its API — the workflow is an implied sequence.
|
|
27
|
+
|
|
28
|
+
### Quick Start
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
cd examples/diagramming
|
|
32
|
+
cp .env.example .env # fill in PRINTAVO_EMAIL and PRINTAVO_TOKEN
|
|
33
|
+
gem install printavo-ruby dotenv
|
|
34
|
+
ruby workflow_diagram.rb
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Output files written to the current directory:
|
|
38
|
+
|
|
39
|
+
| File | Format |
|
|
40
|
+
|---|---|
|
|
41
|
+
| *(stdout)* | ASCII chain |
|
|
42
|
+
| *(stdout)* | Mermaid fenced block |
|
|
43
|
+
| `workflow.dot` | Graphviz DOT source |
|
|
44
|
+
| `workflow_cli.svg` | SVG via `dot` CLI (if graphviz installed) |
|
|
45
|
+
| `workflow_gv.svg` | SVG via `ruby-graphviz` gem (if installed) |
|
|
46
|
+
|
|
47
|
+
### Rendering DOT to Other Formats
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# SVG
|
|
51
|
+
dot -Tsvg workflow.dot > workflow.svg
|
|
52
|
+
|
|
53
|
+
# PNG
|
|
54
|
+
dot -Tpng workflow.dot > workflow.png
|
|
55
|
+
|
|
56
|
+
# PDF
|
|
57
|
+
dot -Tpdf workflow.dot > workflow.pdf
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Embedding Mermaid in GitHub Markdown
|
|
61
|
+
|
|
62
|
+
Paste the Mermaid output between fences in any `.md` file:
|
|
63
|
+
|
|
64
|
+
````markdown
|
|
65
|
+
```mermaid
|
|
66
|
+
flowchart LR
|
|
67
|
+
s_quote["Quote"] --> s_in_production["In Production"] --> s_completed["Completed"]
|
|
68
|
+
```
|
|
69
|
+
````
|
|
70
|
+
|
|
71
|
+
GitHub renders it automatically in READMEs, issues, PRs, and wiki pages.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Colophon
|
|
76
|
+
|
|
77
|
+
[MIT License](LICENSE)
|
|
78
|
+
|
|
79
|
+
©2026 [Stan Carver II](https://stancarver.com)
|
|
80
|
+
|
|
81
|
+

|
data/lib/printavo/client.rb
CHANGED
|
@@ -8,21 +8,38 @@ module Printavo
|
|
|
8
8
|
# Creates a new Printavo API client. Each instance is independent —
|
|
9
9
|
# multiple clients with different credentials can coexist in one process.
|
|
10
10
|
#
|
|
11
|
-
# @param email [String]
|
|
12
|
-
# @param token [String]
|
|
13
|
-
# @param timeout [Integer]
|
|
14
|
-
# @param max_retries [Integer]
|
|
15
|
-
# @param retry_on_rate_limit [Boolean]
|
|
11
|
+
# @param email [String] the email address associated with your Printavo account
|
|
12
|
+
# @param token [String] the API token from your Printavo My Account page
|
|
13
|
+
# @param timeout [Integer] HTTP timeout in seconds (default: 30)
|
|
14
|
+
# @param max_retries [Integer] max retry attempts on 5xx/429 responses (default: 2)
|
|
15
|
+
# @param retry_on_rate_limit [Boolean] retry automatically on 429 Too Many Requests (default: true)
|
|
16
|
+
# @param cache [#fetch, #delete] optional cache store; any object responding to
|
|
17
|
+
# +fetch(key, expires_in:) { }+ and +delete(key)+,
|
|
18
|
+
# e.g. +Rails.cache+ or +Printavo::MemoryStore.new+
|
|
19
|
+
# @param default_ttl [Integer] TTL in seconds for cached queries (default: 300)
|
|
16
20
|
#
|
|
17
|
-
# @example
|
|
21
|
+
# @example No caching (default)
|
|
18
22
|
# client = Printavo::Client.new(
|
|
19
23
|
# email: ENV["PRINTAVO_EMAIL"],
|
|
20
24
|
# token: ENV["PRINTAVO_TOKEN"]
|
|
21
25
|
# )
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# client.
|
|
25
|
-
|
|
26
|
+
#
|
|
27
|
+
# @example With Rails.cache
|
|
28
|
+
# client = Printavo::Client.new(
|
|
29
|
+
# email: ENV["PRINTAVO_EMAIL"],
|
|
30
|
+
# token: ENV["PRINTAVO_TOKEN"],
|
|
31
|
+
# cache: Rails.cache,
|
|
32
|
+
# default_ttl: 300
|
|
33
|
+
# )
|
|
34
|
+
#
|
|
35
|
+
# @example With built-in in-memory store
|
|
36
|
+
# client = Printavo::Client.new(
|
|
37
|
+
# email: ENV["PRINTAVO_EMAIL"],
|
|
38
|
+
# token: ENV["PRINTAVO_TOKEN"],
|
|
39
|
+
# cache: Printavo::MemoryStore.new
|
|
40
|
+
# )
|
|
41
|
+
def initialize(email:, token:, timeout: 30, max_retries: 2, retry_on_rate_limit: true, # rubocop:disable Metrics/ParameterLists
|
|
42
|
+
cache: nil, default_ttl: 300)
|
|
26
43
|
connection = Connection.new(
|
|
27
44
|
email: email,
|
|
28
45
|
token: token,
|
|
@@ -30,7 +47,7 @@ module Printavo
|
|
|
30
47
|
max_retries: max_retries,
|
|
31
48
|
retry_on_rate_limit: retry_on_rate_limit
|
|
32
49
|
).build
|
|
33
|
-
@graphql = GraphqlClient.new(connection)
|
|
50
|
+
@graphql = GraphqlClient.new(connection, cache: cache, default_ttl: default_ttl)
|
|
34
51
|
end
|
|
35
52
|
|
|
36
53
|
def account
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
# lib/printavo/graphql_client.rb
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require 'digest'
|
|
4
5
|
require 'json'
|
|
5
6
|
|
|
6
7
|
module Printavo
|
|
7
8
|
class GraphqlClient
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
# @param connection [Faraday::Connection]
|
|
10
|
+
# @param cache [#fetch, #delete, nil] any cache store implementing
|
|
11
|
+
# +fetch(key, expires_in:) { }+ and +delete(key)+,
|
|
12
|
+
# e.g. +Rails.cache+, +Printavo::MemoryStore.new+, or +nil+
|
|
13
|
+
# @param default_ttl [Integer] default TTL in seconds applied to cached queries (default: 300)
|
|
14
|
+
def initialize(connection, cache: nil, default_ttl: 300)
|
|
15
|
+
@connection = connection
|
|
16
|
+
@cache = cache
|
|
17
|
+
@default_ttl = default_ttl
|
|
10
18
|
end
|
|
11
19
|
|
|
12
20
|
# Executes a GraphQL query and returns the parsed `data` hash.
|
|
@@ -22,7 +30,11 @@ module Printavo
|
|
|
22
30
|
# variables: { id: "42" }
|
|
23
31
|
# )
|
|
24
32
|
def query(query_string, variables: {})
|
|
25
|
-
execute(query_string, variables: variables)
|
|
33
|
+
return execute(query_string, variables: variables) unless @cache
|
|
34
|
+
|
|
35
|
+
@cache.fetch(cache_key(query_string, variables), expires_in: @default_ttl) do
|
|
36
|
+
execute(query_string, variables: variables)
|
|
37
|
+
end
|
|
26
38
|
end
|
|
27
39
|
|
|
28
40
|
# Executes a GraphQL mutation and returns the parsed `data` hash.
|
|
@@ -86,6 +98,13 @@ module Printavo
|
|
|
86
98
|
|
|
87
99
|
private
|
|
88
100
|
|
|
101
|
+
# Generates a stable, namespaced cache key from the query document and variables.
|
|
102
|
+
# Whitespace in the query is collapsed so formatting differences don't cause misses.
|
|
103
|
+
def cache_key(query_string, variables)
|
|
104
|
+
payload = JSON.generate([query_string.gsub(/\s+/, ' ').strip, variables])
|
|
105
|
+
"printavo:gql:#{Digest::SHA256.hexdigest(payload)[0, 16]}"
|
|
106
|
+
end
|
|
107
|
+
|
|
89
108
|
def execute(document, variables: {})
|
|
90
109
|
response = @connection.post('') do |req|
|
|
91
110
|
req.body = JSON.generate(query: document, variables: variables)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# lib/printavo/memory_store.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Printavo
|
|
5
|
+
# A simple thread-safe in-memory cache store for use without Rails or Redis.
|
|
6
|
+
# Implements the same +fetch+ / +delete+ interface as +Rails.cache+, so it
|
|
7
|
+
# can be swapped for any compatible store without changing call sites.
|
|
8
|
+
#
|
|
9
|
+
# @example Standalone use
|
|
10
|
+
# client = Printavo::Client.new(
|
|
11
|
+
# email: ENV["PRINTAVO_EMAIL"],
|
|
12
|
+
# token: ENV["PRINTAVO_TOKEN"],
|
|
13
|
+
# cache: Printavo::MemoryStore.new
|
|
14
|
+
# )
|
|
15
|
+
#
|
|
16
|
+
# @example With custom default TTL
|
|
17
|
+
# client = Printavo::Client.new(
|
|
18
|
+
# email: ENV["PRINTAVO_EMAIL"],
|
|
19
|
+
# token: ENV["PRINTAVO_TOKEN"],
|
|
20
|
+
# cache: Printavo::MemoryStore.new,
|
|
21
|
+
# default_ttl: 600 # 10 minutes
|
|
22
|
+
# )
|
|
23
|
+
class MemoryStore
|
|
24
|
+
def initialize
|
|
25
|
+
@store = {}
|
|
26
|
+
@expires_at = {}
|
|
27
|
+
@mutex = Mutex.new
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the cached value for +key+, or calls the block, stores the
|
|
31
|
+
# result, and returns it. Expired entries are treated as missing.
|
|
32
|
+
#
|
|
33
|
+
# @param key [String]
|
|
34
|
+
# @param expires_in [Integer, nil] TTL in seconds; +nil+ means no expiry
|
|
35
|
+
# @yieldreturn the value to cache on a miss
|
|
36
|
+
# @return the cached or freshly-computed value
|
|
37
|
+
def fetch(key, expires_in: nil)
|
|
38
|
+
@mutex.synchronize do
|
|
39
|
+
cached = read(key)
|
|
40
|
+
return cached unless cached.nil?
|
|
41
|
+
|
|
42
|
+
yield.tap { |v| write(key, v, expires_in: expires_in) }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Removes +key+ from the cache.
|
|
47
|
+
#
|
|
48
|
+
# @param key [String]
|
|
49
|
+
# @return [nil]
|
|
50
|
+
def delete(key)
|
|
51
|
+
@mutex.synchronize do
|
|
52
|
+
@store.delete(key)
|
|
53
|
+
@expires_at.delete(key)
|
|
54
|
+
end
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def read(key)
|
|
61
|
+
exp = @expires_at[key]
|
|
62
|
+
if @store.key?(key) && (exp.nil? || Time.now < exp)
|
|
63
|
+
@store[key]
|
|
64
|
+
else
|
|
65
|
+
@store.delete(key)
|
|
66
|
+
@expires_at.delete(key)
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def write(key, value, expires_in: nil)
|
|
72
|
+
@store[key] = value
|
|
73
|
+
@expires_at[key] = Time.now + expires_in if expires_in
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
data/lib/printavo/version.rb
CHANGED
data/lib/printavo.rb
CHANGED
|
@@ -11,6 +11,7 @@ require_relative 'printavo/connection'
|
|
|
11
11
|
require_relative 'printavo/enums'
|
|
12
12
|
require_relative 'printavo/errors'
|
|
13
13
|
require_relative 'printavo/graphql_client'
|
|
14
|
+
require_relative 'printavo/memory_store'
|
|
14
15
|
require_relative 'printavo/page'
|
|
15
16
|
require_relative 'printavo/models/base'
|
|
16
17
|
require_relative 'printavo/models/account'
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: printavo-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.18.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stan Carver II
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -285,6 +285,7 @@ files:
|
|
|
285
285
|
- docs/CONTRIBUTING.md
|
|
286
286
|
- docs/FUTURE.md
|
|
287
287
|
- docs/TODO.md
|
|
288
|
+
- docs/VISUALIZATION.md
|
|
288
289
|
- lib/printavo.rb
|
|
289
290
|
- lib/printavo/cli.rb
|
|
290
291
|
- lib/printavo/client.rb
|
|
@@ -465,6 +466,7 @@ files:
|
|
|
465
466
|
- lib/printavo/graphql/vendors/all.graphql
|
|
466
467
|
- lib/printavo/graphql/vendors/find.graphql
|
|
467
468
|
- lib/printavo/graphql_client.rb
|
|
469
|
+
- lib/printavo/memory_store.rb
|
|
468
470
|
- lib/printavo/models/account.rb
|
|
469
471
|
- lib/printavo/models/approval_request.rb
|
|
470
472
|
- lib/printavo/models/base.rb
|