@pagouai/api-sdk 0.1.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.
- package/README.md +211 -0
- package/dist/client.d.ts +50 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +448 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +41 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +65 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/pagination.d.ts +10 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +32 -0
- package/dist/pagination.js.map +1 -0
- package/dist/resources/transactions.d.ts +42 -0
- package/dist/resources/transactions.d.ts.map +1 -0
- package/dist/resources/transactions.js +105 -0
- package/dist/resources/transactions.js.map +1 -0
- package/dist/types.d.ts +196 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Pagou.ai TypeScript SDK (`/v2`)
|
|
2
|
+
|
|
3
|
+
Cross-runtime SDK template for Node.js 18+, Bun, and Deno.
|
|
4
|
+
|
|
5
|
+
- Resource-first API (`client.transactions.*`)
|
|
6
|
+
- `fetch`-based (no axios)
|
|
7
|
+
- Typed errors
|
|
8
|
+
- Retries + timeout + idempotency
|
|
9
|
+
- Native `/v2` page/limit pagination
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bun add @pagouai/api-sdk
|
|
15
|
+
# or npm i / pnpm add
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { Client } from "@pagouai/api-sdk";
|
|
22
|
+
|
|
23
|
+
const client = new Client({
|
|
24
|
+
apiKey: "YOUR_API_KEY",
|
|
25
|
+
timeoutMs: 30_000,
|
|
26
|
+
maxRetries: 2,
|
|
27
|
+
telemetry: true,
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Configuration
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
new Client({
|
|
35
|
+
apiKey: "YOUR_API_KEY",
|
|
36
|
+
environment: "production", // default: "production"
|
|
37
|
+
// environment: "sandbox",
|
|
38
|
+
// baseUrl: "https://api.sandbox.pagou.ai", // explicit override if needed
|
|
39
|
+
timeoutMs: 30_000,
|
|
40
|
+
maxRetries: 2,
|
|
41
|
+
telemetry: true,
|
|
42
|
+
userAgent: "PagouTS-SDK/0.1.0 (Transactions; +https://pagou.ai)",
|
|
43
|
+
fetch: customFetch,
|
|
44
|
+
auth: { scheme: "bearer" }, // default
|
|
45
|
+
// apiVersion: "2026-02-01",
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Auth schemes:
|
|
50
|
+
|
|
51
|
+
- `bearer` (default) -> `Authorization: Bearer <apiKey>`
|
|
52
|
+
- `basic` -> `Authorization: Basic <base64(apiKey:x)>`
|
|
53
|
+
- `api_key_header` -> `apikey: <apiKey>` (or custom `headerName`)
|
|
54
|
+
|
|
55
|
+
Default base URLs:
|
|
56
|
+
|
|
57
|
+
- production: `https://api.pagou.ai`
|
|
58
|
+
- sandbox/test: `https://api.sandbox.pagou.ai`
|
|
59
|
+
|
|
60
|
+
## API Coverage (`/v2`)
|
|
61
|
+
|
|
62
|
+
- `POST /v2/transactions`
|
|
63
|
+
- `GET /v2/transactions`
|
|
64
|
+
- `GET /v2/transactions/{id}`
|
|
65
|
+
- `PUT /v2/transactions/{id}` (test/sandbox-only behavior)
|
|
66
|
+
- `PUT /v2/transactions/{id}/refund`
|
|
67
|
+
|
|
68
|
+
## Usage Examples
|
|
69
|
+
|
|
70
|
+
### 1) Create a transaction
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
const created = await client.transactions.create({
|
|
74
|
+
amount: 1500,
|
|
75
|
+
method: "pix",
|
|
76
|
+
currency: "BRL",
|
|
77
|
+
buyer: {
|
|
78
|
+
name: "Jane Doe",
|
|
79
|
+
email: "jane@example.com",
|
|
80
|
+
},
|
|
81
|
+
products: [{ name: "Pro Plan", price: 1500, quantity: 1 }],
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
console.log(created.data.id, created.meta.requestId);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2) Retrieve a transaction
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
const tx = await client.transactions.retrieve("tr_123");
|
|
91
|
+
console.log(tx.data.status);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 3) List transactions with page/limit + filters
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
const page = await client.transactions.list({
|
|
98
|
+
page: 1,
|
|
99
|
+
limit: 20,
|
|
100
|
+
status: ["pending", "paid"],
|
|
101
|
+
paymentMethods: ["pix"],
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
console.log(page.data.metadata.total);
|
|
105
|
+
for (const item of page.data.data) {
|
|
106
|
+
console.log(item.id, item.status);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 4) Auto-paging iterator
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
for await (const item of client.transactions.listAutoPagingIterator({ limit: 100 })) {
|
|
114
|
+
console.log(item.id);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 5) Refund a transaction
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const refunded = await client.transactions.refund(
|
|
122
|
+
"tr_123",
|
|
123
|
+
{ amount: 500, reason: "requested_by_customer" },
|
|
124
|
+
{ idempotencyKey: "idem_refund_tr_123_1" },
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
console.log(refunded.data);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Request Options
|
|
131
|
+
|
|
132
|
+
All resource methods accept `opts?`:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
{
|
|
136
|
+
idempotencyKey?: string;
|
|
137
|
+
requestId?: string;
|
|
138
|
+
timeoutMs?: number;
|
|
139
|
+
signal?: AbortSignal;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Retries and Timeout
|
|
144
|
+
|
|
145
|
+
Retries happen for:
|
|
146
|
+
|
|
147
|
+
- Network errors
|
|
148
|
+
- `429`, `500`, `502`, `503`, `504`
|
|
149
|
+
|
|
150
|
+
Rules:
|
|
151
|
+
|
|
152
|
+
- Always retry for `GET`/`HEAD`
|
|
153
|
+
- Retry `POST`/`PUT` only when `idempotencyKey` is set
|
|
154
|
+
- Respect `Retry-After` header when available
|
|
155
|
+
|
|
156
|
+
Timeout uses `AbortController`.
|
|
157
|
+
|
|
158
|
+
## Error Handling
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import {
|
|
162
|
+
AuthenticationError,
|
|
163
|
+
InvalidRequestError,
|
|
164
|
+
NotFoundError,
|
|
165
|
+
RateLimitError,
|
|
166
|
+
ServerError,
|
|
167
|
+
NetworkError,
|
|
168
|
+
} from "@pagouai/api-sdk";
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
await client.transactions.retrieve("tr_404");
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (error instanceof NotFoundError) {
|
|
174
|
+
console.error("Missing transaction", error.requestId);
|
|
175
|
+
} else if (error instanceof RateLimitError) {
|
|
176
|
+
console.error("Back off", error.status, error.code);
|
|
177
|
+
} else if (error instanceof NetworkError) {
|
|
178
|
+
console.error("Network issue", error.message);
|
|
179
|
+
} else if (error instanceof InvalidRequestError || error instanceof AuthenticationError || error instanceof ServerError) {
|
|
180
|
+
console.error(error.message);
|
|
181
|
+
} else {
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Response Shapes
|
|
188
|
+
|
|
189
|
+
Data endpoints:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
{
|
|
193
|
+
success: boolean;
|
|
194
|
+
requestId: string;
|
|
195
|
+
data: T;
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
List endpoints:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
{
|
|
203
|
+
success: boolean;
|
|
204
|
+
requestId: string;
|
|
205
|
+
metadata: { page: number; limit: number; total: number };
|
|
206
|
+
data: T[];
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Notes:
|
|
211
|
+
- This SDK uses `/v2` endpoint paths.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { TransactionsResource } from "./resources/transactions";
|
|
2
|
+
import type { ApiResponse, ClientOptions, PaginatedEnvelope, RequestOptions } from "./types";
|
|
3
|
+
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD";
|
|
4
|
+
interface RawResponse {
|
|
5
|
+
status: number;
|
|
6
|
+
headers: Headers;
|
|
7
|
+
body: unknown;
|
|
8
|
+
requestId?: string;
|
|
9
|
+
}
|
|
10
|
+
interface RequestInput {
|
|
11
|
+
method: HttpMethod;
|
|
12
|
+
path: string;
|
|
13
|
+
query?: Record<string, unknown>;
|
|
14
|
+
body?: unknown;
|
|
15
|
+
options?: RequestOptions;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Main SDK client for Pagou.ai API.
|
|
19
|
+
*/
|
|
20
|
+
export declare class Client {
|
|
21
|
+
private readonly apiKey;
|
|
22
|
+
private readonly baseUrl;
|
|
23
|
+
private readonly timeoutMs;
|
|
24
|
+
private readonly maxRetries;
|
|
25
|
+
private readonly telemetry;
|
|
26
|
+
private readonly userAgent;
|
|
27
|
+
private readonly fetchImpl;
|
|
28
|
+
private readonly auth;
|
|
29
|
+
private readonly apiVersion?;
|
|
30
|
+
private readonly retryBaseDelayMs;
|
|
31
|
+
private readonly maxRetryDelayMs;
|
|
32
|
+
readonly transactions: TransactionsResource;
|
|
33
|
+
constructor(options: ClientOptions);
|
|
34
|
+
/**
|
|
35
|
+
* Sends a request and unwraps a data envelope.
|
|
36
|
+
*/
|
|
37
|
+
requestData<T>(input: RequestInput): Promise<ApiResponse<T>>;
|
|
38
|
+
/**
|
|
39
|
+
* Sends a request and unwraps a paginated envelope.
|
|
40
|
+
*/
|
|
41
|
+
requestPaginated<T>(input: RequestInput): Promise<ApiResponse<PaginatedEnvelope<T>>>;
|
|
42
|
+
requestRaw(input: RequestInput): Promise<RawResponse>;
|
|
43
|
+
private prepareRequest;
|
|
44
|
+
private applyAuthHeader;
|
|
45
|
+
private applyVersionHeader;
|
|
46
|
+
private applyTelemetryHeaders;
|
|
47
|
+
private toApiError;
|
|
48
|
+
}
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,KAAK,EACX,WAAW,EAGX,aAAa,EAGb,iBAAiB,EACjB,cAAc,EAEd,MAAM,SAAS,CAAC;AAajB,KAAK,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAUvE,UAAU,WAAW;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,YAAY;IACrB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,cAAc,CAAC;CACzB;AAED;;GAEG;AACH,qBAAa,MAAM;IAClB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAU;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IAEzC,SAAgB,YAAY,EAAE,oBAAoB,CAAC;gBAEvC,OAAO,EAAE,aAAa;IAoBlC;;OAEG;IACU,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAiBzE;;OAEG;IACU,gBAAgB,CAAC,CAAC,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;IAiBpF,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC;IAsElE,OAAO,CAAC,cAAc;IA+FtB,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,qBAAqB;IAa7B,OAAO,CAAC,UAAU;CA+BlB"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import { ApiError, AuthenticationError, ConflictError, InvalidRequestError, NetworkError, NotFoundError, PermissionError, parseErrorPayload, RateLimitError, ServerError, } from "./errors";
|
|
2
|
+
import { TransactionsResource } from "./resources/transactions";
|
|
3
|
+
import { SDK_VERSION } from "./version";
|
|
4
|
+
const PRODUCTION_BASE_URL = "https://api.pagou.ai";
|
|
5
|
+
const SANDBOX_BASE_URL = "https://api.sandbox.pagou.ai";
|
|
6
|
+
const DEFAULT_AUTH_SCHEME = "bearer";
|
|
7
|
+
const DEFAULT_AUTH_HEADER_NAME = "apikey";
|
|
8
|
+
const DEFAULT_USER_AGENT = `PagouTS-SDK/${SDK_VERSION} (Transactions; +https://pagou.ai)`;
|
|
9
|
+
const DEFAULT_API_VERSION_HEADER = "{{API_VERSION_HEADER}}";
|
|
10
|
+
const DEFAULT_API_VERSION = "{{API_VERSION}}";
|
|
11
|
+
const RETRY_STATUS_CODES = new Set([429, 500, 502, 503, 504]);
|
|
12
|
+
/**
|
|
13
|
+
* Main SDK client for Pagou.ai API.
|
|
14
|
+
*/
|
|
15
|
+
export class Client {
|
|
16
|
+
apiKey;
|
|
17
|
+
baseUrl;
|
|
18
|
+
timeoutMs;
|
|
19
|
+
maxRetries;
|
|
20
|
+
telemetry;
|
|
21
|
+
userAgent;
|
|
22
|
+
fetchImpl;
|
|
23
|
+
auth;
|
|
24
|
+
apiVersion;
|
|
25
|
+
retryBaseDelayMs;
|
|
26
|
+
maxRetryDelayMs;
|
|
27
|
+
transactions;
|
|
28
|
+
constructor(options) {
|
|
29
|
+
if (!options.apiKey) {
|
|
30
|
+
throw new Error("Client requires an apiKey.");
|
|
31
|
+
}
|
|
32
|
+
this.apiKey = options.apiKey;
|
|
33
|
+
this.baseUrl = normalizeBaseUrl(options.baseUrl ?? resolveEnvironmentBaseUrl(options.environment));
|
|
34
|
+
this.timeoutMs = options.timeoutMs ?? 30_000;
|
|
35
|
+
this.maxRetries = options.maxRetries ?? 2;
|
|
36
|
+
this.telemetry = options.telemetry ?? true;
|
|
37
|
+
this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;
|
|
38
|
+
this.fetchImpl = resolveFetch(options.fetch);
|
|
39
|
+
this.auth = options.auth ?? resolveDefaultAuth();
|
|
40
|
+
this.apiVersion = options.apiVersion ?? resolvePlaceholder(DEFAULT_API_VERSION);
|
|
41
|
+
this.retryBaseDelayMs = options.retryBaseDelayMs ?? 200;
|
|
42
|
+
this.maxRetryDelayMs = options.maxRetryDelayMs ?? 2_000;
|
|
43
|
+
this.transactions = new TransactionsResource(this);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Sends a request and unwraps a data envelope.
|
|
47
|
+
*/
|
|
48
|
+
async requestData(input) {
|
|
49
|
+
const raw = await this.requestRaw(input);
|
|
50
|
+
const envelope = raw.body;
|
|
51
|
+
if (!isDataEnvelope(envelope)) {
|
|
52
|
+
throw new ApiError("Unexpected response format: expected data envelope", {
|
|
53
|
+
status: raw.status,
|
|
54
|
+
requestId: raw.requestId,
|
|
55
|
+
raw: raw.body,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
data: envelope.data,
|
|
60
|
+
meta: toMeta(raw),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Sends a request and unwraps a paginated envelope.
|
|
65
|
+
*/
|
|
66
|
+
async requestPaginated(input) {
|
|
67
|
+
const raw = await this.requestRaw(input);
|
|
68
|
+
const envelope = raw.body;
|
|
69
|
+
if (!isPaginatedEnvelope(envelope)) {
|
|
70
|
+
throw new ApiError("Unexpected response format: expected paginated envelope", {
|
|
71
|
+
status: raw.status,
|
|
72
|
+
requestId: raw.requestId,
|
|
73
|
+
raw: raw.body,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
data: envelope,
|
|
78
|
+
meta: toMeta(raw),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async requestRaw(input) {
|
|
82
|
+
const method = input.method;
|
|
83
|
+
const canRetry = isRetryEligible(method, input.options?.idempotencyKey);
|
|
84
|
+
let attempt = 0;
|
|
85
|
+
for (;;) {
|
|
86
|
+
const prepared = this.prepareRequest(input);
|
|
87
|
+
try {
|
|
88
|
+
const response = await this.fetchImpl(prepared.url, prepared.init);
|
|
89
|
+
const requestId = extractRequestId(response.headers);
|
|
90
|
+
const body = await parseBody(response);
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
const shouldRetryStatus = RETRY_STATUS_CODES.has(response.status);
|
|
93
|
+
const shouldRetry = canRetry && shouldRetryStatus && attempt < this.maxRetries;
|
|
94
|
+
if (shouldRetry) {
|
|
95
|
+
const delayMs = computeRetryDelayMs(attempt, response.headers.get("Retry-After"), this.retryBaseDelayMs, this.maxRetryDelayMs);
|
|
96
|
+
attempt += 1;
|
|
97
|
+
prepared.clear();
|
|
98
|
+
await sleep(delayMs);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
prepared.clear();
|
|
102
|
+
throw this.toApiError(response.status, body, requestId);
|
|
103
|
+
}
|
|
104
|
+
prepared.clear();
|
|
105
|
+
return {
|
|
106
|
+
status: response.status,
|
|
107
|
+
headers: response.headers,
|
|
108
|
+
body,
|
|
109
|
+
requestId: requestId ?? extractRequestIdFromBody(body),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
prepared.clear();
|
|
114
|
+
if (error instanceof ApiError) {
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
const aborted = isAbortError(error);
|
|
118
|
+
const timedOut = prepared.didTimeout();
|
|
119
|
+
const retryable = !aborted;
|
|
120
|
+
if (canRetry && retryable && attempt < this.maxRetries) {
|
|
121
|
+
const delayMs = computeRetryDelayMs(attempt, undefined, this.retryBaseDelayMs, this.maxRetryDelayMs);
|
|
122
|
+
attempt += 1;
|
|
123
|
+
await sleep(delayMs);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const message = timedOut
|
|
127
|
+
? `Request timed out after ${input.options?.timeoutMs ?? this.timeoutMs}ms`
|
|
128
|
+
: "Network request failed";
|
|
129
|
+
throw new NetworkError(message, {
|
|
130
|
+
cause: error,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
prepareRequest(input) {
|
|
136
|
+
const headers = new Headers();
|
|
137
|
+
headers.set("Accept", "application/json");
|
|
138
|
+
if (input.body !== undefined) {
|
|
139
|
+
headers.set("Content-Type", "application/json");
|
|
140
|
+
}
|
|
141
|
+
const requestId = input.options?.requestId;
|
|
142
|
+
if (requestId) {
|
|
143
|
+
headers.set("X-Request-Id", requestId);
|
|
144
|
+
}
|
|
145
|
+
if (input.options?.idempotencyKey) {
|
|
146
|
+
headers.set("Idempotency-Key", input.options.idempotencyKey);
|
|
147
|
+
}
|
|
148
|
+
this.applyAuthHeader(headers);
|
|
149
|
+
this.applyVersionHeader(headers);
|
|
150
|
+
this.applyTelemetryHeaders(headers);
|
|
151
|
+
const url = new URL(input.path, this.baseUrl);
|
|
152
|
+
if (input.query) {
|
|
153
|
+
for (const [key, value] of Object.entries(input.query)) {
|
|
154
|
+
if (value === undefined || value === null) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (Array.isArray(value)) {
|
|
158
|
+
if (value.length === 0) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
url.searchParams.set(key, value.map((v) => String(v)).join(","));
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
url.searchParams.set(key, String(value));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const controller = new AbortController();
|
|
168
|
+
const externalSignal = input.options?.signal;
|
|
169
|
+
const timeoutMs = input.options?.timeoutMs ?? this.timeoutMs;
|
|
170
|
+
let timedOut = false;
|
|
171
|
+
let timeoutHandle;
|
|
172
|
+
const onExternalAbort = () => {
|
|
173
|
+
if (!controller.signal.aborted) {
|
|
174
|
+
controller.abort();
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
if (externalSignal) {
|
|
178
|
+
if (externalSignal.aborted) {
|
|
179
|
+
onExternalAbort();
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
externalSignal.addEventListener("abort", onExternalAbort, { once: true });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (timeoutMs > 0) {
|
|
186
|
+
timeoutHandle = setTimeout(() => {
|
|
187
|
+
timedOut = true;
|
|
188
|
+
if (!controller.signal.aborted) {
|
|
189
|
+
controller.abort();
|
|
190
|
+
}
|
|
191
|
+
}, timeoutMs);
|
|
192
|
+
}
|
|
193
|
+
const init = {
|
|
194
|
+
method: input.method,
|
|
195
|
+
headers,
|
|
196
|
+
signal: controller.signal,
|
|
197
|
+
};
|
|
198
|
+
if (input.body !== undefined) {
|
|
199
|
+
init.body = JSON.stringify(input.body);
|
|
200
|
+
}
|
|
201
|
+
const clear = () => {
|
|
202
|
+
if (timeoutHandle !== undefined) {
|
|
203
|
+
clearTimeout(timeoutHandle);
|
|
204
|
+
}
|
|
205
|
+
if (externalSignal) {
|
|
206
|
+
externalSignal.removeEventListener("abort", onExternalAbort);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
return {
|
|
210
|
+
url,
|
|
211
|
+
init,
|
|
212
|
+
timeoutHandle,
|
|
213
|
+
clear,
|
|
214
|
+
didTimeout: () => timedOut,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
applyAuthHeader(headers) {
|
|
218
|
+
switch (this.auth.scheme) {
|
|
219
|
+
case "bearer":
|
|
220
|
+
headers.set("Authorization", `Bearer ${this.apiKey}`);
|
|
221
|
+
break;
|
|
222
|
+
case "basic":
|
|
223
|
+
headers.set("Authorization", `Basic ${encodeBase64(`${this.apiKey}:x`)}`);
|
|
224
|
+
break;
|
|
225
|
+
case "api_key_header": {
|
|
226
|
+
const fallbackHeaderName = resolvePlaceholder(DEFAULT_AUTH_HEADER_NAME) ?? "apikey";
|
|
227
|
+
headers.set(this.auth.headerName ?? fallbackHeaderName, this.apiKey);
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
applyVersionHeader(headers) {
|
|
233
|
+
const versionHeaderName = resolvePlaceholder(DEFAULT_API_VERSION_HEADER);
|
|
234
|
+
if (!versionHeaderName || !this.apiVersion) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
headers.set(versionHeaderName, this.apiVersion);
|
|
238
|
+
}
|
|
239
|
+
applyTelemetryHeaders(headers) {
|
|
240
|
+
if (!this.telemetry) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (this.userAgent) {
|
|
244
|
+
headers.set("User-Agent", this.userAgent);
|
|
245
|
+
}
|
|
246
|
+
headers.set("X-SDK-Lang", "typescript");
|
|
247
|
+
headers.set("X-SDK-Runtime", detectRuntime());
|
|
248
|
+
}
|
|
249
|
+
toApiError(status, body, requestIdFromHeader) {
|
|
250
|
+
const parsed = parseErrorPayload(body, `Request failed with status ${status}`, requestIdFromHeader);
|
|
251
|
+
const common = {
|
|
252
|
+
status,
|
|
253
|
+
code: parsed.code,
|
|
254
|
+
requestId: parsed.requestId,
|
|
255
|
+
details: parsed.details,
|
|
256
|
+
raw: parsed.raw,
|
|
257
|
+
};
|
|
258
|
+
if (status === 401) {
|
|
259
|
+
return new AuthenticationError(parsed.message, common);
|
|
260
|
+
}
|
|
261
|
+
if (status === 403) {
|
|
262
|
+
return new PermissionError(parsed.message, common);
|
|
263
|
+
}
|
|
264
|
+
if (status === 404) {
|
|
265
|
+
return new NotFoundError(parsed.message, common);
|
|
266
|
+
}
|
|
267
|
+
if (status === 409) {
|
|
268
|
+
return new ConflictError(parsed.message, common);
|
|
269
|
+
}
|
|
270
|
+
if (status === 429) {
|
|
271
|
+
return new RateLimitError(parsed.message, common);
|
|
272
|
+
}
|
|
273
|
+
if (status >= 500) {
|
|
274
|
+
return new ServerError(parsed.message, common);
|
|
275
|
+
}
|
|
276
|
+
return new InvalidRequestError(parsed.message, common);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function resolveFetch(customFetch) {
|
|
280
|
+
if (customFetch) {
|
|
281
|
+
return customFetch;
|
|
282
|
+
}
|
|
283
|
+
if (typeof fetch !== "function") {
|
|
284
|
+
throw new Error("Global fetch is not available. Inject a fetch implementation via Client options.");
|
|
285
|
+
}
|
|
286
|
+
return fetch;
|
|
287
|
+
}
|
|
288
|
+
function normalizeBaseUrl(baseUrl) {
|
|
289
|
+
if (!baseUrl) {
|
|
290
|
+
throw new Error("Client baseUrl cannot be empty.");
|
|
291
|
+
}
|
|
292
|
+
return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
293
|
+
}
|
|
294
|
+
function resolveDefaultAuth() {
|
|
295
|
+
const scheme = resolveAuthScheme(DEFAULT_AUTH_SCHEME);
|
|
296
|
+
if (scheme === "api_key_header") {
|
|
297
|
+
const headerName = DEFAULT_AUTH_HEADER_NAME;
|
|
298
|
+
return { scheme, headerName };
|
|
299
|
+
}
|
|
300
|
+
return { scheme };
|
|
301
|
+
}
|
|
302
|
+
function resolveAuthScheme(value) {
|
|
303
|
+
if (value === "basic" || value === "api_key_header" || value === "bearer") {
|
|
304
|
+
return value;
|
|
305
|
+
}
|
|
306
|
+
return "bearer";
|
|
307
|
+
}
|
|
308
|
+
function resolveEnvironmentBaseUrl(environment) {
|
|
309
|
+
if (environment === "sandbox") {
|
|
310
|
+
return SANDBOX_BASE_URL;
|
|
311
|
+
}
|
|
312
|
+
return PRODUCTION_BASE_URL;
|
|
313
|
+
}
|
|
314
|
+
function resolvePlaceholder(value) {
|
|
315
|
+
if (!value) {
|
|
316
|
+
return undefined;
|
|
317
|
+
}
|
|
318
|
+
if (value.includes("{{") && value.includes("}}")) {
|
|
319
|
+
return undefined;
|
|
320
|
+
}
|
|
321
|
+
return value;
|
|
322
|
+
}
|
|
323
|
+
function extractRequestId(headers) {
|
|
324
|
+
return headers.get("x-request-id") ?? headers.get("request-id") ?? undefined;
|
|
325
|
+
}
|
|
326
|
+
function extractRequestIdFromBody(body) {
|
|
327
|
+
if (!body || typeof body !== "object") {
|
|
328
|
+
return undefined;
|
|
329
|
+
}
|
|
330
|
+
const record = body;
|
|
331
|
+
if (typeof record.requestId === "string") {
|
|
332
|
+
return record.requestId;
|
|
333
|
+
}
|
|
334
|
+
if (typeof record.request_id === "string") {
|
|
335
|
+
return record.request_id;
|
|
336
|
+
}
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
function toMeta(raw) {
|
|
340
|
+
return {
|
|
341
|
+
status: raw.status,
|
|
342
|
+
headers: raw.headers,
|
|
343
|
+
requestId: raw.requestId,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function isDataEnvelope(value) {
|
|
347
|
+
return typeof value.success === "boolean" && "data" in value;
|
|
348
|
+
}
|
|
349
|
+
function isPaginatedEnvelope(value) {
|
|
350
|
+
return (typeof value.success === "boolean" &&
|
|
351
|
+
Array.isArray(value.data) &&
|
|
352
|
+
Boolean(value.metadata) &&
|
|
353
|
+
typeof value.metadata?.page === "number" &&
|
|
354
|
+
typeof value.metadata?.limit === "number" &&
|
|
355
|
+
typeof value.metadata?.total === "number");
|
|
356
|
+
}
|
|
357
|
+
function isRetryEligible(method, idempotencyKey) {
|
|
358
|
+
if (method === "GET" || method === "HEAD") {
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
if ((method === "POST" || method === "PUT") && Boolean(idempotencyKey)) {
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
async function parseBody(response) {
|
|
367
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
368
|
+
if (contentType.includes("application/json")) {
|
|
369
|
+
try {
|
|
370
|
+
return await response.json();
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
const text = await response.text();
|
|
377
|
+
if (!text) {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
return JSON.parse(text);
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
return text;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function parseRetryAfterMs(retryAfterValue) {
|
|
388
|
+
if (!retryAfterValue) {
|
|
389
|
+
return undefined;
|
|
390
|
+
}
|
|
391
|
+
const numeric = Number(retryAfterValue);
|
|
392
|
+
if (Number.isFinite(numeric)) {
|
|
393
|
+
return Math.max(0, numeric * 1_000);
|
|
394
|
+
}
|
|
395
|
+
const parsedDate = Date.parse(retryAfterValue);
|
|
396
|
+
if (!Number.isNaN(parsedDate)) {
|
|
397
|
+
return Math.max(0, parsedDate - Date.now());
|
|
398
|
+
}
|
|
399
|
+
return undefined;
|
|
400
|
+
}
|
|
401
|
+
function computeRetryDelayMs(attempt, retryAfterValue, baseDelayMs, maxDelayMs) {
|
|
402
|
+
const retryAfterMs = parseRetryAfterMs(retryAfterValue);
|
|
403
|
+
if (typeof retryAfterMs === "number") {
|
|
404
|
+
return Math.min(retryAfterMs, maxDelayMs);
|
|
405
|
+
}
|
|
406
|
+
const backoff = baseDelayMs * 2 ** attempt;
|
|
407
|
+
const jitter = Math.floor(Math.random() * baseDelayMs);
|
|
408
|
+
return Math.min(backoff + jitter, maxDelayMs);
|
|
409
|
+
}
|
|
410
|
+
function sleep(ms) {
|
|
411
|
+
if (ms <= 0) {
|
|
412
|
+
return Promise.resolve();
|
|
413
|
+
}
|
|
414
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
415
|
+
}
|
|
416
|
+
function isAbortError(error) {
|
|
417
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
if (!error || typeof error !== "object") {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
return error.name === "AbortError";
|
|
424
|
+
}
|
|
425
|
+
function encodeBase64(input) {
|
|
426
|
+
if (typeof btoa === "function") {
|
|
427
|
+
return btoa(input);
|
|
428
|
+
}
|
|
429
|
+
const bytes = new TextEncoder().encode(input);
|
|
430
|
+
let binary = "";
|
|
431
|
+
for (const byte of bytes) {
|
|
432
|
+
binary += String.fromCharCode(byte);
|
|
433
|
+
}
|
|
434
|
+
if (typeof btoa !== "function") {
|
|
435
|
+
throw new Error("Base64 encoding is unavailable in this runtime.");
|
|
436
|
+
}
|
|
437
|
+
return btoa(binary);
|
|
438
|
+
}
|
|
439
|
+
function detectRuntime() {
|
|
440
|
+
if (typeof Deno !== "undefined") {
|
|
441
|
+
return "deno";
|
|
442
|
+
}
|
|
443
|
+
if (typeof Bun !== "undefined") {
|
|
444
|
+
return "bun";
|
|
445
|
+
}
|
|
446
|
+
return "node";
|
|
447
|
+
}
|
|
448
|
+
//# sourceMappingURL=client.js.map
|