xero-kiwi 0.5.0 → 0.5.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/CHANGELOG.md +6 -0
- data/docs/client.md +67 -9
- data/lib/xero_kiwi/accounting/resource.rb +14 -3
- data/lib/xero_kiwi/version.rb +1 -1
- data/llms-full.txt +67 -9
- 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: 9e67980bd7d7be1131ee07b55847cd4633a527eec4159103ba43551903f0870c
|
|
4
|
+
data.tar.gz: f06a30a5311f02be7d8695344a7f8eb65e76f4b0abab1743f23b9701f7655600
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bf15925c5971526baeb6c3773de4f454ab917509200794b05957b27eabdfb4691e757a01f9defca50357cf0ce8ba7aa504b8e29973685c1428b365b28d80c5bf
|
|
7
|
+
data.tar.gz: 854a1cc894da5a8706e084f1b2e328a3529470f785e4cc22c27d80080ec7c2780cf3db40eb3f919fd9afd359539a879af82d241b6a2061f3673fa80ead876357
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.5.1] - 2026-09-26
|
|
4
|
+
|
|
5
|
+
### Documentation
|
|
6
|
+
|
|
7
|
+
- Pinned down the scope of `Resource#raw`, which was ambiguous enough to mislead a real consumer. It holds the resource's own item hash — `contact.raw` has no `"Contacts"` key — and it is the JSON representation specifically. Xero's XML representation nests differently (no arrays, so one child parses to a Hash and several to an Array), so `raw` cannot reproduce an XML-derived shape. `docs/client.md` gains a short migration section covering what that means for anyone replacing a client that sent `Accept: text/xml` and has stored payloads. Specs added for both the envelope scope and the PascalCase/snake_case split. The migration section sorts readers into the two piles that behave differently: ones that dig the XML-only structure and must change, and ones that already normalise (`[value].flatten.compact`) where only the writer changes and stored rows stay readable.
|
|
8
|
+
|
|
3
9
|
## [0.5.0] - 2026-09-26
|
|
4
10
|
|
|
5
11
|
The sync-support release: everything needed to drive a full-tenant sync through kiwi rather than around it.
|
data/docs/client.md
CHANGED
|
@@ -59,23 +59,81 @@ contact.raw
|
|
|
59
59
|
# => {"ContactID" => "…", "ContactPersons" => [...], "SomeNewField" => "…"}
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
Use it to reach fields kiwi doesn't model yet
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
Use it to reach fields kiwi doesn't model yet.
|
|
63
|
+
|
|
64
|
+
Five things to know:
|
|
65
|
+
|
|
66
|
+
- **It's the item, not the envelope.** `contact.raw` has no `"Contacts"`
|
|
67
|
+
key and `organisation.raw` has no `"Organisations"` key — kiwi unwraps
|
|
68
|
+
the envelope before building a resource, so there's nothing left of it by
|
|
69
|
+
the time `raw` is populated. If you need the envelope back, you're
|
|
70
|
+
rebuilding it yourself.
|
|
71
|
+
- **It's the JSON representation.** Kiwi sends
|
|
72
|
+
`Accept: application/json`. Xero also serves XML, which nests
|
|
73
|
+
differently — XML has no arrays, so a single child parses to a Hash and
|
|
74
|
+
several to an Array, where JSON is always an Array. `raw` cannot
|
|
75
|
+
reproduce an XML-derived shape. See [migrating from an XML
|
|
76
|
+
client](#migrating-from-an-xml-based-client) below.
|
|
67
77
|
- **`#raw` is not `#to_h`.** `to_h` is a snake_case projection rebuilt from
|
|
68
78
|
the modelled attributes — different keys, different nesting. If you store
|
|
69
79
|
`to_h` where you meant to store the payload, readers fail by returning
|
|
70
80
|
nil rather than raising.
|
|
71
|
-
- **It's
|
|
72
|
-
persons
|
|
73
|
-
hash already holds every nested payload, so
|
|
74
|
-
gets there.
|
|
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.
|
|
75
85
|
- **It costs memory.** Every resource holds its source hash alongside the
|
|
76
86
|
hydrated attributes, which roughly doubles the footprint of a large page.
|
|
77
87
|
That's why it's off by default.
|
|
78
88
|
|
|
89
|
+
### Migrating from an XML-based client
|
|
90
|
+
|
|
91
|
+
If you're replacing a Xero client that sent `Accept: text/xml` — HTTParty
|
|
92
|
+
and similar default to it — any payloads you already have stored are
|
|
93
|
+
XML-shaped, and `raw` will not match them. The differences are structural,
|
|
94
|
+
not cosmetic:
|
|
95
|
+
|
|
96
|
+
| | XML (`text/xml`) | JSON (`application/json`) |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| Organisation body | `{"Organisations" => {"Organisation" => {…}}}` | `{"Organisations" => [{…}]}` |
|
|
99
|
+
| One address | `{"Address" => {…}}` | `[{…}]` |
|
|
100
|
+
| Several addresses | `{"Address" => [{…}, {…}]}` | `[{…}, {…}]` |
|
|
101
|
+
|
|
102
|
+
The single-child collapse is the one that catches people: under XML a
|
|
103
|
+
contact with one person parses to a Hash and a contact with two parses to
|
|
104
|
+
an Array, from the same endpoint. Code written against that has a
|
|
105
|
+
normalising step somewhere, whether or not its author knew why.
|
|
106
|
+
|
|
107
|
+
No client setting reproduces these shapes — they're artefacts of an XML
|
|
108
|
+
parse kiwi doesn't do. But the remedy isn't the same everywhere, and it's
|
|
109
|
+
worth sorting your readers into two piles before planning the work.
|
|
110
|
+
|
|
111
|
+
**Readers that dig the XML-only structure have to change.** Something like
|
|
112
|
+
`dig("Organisations", "Organisation", "Addresses", "Address")` returns nil
|
|
113
|
+
against anything kiwi produces, whether you store `raw` or `to_h`.
|
|
114
|
+
Promote the fields those readers need to real columns and use `raw` for
|
|
115
|
+
the backfill — it's the true payload, so it carries everything required to
|
|
116
|
+
populate them.
|
|
117
|
+
|
|
118
|
+
**Readers that already normalise usually survive untouched.** A reader
|
|
119
|
+
doing `[value].flatten.compact` handles the Hash case, the Array case and
|
|
120
|
+
nil identically, so a JSON array flows straight through. If the keys it
|
|
121
|
+
reads are the same in both representations, only the *writer* changes:
|
|
122
|
+
stop unwrapping, store the array. Existing rows stay readable, and you
|
|
123
|
+
skip a migration entirely.
|
|
124
|
+
|
|
125
|
+
Type coercion is usually fine in that second pile too. XML gives you
|
|
126
|
+
strings — `"false"` rather than `false` — and if the reader hands that to
|
|
127
|
+
something like ActiveModel's boolean cast, both the old string and the new
|
|
128
|
+
real boolean land on the same value.
|
|
129
|
+
|
|
130
|
+
One piece of luck worth knowing about: the two piles tend to fail
|
|
131
|
+
differently. A `dig` that misses returns nil and writes a blank record
|
|
132
|
+
quietly. Code that assumed a Hash, such as `Array#to_h` on what is now a
|
|
133
|
+
list, raises `TypeError` on the first record with data in it. The noisy
|
|
134
|
+
failures are the ones you can trust to find themselves — budget your
|
|
135
|
+
review time for the silent ones.
|
|
136
|
+
|
|
79
137
|
## What the client gives you
|
|
80
138
|
|
|
81
139
|
| Method | Returns | Purpose |
|
|
@@ -135,9 +135,20 @@ module XeroKiwi
|
|
|
135
135
|
end
|
|
136
136
|
end
|
|
137
137
|
|
|
138
|
-
#
|
|
139
|
-
# unless the client was built with `retain_raw: true`.
|
|
140
|
-
# fields the gem doesn't model
|
|
138
|
+
# This resource's own hash from Xero's JSON response, exactly as it
|
|
139
|
+
# arrived, or nil unless the client was built with `retain_raw: true`.
|
|
140
|
+
# Use it to reach fields the gem doesn't model.
|
|
141
|
+
#
|
|
142
|
+
# Scope is the item, NOT the enclosing envelope: `contact.raw` has no
|
|
143
|
+
# "Contacts" key, and `organisation.raw` no "Organisations" key. Kiwi
|
|
144
|
+
# unwraps the envelope before a resource is built, so there is nothing
|
|
145
|
+
# left of it by the time this is populated.
|
|
146
|
+
#
|
|
147
|
+
# It is also the JSON representation specifically. Kiwi sends
|
|
148
|
+
# `Accept: application/json`; Xero's XML representation nests
|
|
149
|
+
# differently — no arrays, so one child is a Hash and several are an
|
|
150
|
+
# Array — and `raw` cannot reproduce that shape. Worth knowing when
|
|
151
|
+
# migrating off an XML-based Xero client with stored payloads.
|
|
141
152
|
#
|
|
142
153
|
# Note `to_h` is NOT this — it's a snake_case projection rebuilt from
|
|
143
154
|
# the modelled attributes, with different keys and different nesting.
|
data/lib/xero_kiwi/version.rb
CHANGED
data/llms-full.txt
CHANGED
|
@@ -346,23 +346,81 @@ contact.raw
|
|
|
346
346
|
# => {"ContactID" => "…", "ContactPersons" => [...], "SomeNewField" => "…"}
|
|
347
347
|
```
|
|
348
348
|
|
|
349
|
-
Use it to reach fields kiwi doesn't model yet
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
349
|
+
Use it to reach fields kiwi doesn't model yet.
|
|
350
|
+
|
|
351
|
+
Five things to know:
|
|
352
|
+
|
|
353
|
+
- **It's the item, not the envelope.** `contact.raw` has no `"Contacts"`
|
|
354
|
+
key and `organisation.raw` has no `"Organisations"` key — kiwi unwraps
|
|
355
|
+
the envelope before building a resource, so there's nothing left of it by
|
|
356
|
+
the time `raw` is populated. If you need the envelope back, you're
|
|
357
|
+
rebuilding it yourself.
|
|
358
|
+
- **It's the JSON representation.** Kiwi sends
|
|
359
|
+
`Accept: application/json`. Xero also serves XML, which nests
|
|
360
|
+
differently — XML has no arrays, so a single child parses to a Hash and
|
|
361
|
+
several to an Array, where JSON is always an Array. `raw` cannot
|
|
362
|
+
reproduce an XML-derived shape. See [migrating from an XML
|
|
363
|
+
client](#migrating-from-an-xml-based-client) below.
|
|
354
364
|
- **`#raw` is not `#to_h`.** `to_h` is a snake_case projection rebuilt from
|
|
355
365
|
the modelled attributes — different keys, different nesting. If you store
|
|
356
366
|
`to_h` where you meant to store the payload, readers fail by returning
|
|
357
367
|
nil rather than raising.
|
|
358
|
-
- **It's
|
|
359
|
-
persons
|
|
360
|
-
hash already holds every nested payload, so
|
|
361
|
-
gets there.
|
|
368
|
+
- **It's per resource, not per nested object.** Line items, addresses and
|
|
369
|
+
contact persons have no `#raw` of their own. They don't need one: the
|
|
370
|
+
resource's hash already holds every nested payload, so
|
|
371
|
+
`contact.raw["ContactPersons"]` gets there.
|
|
362
372
|
- **It costs memory.** Every resource holds its source hash alongside the
|
|
363
373
|
hydrated attributes, which roughly doubles the footprint of a large page.
|
|
364
374
|
That's why it's off by default.
|
|
365
375
|
|
|
376
|
+
### Migrating from an XML-based client
|
|
377
|
+
|
|
378
|
+
If you're replacing a Xero client that sent `Accept: text/xml` — HTTParty
|
|
379
|
+
and similar default to it — any payloads you already have stored are
|
|
380
|
+
XML-shaped, and `raw` will not match them. The differences are structural,
|
|
381
|
+
not cosmetic:
|
|
382
|
+
|
|
383
|
+
| | XML (`text/xml`) | JSON (`application/json`) |
|
|
384
|
+
|---|---|---|
|
|
385
|
+
| Organisation body | `{"Organisations" => {"Organisation" => {…}}}` | `{"Organisations" => [{…}]}` |
|
|
386
|
+
| One address | `{"Address" => {…}}` | `[{…}]` |
|
|
387
|
+
| Several addresses | `{"Address" => [{…}, {…}]}` | `[{…}, {…}]` |
|
|
388
|
+
|
|
389
|
+
The single-child collapse is the one that catches people: under XML a
|
|
390
|
+
contact with one person parses to a Hash and a contact with two parses to
|
|
391
|
+
an Array, from the same endpoint. Code written against that has a
|
|
392
|
+
normalising step somewhere, whether or not its author knew why.
|
|
393
|
+
|
|
394
|
+
No client setting reproduces these shapes — they're artefacts of an XML
|
|
395
|
+
parse kiwi doesn't do. But the remedy isn't the same everywhere, and it's
|
|
396
|
+
worth sorting your readers into two piles before planning the work.
|
|
397
|
+
|
|
398
|
+
**Readers that dig the XML-only structure have to change.** Something like
|
|
399
|
+
`dig("Organisations", "Organisation", "Addresses", "Address")` returns nil
|
|
400
|
+
against anything kiwi produces, whether you store `raw` or `to_h`.
|
|
401
|
+
Promote the fields those readers need to real columns and use `raw` for
|
|
402
|
+
the backfill — it's the true payload, so it carries everything required to
|
|
403
|
+
populate them.
|
|
404
|
+
|
|
405
|
+
**Readers that already normalise usually survive untouched.** A reader
|
|
406
|
+
doing `[value].flatten.compact` handles the Hash case, the Array case and
|
|
407
|
+
nil identically, so a JSON array flows straight through. If the keys it
|
|
408
|
+
reads are the same in both representations, only the *writer* changes:
|
|
409
|
+
stop unwrapping, store the array. Existing rows stay readable, and you
|
|
410
|
+
skip a migration entirely.
|
|
411
|
+
|
|
412
|
+
Type coercion is usually fine in that second pile too. XML gives you
|
|
413
|
+
strings — `"false"` rather than `false` — and if the reader hands that to
|
|
414
|
+
something like ActiveModel's boolean cast, both the old string and the new
|
|
415
|
+
real boolean land on the same value.
|
|
416
|
+
|
|
417
|
+
One piece of luck worth knowing about: the two piles tend to fail
|
|
418
|
+
differently. A `dig` that misses returns nil and writes a blank record
|
|
419
|
+
quietly. Code that assumed a Hash, such as `Array#to_h` on what is now a
|
|
420
|
+
list, raises `TypeError` on the first record with data in it. The noisy
|
|
421
|
+
failures are the ones you can trust to find themselves — budget your
|
|
422
|
+
review time for the silent ones.
|
|
423
|
+
|
|
366
424
|
## What the client gives you
|
|
367
425
|
|
|
368
426
|
| Method | Returns | Purpose |
|