@alter-ai/alter-sdk 0.3.1 → 0.5.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/README.md +163 -33
- package/dist/index.cjs +767 -92
- package/dist/index.d.cts +298 -24
- package/dist/index.d.ts +298 -24
- package/dist/index.js +744 -88
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Alter SDK for TypeScript / Node.js
|
|
2
2
|
|
|
3
|
-
Official TypeScript SDK for [Alter Vault](https://alterai.dev)
|
|
3
|
+
Official TypeScript SDK for [Alter Vault](https://alterai.dev) -Credential management for agents with policy enforcement.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **Zero Token Exposure**: Tokens are never exposed to developers
|
|
7
|
+
- **Zero Token Exposure**: Tokens are never exposed to developers -injected automatically
|
|
8
8
|
- **Single Entry Point**: One method (`vault.request()`) for all provider APIs
|
|
9
9
|
- **Type-Safe Enums**: `HttpMethod` enums with autocomplete
|
|
10
10
|
- **URL Templating**: Path parameter substitution with automatic URL encoding
|
|
@@ -14,7 +14,7 @@ Official TypeScript SDK for [Alter Vault](https://alterai.dev) — Credential ma
|
|
|
14
14
|
- **API Key and Custom Credential Support**: Handles OAuth tokens, API keys, and custom credential formats automatically
|
|
15
15
|
- **Actor Tracking**: First-class support for AI agent and MCP server observability
|
|
16
16
|
- **HMAC Request Signing**: All SDK-to-backend requests are signed with a derived HMAC-SHA256 key for integrity, authenticity, and replay protection
|
|
17
|
-
- **Native Promises**: Built on native `fetch`
|
|
17
|
+
- **Native Promises**: Built on native `fetch` -no heavy dependencies
|
|
18
18
|
|
|
19
19
|
## Installation
|
|
20
20
|
|
|
@@ -33,7 +33,7 @@ const vault = new AlterVault({
|
|
|
33
33
|
actorIdentifier: "my-agent",
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
// Make API request
|
|
36
|
+
// Make API request -token injected automatically, never exposed
|
|
37
37
|
const response = await vault.request(
|
|
38
38
|
"CONNECTION_ID", // from Alter Connect (see below)
|
|
39
39
|
HttpMethod.GET,
|
|
@@ -51,16 +51,16 @@ await vault.close();
|
|
|
51
51
|
|
|
52
52
|
### Where does `connectionId` come from?
|
|
53
53
|
|
|
54
|
-
**OAuth connections
|
|
55
|
-
1.
|
|
56
|
-
2. The `onSuccess` callback returns a `connectionId` (UUID)
|
|
54
|
+
**OAuth connections** (per-user, from end user action):
|
|
55
|
+
1. Your **end user** completes OAuth via [Alter Connect](https://docs.alterai.dev/sdks/javascript/quickstart) (frontend widget) or `vault.connect()` (headless)
|
|
56
|
+
2. The `onSuccess` callback returns a `connectionId` (UUID) -one per user per account
|
|
57
57
|
3. You save it in your database, mapped to your user
|
|
58
58
|
4. You pass it to `vault.request()` when making API calls
|
|
59
59
|
|
|
60
|
-
**Managed secrets** (
|
|
61
|
-
1.
|
|
62
|
-
2.
|
|
63
|
-
3. Use the same `vault.request()`
|
|
60
|
+
**Managed secrets** (per-service, from developer action):
|
|
61
|
+
1. **You** store credentials in the [Developer Portal](https://portal.alterai.dev) under **Managed Secrets**
|
|
62
|
+
2. The portal returns a `connectionId` -one per stored credential, shared across your backend
|
|
63
|
+
3. Use the same `vault.request()` -credentials are injected automatically
|
|
64
64
|
|
|
65
65
|
```typescript
|
|
66
66
|
// You can also discover connectionIds programmatically:
|
|
@@ -184,7 +184,6 @@ Generate a session URL for end-users to authenticate with OAuth providers:
|
|
|
184
184
|
|
|
185
185
|
```typescript
|
|
186
186
|
const session = await vault.createConnectSession({
|
|
187
|
-
endUser: { id: "alice" },
|
|
188
187
|
allowedProviders: ["google", "github"],
|
|
189
188
|
returnUrl: "https://myapp.com/callback",
|
|
190
189
|
});
|
|
@@ -194,12 +193,50 @@ console.log(`Expires in: ${session.expiresIn}s`);
|
|
|
194
193
|
|
|
195
194
|
| Parameter | Type | Default | Description |
|
|
196
195
|
|-----------|------|---------|-------------|
|
|
197
|
-
| `endUser` | `{ id: string }` | *required* | End user identity |
|
|
198
196
|
| `allowedProviders` | `string[]` | - | Restrict to specific providers |
|
|
199
197
|
| `returnUrl` | `string` | - | Redirect URL after OAuth flow |
|
|
200
198
|
|
|
201
199
|
Returns `ConnectSession` with: `sessionToken`, `connectUrl`, `expiresIn`, `expiresAt`.
|
|
202
200
|
|
|
201
|
+
#### Headless Connect (from code)
|
|
202
|
+
|
|
203
|
+
For CLI tools, scripts, and server-side applications -- opens the browser, waits for the user to complete OAuth, and returns the result:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
const results = await vault.connect({
|
|
207
|
+
providers: ["google"],
|
|
208
|
+
connectionPolicy: { // optional TTL bounds
|
|
209
|
+
maxTtlSeconds: 86400,
|
|
210
|
+
defaultTtlSeconds: 3600,
|
|
211
|
+
},
|
|
212
|
+
timeout: 300, // max wait in seconds (default: 5 min)
|
|
213
|
+
openBrowser: true, // set false to print URL instead
|
|
214
|
+
});
|
|
215
|
+
for (const result of results) {
|
|
216
|
+
console.log(`Connected: ${result.connectionId} (${result.providerId})`);
|
|
217
|
+
console.log(`Account: ${result.accountIdentifier}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Now use the connectionId with vault.request()
|
|
221
|
+
const response = await vault.request(
|
|
222
|
+
results[0].connectionId,
|
|
223
|
+
HttpMethod.GET,
|
|
224
|
+
"https://www.googleapis.com/calendar/v3/calendars/primary/events",
|
|
225
|
+
);
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
| Parameter | Type | Default | Description |
|
|
229
|
+
|-----------|------|---------|-------------|
|
|
230
|
+
| `providers` | `string[]` | - | Restrict to specific providers |
|
|
231
|
+
| `timeout` | `number` | `300` | Max seconds to wait for completion |
|
|
232
|
+
| `pollInterval` | `number` | `2` | Seconds between status checks |
|
|
233
|
+
| `connectionPolicy` | `{ maxTtlSeconds?: number; defaultTtlSeconds?: number }` | - | TTL bounds (optional) |
|
|
234
|
+
| `openBrowser` | `boolean` | `true` | Open browser automatically |
|
|
235
|
+
|
|
236
|
+
Returns `ConnectResult[]` -one per connected provider. Each has: `connectionId`, `providerId`, `accountIdentifier`, `scopes`, and optionally `connectionPolicy` (if a TTL was set).
|
|
237
|
+
|
|
238
|
+
Throws `ConnectTimeoutError` if the user doesn't complete in time, `ConnectDeniedError` if the user denies authorization, `ConnectConfigError` if the OAuth app is misconfigured.
|
|
239
|
+
|
|
203
240
|
### AI Agent Actor Tracking
|
|
204
241
|
|
|
205
242
|
```typescript
|
|
@@ -284,20 +321,49 @@ const vault = new AlterVault({
|
|
|
284
321
|
|
|
285
322
|
## Error Handling
|
|
286
323
|
|
|
324
|
+
The SDK provides a typed exception hierarchy so you can handle each failure mode precisely:
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
AlterSDKError (base)
|
|
328
|
+
├── BackendError // Generic backend error
|
|
329
|
+
│ ├── ReAuthRequiredError // User must re-authorize via Alter Connect
|
|
330
|
+
│ │ ├── ConnectionExpiredError // 403 — connection TTL elapsed
|
|
331
|
+
│ │ ├── ConnectionRevokedError // 400 — auth permanently broken (revoked, invalid_grant)
|
|
332
|
+
│ │ └── ConnectionDeletedError // 410 — user disconnected via Wallet (new ID on re-auth)
|
|
333
|
+
│ ├── ConnectionNotFoundError // 404 — wrong connection_id
|
|
334
|
+
│ └── PolicyViolationError // 403 — policy denied (business hours, IP, etc.)
|
|
335
|
+
├── ConnectFlowError // Headless connect() failed
|
|
336
|
+
│ ├── ConnectDeniedError // User clicked Deny
|
|
337
|
+
│ ├── ConnectConfigError // OAuth app misconfigured
|
|
338
|
+
│ └── ConnectTimeoutError // User didn't complete in time
|
|
339
|
+
├── ProviderAPIError // Provider returned 4xx/5xx
|
|
340
|
+
│ └── ScopeReauthRequiredError // 403 + scope mismatch — user must re-authorize
|
|
341
|
+
└── NetworkError // Backend or provider unreachable
|
|
342
|
+
└── TimeoutError // Request timed out (safe to retry)
|
|
343
|
+
```
|
|
344
|
+
|
|
287
345
|
> **Note:** Input validation errors (invalid `apiKey`, invalid/missing `actorType`, missing `actorIdentifier`, invalid URL scheme, missing `pathParams`) throw `AlterSDKError`.
|
|
288
346
|
|
|
289
347
|
```typescript
|
|
290
348
|
import {
|
|
291
349
|
AlterVault,
|
|
292
350
|
HttpMethod,
|
|
293
|
-
AlterSDKError, // Base error (including validation
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
351
|
+
AlterSDKError, // Base error (including validation errors)
|
|
352
|
+
BackendError, // Generic backend error
|
|
353
|
+
ReAuthRequiredError, // Parent for all re-auth errors
|
|
354
|
+
ConnectionDeletedError, // User disconnected via Wallet — new ID on re-auth (410)
|
|
355
|
+
ConnectionExpiredError, // TTL expired — user must re-authorize (403)
|
|
356
|
+
ConnectionNotFoundError, // Wrong connection_id — check for typos (404)
|
|
357
|
+
ConnectionRevokedError, // Auth permanently broken — revoked/invalid_grant (400)
|
|
358
|
+
PolicyViolationError, // Policy denied — business hours, IP, etc. (403)
|
|
359
|
+
ConnectFlowError, // Headless connect() failed (denied, provider error)
|
|
360
|
+
ConnectDeniedError, // User denied authorization
|
|
361
|
+
ConnectConfigError, // OAuth app misconfigured
|
|
362
|
+
ConnectTimeoutError, // Headless connect() timed out
|
|
363
|
+
NetworkError, // Backend or provider unreachable
|
|
364
|
+
TimeoutError, // Request timed out (subclass of NetworkError)
|
|
365
|
+
ProviderAPIError, // Provider API returned error (4xx/5xx)
|
|
366
|
+
ScopeReauthRequiredError, // 403 + scope mismatch (subclass of ProviderAPIError)
|
|
301
367
|
} from "@alter-ai/alter-sdk";
|
|
302
368
|
|
|
303
369
|
try {
|
|
@@ -307,22 +373,54 @@ try {
|
|
|
307
373
|
"https://www.googleapis.com/calendar/v3/calendars/primary/events",
|
|
308
374
|
);
|
|
309
375
|
} catch (error) {
|
|
310
|
-
|
|
311
|
-
|
|
376
|
+
// --- Connection unusable — user must re-authorize via Alter Connect ---
|
|
377
|
+
if (error instanceof ConnectionExpiredError) {
|
|
378
|
+
// TTL set during Connect flow has elapsed
|
|
379
|
+
console.error("Connection expired — prompt user to re-authorize");
|
|
380
|
+
} else if (error instanceof ConnectionRevokedError) {
|
|
381
|
+
// Auth permanently broken — user revoked at provider, refresh token
|
|
382
|
+
// expired, or token refresh permanently failed (invalid_grant)
|
|
383
|
+
console.error("Connection revoked — prompt user to re-authorize");
|
|
384
|
+
} else if (error instanceof ConnectionDeletedError) {
|
|
385
|
+
// User disconnected via Wallet — re-auth generates a NEW connectionId
|
|
386
|
+
console.error("Connection deleted — prompt user to re-connect, store the new ID");
|
|
312
387
|
} else if (error instanceof ConnectionNotFoundError) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
388
|
+
// No connection with this ID exists — check for typos or stale references
|
|
389
|
+
console.error("Connection not found — verify your connectionId");
|
|
390
|
+
|
|
391
|
+
// --- Policy restrictions — may resolve on its own ---
|
|
392
|
+
} else if (error instanceof PolicyViolationError) {
|
|
393
|
+
// Business hours, IP allowlist, or other Cerbos policy denial
|
|
394
|
+
console.error(`Policy denied: ${error.message} (reason: ${error.policyError})`);
|
|
395
|
+
|
|
396
|
+
// --- Transient / infrastructure errors — safe to retry ---
|
|
318
397
|
} else if (error instanceof NetworkError) {
|
|
319
|
-
|
|
398
|
+
// TimeoutError is a subclass, so this catches both
|
|
399
|
+
console.error(`Network issue — retry with backoff: ${error.message}`);
|
|
400
|
+
|
|
401
|
+
// --- Provider errors ---
|
|
402
|
+
} else if (error instanceof ScopeReauthRequiredError) {
|
|
403
|
+
console.error(`Scope mismatch on ${error.connectionId} - user needs to re-authorize`);
|
|
404
|
+
// Create a new Connect session so the user can grant updated scopes
|
|
320
405
|
} else if (error instanceof ProviderAPIError) {
|
|
321
406
|
console.error(`Provider error ${error.statusCode}: ${error.responseBody}`);
|
|
322
407
|
}
|
|
323
408
|
}
|
|
324
409
|
```
|
|
325
410
|
|
|
411
|
+
### Re-authorization and Connection IDs
|
|
412
|
+
|
|
413
|
+
When a user re-authorizes through Alter Connect, the **same `connectionId` is preserved** in most cases. The existing connection record is updated in place with fresh tokens. You do **not** need to update your stored `connectionId`.
|
|
414
|
+
|
|
415
|
+
The exception is `ConnectionDeletedError` — the user disconnected via the Wallet, so re-authorization creates a new connection with a **new `connectionId`**. Store the new ID from the `ConnectResult`.
|
|
416
|
+
|
|
417
|
+
| Exception | Same `connectionId` after re-auth? |
|
|
418
|
+
|---|---|
|
|
419
|
+
| `ConnectionExpiredError` | Yes |
|
|
420
|
+
| `ConnectionRevokedError` | Yes |
|
|
421
|
+
| `ConnectionDeletedError` | **No** — new ID generated |
|
|
422
|
+
| `ConnectionNotFoundError` | N/A — ID never existed |
|
|
423
|
+
|
|
326
424
|
## Resource Management
|
|
327
425
|
|
|
328
426
|
```typescript
|
|
@@ -338,19 +436,51 @@ try {
|
|
|
338
436
|
await vault.close(); // Waits for pending audit logs, cleans up timers
|
|
339
437
|
}
|
|
340
438
|
|
|
341
|
-
// close() is idempotent
|
|
439
|
+
// close() is idempotent -safe to call multiple times
|
|
342
440
|
// Calling request() after close() throws AlterSDKError
|
|
343
441
|
```
|
|
344
442
|
|
|
345
443
|
## Supported Providers
|
|
346
444
|
|
|
347
|
-
The SDK
|
|
445
|
+
The SDK includes type-safe `Provider` enums for all 66 supported providers. Use them for filtering connections or as documentation -- `request()` takes a `connectionId` string, not a provider enum.
|
|
348
446
|
|
|
349
447
|
```typescript
|
|
350
|
-
|
|
351
|
-
|
|
448
|
+
import { Provider } from "@alter-ai/alter-sdk";
|
|
449
|
+
|
|
450
|
+
// Custom providers (full OAuth implementations)
|
|
451
|
+
Provider.GOOGLE // "google"
|
|
452
|
+
Provider.GITHUB // "github"
|
|
453
|
+
Provider.SLACK // "slack"
|
|
454
|
+
Provider.SENTRY // "sentry"
|
|
455
|
+
|
|
456
|
+
// Config-driven providers (63 total) -- examples:
|
|
457
|
+
Provider.HUBSPOT // "hubspot"
|
|
458
|
+
Provider.SALESFORCE // "salesforce"
|
|
459
|
+
Provider.STRIPE // "stripe"
|
|
460
|
+
Provider.MICROSOFT // "microsoft"
|
|
461
|
+
Provider.DISCORD // "discord"
|
|
462
|
+
Provider.SPOTIFY // "spotify"
|
|
463
|
+
Provider.LINKEDIN // "linkedin"
|
|
464
|
+
Provider.DROPBOX // "dropbox"
|
|
465
|
+
Provider.FIGMA // "figma"
|
|
466
|
+
Provider.CALENDLY // "calendly"
|
|
467
|
+
Provider.CLICKUP // "clickup"
|
|
468
|
+
// ... and 52 more (see Provider enum for full list)
|
|
469
|
+
|
|
470
|
+
// Usage: filter connections by provider
|
|
471
|
+
const result = await vault.listConnections({ providerId: Provider.HUBSPOT });
|
|
472
|
+
|
|
473
|
+
// Usage: make requests with connectionId
|
|
474
|
+
await vault.request(connectionId, HttpMethod.GET, url);
|
|
352
475
|
```
|
|
353
476
|
|
|
477
|
+
<details>
|
|
478
|
+
<summary>All 66 providers</summary>
|
|
479
|
+
|
|
480
|
+
Acuity Scheduling, Adobe, Aircall, Airtable, Apollo, Asana, Atlassian, Attio, Autodesk, Basecamp, Bitbucket, Bitly, Box, Brex, Cal.com, Calendly, Canva, ClickUp, Close, Constant Contact, Contentful, Deel, Dialpad, DigitalOcean, Discord, DocuSign, Dropbox, eBay, Eventbrite, Facebook, Figma, GitHub, Google, HubSpot, Instagram, Linear, LinkedIn, Mailchimp, Mercury, Microsoft, Miro, Monday, Notion, Outreach, PagerDuty, PayPal, Pinterest, Pipedrive, QuickBooks, Ramp, Reddit, RingCentral, Salesforce, Sentry, Slack, Snapchat, Spotify, Square, Squarespace, Stripe, TikTok, Todoist, Twitter, Typeform, Webex, Webflow
|
|
481
|
+
|
|
482
|
+
</details>
|
|
483
|
+
|
|
354
484
|
## Requirements
|
|
355
485
|
|
|
356
486
|
- Node.js 18+ (uses native `fetch`)
|