@aluvia/sdk 1.0.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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +423 -0
  3. package/dist/cjs/api/AluviaApi.js +51 -0
  4. package/dist/cjs/api/account.js +155 -0
  5. package/dist/cjs/api/geos.js +76 -0
  6. package/dist/cjs/api/request.js +84 -0
  7. package/dist/cjs/api/types.js +2 -0
  8. package/dist/cjs/client/AluviaClient.js +325 -0
  9. package/dist/cjs/client/ConfigManager.js +303 -0
  10. package/dist/cjs/client/ProxyServer.js +182 -0
  11. package/dist/cjs/client/adapters.js +49 -0
  12. package/dist/cjs/client/logger.js +52 -0
  13. package/dist/cjs/client/rules.js +128 -0
  14. package/dist/cjs/client/types.js +3 -0
  15. package/dist/cjs/errors.js +49 -0
  16. package/dist/cjs/index.js +16 -0
  17. package/dist/cjs/package.json +1 -0
  18. package/dist/esm/api/AluviaApi.js +47 -0
  19. package/dist/esm/api/account.js +152 -0
  20. package/dist/esm/api/geos.js +73 -0
  21. package/dist/esm/api/request.js +81 -0
  22. package/dist/esm/api/types.js +1 -0
  23. package/dist/esm/client/AluviaClient.js +321 -0
  24. package/dist/esm/client/ConfigManager.js +299 -0
  25. package/dist/esm/client/ProxyServer.js +178 -0
  26. package/dist/esm/client/adapters.js +39 -0
  27. package/dist/esm/client/logger.js +48 -0
  28. package/dist/esm/client/rules.js +124 -0
  29. package/dist/esm/client/types.js +2 -0
  30. package/dist/esm/errors.js +42 -0
  31. package/dist/esm/index.js +7 -0
  32. package/dist/types/api/AluviaApi.d.ts +29 -0
  33. package/dist/types/api/account.d.ts +41 -0
  34. package/dist/types/api/geos.d.ts +5 -0
  35. package/dist/types/api/request.d.ts +20 -0
  36. package/dist/types/api/types.d.ts +30 -0
  37. package/dist/types/client/AluviaClient.d.ts +50 -0
  38. package/dist/types/client/ConfigManager.d.ts +100 -0
  39. package/dist/types/client/ProxyServer.d.ts +47 -0
  40. package/dist/types/client/adapters.d.ts +26 -0
  41. package/dist/types/client/logger.d.ts +33 -0
  42. package/dist/types/client/rules.d.ts +34 -0
  43. package/dist/types/client/types.d.ts +194 -0
  44. package/dist/types/errors.d.ts +25 -0
  45. package/dist/types/index.d.ts +5 -0
  46. package/package.json +65 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Aluvia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,423 @@
