@hunt-labs/bytekit-sdk 0.1.0 → 0.2.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.
- package/CHANGELOG.md +91 -0
- package/LICENSE +21 -0
- package/README.md +171 -0
- package/dist/allowlist.d.ts +0 -1
- package/dist/allowlist.js +0 -1
- package/dist/client.d.ts +69 -10
- package/dist/client.js +224 -13
- package/dist/generated/api.d.ts +6607 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +3 -2
- package/dist/resources/account.d.ts +50 -9
- package/dist/resources/account.js +17 -3
- package/dist/resources/bulk.d.ts +0 -1
- package/dist/resources/bulk.js +0 -1
- package/dist/resources/fetch.d.ts +0 -1
- package/dist/resources/fetch.js +0 -1
- package/dist/resources/monitors.d.ts +0 -1
- package/dist/resources/monitors.js +0 -1
- package/dist/resources/scrape.d.ts +50 -8
- package/dist/resources/scrape.js +30 -7
- package/dist/resources/screenshots.d.ts +54 -4
- package/dist/resources/screenshots.js +6 -2
- package/dist/resources/search.d.ts +0 -1
- package/dist/resources/search.js +0 -1
- package/dist/resources/sitemap.d.ts +0 -1
- package/dist/resources/sitemap.js +0 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.js +0 -1
- package/package.json +28 -11
- package/dist/allowlist.d.ts.map +0 -1
- package/dist/allowlist.js.map +0 -1
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/resources/account.d.ts.map +0 -1
- package/dist/resources/account.js.map +0 -1
- package/dist/resources/bulk.d.ts.map +0 -1
- package/dist/resources/bulk.js.map +0 -1
- package/dist/resources/fetch.d.ts.map +0 -1
- package/dist/resources/fetch.js.map +0 -1
- package/dist/resources/monitors.d.ts.map +0 -1
- package/dist/resources/monitors.js.map +0 -1
- package/dist/resources/recordings.d.ts +0 -11
- package/dist/resources/recordings.d.ts.map +0 -1
- package/dist/resources/recordings.js +0 -15
- package/dist/resources/recordings.js.map +0 -1
- package/dist/resources/scrape.d.ts.map +0 -1
- package/dist/resources/scrape.js.map +0 -1
- package/dist/resources/screenshots.d.ts.map +0 -1
- package/dist/resources/screenshots.js.map +0 -1
- package/dist/resources/search.d.ts.map +0 -1
- package/dist/resources/search.js.map +0 -1
- package/dist/resources/sitemap.d.ts.map +0 -1
- package/dist/resources/sitemap.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
package/dist/client.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ScrapeResource } from './resources/scrape.js';
|
|
2
2
|
import { ScreenshotsResource } from './resources/screenshots.js';
|
|
3
|
-
import { RecordingsResource } from './resources/recordings.js';
|
|
4
3
|
import { BulkResource } from './resources/bulk.js';
|
|
5
4
|
import { FetchResource } from './resources/fetch.js';
|
|
6
5
|
import { MonitorsResource } from './resources/monitors.js';
|
|
@@ -8,23 +7,155 @@ import { SitemapResource } from './resources/sitemap.js';
|
|
|
8
7
|
import { SearchResource } from './resources/search.js';
|
|
9
8
|
import { AccountResource } from './resources/account.js';
|
|
10
9
|
const DEFAULT_BASE_URL = 'https://api.bytekit.com';
|
|
10
|
+
/**
|
|
11
|
+
* Default client-side request timeout (2 minutes).
|
|
12
|
+
*
|
|
13
|
+
* Above the slowest expected synchronous scrape, and well below undici's ~5-minute
|
|
14
|
+
* headers timeout, so the SDK is always the tighter bound and a hung upstream can
|
|
15
|
+
* never hang the caller indefinitely.
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
11
18
|
/** Thrown when the API responds with a 4xx or 5xx status code. */
|
|
12
|
-
export class
|
|
19
|
+
export class ByteKitError extends Error {
|
|
13
20
|
status;
|
|
14
21
|
code;
|
|
15
22
|
constructor(status, code, message) {
|
|
16
23
|
super(message);
|
|
17
|
-
this.name = '
|
|
24
|
+
this.name = 'ByteKitError';
|
|
18
25
|
this.status = status;
|
|
19
26
|
this.code = code;
|
|
20
27
|
}
|
|
21
28
|
}
|
|
22
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Thrown when the request never produced an HTTP response at all — DNS, socket, TLS,
|
|
31
|
+
* a client-side timeout, or a caller-initiated abort. Distinct from `ByteKitError`,
|
|
32
|
+
* which always carries a real HTTP status: catch both to cover every failure.
|
|
33
|
+
*
|
|
34
|
+
* The original failure is preserved on `.cause`.
|
|
35
|
+
*/
|
|
36
|
+
export class ByteKitConnectionError extends Error {
|
|
37
|
+
code;
|
|
38
|
+
constructor(code, message, options) {
|
|
39
|
+
super(message, options);
|
|
40
|
+
this.name = 'ByteKitConnectionError';
|
|
41
|
+
this.code = code;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const NOOP = () => { };
|
|
45
|
+
/**
|
|
46
|
+
* Validate a timeout option. `undefined` passes through; anything that is not a
|
|
47
|
+
* non-negative finite number throws a `TypeError` naming the option as the caller
|
|
48
|
+
* spelled it (`"timeoutMs"` at the constructor, `"requestTimeoutMs"` per call).
|
|
49
|
+
*/
|
|
50
|
+
function validateTimeout(value, optionName) {
|
|
51
|
+
if (value === undefined)
|
|
52
|
+
return undefined;
|
|
53
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
|
|
54
|
+
throw new TypeError(`ByteKit: ${optionName} must be a non-negative finite number (got ${describe(value)})`);
|
|
55
|
+
}
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
/** Render a rejected option value for an error message without ever stringifying an object. */
|
|
59
|
+
function describe(value) {
|
|
60
|
+
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
|
|
61
|
+
return String(value);
|
|
62
|
+
}
|
|
63
|
+
if (typeof value === 'string')
|
|
64
|
+
return JSON.stringify(value);
|
|
65
|
+
if (value === null)
|
|
66
|
+
return 'null';
|
|
67
|
+
return typeof value;
|
|
68
|
+
}
|
|
69
|
+
/** The abort reason used by the `setTimeout` fallback; named so it maps to `code: 'timeout'`. */
|
|
70
|
+
function timeoutReason(ms) {
|
|
71
|
+
const reason = new Error(`ByteKit: request timed out after ${ms}ms`);
|
|
72
|
+
reason.name = 'TimeoutError';
|
|
73
|
+
return reason;
|
|
74
|
+
}
|
|
75
|
+
/** `AbortSignal.timeout(ms)` where available, else an `AbortController` + `setTimeout`. */
|
|
76
|
+
function createTimeoutSignal(ms) {
|
|
77
|
+
if (typeof AbortSignal.timeout === 'function') {
|
|
78
|
+
return { signal: AbortSignal.timeout(ms), cleanup: NOOP };
|
|
79
|
+
}
|
|
80
|
+
const controller = new AbortController();
|
|
81
|
+
const timer = setTimeout(() => controller.abort(timeoutReason(ms)), ms);
|
|
82
|
+
timer.unref?.();
|
|
83
|
+
return {
|
|
84
|
+
signal: controller.signal,
|
|
85
|
+
cleanup: () => clearTimeout(timer),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Abort on whichever source fires first.
|
|
90
|
+
*
|
|
91
|
+
* Prefers `AbortSignal.any`, whose sources hold only WEAK references to the composed
|
|
92
|
+
* signal — so nothing has to be (and nothing must be) retained to keep it collectable.
|
|
93
|
+
* The manual fallback is the path that has to clean up after itself: it attaches a real
|
|
94
|
+
* listener to the CALLER's signal, which typically outlives the request (one long-lived
|
|
95
|
+
* `AbortController` reused across many calls), so `cleanup()` removes it again.
|
|
96
|
+
*/
|
|
97
|
+
function composeSignals(caller, timeout) {
|
|
98
|
+
const anyFn = AbortSignal.any?.bind(AbortSignal);
|
|
99
|
+
if (typeof anyFn === 'function') {
|
|
100
|
+
return { signal: anyFn([caller, timeout]), cleanup: NOOP };
|
|
101
|
+
}
|
|
102
|
+
const controller = new AbortController();
|
|
103
|
+
const sources = [caller, timeout];
|
|
104
|
+
const onAbort = (event) => {
|
|
105
|
+
controller.abort(event.target.reason);
|
|
106
|
+
};
|
|
107
|
+
let done = false;
|
|
108
|
+
const cleanup = () => {
|
|
109
|
+
if (done)
|
|
110
|
+
return;
|
|
111
|
+
done = true;
|
|
112
|
+
for (const source of sources)
|
|
113
|
+
source.removeEventListener('abort', onAbort);
|
|
114
|
+
};
|
|
115
|
+
for (const source of sources)
|
|
116
|
+
source.addEventListener('abort', onAbort);
|
|
117
|
+
for (const source of sources) {
|
|
118
|
+
if (source.aborted) {
|
|
119
|
+
controller.abort(source.reason);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { signal: controller.signal, cleanup };
|
|
124
|
+
}
|
|
125
|
+
/** Map a `fetch` rejection onto the connection-error taxonomy. Never leaks the API key. */
|
|
126
|
+
function toConnectionError(err, method, url, timeoutSignal) {
|
|
127
|
+
// undici rejects an aborted fetch with the signal's own reason, so the thrown value's
|
|
128
|
+
// `name` is the primary discriminator (`TimeoutError` / `AbortError`).
|
|
129
|
+
const rawName = typeof err === 'object' && err !== null && 'name' in err ? err.name : undefined;
|
|
130
|
+
const name = typeof rawName === 'string' ? rawName : '';
|
|
131
|
+
let code;
|
|
132
|
+
if (name === 'TimeoutError') {
|
|
133
|
+
code = 'timeout';
|
|
134
|
+
}
|
|
135
|
+
else if (name === 'AbortError') {
|
|
136
|
+
code = 'aborted';
|
|
137
|
+
}
|
|
138
|
+
else if (timeoutSignal?.aborted === true) {
|
|
139
|
+
code = 'timeout';
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
code = 'connection';
|
|
143
|
+
}
|
|
144
|
+
const detail = code === 'timeout'
|
|
145
|
+
? 'request timed out'
|
|
146
|
+
: code === 'aborted'
|
|
147
|
+
? 'request aborted by the caller'
|
|
148
|
+
: 'connection failed before a response was received';
|
|
149
|
+
return new ByteKitConnectionError(code, `ByteKit: ${method} ${url} — ${detail}`, {
|
|
150
|
+
cause: err,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
export class ByteKit {
|
|
23
154
|
apiKey;
|
|
24
155
|
baseUrl;
|
|
156
|
+
defaultTimeoutMs;
|
|
25
157
|
scrape;
|
|
26
158
|
screenshots;
|
|
27
|
-
recordings;
|
|
28
159
|
bulk;
|
|
29
160
|
fetch;
|
|
30
161
|
monitors;
|
|
@@ -32,11 +163,14 @@ export class RapidCrawl {
|
|
|
32
163
|
search;
|
|
33
164
|
account;
|
|
34
165
|
constructor(options) {
|
|
166
|
+
if (!options?.apiKey) {
|
|
167
|
+
throw new TypeError('ByteKit: `apiKey` is required — got an empty value. Pass options.apiKey (e.g. from process.env.BYTEKIT_API_KEY).');
|
|
168
|
+
}
|
|
35
169
|
this.apiKey = options.apiKey;
|
|
36
170
|
this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
|
|
171
|
+
this.defaultTimeoutMs = validateTimeout(options.timeoutMs, '"timeoutMs"') ?? DEFAULT_TIMEOUT_MS;
|
|
37
172
|
this.scrape = new ScrapeResource(this);
|
|
38
173
|
this.screenshots = new ScreenshotsResource(this);
|
|
39
|
-
this.recordings = new RecordingsResource(this);
|
|
40
174
|
this.bulk = new BulkResource(this);
|
|
41
175
|
this.fetch = new FetchResource(this);
|
|
42
176
|
this.monitors = new MonitorsResource(this);
|
|
@@ -45,12 +179,51 @@ export class RapidCrawl {
|
|
|
45
179
|
this.account = new AccountResource(this);
|
|
46
180
|
}
|
|
47
181
|
/**
|
|
48
|
-
*
|
|
182
|
+
* Resolve the abort budget for one request from the caller's options and the
|
|
183
|
+
* constructor default. Precedence:
|
|
184
|
+
*
|
|
185
|
+
* | caller `signal` | per-call `requestTimeoutMs` | signal attached to `fetch` |
|
|
186
|
+
* |-----------------|----------------------------|---------------------------------------|
|
|
187
|
+
* | absent | absent | timeout from the constructor default |
|
|
188
|
+
* | absent | present | timeout from the per-call value |
|
|
189
|
+
* | present | absent | the caller's signal, verbatim |
|
|
190
|
+
* | present | present | composition — aborts on either |
|
|
191
|
+
*
|
|
192
|
+
* A timeout of `0` (either level) means "no SDK-side timeout".
|
|
193
|
+
*/
|
|
194
|
+
resolveSignal(options) {
|
|
195
|
+
const callerSignal = options?.signal;
|
|
196
|
+
const perCallMs = validateTimeout(options?.requestTimeoutMs, '"requestTimeoutMs"');
|
|
197
|
+
// A caller-supplied signal suppresses the constructor default (it does not
|
|
198
|
+
// suppress an explicit per-call budget, which composes with it instead).
|
|
199
|
+
const effectiveMs = perCallMs ?? (callerSignal !== undefined ? 0 : this.defaultTimeoutMs);
|
|
200
|
+
if (effectiveMs === 0) {
|
|
201
|
+
return { signal: callerSignal, timeoutSignal: undefined, cleanup: NOOP };
|
|
202
|
+
}
|
|
203
|
+
const timeout = createTimeoutSignal(effectiveMs);
|
|
204
|
+
if (callerSignal === undefined) {
|
|
205
|
+
return { signal: timeout.signal, timeoutSignal: timeout.signal, cleanup: timeout.cleanup };
|
|
206
|
+
}
|
|
207
|
+
const composed = composeSignals(callerSignal, timeout.signal);
|
|
208
|
+
return {
|
|
209
|
+
signal: composed.signal,
|
|
210
|
+
timeoutSignal: timeout.signal,
|
|
211
|
+
cleanup: () => {
|
|
212
|
+
composed.cleanup();
|
|
213
|
+
timeout.cleanup();
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Make an authenticated JSON request to the ByteKit API.
|
|
49
219
|
*
|
|
50
|
-
* Throws `
|
|
220
|
+
* Throws `ByteKitError` on 4xx/5xx responses, and `ByteKitConnectionError`
|
|
221
|
+
* when no HTTP response was produced at all (DNS/socket/TLS failure, client-side
|
|
222
|
+
* timeout, or caller abort).
|
|
51
223
|
*/
|
|
52
|
-
async request(method, path, body) {
|
|
224
|
+
async request(method, path, body, options) {
|
|
53
225
|
const url = `${this.baseUrl}${path}`;
|
|
226
|
+
const { signal, timeoutSignal, cleanup } = this.resolveSignal(options);
|
|
54
227
|
const init = {
|
|
55
228
|
method,
|
|
56
229
|
headers: {
|
|
@@ -61,7 +234,22 @@ export class RapidCrawl {
|
|
|
61
234
|
if (body !== undefined) {
|
|
62
235
|
init.body = JSON.stringify(body);
|
|
63
236
|
}
|
|
64
|
-
|
|
237
|
+
// `exactOptionalPropertyTypes` — assign only when we actually have a signal.
|
|
238
|
+
if (signal !== undefined) {
|
|
239
|
+
init.signal = signal;
|
|
240
|
+
}
|
|
241
|
+
let response;
|
|
242
|
+
try {
|
|
243
|
+
response = await fetch(url, init);
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
throw toConnectionError(err, method, url, timeoutSignal);
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
// Runs on success, on HTTP error, and on abort alike — no dangling timer,
|
|
250
|
+
// no listener left behind on the caller's (long-lived) signal.
|
|
251
|
+
cleanup();
|
|
252
|
+
}
|
|
65
253
|
if (!response.ok) {
|
|
66
254
|
let code = 'internal_error';
|
|
67
255
|
let message = response.statusText;
|
|
@@ -73,9 +261,32 @@ export class RapidCrawl {
|
|
|
73
261
|
catch {
|
|
74
262
|
// leave defaults
|
|
75
263
|
}
|
|
76
|
-
throw new
|
|
264
|
+
throw new ByteKitError(response.status, code, message);
|
|
265
|
+
}
|
|
266
|
+
// A 204 No Content has an empty body, and a genuinely empty body can arrive on
|
|
267
|
+
// other success codes too (defensive). Calling response.json() on an empty body
|
|
268
|
+
// rejects with a SyntaxError — which is how a successful revoke() (DELETE → 204)
|
|
269
|
+
// used to throw instead of resolving. Read the body once as text and short-circuit
|
|
270
|
+
// to `undefined` when it is empty; a JSON-bearing response (scrape.create,
|
|
271
|
+
// apiKeys.create/get, …) still parses and returns unchanged.
|
|
272
|
+
if (response.status === 204) {
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
const text = await response.text();
|
|
276
|
+
if (text.trim().length === 0) {
|
|
277
|
+
return undefined;
|
|
77
278
|
}
|
|
78
|
-
return
|
|
279
|
+
return JSON.parse(text);
|
|
79
280
|
}
|
|
80
281
|
}
|
|
81
|
-
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// Deprecated RapidCrawl* aliases (#2421).
|
|
284
|
+
//
|
|
285
|
+
// The public surface renamed from RapidCrawl to ByteKit. These aliases point at
|
|
286
|
+
// the SAME implementations (identity preserved: `RapidCrawl === ByteKit`), so an
|
|
287
|
+
// existing consumer's `instanceof` / error-shape contract keeps holding for one
|
|
288
|
+
// minor version. Exported here — not only from index.ts — so the SDK's own
|
|
289
|
+
// resource files can keep importing the legacy type name from './client.js'
|
|
290
|
+
// without churn. Re-exported (with @deprecated JSDoc) from index.ts.
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
export { ByteKit as RapidCrawl, ByteKitError as RapidCrawlError, ByteKitConnectionError as RapidCrawlConnectionError, };
|