@getanyapi/sdk 0.9.1 → 0.9.5
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/README.md +21 -4
- package/dist/index.cjs +150 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -8
- package/dist/index.d.ts +16 -8
- package/dist/index.js +150 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -126,13 +126,30 @@ Per-call transport overrides: `timeoutMs`, `maxRetries`, and an `AbortSignal` vi
|
|
|
126
126
|
| `ResultNotFoundError` | - | `unwrap` on an empty found-data result |
|
|
127
127
|
| `RateLimitedError` | 429 | Too many requests (retried automatically) |
|
|
128
128
|
| `UpstreamError` | 502 | An upstream backend failed |
|
|
129
|
-
| `ConnectionError` | 0 | Network or transport failure
|
|
129
|
+
| `ConnectionError` | 0 | Network or transport failure |
|
|
130
130
|
| `TimeoutError` | 0 | Request exceeded its timeout (not retried) |
|
|
131
131
|
|
|
132
132
|
All extend `AnyAPIError` (with `status` and `requestId`). Retries cover only 429 and network
|
|
133
|
-
failures, with jittered exponential backoff honoring
|
|
134
|
-
(up to 3 attempts); set it on the client or per request.
|
|
135
|
-
|
|
133
|
+
failures proven to happen before a request was sent, with jittered exponential backoff honoring
|
|
134
|
+
`Retry-After`. Default `maxRetries` is 2 (up to 3 attempts); set it on the client or per request.
|
|
135
|
+
Timeouts are never retried. Connection failures during or after a billed `POST /v1/run` are not
|
|
136
|
+
retried because the call may already have been charged. When the send phase is unknown, the SDK
|
|
137
|
+
does not retry. Configure with `new AnyAPI({ timeoutMs, maxRetries })`.
|
|
138
|
+
|
|
139
|
+
Automatic network retry of a billed `run()` requires structured runtime evidence that the
|
|
140
|
+
request body was not sent:
|
|
141
|
+
|
|
142
|
+
| Runtime | Automatic billed-run network retry | Evidence available to the SDK |
|
|
143
|
+
| ------- | ---------------------------------- | ----------------------------- |
|
|
144
|
+
| Node 18+ with built-in undici `fetch` | Yes | DNS and connect codes, connect-phase timeouts, or an undici socket reporting zero bytes written |
|
|
145
|
+
| Bun 1.3.11 | Yes | `ConnectionRefused`, which Bun 1.3.11 emits only while establishing the origin or proxy connection |
|
|
146
|
+
| Cloudflare Workers | No | `retryable: true` means transient, not undelivered; it can appear after the origin received the full body |
|
|
147
|
+
| Deno | No | Fetch exposes only prose without a structured connection code |
|
|
148
|
+
| Browsers | No | Fetch generally exposes an opaque `TypeError` |
|
|
149
|
+
|
|
150
|
+
On a runtime without strict non-delivery evidence, the SDK makes no automatic network retry for a
|
|
151
|
+
billed `run()`. HTTP 429 retry is unchanged. Handle other retries explicitly only when your
|
|
152
|
+
application can establish non-delivery.
|
|
136
153
|
|
|
137
154
|
## Agent signup
|
|
138
155
|
|
package/dist/index.cjs
CHANGED
|
@@ -103,13 +103,18 @@ var AnyAPIError = class extends Error {
|
|
|
103
103
|
status;
|
|
104
104
|
/** The x-request-id response header when present, else undefined. */
|
|
105
105
|
requestId;
|
|
106
|
-
|
|
106
|
+
/** Stable gateway error code when the JSON body includes one, else undefined. */
|
|
107
|
+
code;
|
|
108
|
+
constructor(message, status, requestId, code) {
|
|
107
109
|
super(message);
|
|
108
110
|
this.name = new.target.name;
|
|
109
111
|
this.status = status;
|
|
110
112
|
if (requestId !== void 0) {
|
|
111
113
|
this.requestId = requestId;
|
|
112
114
|
}
|
|
115
|
+
if (code !== void 0) {
|
|
116
|
+
this.code = code;
|
|
117
|
+
}
|
|
113
118
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
114
119
|
}
|
|
115
120
|
};
|
|
@@ -131,25 +136,72 @@ var ConnectionError = class extends AnyAPIError {
|
|
|
131
136
|
};
|
|
132
137
|
var TimeoutError = class extends AnyAPIError {
|
|
133
138
|
};
|
|
134
|
-
function errorFromStatus(status, message, requestId) {
|
|
139
|
+
function errorFromStatus(status, message, requestId, code) {
|
|
135
140
|
switch (status) {
|
|
136
141
|
case 400:
|
|
137
|
-
return new BadRequestError(message, status, requestId);
|
|
142
|
+
return new BadRequestError(message, status, requestId, code);
|
|
138
143
|
case 401:
|
|
139
|
-
return new AuthenticationError(message, status, requestId);
|
|
144
|
+
return new AuthenticationError(message, status, requestId, code);
|
|
140
145
|
case 402:
|
|
141
|
-
return new InsufficientBalanceError(message, status, requestId);
|
|
146
|
+
return new InsufficientBalanceError(message, status, requestId, code);
|
|
142
147
|
case 404:
|
|
143
|
-
return new NotFoundError(message, status, requestId);
|
|
148
|
+
return new NotFoundError(message, status, requestId, code);
|
|
144
149
|
case 429:
|
|
145
|
-
return new RateLimitedError(message, status, requestId);
|
|
150
|
+
return new RateLimitedError(message, status, requestId, code);
|
|
146
151
|
case 502:
|
|
147
|
-
return new UpstreamError(message, status, requestId);
|
|
152
|
+
return new UpstreamError(message, status, requestId, code);
|
|
148
153
|
default:
|
|
149
|
-
return new AnyAPIError(message, status, requestId);
|
|
154
|
+
return new AnyAPIError(message, status, requestId, code);
|
|
150
155
|
}
|
|
151
156
|
}
|
|
152
157
|
|
|
158
|
+
// src/core/idempotency.ts
|
|
159
|
+
var MAX_IDEMPOTENCY_KEY_BYTES = 255;
|
|
160
|
+
function generateIdempotencyKey() {
|
|
161
|
+
let runtimeCrypto;
|
|
162
|
+
try {
|
|
163
|
+
runtimeCrypto = globalThis.crypto;
|
|
164
|
+
} catch {
|
|
165
|
+
runtimeCrypto = void 0;
|
|
166
|
+
}
|
|
167
|
+
if (typeof runtimeCrypto?.randomUUID === "function") {
|
|
168
|
+
try {
|
|
169
|
+
return runtimeCrypto.randomUUID();
|
|
170
|
+
} catch {
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (typeof runtimeCrypto?.getRandomValues === "function") {
|
|
174
|
+
try {
|
|
175
|
+
const bytes = runtimeCrypto.getRandomValues(new Uint8Array(16));
|
|
176
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
177
|
+
} catch {
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return Array.from(
|
|
181
|
+
{ length: 4 },
|
|
182
|
+
() => Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0")
|
|
183
|
+
).join("");
|
|
184
|
+
}
|
|
185
|
+
function validateIdempotencyKey(key) {
|
|
186
|
+
if (key.length === 0 || key.length > MAX_IDEMPOTENCY_KEY_BYTES || [...key].some((char) => {
|
|
187
|
+
const code = char.charCodeAt(0);
|
|
188
|
+
return code < 33 || code > 126;
|
|
189
|
+
})) {
|
|
190
|
+
throw new TypeError(
|
|
191
|
+
"idempotencyKey must be 1-255 bytes of visible ASCII (0x21-0x7e)"
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function pageIdempotencyKey(key, pageNumber) {
|
|
196
|
+
validateIdempotencyKey(key);
|
|
197
|
+
const suffix = `-p${pageNumber}`;
|
|
198
|
+
const prefixLength = MAX_IDEMPOTENCY_KEY_BYTES - suffix.length;
|
|
199
|
+
if (prefixLength < 1) {
|
|
200
|
+
throw new TypeError("pagination page number is too large for an idempotency key");
|
|
201
|
+
}
|
|
202
|
+
return `${key.slice(0, prefixLength)}${suffix}`;
|
|
203
|
+
}
|
|
204
|
+
|
|
153
205
|
// src/core/account.ts
|
|
154
206
|
var DEFAULT_BASE_URL = "https://api.getanyapi.com";
|
|
155
207
|
function malformed(path) {
|
|
@@ -458,14 +510,18 @@ async function agentSignup(options = {}) {
|
|
|
458
510
|
const text = await response.text().catch(() => "");
|
|
459
511
|
if (response.status !== 200) {
|
|
460
512
|
let message = `request failed with status ${response.status}`;
|
|
513
|
+
let code;
|
|
461
514
|
try {
|
|
462
515
|
const parsed2 = JSON.parse(text);
|
|
463
516
|
if (typeof parsed2.error === "string" && parsed2.error !== "") {
|
|
464
517
|
message = parsed2.error;
|
|
465
518
|
}
|
|
519
|
+
if (typeof parsed2.code === "string" && parsed2.code !== "") {
|
|
520
|
+
code = parsed2.code;
|
|
521
|
+
}
|
|
466
522
|
} catch {
|
|
467
523
|
}
|
|
468
|
-
throw errorFromStatus(response.status, message, requestId);
|
|
524
|
+
throw errorFromStatus(response.status, message, requestId, code);
|
|
469
525
|
}
|
|
470
526
|
const parsed = JSON.parse(text);
|
|
471
527
|
return {
|
|
@@ -482,6 +538,18 @@ var DEFAULT_TIMEOUT_MS = 6e4;
|
|
|
482
538
|
var DEFAULT_MAX_RETRIES = 2;
|
|
483
539
|
var RETRY_BASE_DELAY_MS = 500;
|
|
484
540
|
var RETRY_MAX_DELAY_MS = 8e3;
|
|
541
|
+
var PRE_SEND_NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
542
|
+
"EADDRNOTAVAIL",
|
|
543
|
+
"EAI_AGAIN",
|
|
544
|
+
"EAI_NODATA",
|
|
545
|
+
"EAI_NONAME",
|
|
546
|
+
"ECONNREFUSED",
|
|
547
|
+
"EHOSTUNREACH",
|
|
548
|
+
"ENETUNREACH",
|
|
549
|
+
"ENOTFOUND",
|
|
550
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
551
|
+
"ConnectionRefused"
|
|
552
|
+
]);
|
|
485
553
|
function envApiKey() {
|
|
486
554
|
try {
|
|
487
555
|
if (typeof process !== "undefined" && process?.env) {
|
|
@@ -566,6 +634,33 @@ function isTimeoutSignal(timeoutSignal, callerSignal) {
|
|
|
566
634
|
}
|
|
567
635
|
return callerSignal?.aborted !== true || timeoutSignal.aborted;
|
|
568
636
|
}
|
|
637
|
+
function isDefinitelyPreSendConnectionError(error) {
|
|
638
|
+
const seen = /* @__PURE__ */ new Set();
|
|
639
|
+
const visit = (value) => {
|
|
640
|
+
if (typeof value !== "object" && typeof value !== "function" || value === null) {
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
if (seen.has(value)) {
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
seen.add(value);
|
|
647
|
+
const candidate = value;
|
|
648
|
+
if (typeof candidate.code === "string" && PRE_SEND_NETWORK_ERROR_CODES.has(candidate.code)) {
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
if (candidate.code === "ETIMEDOUT" && candidate.syscall === "connect") {
|
|
652
|
+
return true;
|
|
653
|
+
}
|
|
654
|
+
if (candidate.code === "UND_ERR_SOCKET" && candidate.socket?.bytesWritten === 0) {
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
if (Array.isArray(candidate.errors) && candidate.errors.length > 0) {
|
|
658
|
+
return candidate.errors.some((item) => visit(item)) || visit(candidate.cause);
|
|
659
|
+
}
|
|
660
|
+
return visit(candidate.cause);
|
|
661
|
+
};
|
|
662
|
+
return visit(error);
|
|
663
|
+
}
|
|
569
664
|
function buildUrl(baseUrl, slug, options) {
|
|
570
665
|
const base = baseUrl.replace(/\/+$/, "");
|
|
571
666
|
const url = new URL(`${base}/v1/run/${slug}`);
|
|
@@ -584,13 +679,21 @@ function messageFromBody(body, status) {
|
|
|
584
679
|
if (body) {
|
|
585
680
|
try {
|
|
586
681
|
const parsed = JSON.parse(body);
|
|
682
|
+
const code = typeof parsed.code === "string" && parsed.code !== "" ? parsed.code : void 0;
|
|
587
683
|
if (typeof parsed.error === "string" && parsed.error !== "") {
|
|
588
|
-
return
|
|
684
|
+
return {
|
|
685
|
+
message: parsed.error,
|
|
686
|
+
...code !== void 0 ? { code } : {}
|
|
687
|
+
};
|
|
589
688
|
}
|
|
689
|
+
return {
|
|
690
|
+
message: `request failed with status ${status}`,
|
|
691
|
+
...code !== void 0 ? { code } : {}
|
|
692
|
+
};
|
|
590
693
|
} catch {
|
|
591
694
|
}
|
|
592
695
|
}
|
|
593
|
-
return `request failed with status ${status}
|
|
696
|
+
return { message: `request failed with status ${status}` };
|
|
594
697
|
}
|
|
595
698
|
var AnyAPI = class {
|
|
596
699
|
apiKey;
|
|
@@ -598,6 +701,7 @@ var AnyAPI = class {
|
|
|
598
701
|
fetchImpl;
|
|
599
702
|
maxRetries;
|
|
600
703
|
timeoutMs;
|
|
704
|
+
idempotency;
|
|
601
705
|
/**
|
|
602
706
|
* The network seam the generated per-platform namespaces target. The base client IS a
|
|
603
707
|
* ClientCore (it implements `run`), so the generated subclass hands `this._core` to each
|
|
@@ -620,6 +724,11 @@ var AnyAPI = class {
|
|
|
620
724
|
this.fetchImpl = resolvedFetch;
|
|
621
725
|
this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
622
726
|
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
727
|
+
const idempotency = options.idempotency ?? "auto";
|
|
728
|
+
if (idempotency !== "auto" && idempotency !== "off") {
|
|
729
|
+
throw new TypeError('idempotency must be "auto" or "off"');
|
|
730
|
+
}
|
|
731
|
+
this.idempotency = idempotency;
|
|
623
732
|
}
|
|
624
733
|
/**
|
|
625
734
|
* Generic run for any SKU by slug (the untyped network seam + the string fallback). The
|
|
@@ -627,13 +736,15 @@ var AnyAPI = class {
|
|
|
627
736
|
* base signature is the fallback that returns RunResult<unknown> for an unknown slug.
|
|
628
737
|
*/
|
|
629
738
|
run(slug, input, options) {
|
|
739
|
+
const body = JSON.stringify(input ?? {});
|
|
630
740
|
return this.request(
|
|
631
741
|
"POST",
|
|
632
742
|
buildUrl(this.baseUrl, slug, options),
|
|
633
743
|
{
|
|
634
|
-
body
|
|
744
|
+
body,
|
|
635
745
|
timeoutMs: options?.timeoutMs ?? this.timeoutMs,
|
|
636
746
|
maxRetries: options?.maxRetries ?? this.maxRetries,
|
|
747
|
+
...options?.idempotencyKey !== void 0 ? { idempotencyKey: options.idempotencyKey } : {},
|
|
637
748
|
...options?.signal ? { signal: options.signal } : {}
|
|
638
749
|
}
|
|
639
750
|
);
|
|
@@ -698,6 +809,12 @@ var AnyAPI = class {
|
|
|
698
809
|
if (this.apiKey) {
|
|
699
810
|
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
700
811
|
}
|
|
812
|
+
const billedPost = method === "POST" && opts.body !== void 0;
|
|
813
|
+
if (billedPost && this.idempotency === "auto") {
|
|
814
|
+
const key = opts.idempotencyKey ?? generateIdempotencyKey();
|
|
815
|
+
validateIdempotencyKey(key);
|
|
816
|
+
headers["Idempotency-Key"] = key;
|
|
817
|
+
}
|
|
701
818
|
let attempt = 0;
|
|
702
819
|
for (; ; ) {
|
|
703
820
|
const { signal, timeoutSignal } = composeSignal(
|
|
@@ -723,7 +840,9 @@ var AnyAPI = class {
|
|
|
723
840
|
err instanceof Error ? err.message : "connection failed",
|
|
724
841
|
0
|
|
725
842
|
);
|
|
726
|
-
|
|
843
|
+
const requestMayBeBilled = method === "POST" && opts.body !== void 0;
|
|
844
|
+
const safeToRetry = !requestMayBeBilled || isDefinitelyPreSendConnectionError(err);
|
|
845
|
+
if (safeToRetry && attempt < opts.maxRetries) {
|
|
727
846
|
await sleep(backoffDelay(attempt), opts.signal);
|
|
728
847
|
attempt += 1;
|
|
729
848
|
continue;
|
|
@@ -744,7 +863,7 @@ var AnyAPI = class {
|
|
|
744
863
|
}
|
|
745
864
|
}
|
|
746
865
|
const body = await response.text().catch(() => "");
|
|
747
|
-
const message = messageFromBody(body, response.status);
|
|
866
|
+
const { message, code } = messageFromBody(body, response.status);
|
|
748
867
|
if (response.status === 429 && attempt < opts.maxRetries) {
|
|
749
868
|
const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
|
|
750
869
|
const delay = retryAfter ?? backoffDelay(attempt);
|
|
@@ -752,7 +871,7 @@ var AnyAPI = class {
|
|
|
752
871
|
attempt += 1;
|
|
753
872
|
continue;
|
|
754
873
|
}
|
|
755
|
-
throw errorFromStatus(response.status, message, requestId);
|
|
874
|
+
throw errorFromStatus(response.status, message, requestId, code);
|
|
756
875
|
}
|
|
757
876
|
}
|
|
758
877
|
/** Internal accessor for GET helpers in account.ts (same base URL / machinery). */
|
|
@@ -797,12 +916,14 @@ function paginate(core, slug, input, itemsField, bare, options) {
|
|
|
797
916
|
const maxItems = options?.maxItems;
|
|
798
917
|
async function* walkPages() {
|
|
799
918
|
let cursor;
|
|
919
|
+
let pageNumber = 1;
|
|
800
920
|
for (; ; ) {
|
|
801
921
|
const pageInput = { ...input };
|
|
802
922
|
if (cursor !== void 0) {
|
|
803
923
|
pageInput["cursor"] = cursor;
|
|
804
924
|
}
|
|
805
|
-
const
|
|
925
|
+
const pageOptions = optionsForPage(wireOptions, pageNumber);
|
|
926
|
+
const result = await core.run(slug, pageInput, pageOptions);
|
|
806
927
|
yield result;
|
|
807
928
|
const data = pageData(result, bare);
|
|
808
929
|
if (data === null) {
|
|
@@ -813,6 +934,7 @@ function paginate(core, slug, input, itemsField, bare, options) {
|
|
|
813
934
|
return;
|
|
814
935
|
}
|
|
815
936
|
cursor = next;
|
|
937
|
+
pageNumber += 1;
|
|
816
938
|
}
|
|
817
939
|
}
|
|
818
940
|
async function* walkItems() {
|
|
@@ -844,6 +966,15 @@ function paginate(core, slug, input, itemsField, bare, options) {
|
|
|
844
966
|
};
|
|
845
967
|
return paginator;
|
|
846
968
|
}
|
|
969
|
+
function optionsForPage(options, pageNumber) {
|
|
970
|
+
if (options?.idempotencyKey === void 0) {
|
|
971
|
+
return options;
|
|
972
|
+
}
|
|
973
|
+
return {
|
|
974
|
+
...options,
|
|
975
|
+
idempotencyKey: pageIdempotencyKey(options.idempotencyKey, pageNumber)
|
|
976
|
+
};
|
|
977
|
+
}
|
|
847
978
|
function stripMaxItems(options) {
|
|
848
979
|
if (!options) {
|
|
849
980
|
return void 0;
|
|
@@ -1421,7 +1552,7 @@ var EmailNamespace = class {
|
|
|
1421
1552
|
/**
|
|
1422
1553
|
* Email Verifier
|
|
1423
1554
|
*
|
|
1424
|
-
* Verify
|
|
1555
|
+
* Verify an email address for deliverability: a status verdict (valid, risky, or invalid) with domain, mailbox, catch-all, disposable, and role signals plus a confidence score. Malformed addresses are rejected by the input schema with no charge; every syntactically valid address returns a billed verdict, including undeliverable ones.
|
|
1425
1556
|
*
|
|
1426
1557
|
* Price: $0 per request plus $0.0008 per result (maximum $0.0008).
|
|
1427
1558
|
*
|
|
@@ -1668,7 +1799,7 @@ var FacebookNamespace = class {
|
|
|
1668
1799
|
/**
|
|
1669
1800
|
* Facebook Marketplace
|
|
1670
1801
|
*
|
|
1671
|
-
* Search Facebook Marketplace listings by keyword near a location,
|
|
1802
|
+
* Search Facebook Marketplace listings by keyword near a location, filter by price, condition, delivery, recency, and availability, and get title, price, location, and image as normalized JSON.
|
|
1672
1803
|
*
|
|
1673
1804
|
* Price: $0.002 per request.
|
|
1674
1805
|
*
|