1
+ # Aluvia Node.js SDK
2
+
3
+ [![npm](https://img.shields.io/npm/v/@aluvia/sdk.svg)](https://www.npmjs.com/package/@aluvia/sdk)
4
+ [![downloads](https://img.shields.io/npm/dm/@aluvia/sdk.svg)](https://www.npmjs.com/package/@aluvia/sdk)
5
+ [![license](https://img.shields.io/npm/l/@aluvia/sdk.svg)](./LICENSE)
6
+ [![node](https://img.shields.io/node/v/@aluvia/sdk.svg)](./package.json)
7
+
8
+ ## Introduction
9
+
10
+ AI agents require reliable web access, yet they often encounter 403 blocks, CAPTCHAs, and rate limits. Real humans don't live in datacenters, so websites often treat agent coming from datacenter/cloud IPs as suspicious.
11
+
12
+ **Aluvia solves this problem** by connecting agents to the web through premium mobile IPs on US carrier networks. Unlike datacenter IPs, these reputable IPs are used by real humans, and they don’t get blocked by websites.
13
+
14
+ **This Node.js SDK** makes it simple to integrate Aluvia into your agent workflow. There are two key components:
15
+ 1. `AluviaClient` - a local client for connecting to Aluvia.
16
+ 2. `AluviaApi` - a lightweight JavaScript/TypeScript wrapper for the Aluvia REST API.
17
+
18
+ ---
19
+
20
+ ## Aluvia client
21
+
22
+ The Aluvia client runs a local rules-based proxy server on your agent's host, handles authentication and connection management, and provides ready-to-use adapters for popular tools like Playwright, Puppeteer, and Axios.
23
+
24
+ Simply point your automation tool at the local proxy address (`127.0.0.1`) and the client handles the rest. For each request, the client checks the destination hostname against user-defined (or agent-defined) routing rules and decides whether to send it through Aluvia's mobile IPs or direct to the destination.
25
+
26
+ ```
27
+ ┌──────────────────┐ ┌──────────────────────────┐ ┌──────────────────────┐
28
+ │ │ │ │ │ │
29
+ │ Your Agent │─────▶ Aluvia Client ─────▶ gateway.aluvia.io │
30
+ │ │ │ 127.0.0.1:port │ │ (Mobile IPs) │
31
+ │ │ │ │ │ │
32
+ └──────────────────┘ │ Per-request routing: │ └──────────────────────┘
33
+ │ │
34
+ │ not-blocked.com ──────────────▶ Direct
35
+ │ blocked-site.com ─────────────▶ Via Aluvia
36
+ │ │
37
+ └──────────────────────────┘
38
+ ```
39
+
40
+
41
+ **Benefits:**
42
+ - **Avoid blocks:** Websites flag datacenter IPs as bot traffic, leading to 403s, CAPTCHAs, and rate limits. Mobile IPs appear as real users, so requests go through.
43
+ - **Reduce costs and latency:** Hostname-based routing rules let you proxy only the sites that need it. Traffic to non-blocked sites goes direct, saving money and reducing latency.
44
+ - **Unblock without restarts:** Rules update at runtime. When a site blocks your agent, add it to the proxy rules and retry—no need to restart workers or redeploy.
45
+ - **Simplify integration:** One SDK with ready-to-use adapters for Playwright, Puppeteer, Selenium, Axios, got, and Node's fetch.
46
+
47
+
48
+ ---
49
+
50
+ ## Quick start
51
+
52
+ ### Understand the basics
53
+ * [What is Aluvia?](https://docs.aluvia.io/)
54
+ * [Understanding connections](https://docs.aluvia.io/fundamentals/connections)
55
+
56
+ ### Get Aluvia API key
57
+
58
+ 1. Create an account at [dashboard.aluvia.io](https://dashboard.aluvia.io)
59
+ 2. Go to **API and SDKs** and get your **API Key**
60
+
61
+ ### Install the SDK
62
+
63
+ ```bash
64
+ npm install @aluvia/sdk
65
+ ```
66
+
67
+ **Requirements:** Node.js 18 or later
68
+
69
+ ### Example: Dynamic unblocking with Playwright
70
+
71
+ This example shows how an agent can use the Aluvia client to dynamically unblock websites. It demonstrates starting the client, using the Playwright integration adapter, configuring geo targeting and session ID, detecting blocks, and updating routing rules on the fly.
72
+
73
+ ```ts
74
+ import { chromium } from 'playwright';
75
+ import { AluviaClient } from '@aluvia/sdk';
76
+
77
+ // Initialize the Aluvia client with your API key
78
+ const client = new AluviaClient({ apiKey: process.env.ALUVIA_API_KEY! });
79
+
80
+ // Start the client (launches local proxy, fetches connection config)
81
+ const connection = await client.start();
82
+
83
+ // Configure geo targeting (use California IPs)
84
+ await client.updateTargetGeo('us_ca');
85
+
86
+ // Set session ID (requests with the same session ID use the same IP)
87
+ await client.updateSessionId('agentsession1');
88
+
89
+ // Launch browser using the Playwright integration adapter
90
+ // The adapter returns proxy settings in Playwright's expected format
91
+ const browser = await chromium.launch({ proxy: connection.asPlaywright() });
92
+
93
+ // Track hostnames we've added to proxy rules
94
+ const proxiedHosts = new Set<string>();
95
+
96
+ async function visitWithRetry(url: string): Promise<string> {
97
+ const page = await browser.newPage();
98
+
99
+ try {
100
+ const response = await page.goto(url, { waitUntil: 'domcontentloaded' });
101
+ const hostname = new URL(url).hostname;
102
+
103
+ // Detect if the site blocked us (403, 429, or challenge page)
104
+ const status = response?.status() ?? 0;
105
+ const isBlocked =
106
+ status === 403 ||
107
+ status === 429 ||
108
+ (await page.title()).toLowerCase().includes('blocked');
109
+
110
+ if (isBlocked && !proxiedHosts.has(hostname)) {
111
+ console.log(`Blocked by ${hostname} — adding to proxy rules`);
112
+
113
+ // Update routing rules to proxy this hostname through Aluvia
114
+ // Rules update at runtime—no need to restart the browser
115
+ proxiedHosts.add(hostname);
116
+ await client.updateRules([...proxiedHosts]);
117
+
118
+ // Rotate to a fresh IP by changing the session ID
119
+ await client.updateSessionId(`retry-${Date.now()}`);
120
+
121
+ await page.close();
122
+ return visitWithRetry(url);
123
+ }
124
+
125
+ return await page.content();
126
+ } finally {
127
+ await page.close();
128
+ }
129
+ }
130
+
131
+ try {
132
+ // First attempt goes direct; if blocked, retries through Aluvia
133
+ const html = await visitWithRetry('https://example.com/data');
134
+ console.log('Success:', html.slice(0, 200));
135
+ } finally {
136
+ // Always close the browser and connection when done
137
+ await browser.close();
138
+ await connection.close();
139
+ }
140
+ ```
141
+
142
+ ### Integration guides
143
+
144
+ The Aluvia client provides ready-to-use adapters for popular automation and HTTP tools:
145
+
146
+ - [Playwright](docs/integrations/integration-playwright.md)
147
+ - [Puppeteer](docs/integrations/integration-puppeteer.md)
148
+ - [Selenium](docs/integrations/integration-selenium.md)
149
+ - [Axios](docs/integrations/integration-axios.md)
150
+ - [got](docs/integrations/integration-got.md)
151
+ - [fetch (Node 18+)](docs/integrations/integration-fetch.md)
152
+
153
+ ---
154
+
155
+
156
+
157
+ ## Architecture
158
+
159
+ The client is split into two independent **planes**:
160
+
161
+ ```
162
+ ┌─────────────────────────────────────────────────────────────────┐
163
+ │ AluviaClient │
164
+ ├─────────────────────────────┬───────────────────────────────────┤
165
+ │ Control Plane │ Data Plane │
166
+ │ (ConfigManager) │ (ProxyServer) │
167
+ ├─────────────────────────────┼───────────────────────────────────┤
168
+ │ • Fetches/creates config │ • Local HTTP proxy (proxy-chain) │
169
+ │ • Polls for updates (ETag) │ • Per-request routing decisions │
170
+ │ • PATCH updates (rules, │ • Uses rules engine to decide: │
171
+ │ session, geo) │ direct vs gateway │
172
+ └─────────────────────────────┴───────────────────────────────────┘
173
+ ```
174
+
175
+ ### Control Plane (ConfigManager)
176
+
177
+ - Communicates with the Aluvia REST API (`/account/connections/...`)
178
+ - Fetches proxy credentials and routing rules
179
+ - Polls for configuration updates
180
+ - Pushes updates (rules, session ID, geo)
181
+
182
+ ### Data Plane (ProxyServer)
183
+
184
+ - Runs a local HTTP proxy on `127.0.0.1`
185
+ - For each request, uses the **rules engine** to decide whether to route direct or via Aluvia.
186
+ - Because the proxy reads the latest config per-request, rule updates take effect immediately
187
+
188
+ ---
189
+
190
+ ## Operating modes
191
+
192
+ The Aluvia client has two operating modes: **Client Proxy Mode** (default) and **Gateway Mode**.
193
+
194
+ ### Client Proxy Mode
195
+
196
+ **How it works:** The SDK runs a local proxy on `127.0.0.1`. For each request, it checks your routing rules and sends traffic either direct or through Aluvia.
197
+
198
+ **Why use it:**
199
+ - Selective routing reduces cost and latency (only proxy what you need)
200
+ - Credentials stay inside the SDK (nothing secret in your config)
201
+ - Rule changes apply immediately (no restarts)
202
+
203
+ **Best for:** Using per-hostname routing rules.
204
+
205
+ ### Gateway Mode
206
+
207
+ Set `localProxy: false` to enable.
208
+
209
+ **How it works:** No local proxy. Your tools connect directly to `gateway.aluvia.io` and **ALL** traffic goes through Aluvia.
210
+
211
+ **Why use it:**
212
+ - No local process to manage
213
+ - Simpler setup for tools with native proxy auth support
214
+
215
+ **Best for:** When you want all traffic proxied without selective routing.
216
+
217
+ ---
218
+
219
+ ## Using Aluvia client
220
+
221
+ ### 1. Create a client
222
+
223
+ ```ts
224
+ const client = new AluviaClient({
225
+ apiKey: process.env.ALUVIA_API_KEY!,
226
+ connectionId: 123, // Optional: reuse an existing connection
227
+ localProxy: true, // Optional: default true (recommended)
228
+ });
229
+ ```
230
+
231
+ For all options, see the [Client Technical Guide](docs/client-technical-guide.md#constructor-options).
232
+
233
+ ### 2. Start the client and get a connection
234
+
235
+ ```ts
236
+ const connection = await client.start();
237
+ ```
238
+
239
+ This starts the local proxy and returns a connection object you'll use with your tools.
240
+ [Understanding the connection object](https://docs.aluvia.io/fundamentals/connections)
241
+
242
+ ### 3. Use the connection with your tools
243
+
244
+ Pass the connection to your automation tool using the appropriate adapter:
245
+
246
+ ```ts
247
+ const browser = await chromium.launch({ proxy: connection.asPlaywright() });
248
+ ```
249
+
250
+ ### 4. Update routing as necessary
251
+
252
+ While your agent is running, you can update routing rules, rotate IPs, or change geo targeting—no restart needed:
253
+
254
+ ```ts
255
+ await client.updateRules(['blocked-site.com']); // Add hostname to proxy rules
256
+ await client.updateSessionId('newsession'); // Rotate to a new IP
257
+ await client.updateTargetGeo('us_ca'); // Target California IPs
258
+ ```
259
+
260
+ ### 5. Clean up when done
261
+
262
+ ```ts
263
+ await connection.close(); // Stops proxy, polling, and releases resources
264
+ ```
265
+
266
+ ---
267
+
268
+ ## Routing rules
269
+
270
+ The Aluvia Client starts a local proxy server that routes each request based on hostname rules that you (or our agent) set. **Rules can be updated at runtime without restarting the agent.**
271
+
272
+ Traffic can be sent either:
273
+ * direct (using the agent's datacenter/cloud IP) or,
274
+ * through Aluvia's mobile proxy IPs,
275
+
276
+ ### Benefits
277
+
278
+ * Selectively routing traffic to mobile proxies reduces proxy costs and connection latency.
279
+ * Rules can be updated during runtime, allowing agents to work around website blocks on the fly.
280
+
281
+ ### Example rules
282
+
283
+ ```ts
284
+ await client.updateRules(['*']); // Proxy all traffic
285
+ await client.updateRules(['target-site.com', '*.google.com']); // Proxy specific hosts
286
+ await client.updateRules(['*', '-api.stripe.com']); // Proxy all except specified
287
+ await client.updateRules([]); // Route all traffic direct
288
+ ```
289
+
290
+ ### Supported routing rule patterns:
291
+
292
+ | Pattern | Matches |
293
+ |---------|---------|
294
+ | `*` | All hostnames |
295
+ | `example.com` | Exact match |
296
+ | `*.example.com` | Subdomains of example.com |
297
+ | `google.*` | google.com, google.co.uk, and similar |
298
+ | `-example.com` | Exclude from proxying |
299
+
300
+ ---
301
+
302
+ ## Dynamic unblocking
303
+
304
+ Most proxy solutions require you to decide upfront which sites to proxy. If a site blocks you later, you're stuck—restart your workers, redeploy your fleet, or lose the workflow.
305
+
306
+ **With Aluvia, your agent can unblock itself.** When a request fails with a 403 or 429, your agent adds that hostname to its routing rules and retries. The update takes effect immediately—no restart, no redeployment, no lost state.
307
+
308
+ This turns blocking from a workflow-ending failure into a minor speed bump.
309
+
310
+ ```ts
311
+ const response = await page.goto(url);
312
+
313
+ if (response?.status() === 403) {
314
+ // Blocked! Add this hostname to proxy rules and retry
315
+ await client.updateRules([...currentRules, new URL(url).hostname]);
316
+ await page.goto(url); // This request goes through Aluvia
317
+ }
318
+ ```
319
+
320
+ Your agent learns which sites need proxying as it runs. Sites that don't block you stay direct (faster, cheaper). Sites that do block you get routed through mobile IPs automatically.
321
+
322
+ ---
323
+
324
+ ## Tool integration adapters
325
+
326
+ Every tool has its own way of configuring proxies—Playwright wants { server, username, password }, Puppeteer wants CLI args, Axios wants agents, and Node's native fetch doesn't support proxies at all. The SDK handles all of this for you:
327
+
328
+ | Tool | Method | Returns |
329
+ |------|--------|---------|
330
+ | Playwright | `connection.asPlaywright()` | `{ server, username?, password? }` |
331
+ | Puppeteer | `connection.asPuppeteer()` | `['--proxy-server=...']` |
332
+ | Selenium | `connection.asSelenium()` | `'--proxy-server=...'` |
333
+ | Axios | `connection.asAxiosConfig()` | `{ proxy: false, httpAgent, httpsAgent }` |
334
+ | got | `connection.asGotOptions()` | `{ agent: { http, https } }` |
335
+ | fetch | `connection.asUndiciFetch()` | Proxy-enabled `fetch` function |
336
+ | Node.js http | `connection.asNodeAgents()` | `{ http: Agent, https: Agent }` |
337
+
338
+ For more details regarding integration adapters, see the [Client Technical Guide](docs/client-technical-guide.md#tool-adapters).
339
+
340
+
341
+
342
+ ---
343
+
344
+ ## Aluvia API
345
+
346
+ `AluviaApi` is a typed wrapper for the Aluvia REST API. Use it to manage connections, query account info, or build custom tooling—without starting a proxy.
347
+
348
+ `AluviaApi` is built from modular layers:
349
+
350
+ ```
351
+ ┌───────────────────────────────────────────────────────────────┐
352
+ │ AluviaApi │
353
+ │ Constructor validates apiKey, creates namespace objects │
354
+ ├───────────────────────────────────────────────────────────────┤
355
+ │ │
356
+ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
357
+ │ │ account │ │ geos │ │ request │ │
358
+ │ │ namespace │ │ namespace │ │ (escape │ │
359
+ │ │ │ │ │ │ hatch) │ │
360
+ │ └─────────────┘ └─────────────┘ └─────────────┘ │
361
+ │ │ │ │ │
362
+ │ ▼ ▼ ▼ │
363
+ │ ┌────────────────────────────────────────────────────┐ │
364
+ │ │ requestAndUnwrap / ctx.request │ │
365
+ │ │ (envelope unwrapping, error throwing) │ │
366
+ │ └────────────────────────────────────────────────────┘ │
367
+ │ │ │
368
+ │ ▼ │
369
+ │ ┌────────────────────────────────────────────────────┐ │
370
+ │ │ requestCore │ │
371
+ │ │ (URL building, headers, timeout, JSON parsing) │ │
372
+ │ └────────────────────────────────────────────────────┘ │
373
+ │ │ │
374
+ │ ▼ │
375
+ │ globalThis.fetch │
376
+ └───────────────────────────────────────────────────────────────┘
377
+ ```
378
+
379
+ ### What you can do
380
+
381
+ | Endpoint | Description |
382
+ |----------|-------------|
383
+ | `api.account.get()` | Get account info (balance, usage) |
384
+ | `api.account.connections.list()` | List all connections |
385
+ | `api.account.connections.create()` | Create a new connection |
386
+ | `api.account.connections.get(id)` | Get connection details |
387
+ | `api.account.connections.patch(id)` | Update connection (rules, geo, session) |
388
+ | `api.account.connections.delete(id)` | Delete a connection |
389
+ | `api.geos.list()` | List available geo-targeting options |
390
+
391
+ ### Example
392
+
393
+ ```ts
394
+ import { AluviaApi } from '@aluvia/sdk';
395
+
396
+ const api = new AluviaApi({ apiKey: process.env.ALUVIA_API_KEY! });
397
+
398
+ // Check account balance
399
+ const account = await api.account.get();
400
+ console.log('Balance:', account.balance_gb, 'GB');
401
+
402
+ // Create a connection for a new agent
403
+ const connection = await api.account.connections.create({
404
+ description: 'pricing-scraper',
405
+ rules: ['competitor-site.com'],
406
+ target_geo: 'us_ca',
407
+ });
408
+ console.log('Created:', connection.connection_id);
409
+
410
+ // List available geos
411
+ const geos = await api.geos.list();
412
+ console.log('Geos:', geos.map(g => g.code));
413
+ ```
414
+
415
+ **Tip:** `AluviaApi` is also available as `client.api` when using `AluviaClient`.
416
+
417
+ For the complete API reference, see [API Technical Guide](docs/api-technical-guide.md).
418
+
419
+ ---
420
+
421
+ ## License
422
+
423
+ MIT — see [LICENSE](./LICENSE)
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AluviaApi = void 0;
4
+ const request_js_1 = require("./request.js");
5
+ const account_js_1 = require("./account.js");
6
+ const geos_js_1 = require("./geos.js");
7
+ const errors_js_1 = require("../errors.js");
8
+ class AluviaApi {
9
+ constructor(options) {
10
+ const apiKey = String(options.apiKey ?? '').trim();
11
+ if (!apiKey) {
12
+ throw new errors_js_1.MissingApiKeyError('Aluvia apiKey is required');
13
+ }
14
+ this.apiKey = apiKey;
15
+ this.apiBaseUrl = options.apiBaseUrl ?? 'https://api.aluvia.io/v1';
16
+ this.timeoutMs = options.timeoutMs;
17
+ this.fetchImpl = options.fetch;
18
+ const ctx = {
19
+ request: async (args) => {
20
+ return await (0, request_js_1.requestCore)({
21
+ apiBaseUrl: this.apiBaseUrl,
22
+ apiKey: this.apiKey,
23
+ method: args.method,
24
+ path: args.path,
25
+ query: args.query,
26
+ body: args.body,
27
+ headers: args.headers,
28
+ ifNoneMatch: args.etag,
29
+ timeoutMs: this.timeoutMs,
30
+ fetch: this.fetchImpl,
31
+ });
32
+ },
33
+ };
34
+ this.account = (0, account_js_1.createAccountApi)(ctx);
35
+ this.geos = (0, geos_js_1.createGeosApi)(ctx);
36
+ }
37
+ async request(args) {
38
+ return await (0, request_js_1.requestCore)({
39
+ apiBaseUrl: this.apiBaseUrl,
40
+ apiKey: this.apiKey,
41
+ method: args.method,
42
+ path: args.path,
43
+ query: args.query,
44
+ body: args.body,
45
+ headers: args.headers,
46
+ timeoutMs: this.timeoutMs,
47
+ fetch: this.fetchImpl,
48
+ });
49
+ }
50
+ }
51
+ exports.AluviaApi = AluviaApi;
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAccountApi = createAccountApi;
4
+ const errors_js_1 = require("../errors.js");
5
+ function isRecord(value) {
6
+ return typeof value === 'object' && value !== null;
7
+ }
8
+ function asErrorEnvelope(value) {
9
+ if (!isRecord(value))
10
+ return null;
11
+ if (value['success'] !== false)
12
+ return null;
13
+ const error = value['error'];
14
+ if (!isRecord(error))
15
+ return null;
16
+ const code = error['code'];
17
+ const message = error['message'];
18
+ if (typeof code !== 'string' || typeof message !== 'string')
19
+ return null;
20
+ return { success: false, error: { code, message, details: error['details'] } };
21
+ }
22
+ function unwrapSuccess(value) {
23
+ if (!isRecord(value))
24
+ return null;
25
+ if (value['success'] === true && 'data' in value) {
26
+ return value.data;
27
+ }
28
+ if ('data' in value) {
29
+ return value.data;
30
+ }
31
+ return null;
32
+ }
33
+ function formatErrorDetails(details) {
34
+ if (details == null)
35
+ return '';
36
+ try {
37
+ const json = JSON.stringify(details);
38
+ if (!json)
39
+ return '';
40
+ return json.length > 500 ? `${json.slice(0, 500)}…` : json;
41
+ }
42
+ catch {
43
+ return String(details);
44
+ }
45
+ }
46
+ function throwForNon2xx(result) {
47
+ const status = result.status;
48
+ if (status === 401 || status === 403) {
49
+ throw new errors_js_1.InvalidApiKeyError(`Authentication failed (HTTP ${status}). Check token validity and that you are using an account API token for account endpoints.`);
50
+ }
51
+ const maybeError = asErrorEnvelope(result.body);
52
+ if (maybeError) {
53
+ const details = formatErrorDetails(maybeError.error.details);
54
+ const detailsSuffix = details ? ` details=${details}` : '';
55
+ throw new errors_js_1.ApiError(`API request failed (HTTP ${status}) code=${maybeError.error.code} message=${maybeError.error.message}${detailsSuffix}`, status);
56
+ }
57
+ throw new errors_js_1.ApiError(`API request failed (HTTP ${status})`, status);
58
+ }
59
+ async function requestAndUnwrap(ctx, args) {
60
+ const result = await ctx.request(args);
61
+ if (result.status < 200 || result.status >= 300) {
62
+ throwForNon2xx(result);
63
+ }
64
+ const data = unwrapSuccess(result.body);
65
+ if (data == null) {
66
+ throw new errors_js_1.ApiError('API response missing expected success envelope data', result.status);
67
+ }
68
+ return { data, etag: result.etag };
69
+ }
70
+ function createAccountApi(ctx) {
71
+ return {
72
+ get: async () => {
73
+ const { data } = await requestAndUnwrap(ctx, {
74
+ method: 'GET',
75
+ path: '/account',
76
+ });
77
+ return data;
78
+ },
79
+ usage: {
80
+ get: async (params = {}) => {
81
+ const { data } = await requestAndUnwrap(ctx, {
82
+ method: 'GET',
83
+ path: '/account/usage',
84
+ query: {
85
+ start: params.start,
86
+ end: params.end,
87
+ },
88
+ });
89
+ return data;
90
+ },
91
+ },
92
+ payments: {
93
+ list: async (params = {}) => {
94
+ const { data } = await requestAndUnwrap(ctx, {
95
+ method: 'GET',
96
+ path: '/account/payments',
97
+ query: {
98
+ start: params.start,
99
+ end: params.end,
100
+ },
101
+ });
102
+ return data;
103
+ },
104
+ },
105
+ connections: {
106
+ list: async () => {
107
+ const { data } = await requestAndUnwrap(ctx, {
108
+ method: 'GET',
109
+ path: '/account/connections',
110
+ });
111
+ return data;
112
+ },
113
+ create: async (body) => {
114
+ const { data } = await requestAndUnwrap(ctx, {
115
+ method: 'POST',
116
+ path: '/account/connections',
117
+ body,
118
+ });
119
+ return data;
120
+ },
121
+ get: async (connectionId, options = {}) => {
122
+ const result = await ctx.request({
123
+ method: 'GET',
124
+ path: `/account/connections/${String(connectionId)}`,
125
+ etag: options.etag ?? null,
126
+ });
127
+ if (result.status === 304)
128
+ return null;
129
+ if (result.status < 200 || result.status >= 300) {
130
+ throwForNon2xx(result);
131
+ }
132
+ const data = unwrapSuccess(result.body);
133
+ if (data == null) {
134
+ throw new errors_js_1.ApiError('API response missing expected success envelope data', result.status);
135
+ }
136
+ return data;
137
+ },
138
+ patch: async (connectionId, body) => {
139
+ const { data } = await requestAndUnwrap(ctx, {
140
+ method: 'PATCH',
141
+ path: `/account/connections/${String(connectionId)}`,
142
+ body,
143
+ });
144
+ return data;
145
+ },
146
+ delete: async (connectionId) => {
147
+ const { data } = await requestAndUnwrap(ctx, {
148
+ method: 'DELETE',
149
+ path: `/account/connections/${String(connectionId)}`,
150
+ });
151
+ return data;
152
+ },
153
+ },
154
+ };
155
+ }