@hunt-labs/bytekit-sdk 0.9.3 → 0.11.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.
- package/CHANGELOG.md +170 -0
- package/README.md +26 -0
- package/dist/client.d.ts +2 -2
- package/dist/client.js +61 -3
- package/dist/generated/api.d.ts +459 -233
- package/dist/index.d.ts +1 -1
- package/dist/resources/account.d.ts +2 -2
- package/dist/resources/account.js +2 -2
- package/dist/resources/bulk.d.ts +14 -2
- package/dist/resources/bulk.js +14 -3
- package/dist/resources/fetch.d.ts +5 -2
- package/dist/resources/fetch.js +16 -5
- package/dist/resources/monitors.d.ts +2 -2
- package/dist/resources/monitors.js +2 -2
- package/dist/resources/scrape.d.ts +45 -6
- package/dist/resources/scrape.js +30 -5
- package/dist/resources/screenshots.d.ts +12 -7
- package/dist/resources/screenshots.js +6 -6
- package/dist/resources/sitemap.d.ts +1 -1
- package/dist/resources/sitemap.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,176 @@ today, see [README.md](./README.md), which remains the authoritative usage guide
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [0.11.0] - 2026-09-14
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`solve_challenge` on `scrape.create` (#4447).** `ScrapeOpts` now carries
|
|
18
|
+
`solve_challenge?: boolean`, and the generated `ScrapeRequest` schema type carries the field
|
|
19
|
+
with its server default `false`. When `true`,
|
|
20
|
+
the scrape attempts to clear a Cloudflare challenge; success is not guaranteed. It is billed
|
|
21
|
+
as a normal browser render. A scrape that omits the option sends no `solve_challenge` key,
|
|
22
|
+
so existing calls are unchanged on the wire.
|
|
23
|
+
- **`credits_scrape` on the usage response types (#4452).** The generated `GET /v1/usage`
|
|
24
|
+
response type and each `GET /v1/usage/daily` data item now carry `credits_scrape: number`:
|
|
25
|
+
credits drawn under the scrape usage source, such as `/v1/schema` extractions. The gateway
|
|
26
|
+
always charged this bucket but never returned it, so the published per-source credit fields
|
|
27
|
+
could not add up to `credits_used`.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **Internal `GET /v1/bulk` is cursor-paginated and its 100-job cap is gone (#4508).** The
|
|
32
|
+
generated `listBulk` types record a changed response shape: the operation used to return a
|
|
33
|
+
bare array of at most the 100 newest jobs, and now returns the same page envelope
|
|
34
|
+
`GET /v1/monitors` does, `{ data, next_cursor, has_more }`, plus `limit` and `cursor` query
|
|
35
|
+
parameters (`limit` is 1 to 100, default 25). A `status` filter and a `cursor` combine in
|
|
36
|
+
one request: `status` narrows the set and the cursor addresses a position inside it. The
|
|
37
|
+
operation's summary also no longer says "in-flight", which was wrong — it has always
|
|
38
|
+
returned jobs of every status, `cancelled` included. This is a breaking change to the wire
|
|
39
|
+
shape of an `x-sdk-scope: internal` operation; no SDK method calls it, so no published
|
|
40
|
+
method signature moves.
|
|
41
|
+
- **`GET /v1/logs` `billing_multiplier` is the bandwidth factor, not a credit count (#4469).**
|
|
42
|
+
The generated logs-row type records the changed meaning of a public field: the value is now
|
|
43
|
+
`billable_bytes` / `raw_bytes` rounded to 2 decimal places — the factor the request settled
|
|
44
|
+
at, carrying the endpoint, cache-hit and clean-markdown factors, and the flat 1.5 Phase-1
|
|
45
|
+
factor on screenshots and recordings. It used to be the row's persisted `credits_charged`,
|
|
46
|
+
an account credit count charged per capture option, so two screenshots billed at the same
|
|
47
|
+
1.5 factor reported `2` and `1`. The 2 decimal places are load-bearing rather than
|
|
48
|
+
cosmetic: `billable_bytes` is itself rounded, so an odd byte basis would otherwise surface
|
|
49
|
+
as `1.500001943158721` instead of `1.5` on roughly half of all such rows. Every factor the
|
|
50
|
+
field can report is a multiple of 0.25, so 2 places is exact for any basis of 100 bytes or
|
|
51
|
+
more, and `raw_bytes` × `billing_multiplier` rounded to the nearest byte still reproduces
|
|
52
|
+
`billable_bytes`. The `billable_bytes` doc comment therefore no longer says it is NOT
|
|
53
|
+
`raw_bytes` × `billing_multiplier`. The field is also null on more rows than before: a
|
|
54
|
+
search or webhook row has no byte basis, a capture whose Phase 1 was skipped has a zero one,
|
|
55
|
+
and a capture old enough to predate the Phase-1 basis being recorded has none at all. One
|
|
56
|
+
residual is documented rather than fixed: a markdown `/v1/fetch` row completed before the
|
|
57
|
+
billing decomposition was recorded reports 1.0 where settlement charged 1.5, because nothing
|
|
58
|
+
on such a row distinguishes it. The type shapes are unchanged (`number | null`), and no SDK
|
|
59
|
+
method calls this endpoint.
|
|
60
|
+
- **An all-null `/v1/schema` extraction your schema allows is validated again (#4490).** The
|
|
61
|
+
generated `/v1/schema` 200 response type records the exception to #4455's rule: an output
|
|
62
|
+
whose every leaf is `null` still arrives `validated: true` with the object intact, and is
|
|
63
|
+
charged, when the schema you sent declares `"null"` in the `type` union of every leaf the
|
|
64
|
+
output returned as `null`. Only that form counts — `nullable: true`, an `enum` containing
|
|
65
|
+
`null` and `const: null` do not, and a response that is itself `null` rather than an object is
|
|
66
|
+
never accepted however the root is declared. Everything else stays the #4455 miss. The `data`
|
|
67
|
+
doc comment now also states the other half: a `validated: true` response always carries an
|
|
68
|
+
object, never `null`. The type shapes are unchanged; only their doc comments moved.
|
|
69
|
+
- **An all-null `/v1/schema` extraction is reported, not validated (#4455).** The generated
|
|
70
|
+
`/v1/schema` 200 response type documents a new additive warning code, `empty_extraction`: a
|
|
71
|
+
schema-valid output whose every leaf is `null` now arrives with `data: null` and
|
|
72
|
+
`validated: false`, and is not charged the per-request credits. Previously it arrived as
|
|
73
|
+
`validated: true` with the all-null object and no warning. The `data` and `validated` doc
|
|
74
|
+
comments now also say that every `validated: false` response carries `data: null`; the old
|
|
75
|
+
comment claimed exhausted retries returned the last raw output, which the API never did. The
|
|
76
|
+
type shapes are unchanged; only their doc comments moved.
|
|
77
|
+
- **An inline `POST /v1/scrape` failure is documented with a persisted `id` (#4454).** The
|
|
78
|
+
gateway now records a sync scrape that ends `upstream_error` or `blocked` as a failed scrape
|
|
79
|
+
job, so its `failed` envelope carries that job's `sc_` id instead of `null`, `scrape.get(id)`
|
|
80
|
+
returns the same envelope, and the request appears in the dashboard Logs. The generated
|
|
81
|
+
`createScrape` 200 and `ScrapeErrorEnvelope` doc comments and the `scrape.create` doc comment
|
|
82
|
+
no longer say `id: null`. The type shapes are unchanged (`id` stays `string | null`), and
|
|
83
|
+
`scrape.create` still throws `ByteKitError` on that envelope exactly as before.
|
|
84
|
+
- **Internal `GET /v1/logs` types list search and webhook rows (#4460).** Both `endpoint` enums
|
|
85
|
+
add `search` and `webhook`; a row's `source` is now `string | null`. No SDK method calls it.
|
|
86
|
+
|
|
87
|
+
## [0.10.0] - 2026-09-11
|
|
88
|
+
|
|
89
|
+
### Changed
|
|
90
|
+
|
|
91
|
+
- **A terminal `POST /v1/scrape` failure now throws instead of resolving (#1870).** The
|
|
92
|
+
gateway used to report an upstream fetch failure — unreachable host, refused connection,
|
|
93
|
+
or bot protection that defeated every proxy tier — as HTTP 503; it now answers **HTTP 200**
|
|
94
|
+
with the canonical `failed` envelope (`id: null`), because a definitive upstream outcome is
|
|
95
|
+
a result, not a transport error. `!response.ok` was this SDK's only failure seam, so without
|
|
96
|
+
this change that body would resolve like a success and silence every caller's `catch`.
|
|
97
|
+
`scrape.create` (and any `request` / `requestWithResponse` call to `POST /v1/scrape`) now
|
|
98
|
+
raises `ByteKitError` carrying the envelope's `code` and `message`, the **actual** status
|
|
99
|
+
`200`, the response headers, and `error.details` when one is supplied. A legacy 503 from a
|
|
100
|
+
not-yet-upgraded gateway still throws through the unchanged non-2xx decoder, so the SDK
|
|
101
|
+
works against both. **Upgrade note:** if you inspected `result.status === 'failed'` after
|
|
102
|
+
`scrape.create`, move that handling into a `catch`.
|
|
103
|
+
- Everything else is untouched: `scrape.get(id)`, bulk item results, and every other endpoint
|
|
104
|
+
still deliver `failed` as data; `requestRaw` (`/v1/fetch`) never parses JSON and is
|
|
105
|
+
unaffected; 204 and empty-body handling are unchanged. `request()` now delegates to
|
|
106
|
+
`requestWithResponse()` so the guard has a single application site — identical in every
|
|
107
|
+
observable respect but the return value, as both were already documented.
|
|
108
|
+
|
|
109
|
+
## [0.9.4] - 2026-09-06 [NEVER PUBLISHED]
|
|
110
|
+
|
|
111
|
+
**This version was cut but NEVER PUBLISHED — npm `latest` goes from 0.9.3 straight to 0.10.0.**
|
|
112
|
+
Publication is triggered by CI on `main` from the version the manifest declares, and 0.10.0
|
|
113
|
+
(#1870) superseded 0.9.4 in the tree before any release reached `main`. Everything below
|
|
114
|
+
reaches consumers in the **0.10.0** artifact, which contains all of it; nothing is installable
|
|
115
|
+
at 0.9.4: `npm i @hunt-labs/bytekit-sdk@0.9.4` fails.
|
|
116
|
+
|
|
117
|
+
### Added
|
|
118
|
+
|
|
119
|
+
- Bulk fetch, scrape, and mixed-result reads now accept optional cursor, limit, and status
|
|
120
|
+
page parameters while preserving the existing `(id, requestOptions?)` call shape (#3717).
|
|
121
|
+
Generated response types publish stable item identities, page cursors and completeness,
|
|
122
|
+
whole-job counters, exact mixed artifact keys, artifact warnings, bounded webhook recovery
|
|
123
|
+
metadata, and the `artifact_unavailable` error contract.
|
|
124
|
+
|
|
125
|
+
### Fixed
|
|
126
|
+
|
|
127
|
+
- Corrected the artifact-URL fallback documentation on `GET /v1/bulk/{id}/screenshots`
|
|
128
|
+
(`image_url`, `content_url`, `markdown_url`) and `GET /v1/logs` (`artifact_url`) (#3717).
|
|
129
|
+
Those fields were documented as null-plus-`artifact_unavailable` when no signature can be
|
|
130
|
+
minted; the servers have always returned the row's stored value unchanged instead — `null`
|
|
131
|
+
while nothing is stored, and otherwise the private unsigned storage URL, which answers `401`
|
|
132
|
+
— and raise no warning. `GET /v1/fetch/bulk/{id}` does null the field and report
|
|
133
|
+
`artifact_unavailable`, and its documentation is unchanged. No server behaviour changed and
|
|
134
|
+
no type moved; the descriptions now match what the endpoints return.
|
|
135
|
+
|
|
136
|
+
### Changed
|
|
137
|
+
|
|
138
|
+
- Regenerated `src/generated/api.d.ts` against the corrected `topup_balance_bytes` description
|
|
139
|
+
on `GET /v1/usage` (#4251). The description read "Remaining purchased top-up bytes", which
|
|
140
|
+
contradicted its own "Equal to `topup_bytes`" clause: the value covers purchased top-up lots
|
|
141
|
+
and the one-time starter grant, which is what the endpoint has always returned. The clause
|
|
142
|
+
recording that the figure is already net of consumed overage, and the "Present only in
|
|
143
|
+
dual/bytes billing mode" clause, are both unchanged and still true. Types are unchanged and
|
|
144
|
+
no server value changed; only the documentation on them moved.
|
|
145
|
+
- Regenerated `src/generated/api.d.ts` against the clarified `cache_age_s` description on
|
|
146
|
+
`ScrapeSuccessEnvelope` (#4237). The description now records that `GET /v1/scrape/{id}`
|
|
147
|
+
reports `cache: hit` without `cache_age_s`: the age is a property of the replay that happened
|
|
148
|
+
at completion time, is not persisted on the row, and cannot be derived honestly afterwards.
|
|
149
|
+
The one-directional "Present only on `cache: hit`" clause is unchanged and still true. Types
|
|
150
|
+
are unchanged; only the documentation on them moved.
|
|
151
|
+
- Regenerated `src/generated/api.d.ts` against the completed `cache` presence list on
|
|
152
|
+
`ScrapeSuccessEnvelope` (#4203). The field's description now names `GET /v1/scrape/{id}` as a
|
|
153
|
+
third delivery path, alongside the synchronous response and webhook delivery: a polled scrape
|
|
154
|
+
now reports the cache disposition its row recorded, and omits the field — rather than
|
|
155
|
+
defaulting it to `miss` — for a row that recorded none. Types are unchanged; only the
|
|
156
|
+
documentation on them moved.
|
|
157
|
+
- Regenerated `src/generated/api.d.ts` against the corrected `PUT /v1/billing/auto-topup`
|
|
158
|
+
request body (#3984). `monthly_cap_cents` is now typed `number | null`: an explicit `null` is
|
|
159
|
+
the wire representation of "no monthly cap", and it is what the endpoint has always accepted —
|
|
160
|
+
the request schema simply never documented it, while the 200 response schema already did.
|
|
161
|
+
Omitting the key while `enabled` is `true` is still rejected with `422`, so send `null`
|
|
162
|
+
rather than dropping the field. No server behaviour changes; the type widens to match what
|
|
163
|
+
the API already accepted.
|
|
164
|
+
- Regenerated `src/generated/api.d.ts` against the corrected monitor `scrape_options`
|
|
165
|
+
documentation (#4024). The description no longer lists `headers` among the accepted
|
|
166
|
+
fields — `POST /v1/monitors` never honoured a caller-supplied `headers` option and now
|
|
167
|
+
rejects it with the same 400 every other capture endpoint returns for the field. Types are
|
|
168
|
+
unchanged; only the documentation on them moved.
|
|
169
|
+
- Regenerated `src/generated/api.d.ts` against the completed `X-Scrape-Proxy-Tier-Label`
|
|
170
|
+
omission list (#4030). The header's description now names a third case in which no tier is
|
|
171
|
+
reported: a response replayed from stored bytes after this request's own cache lookup missed,
|
|
172
|
+
which arrives as `X-Scrape-Cache: miss` and likewise ran no proxy tier. The two cases already
|
|
173
|
+
documented — a cache hit, and responses the fast path did not serve — are unchanged. Types are
|
|
174
|
+
unchanged; only the documentation on them moved.
|
|
175
|
+
- Regenerated `src/generated/api.d.ts` against the qualified `X-Scrape-Proxy-Tier-Label`
|
|
176
|
+
omission list (#4132). The third case that list records — a response replayed from stored bytes
|
|
177
|
+
after this request's own cache lookup missed — is not reachable through this API: every caller
|
|
178
|
+
of the internal fetch path closes the handler branch that produces it, and the branch survives
|
|
179
|
+
only as a deploy-skew safeguard. The description now says so rather than presenting the case as
|
|
180
|
+
ordinary behaviour. The two reachable cases — a cache hit, and responses the fast path did not
|
|
181
|
+
serve — are unchanged. Types are unchanged; only the documentation on them moved.
|
|
182
|
+
|
|
13
183
|
## [0.9.3] - 2026-09-04
|
|
14
184
|
|
|
15
185
|
### Changed
|
package/README.md
CHANGED
|
@@ -300,6 +300,32 @@ try {
|
|
|
300
300
|
}
|
|
301
301
|
```
|
|
302
302
|
|
|
303
|
+
### A failed scrape throws, even though it arrives on a 200
|
|
304
|
+
|
|
305
|
+
`POST /v1/scrape` reports a terminal upstream fetch failure — an unreachable
|
|
306
|
+
host, a refused connection, or a target whose bot protection defeats every proxy
|
|
307
|
+
tier — as **HTTP 200** carrying the canonical `failed` envelope, because a
|
|
308
|
+
definitive upstream outcome is a result rather than a transport error. The SDK
|
|
309
|
+
turns that one body into the same `ByteKitError` you already catch, so a failed
|
|
310
|
+
scrape can never be mistaken for a successful one:
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
import { ByteKit, ByteKitError } from '@hunt-labs/bytekit-sdk';
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
await client.scrape.create({ url: 'https://does-not-resolve.invalid' });
|
|
317
|
+
} catch (err) {
|
|
318
|
+
if (err instanceof ByteKitError) {
|
|
319
|
+
// 200 'upstream_error' 'Failed to fetch URL.' (or 'blocked')
|
|
320
|
+
console.error(err.status, err.code, err.message);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
`err.status` is the real transport status, `200` — nothing is synthesized. This
|
|
326
|
+
applies to `scrape.create` only. `scrape.get(id)` and bulk item results deliver
|
|
327
|
+
`failed` as **data**, exactly as before: narrow on `result.status` there.
|
|
328
|
+
|
|
303
329
|
### Reading which field was rejected
|
|
304
330
|
|
|
305
331
|
`err.details` carries the API envelope's structured rejection reason when the
|
package/dist/client.d.ts
CHANGED
|
@@ -17,8 +17,8 @@ export interface ByteKitOptions {
|
|
|
17
17
|
* read (so a large legitimate body counts against it too). `0` disables the SDK-side
|
|
18
18
|
* timeout. Default 120000.
|
|
19
19
|
*
|
|
20
|
-
* NOT the same thing as `ScrapeOpts.timeout_ms`, which is the SERVER-side budget
|
|
21
|
-
*
|
|
20
|
+
* NOT the same thing as `ScrapeOpts.timeout_ms`, which is the SERVER-side budget
|
|
21
|
+
* ByteKit applies to the scrape itself. Override per call with
|
|
22
22
|
* `RequestOptions.requestTimeoutMs`.
|
|
23
23
|
*/
|
|
24
24
|
timeoutMs?: number;
|
package/dist/client.js
CHANGED
|
@@ -214,6 +214,53 @@ function parseJsonBody(text) {
|
|
|
214
214
|
}
|
|
215
215
|
return JSON.parse(text);
|
|
216
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Throw a `ByteKitError` for the one 2xx body that is a FAILURE: the terminal
|
|
219
|
+
* `status: 'failed'` envelope `POST /v1/scrape` delivers on HTTP 200 (#1870).
|
|
220
|
+
*
|
|
221
|
+
* The gateway used to report a terminal upstream fetch failure as HTTP 503, and
|
|
222
|
+
* `!response.ok` in {@link ByteKit.send} was this SDK's only failure seam. Now that the
|
|
223
|
+
* same outcome arrives as an intentional 200 — a definitive upstream result is not a
|
|
224
|
+
* transport error, and `id` names the failed job the gateway recorded it as —
|
|
225
|
+
* that seam alone would let the failure resolve like a success and silence every caller's
|
|
226
|
+
* `catch`. This restores the exception integrators already write code for, with the
|
|
227
|
+
* envelope's own `code` and `message`.
|
|
228
|
+
*
|
|
229
|
+
* Scope is deliberately narrow — `POST` to exactly `/v1/scrape`:
|
|
230
|
+
*
|
|
231
|
+
* - `GET /v1/scrape/{id}`, bulk item results, and the `scrape.failed` webhook payload
|
|
232
|
+
* have always delivered `failed` as DATA, and a caller polling a job must keep
|
|
233
|
+
* receiving it as a value;
|
|
234
|
+
* - `requestRaw` (`/v1/fetch`) never parses JSON and is not routed through here at all.
|
|
235
|
+
*
|
|
236
|
+
* `status` is the ACTUAL transport status (200). The envelope's optional
|
|
237
|
+
* `error.http_status` is the TARGET's status when one was received, never ours, so it is
|
|
238
|
+
* not substituted here — and the old 503 is not synthesized either.
|
|
239
|
+
*
|
|
240
|
+
* Runs on the already-parsed body, outside `send`'s connection-error catch, so it can
|
|
241
|
+
* never be remapped into a `ByteKitConnectionError`.
|
|
242
|
+
*/
|
|
243
|
+
function throwIfScrapeFailureEnvelope(method, path, data, status, headers) {
|
|
244
|
+
if (method.toUpperCase() !== 'POST' || path !== '/v1/scrape')
|
|
245
|
+
return;
|
|
246
|
+
if (typeof data !== 'object' || data === null)
|
|
247
|
+
return;
|
|
248
|
+
const envelope = data;
|
|
249
|
+
if (envelope.status !== 'failed')
|
|
250
|
+
return;
|
|
251
|
+
const error = typeof envelope.error === 'object' && envelope.error !== null
|
|
252
|
+
? envelope.error
|
|
253
|
+
: {};
|
|
254
|
+
const code = typeof error.code === 'string' ? error.code : 'internal_error';
|
|
255
|
+
const message = typeof error.message === 'string' ? error.message : 'Scrape failed.';
|
|
256
|
+
// Same discipline as the non-2xx decoder above: arrays and scalars are rejected,
|
|
257
|
+
// because `ByteKitErrorDetails` promises something indexable.
|
|
258
|
+
const rawDetails = error.details;
|
|
259
|
+
const details = typeof rawDetails === 'object' && rawDetails !== null && !Array.isArray(rawDetails)
|
|
260
|
+
? rawDetails
|
|
261
|
+
: undefined;
|
|
262
|
+
throw new ByteKitError(status, code, message, details, headers);
|
|
263
|
+
}
|
|
217
264
|
/** The `name` of an error-shaped value, or `undefined` when it carries none. */
|
|
218
265
|
const nameOf = (e) => typeof e === 'object' && e !== null && 'name' in e ? String(e.name) : undefined;
|
|
219
266
|
/** The `cause` of an error-shaped value, or `undefined` when it carries none. */
|
|
@@ -477,8 +524,14 @@ export class ByteKit {
|
|
|
477
524
|
// Only the read runs inside the transport's guarded region (#2546) — `JSON.parse`
|
|
478
525
|
// stays out here (in `parseJsonBody`), so a malformed body still throws its own
|
|
479
526
|
// SyntaxError rather than being remapped to a ByteKitConnectionError.
|
|
480
|
-
|
|
481
|
-
|
|
527
|
+
//
|
|
528
|
+
// #1870: delegated to `requestWithResponse` so the terminal-scrape-failure guard has
|
|
529
|
+
// exactly ONE application site across both public JSON transports, rather than a copy
|
|
530
|
+
// per entrypoint that could drift. The two were already documented as identical in
|
|
531
|
+
// every observable respect but the return value, and the header/status pair the
|
|
532
|
+
// delegate captures is what the thrown `ByteKitError` needs anyway.
|
|
533
|
+
const { data } = await this.requestWithResponse(method, path, body, options);
|
|
534
|
+
return data;
|
|
482
535
|
}
|
|
483
536
|
/**
|
|
484
537
|
* Make an authenticated request whose response body IS the content, not a JSON
|
|
@@ -536,6 +589,11 @@ export class ByteKit {
|
|
|
536
589
|
headers: response.headers,
|
|
537
590
|
status: response.status,
|
|
538
591
|
}));
|
|
539
|
-
|
|
592
|
+
const data = parseJsonBody(text);
|
|
593
|
+
// #1870: the one 2xx body that is a failure. Placed after the parse and outside
|
|
594
|
+
// `send`'s guarded region, so the throw is a ByteKitError and never a remapped
|
|
595
|
+
// connection error.
|
|
596
|
+
throwIfScrapeFailureEnvelope(method, path, data, status, headers);
|
|
597
|
+
return { data, headers, status };
|
|
540
598
|
}
|
|
541
599
|
}
|