xero-kiwi 0.5.1 → 0.5.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 +4 -4
- data/CHANGELOG.md +8 -0
- data/Rakefile +22 -63
- data/docs/client.md +48 -4
- data/lib/xero_kiwi/accounting/resource.rb +12 -0
- data/lib/xero_kiwi/version.rb +1 -1
- data/llms-full.txt +1800 -1756
- data/llms.txt +24 -10
- 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: fb7181193042ba51598251bc4bee70c0e84ae0333216d94838e996f813b05770
|
|
4
|
+
data.tar.gz: 7103cdb42e4cb742cbafa0eae6c807f7958626751dbdd1cd86aa14fbbe4939a8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f5a369e373c45e9d4343666eb59e2adbf16a1620efe57052b89f655be7d09bc63c30d120c7a47dc873e5ed0ac1f6af5fb2b23c4b71847819ac15c9f3c2e0f5a3
|
|
7
|
+
data.tar.gz: 51ac21d3ace42a9ebefaa03ee2fdf924a6dd9549eb41d053901f0a2c6d115147e35a9f90e77438241937ca2171fdbf6a295792bfbef4528e209b5211f1d598b4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.5.2] - 2026-09-27
|
|
4
|
+
|
|
5
|
+
### Documentation
|
|
6
|
+
|
|
7
|
+
- Documented that `Resource#raw` returns nil on nested objects (line items, addresses, contact persons, payment terms) even with `retain_raw: true`, and that the enclosing resource's hash is the access path — `org.raw["Addresses"]` rather than `org.addresses.map(&:raw)`. The behaviour was deliberate and explained in a comment on `from_response`, but the `#raw` docstring never mentioned it and the failure is a silent nil, so a consumer mapped over a nested collection and was one step from storing `[null]`.
|
|
8
|
+
- Added a written contract for `raw` in `docs/client.md`: it is the parsed JSON with only top-level keys stringified, kiwi never normalises it (absent keys stay absent rather than becoming `[]`), unmodelled keys are preserved, and the freeze is shallow — the hash is frozen, the structures inside it are not. Specs pin all four, plus the nested-nil behaviour. The previous "is frozen" example overstated the guarantee and has been replaced.
|
|
9
|
+
- `llms.txt` is now generated alongside `llms-full.txt` from a single manifest in `tasks/llms.rb`, rather than hand-maintained. It had not been touched since the initial commit and was missing four releases of material — no `querying.md`, no `throttling.md`, no `tracking-category.md`, and no mention of `Page`, `RateLimit`, the throttle limiters or the tracking classes. `rake llms:check` now also fails when a doc exists under `docs/` that the manifest does not list, which is the half that was missing: the freshness check could not notice a doc that was never indexed in the first place.
|
|
10
|
+
|
|
3
11
|
## [0.5.1] - 2026-09-26
|
|
4
12
|
|
|
5
13
|
### Documentation
|
data/Rakefile
CHANGED
|
@@ -9,76 +9,35 @@ require "rubocop/rake_task"
|
|
|
9
9
|
|
|
10
10
|
RuboCop::RakeTask.new
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
# the docs in the same order the README's table of contents lists them.
|
|
14
|
-
LLMS_SOURCE_FILES = %w[
|
|
15
|
-
README.md
|
|
16
|
-
docs/getting-started.md
|
|
17
|
-
docs/client.md
|
|
18
|
-
docs/oauth.md
|
|
19
|
-
docs/tokens.md
|
|
20
|
-
docs/connections.md
|
|
21
|
-
docs/accounting/contact.md
|
|
22
|
-
docs/accounting/contact-group.md
|
|
23
|
-
docs/accounting/organisation.md
|
|
24
|
-
docs/accounting/user.md
|
|
25
|
-
docs/accounting/credit-note.md
|
|
26
|
-
docs/accounting/invoice.md
|
|
27
|
-
docs/accounting/payment.md
|
|
28
|
-
docs/accounting/overpayment.md
|
|
29
|
-
docs/accounting/prepayment.md
|
|
30
|
-
docs/accounting/branding-theme.md
|
|
31
|
-
docs/accounting/tracking-category.md
|
|
32
|
-
docs/accounting/address.md
|
|
33
|
-
docs/accounting/phone.md
|
|
34
|
-
docs/accounting/external-link.md
|
|
35
|
-
docs/accounting/payment-terms.md
|
|
36
|
-
docs/errors.md
|
|
37
|
-
docs/retries-and-rate-limits.md
|
|
38
|
-
docs/throttling.md
|
|
39
|
-
docs/querying.md
|
|
40
|
-
].freeze
|
|
41
|
-
|
|
42
|
-
LLMS_FULL_PATH = "llms-full.txt"
|
|
43
|
-
|
|
44
|
-
# Builds the llms-full.txt content from LLMS_SOURCE_FILES. Pure function:
|
|
45
|
-
# returns a String, doesn't touch the filesystem. Both `llms:build` and
|
|
46
|
-
# `llms:check` use this so the two tasks can never disagree about the
|
|
47
|
-
# expected output.
|
|
48
|
-
def build_llms_full
|
|
49
|
-
out = String.new(encoding: "UTF-8")
|
|
50
|
-
out << "# Xero Kiwi — full documentation\n\n"
|
|
51
|
-
out << "This file is the complete documentation for the Xero Kiwi gem (a Ruby wrapper for the Xero Accounting API), assembled into a single document for LLM consumption. It contains the README and every doc in the docs/ folder, in reading order.\n\n"
|
|
52
|
-
out << "For the curated index version, see llms.txt in the same directory.\n\n"
|
|
53
|
-
out << "Source: https://github.com/douglasgreyling/xero-kiwi\n\n"
|
|
54
|
-
LLMS_SOURCE_FILES.each { |path| append_file_block(out, path) }
|
|
55
|
-
out
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def append_file_block(out, path)
|
|
59
|
-
separator = "=" * 80
|
|
60
|
-
out << "\n" << separator << "\n"
|
|
61
|
-
out << "FILE: #{path}\n"
|
|
62
|
-
out << separator << "\n\n"
|
|
63
|
-
out << File.read(path, encoding: "UTF-8") << "\n"
|
|
64
|
-
end
|
|
12
|
+
require_relative "tasks/llms"
|
|
65
13
|
|
|
66
14
|
namespace :llms do
|
|
67
|
-
desc "Regenerate llms-full.txt from README and docs/"
|
|
15
|
+
desc "Regenerate llms.txt and llms-full.txt from README and docs/"
|
|
68
16
|
task :build do
|
|
69
|
-
|
|
70
|
-
|
|
17
|
+
Llms.outputs.each do |path, content|
|
|
18
|
+
File.write(path, content)
|
|
19
|
+
puts "Wrote #{path} (#{File.size(path)} bytes)"
|
|
20
|
+
end
|
|
71
21
|
end
|
|
72
22
|
|
|
73
|
-
desc "Verify llms-full.txt
|
|
23
|
+
desc "Verify llms.txt and llms-full.txt are up to date, and that every doc is indexed"
|
|
74
24
|
task :check do
|
|
75
|
-
|
|
76
|
-
|
|
25
|
+
missing = Llms.unindexed_docs
|
|
26
|
+
unless missing.empty?
|
|
27
|
+
warn "✗ These docs are not listed in Llms::DOCS, so they are missing from llms.txt and llms-full.txt:"
|
|
28
|
+
missing.each { |path| warn " #{path}" }
|
|
29
|
+
warn " Add them to tasks/llms.rb and run `bundle exec rake llms:build`."
|
|
30
|
+
exit 1
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
stale = Llms.outputs.reject do |path, expected|
|
|
34
|
+
File.exist?(path) && File.read(path, encoding: "UTF-8") == expected
|
|
35
|
+
end
|
|
77
36
|
|
|
78
|
-
if
|
|
79
|
-
puts "✓
|
|
37
|
+
if stale.empty?
|
|
38
|
+
puts "✓ llms.txt and llms-full.txt are up to date"
|
|
80
39
|
else
|
|
81
|
-
warn "✗
|
|
40
|
+
warn "✗ Out of date with README/docs: #{stale.keys.join(", ")}"
|
|
82
41
|
warn " Run `bundle exec rake llms:build` and commit the result."
|
|
83
42
|
exit 1
|
|
84
43
|
end
|
|
@@ -86,7 +45,7 @@ namespace :llms do
|
|
|
86
45
|
end
|
|
87
46
|
|
|
88
47
|
# Top-level convenience alias.
|
|
89
|
-
desc "Regenerate
|
|
48
|
+
desc "Regenerate the LLM docs bundles (alias for llms:build)"
|
|
90
49
|
task llms: "llms:build"
|
|
91
50
|
|
|
92
51
|
task default: %i[spec rubocop llms:check]
|
data/docs/client.md
CHANGED
|
@@ -68,6 +68,18 @@ Five things to know:
|
|
|
68
68
|
the envelope before building a resource, so there's nothing left of it by
|
|
69
69
|
the time `raw` is populated. If you need the envelope back, you're
|
|
70
70
|
rebuilding it yourself.
|
|
71
|
+
- **Nested objects return `nil`, even with `retain_raw: true`.** Line
|
|
72
|
+
items, addresses, contact persons and payment terms do not carry their
|
|
73
|
+
own payload. Reach them through the enclosing resource's hash:
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
org.addresses.map(&:raw) # => [nil] ← not what you want
|
|
77
|
+
org.raw["Addresses"] # => [{"AddressType" => "POBOX", …}]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This is deliberate — see [why nested objects don't carry
|
|
81
|
+
raw](#why-nested-objects-dont-carry-raw) — but it fails quietly, so it's
|
|
82
|
+
worth knowing before you map over a collection and store the result.
|
|
71
83
|
- **It's the JSON representation.** Kiwi sends
|
|
72
84
|
`Accept: application/json`. Xero also serves XML, which nests
|
|
73
85
|
differently — XML has no arrays, so a single child parses to a Hash and
|
|
@@ -78,14 +90,46 @@ Five things to know:
|
|
|
78
90
|
the modelled attributes — different keys, different nesting. If you store
|
|
79
91
|
`to_h` where you meant to store the payload, readers fail by returning
|
|
80
92
|
nil rather than raising.
|
|
81
|
-
- **It's per resource, not per nested object.** Line items, addresses and
|
|
82
|
-
contact persons have no `#raw` of their own. They don't need one: the
|
|
83
|
-
resource's hash already holds every nested payload, so
|
|
84
|
-
`contact.raw["ContactPersons"]` gets there.
|
|
85
93
|
- **It costs memory.** Every resource holds its source hash alongside the
|
|
86
94
|
hydrated attributes, which roughly doubles the footprint of a large page.
|
|
87
95
|
That's why it's off by default.
|
|
88
96
|
|
|
97
|
+
### What `raw` guarantees
|
|
98
|
+
|
|
99
|
+
Depend on these; they won't change without a major version.
|
|
100
|
+
|
|
101
|
+
- It is the parsed JSON for that resource, with **only the top-level keys
|
|
102
|
+
stringified**. Everything nested is exactly what `JSON.parse` produced.
|
|
103
|
+
- **Kiwi does not normalise it.** No key renaming, no defaulting, no
|
|
104
|
+
filling in of absent keys. If Xero omits `Addresses`, `raw` has no
|
|
105
|
+
`"Addresses"` key — it does not become `[]`. If Xero sends `[]`, you get
|
|
106
|
+
`[]`. `raw.fetch("Addresses", [])` and `raw["Addresses"] || []` are both
|
|
107
|
+
safe and mean the same thing here.
|
|
108
|
+
- Keys the gem doesn't model are preserved. That's the point of it.
|
|
109
|
+
- The top-level hash is **frozen; nested structures are not**. `raw` itself
|
|
110
|
+
can't be mutated, but `raw["Addresses"]` is an ordinary mutable Array.
|
|
111
|
+
Dup before mutating if that matters to you.
|
|
112
|
+
|
|
113
|
+
The one thing deliberately *not* guaranteed is the presence of `raw` on
|
|
114
|
+
nested objects, per the bullet above.
|
|
115
|
+
|
|
116
|
+
### Why nested objects don't carry raw
|
|
117
|
+
|
|
118
|
+
Two reasons, both worth knowing if you're tempted to ask for it.
|
|
119
|
+
|
|
120
|
+
It would cost real memory on the workload `retain_raw` exists for. A page
|
|
121
|
+
of 1,000 invoices with five line items each retains about 3.4 MB with
|
|
122
|
+
top-level raw; giving every nested object its own hash adds about 1.6 MB
|
|
123
|
+
on top, roughly half again, and that's on a flag that has already doubled
|
|
124
|
+
your footprint.
|
|
125
|
+
|
|
126
|
+
More importantly it couldn't be done consistently. Several attributes —
|
|
127
|
+
`payment_terms` on Contact and Organisation, `invoice_addresses` on
|
|
128
|
+
Invoice — hydrate through custom lambdas that construct their objects
|
|
129
|
+
directly, so they'd stay `nil` while their siblings worked. "Nested
|
|
130
|
+
objects never carry raw" is a rule you can hold in your head. "Nested
|
|
131
|
+
objects carry raw except these four" is not.
|
|
132
|
+
|
|
89
133
|
### Migrating from an XML-based client
|
|
90
134
|
|
|
91
135
|
If you're replacing a Xero client that sent `Accept: text/xml` — HTTParty
|
|
@@ -139,11 +139,23 @@ module XeroKiwi
|
|
|
139
139
|
# arrived, or nil unless the client was built with `retain_raw: true`.
|
|
140
140
|
# Use it to reach fields the gem doesn't model.
|
|
141
141
|
#
|
|
142
|
+
# Returns nil on NESTED objects even when retain_raw is on — line
|
|
143
|
+
# items, addresses, contact persons, payment terms. Reach those
|
|
144
|
+
# through the enclosing resource instead:
|
|
145
|
+
#
|
|
146
|
+
# org.addresses.map(&:raw) # => [nil]
|
|
147
|
+
# org.raw["Addresses"] # => [{"AddressType" => "POBOX", ...}]
|
|
148
|
+
#
|
|
142
149
|
# Scope is the item, NOT the enclosing envelope: `contact.raw` has no
|
|
143
150
|
# "Contacts" key, and `organisation.raw` no "Organisations" key. Kiwi
|
|
144
151
|
# unwraps the envelope before a resource is built, so there is nothing
|
|
145
152
|
# left of it by the time this is populated.
|
|
146
153
|
#
|
|
154
|
+
# Kiwi never normalises what it stores here: absent keys stay absent
|
|
155
|
+
# rather than becoming [] or nil entries, and keys the gem doesn't
|
|
156
|
+
# model are preserved. The hash itself is frozen; the structures
|
|
157
|
+
# nested inside it are not.
|
|
158
|
+
#
|
|
147
159
|
# It is also the JSON representation specifically. Kiwi sends
|
|
148
160
|
# `Accept: application/json`; Xero's XML representation nests
|
|
149
161
|
# differently — no arrays, so one child is a Hash and several are an
|
data/lib/xero_kiwi/version.rb
CHANGED