@vouchline/sdk 0.0.0-stage → 0.1.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 +117 -2
- package/dist/client.d.ts +85 -0
- package/dist/client.js +174 -0
- package/dist/errors.d.ts +51 -0
- package/dist/errors.js +84 -0
- package/dist/generated.d.ts +10962 -0
- package/dist/generated.js +448 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3 -0
- package/dist/sse.d.ts +9 -0
- package/dist/sse.js +57 -0
- package/package.json +26 -4
package/README.md
CHANGED
|
@@ -1,3 +1,118 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @vouchline/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A typed client for the Vouchline API, generated from `schema/openapi.yaml`. It has no
|
|
4
|
+
runtime dependencies: it uses `fetch`, so it runs on Node 20.6+, Bun, Deno, workers and
|
|
5
|
+
browsers.
|
|
6
|
+
|
|
7
|
+
- `run()` and `runAndWait()` for the one-call run API
|
|
8
|
+
- `streamEvents()` to follow a run live (SSE)
|
|
9
|
+
- Typed errors for every error code
|
|
10
|
+
- Automatic `Idempotency-Key` on POSTs, reused on retries; 429 and 5xx retried with backoff
|
|
11
|
+
- Every schema type exported (`QuickRunResult`, `RunDetail`, `UsageReport`, …), plus the
|
|
12
|
+
`operations` table: each key-callable operation's method, path and scope
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
The package is not published yet. Build it from this directory:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install && npm run build
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Use
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { Vouchline } from "@vouchline/sdk";
|
|
26
|
+
|
|
27
|
+
const vouchline = new Vouchline({
|
|
28
|
+
apiKey: process.env.VOUCHLINE_API_KEY,
|
|
29
|
+
baseUrl: "https://api.vouchline.example",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const result = await vouchline.runAndWait({
|
|
33
|
+
agent: "vendor-risk",
|
|
34
|
+
question: "Is Acme Logistics GmbH a safe supplier?",
|
|
35
|
+
inputs: { company: "Acme Logistics GmbH" },
|
|
36
|
+
});
|
|
37
|
+
console.info(result.summary, result.claims.length);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`run()` returns `{ finished: true, result }`, or `{ finished: false, pending }` when the
|
|
41
|
+
run is still working after `wait` seconds (0-120). `runAndWait()` holds one request and
|
|
42
|
+
then polls `GET /v1/runs/{id}/result` until the run is done or `timeoutSeconds` (default
|
|
43
|
+
600) passes, then throws `TimeoutError`.
|
|
44
|
+
|
|
45
|
+
### Stream a run
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { Vouchline } from "@vouchline/sdk";
|
|
49
|
+
|
|
50
|
+
const vouchline = new Vouchline();
|
|
51
|
+
const started = await vouchline.run({ question: "Check Acme Logistics GmbH" });
|
|
52
|
+
const runId = started.finished ? started.result.id : started.pending.id;
|
|
53
|
+
for await (const event of vouchline.streamEvents(runId)) {
|
|
54
|
+
if (event.event === "delta") continue;
|
|
55
|
+
console.info(event.id, event.event, event.data);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
To resume after a disconnect, pass `{ after: lastEventId }`.
|
|
60
|
+
|
|
61
|
+
### Errors
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { PaymentRequiredError, PermissionDeniedError, Vouchline, RateLimitError } from "@vouchline/sdk";
|
|
65
|
+
|
|
66
|
+
const vouchline = new Vouchline();
|
|
67
|
+
try {
|
|
68
|
+
await vouchline.run({ question: "Check Acme" });
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (error instanceof PaymentRequiredError) console.warn("Out of credits:", error.code);
|
|
71
|
+
else if (error instanceof PermissionDeniedError) console.warn("Missing scope:", error.requiredScope);
|
|
72
|
+
else if (error instanceof RateLimitError) console.warn("Retry in", error.retryAfter, "s");
|
|
73
|
+
else throw error;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
| Status | Class |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| 400 | `ValidationError` |
|
|
80
|
+
| 401 | `AuthenticationError` |
|
|
81
|
+
| 402 | `PaymentRequiredError` |
|
|
82
|
+
| 403 | `PermissionDeniedError` |
|
|
83
|
+
| 404 | `NotFoundError` |
|
|
84
|
+
| 409 | `ConflictError` |
|
|
85
|
+
| 429 | `RateLimitError` |
|
|
86
|
+
| 5xx | `ServerError` |
|
|
87
|
+
| no answer | `ConnectionError` |
|
|
88
|
+
|
|
89
|
+
Every class extends `VouchlineError` and carries `status`, `code`, `requestId`, `field` and
|
|
90
|
+
`details`, which is the full error envelope.
|
|
91
|
+
|
|
92
|
+
### Anything else
|
|
93
|
+
|
|
94
|
+
Each operation in the schema can be called with `request`:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { type Batch, Vouchline } from "@vouchline/sdk";
|
|
98
|
+
|
|
99
|
+
const vouchline = new Vouchline();
|
|
100
|
+
const batch = await vouchline.request<Batch>("POST", "/v1/batches", {
|
|
101
|
+
body: { agent: "vendor-risk", inputs: [{ company: "Acme GmbH" }] },
|
|
102
|
+
});
|
|
103
|
+
console.info(batch.id);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`listRuns`, `getRun`, `getRunResult`, `cancelRun`, `listAgents`, `getAgent`, `startBatch` and
|
|
107
|
+
`usage()` are shortcuts. `usage()` returns credits used and left this period, by source.
|
|
108
|
+
|
|
109
|
+
## Develop
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npm test # node --test against a local mock server
|
|
113
|
+
npm run typecheck
|
|
114
|
+
python ../../scripts/generate_sdk_types.py # after the schema changes
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Billing: every call is billed to the workspace exactly like the same call from the
|
|
118
|
+
dashboard.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { VouchlineError } from "./errors.ts";
|
|
2
|
+
import type { Agent, AgentDetail, Batch, BatchCreateRequest, PaginatedRunList, QuickRunPending, QuickRunRequest, QuickRunResult, RunDetail, UsageReport } from "./generated.ts";
|
|
3
|
+
import { type RunEvent } from "./sse.ts";
|
|
4
|
+
export type FetchLike = (input: string, init: RequestInit) => Promise<Response>;
|
|
5
|
+
export interface ClientOptions {
|
|
6
|
+
/** `pl_live_…` or `pl_test_…`. Defaults to the VOUCHLINE_API_KEY environment variable. */
|
|
7
|
+
apiKey?: string;
|
|
8
|
+
/** API origin, e.g. https://api.vouchline.example. Defaults to VOUCHLINE_BASE_URL. */
|
|
9
|
+
baseUrl?: string;
|
|
10
|
+
/** Retries for 429, 5xx and network errors (POSTs reuse their Idempotency-Key). Default 2. */
|
|
11
|
+
maxRetries?: number;
|
|
12
|
+
/** Per-request timeout in milliseconds. Default 150 000 (a `wait` run holds up to 120 s). */
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
fetch?: FetchLike;
|
|
15
|
+
/** For tests: how to wait between retries and polls. */
|
|
16
|
+
sleep?: (ms: number) => Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export type Query = Record<string, string | number | boolean | undefined>;
|
|
19
|
+
export interface RequestOptions {
|
|
20
|
+
query?: Query;
|
|
21
|
+
body?: unknown;
|
|
22
|
+
idempotencyKey?: string;
|
|
23
|
+
signal?: AbortSignal;
|
|
24
|
+
}
|
|
25
|
+
export type RunAnswer = {
|
|
26
|
+
finished: true;
|
|
27
|
+
result: QuickRunResult;
|
|
28
|
+
} | {
|
|
29
|
+
finished: false;
|
|
30
|
+
pending: QuickRunPending;
|
|
31
|
+
};
|
|
32
|
+
export interface RunAndWaitOptions {
|
|
33
|
+
/** Give up after this many seconds overall. Default 600. */
|
|
34
|
+
timeoutSeconds?: number;
|
|
35
|
+
/** Delay between result polls once the held request ends. Default 2000 ms. */
|
|
36
|
+
pollIntervalMs?: number;
|
|
37
|
+
}
|
|
38
|
+
export interface RunFilters {
|
|
39
|
+
status?: string;
|
|
40
|
+
agent?: string;
|
|
41
|
+
effort?: "fast" | "standard" | "deep";
|
|
42
|
+
q?: string;
|
|
43
|
+
page_size?: number;
|
|
44
|
+
cursor?: string;
|
|
45
|
+
}
|
|
46
|
+
export declare class TimeoutError extends Error {
|
|
47
|
+
readonly runId: string;
|
|
48
|
+
constructor(runId: string);
|
|
49
|
+
}
|
|
50
|
+
export declare class Vouchline {
|
|
51
|
+
readonly baseUrl: string;
|
|
52
|
+
private readonly apiKey;
|
|
53
|
+
private readonly maxRetries;
|
|
54
|
+
private readonly timeoutMs;
|
|
55
|
+
private readonly fetchImpl;
|
|
56
|
+
private readonly sleep;
|
|
57
|
+
constructor(options?: ClientOptions);
|
|
58
|
+
/** Any operation by method and flat path (see `operations` in generated.ts). */
|
|
59
|
+
request<T>(method: string, path: string, options?: RequestOptions): Promise<T>;
|
|
60
|
+
/** `POST /v1/run`. With `wait` (seconds, 0-120) the call holds for the result. */
|
|
61
|
+
run(params: QuickRunRequest, options?: {
|
|
62
|
+
wait?: number;
|
|
63
|
+
}): Promise<RunAnswer>;
|
|
64
|
+
/** Start a run and resolve with its result: a held request, then polling the result. */
|
|
65
|
+
runAndWait(params: QuickRunRequest, options?: RunAndWaitOptions): Promise<QuickRunResult>;
|
|
66
|
+
waitForResult(runId: string, deadline: number, pollIntervalMs?: number): Promise<QuickRunResult>;
|
|
67
|
+
getRun(runId: string): Promise<RunDetail>;
|
|
68
|
+
getRunResult(runId: string): Promise<QuickRunResult>;
|
|
69
|
+
listRuns(filters?: RunFilters): Promise<PaginatedRunList>;
|
|
70
|
+
cancelRun(runId: string): Promise<RunDetail>;
|
|
71
|
+
listAgents(): Promise<Agent[]>;
|
|
72
|
+
getAgent(slug: string): Promise<AgentDetail>;
|
|
73
|
+
startBatch(params: BatchCreateRequest): Promise<Batch>;
|
|
74
|
+
/** Credits used and left this period, by source (dashboard, api_key, mcp, schedule, batch). */
|
|
75
|
+
usage(): Promise<UsageReport>;
|
|
76
|
+
/** The run's events as they happen; resume with `after` (an event `id`). Ends on `end`. */
|
|
77
|
+
streamEvents(runId: string, options?: {
|
|
78
|
+
after?: number;
|
|
79
|
+
signal?: AbortSignal;
|
|
80
|
+
}): AsyncGenerator<RunEvent>;
|
|
81
|
+
private url;
|
|
82
|
+
private send;
|
|
83
|
+
private backoff;
|
|
84
|
+
}
|
|
85
|
+
export { VouchlineError };
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { ConflictError, ConnectionError, VouchlineError, errorFor } from "./errors.js";
|
|
2
|
+
import { readEvents } from "./sse.js";
|
|
3
|
+
const MAX_WAIT_SECONDS = 120;
|
|
4
|
+
const RETRYABLE = new Set([429, 500, 502, 503, 504]);
|
|
5
|
+
function env(name) {
|
|
6
|
+
// Node, Bun and Deno expose process.env; browsers don't.
|
|
7
|
+
const proc = Reflect.get(globalThis, "process");
|
|
8
|
+
const vars = typeof proc === "object" && proc !== null ? Reflect.get(proc, "env") : undefined;
|
|
9
|
+
const value = typeof vars === "object" && vars !== null ? Reflect.get(vars, name) : undefined;
|
|
10
|
+
return typeof value === "string" ? value : undefined;
|
|
11
|
+
}
|
|
12
|
+
function newKey() {
|
|
13
|
+
return globalThis.crypto.randomUUID();
|
|
14
|
+
}
|
|
15
|
+
export class TimeoutError extends Error {
|
|
16
|
+
runId;
|
|
17
|
+
constructor(runId) {
|
|
18
|
+
super(`Run ${runId} did not finish in time.`);
|
|
19
|
+
this.name = "TimeoutError";
|
|
20
|
+
this.runId = runId;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class Vouchline {
|
|
24
|
+
baseUrl;
|
|
25
|
+
apiKey;
|
|
26
|
+
maxRetries;
|
|
27
|
+
timeoutMs;
|
|
28
|
+
fetchImpl;
|
|
29
|
+
sleep;
|
|
30
|
+
constructor(options = {}) {
|
|
31
|
+
const apiKey = options.apiKey ?? env("VOUCHLINE_API_KEY");
|
|
32
|
+
const baseUrl = options.baseUrl ?? env("VOUCHLINE_BASE_URL");
|
|
33
|
+
if (!apiKey)
|
|
34
|
+
throw new Error("Pass apiKey or set VOUCHLINE_API_KEY.");
|
|
35
|
+
if (!baseUrl)
|
|
36
|
+
throw new Error("Pass baseUrl or set VOUCHLINE_BASE_URL.");
|
|
37
|
+
this.apiKey = apiKey;
|
|
38
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
39
|
+
this.maxRetries = options.maxRetries ?? 2;
|
|
40
|
+
this.timeoutMs = options.timeoutMs ?? 150_000;
|
|
41
|
+
this.fetchImpl = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
42
|
+
this.sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
43
|
+
}
|
|
44
|
+
/** Any operation by method and flat path (see `operations` in generated.ts). */
|
|
45
|
+
async request(method, path, options = {}) {
|
|
46
|
+
const response = await this.send(method, path, options);
|
|
47
|
+
const text = await response.text();
|
|
48
|
+
// The schema is the contract: bodies are typed by the generated types, not re-validated.
|
|
49
|
+
const data = text ? JSON.parse(text) : null;
|
|
50
|
+
return data;
|
|
51
|
+
}
|
|
52
|
+
/** `POST /v1/run`. With `wait` (seconds, 0-120) the call holds for the result. */
|
|
53
|
+
async run(params, options = {}) {
|
|
54
|
+
const wait = Math.max(0, Math.min(options.wait ?? 0, MAX_WAIT_SECONDS));
|
|
55
|
+
const response = await this.send("POST", "/v1/run", { body: params, query: { wait } });
|
|
56
|
+
if (response.status === 202) {
|
|
57
|
+
const pending = await response.json();
|
|
58
|
+
return { finished: false, pending };
|
|
59
|
+
}
|
|
60
|
+
const result = await response.json();
|
|
61
|
+
return { finished: true, result };
|
|
62
|
+
}
|
|
63
|
+
/** Start a run and resolve with its result: a held request, then polling the result. */
|
|
64
|
+
async runAndWait(params, options = {}) {
|
|
65
|
+
const timeout = options.timeoutSeconds ?? 600;
|
|
66
|
+
const deadline = Date.now() + timeout * 1000;
|
|
67
|
+
const answer = await this.run(params, { wait: Math.min(timeout, MAX_WAIT_SECONDS) });
|
|
68
|
+
if (answer.finished)
|
|
69
|
+
return answer.result;
|
|
70
|
+
return this.waitForResult(answer.pending.id, deadline, options.pollIntervalMs ?? 2000);
|
|
71
|
+
}
|
|
72
|
+
async waitForResult(runId, deadline, pollIntervalMs = 2000) {
|
|
73
|
+
for (;;) {
|
|
74
|
+
try {
|
|
75
|
+
return await this.getRunResult(runId);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (!(error instanceof ConflictError) || error.code !== "run_not_finished")
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
if (Date.now() + pollIntervalMs > deadline)
|
|
82
|
+
throw new TimeoutError(runId);
|
|
83
|
+
await this.sleep(pollIntervalMs);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
getRun(runId) {
|
|
87
|
+
return this.request("GET", `/v1/runs/${encodeURIComponent(runId)}`);
|
|
88
|
+
}
|
|
89
|
+
getRunResult(runId) {
|
|
90
|
+
return this.request("GET", `/v1/runs/${encodeURIComponent(runId)}/result`);
|
|
91
|
+
}
|
|
92
|
+
listRuns(filters = {}) {
|
|
93
|
+
return this.request("GET", "/v1/runs", { query: { ...filters } });
|
|
94
|
+
}
|
|
95
|
+
cancelRun(runId) {
|
|
96
|
+
return this.request("POST", `/v1/runs/${encodeURIComponent(runId)}/cancel`, { body: {} });
|
|
97
|
+
}
|
|
98
|
+
listAgents() {
|
|
99
|
+
return this.request("GET", "/v1/agents");
|
|
100
|
+
}
|
|
101
|
+
getAgent(slug) {
|
|
102
|
+
return this.request("GET", `/v1/agents/${encodeURIComponent(slug)}`);
|
|
103
|
+
}
|
|
104
|
+
startBatch(params) {
|
|
105
|
+
return this.request("POST", "/v1/batches", { body: params });
|
|
106
|
+
}
|
|
107
|
+
/** Credits used and left this period, by source (dashboard, api_key, mcp, schedule, batch). */
|
|
108
|
+
usage() {
|
|
109
|
+
return this.request("GET", "/v1/usage");
|
|
110
|
+
}
|
|
111
|
+
/** The run's events as they happen; resume with `after` (an event `id`). Ends on `end`. */
|
|
112
|
+
async *streamEvents(runId, options = {}) {
|
|
113
|
+
const path = `/v1/runs/${encodeURIComponent(runId)}/stream`;
|
|
114
|
+
const response = await this.send("GET", path, {
|
|
115
|
+
query: { after_seq: options.after },
|
|
116
|
+
signal: options.signal,
|
|
117
|
+
}, "text/event-stream", false);
|
|
118
|
+
if (!response.body)
|
|
119
|
+
return;
|
|
120
|
+
for await (const event of readEvents(response.body)) {
|
|
121
|
+
yield event;
|
|
122
|
+
if (event.event === "end")
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
url(path, query) {
|
|
127
|
+
const params = new URLSearchParams();
|
|
128
|
+
for (const [name, value] of Object.entries(query ?? {})) {
|
|
129
|
+
if (value !== undefined)
|
|
130
|
+
params.set(name, String(value));
|
|
131
|
+
}
|
|
132
|
+
const search = params.toString();
|
|
133
|
+
return `${this.baseUrl}${path}${search ? `?${search}` : ""}`;
|
|
134
|
+
}
|
|
135
|
+
async send(method, path, options, accept = "application/json", timed = true) {
|
|
136
|
+
const headers = {
|
|
137
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
138
|
+
Accept: accept,
|
|
139
|
+
"User-Agent": "vouchline-typescript/0.1.0",
|
|
140
|
+
};
|
|
141
|
+
if (options.body !== undefined)
|
|
142
|
+
headers["Content-Type"] = "application/json";
|
|
143
|
+
if (method === "POST")
|
|
144
|
+
headers["Idempotency-Key"] = options.idempotencyKey ?? newKey();
|
|
145
|
+
const body = options.body === undefined ? undefined : JSON.stringify(options.body);
|
|
146
|
+
for (let attempt = 0;; attempt += 1) {
|
|
147
|
+
const signal = timed ? AbortSignal.any([AbortSignal.timeout(this.timeoutMs), ...(options.signal ? [options.signal] : [])]) : options.signal;
|
|
148
|
+
let response;
|
|
149
|
+
try {
|
|
150
|
+
response = await this.fetchImpl(this.url(path, options.query), { method, headers, body, signal });
|
|
151
|
+
}
|
|
152
|
+
catch (cause) {
|
|
153
|
+
if (attempt >= this.maxRetries || options.signal?.aborted) {
|
|
154
|
+
throw new ConnectionError(`${method} ${path} failed: ${String(cause)}`, { cause });
|
|
155
|
+
}
|
|
156
|
+
await this.sleep(this.backoff(attempt, undefined));
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (response.ok)
|
|
160
|
+
return response;
|
|
161
|
+
const retryAfter = Number(response.headers.get("Retry-After") ?? "") || undefined;
|
|
162
|
+
if (RETRYABLE.has(response.status) && attempt < this.maxRetries) {
|
|
163
|
+
await response.body?.cancel();
|
|
164
|
+
await this.sleep(this.backoff(attempt, retryAfter));
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
throw errorFor(response.status, await response.json().catch(() => null), retryAfter);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
backoff(attempt, retryAfter) {
|
|
171
|
+
return retryAfter !== undefined ? retryAfter * 1000 : 500 * 2 ** attempt;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export { VouchlineError };
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Typed errors for every non-2xx answer, read from the API's one error envelope. */
|
|
2
|
+
export interface ErrorDetails {
|
|
3
|
+
type?: string;
|
|
4
|
+
code: string;
|
|
5
|
+
message: string;
|
|
6
|
+
field?: string | null;
|
|
7
|
+
request_id?: string;
|
|
8
|
+
required_scope?: string;
|
|
9
|
+
[extra: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export declare class VouchlineError extends Error {
|
|
12
|
+
readonly status: number;
|
|
13
|
+
readonly code: string;
|
|
14
|
+
readonly requestId: string | undefined;
|
|
15
|
+
readonly field: string | null | undefined;
|
|
16
|
+
readonly details: ErrorDetails;
|
|
17
|
+
constructor(status: number, details: ErrorDetails);
|
|
18
|
+
}
|
|
19
|
+
/** 400: a field or the body is invalid (`field`, `details.errors`). */
|
|
20
|
+
export declare class ValidationError extends VouchlineError {
|
|
21
|
+
}
|
|
22
|
+
/** 401: missing, invalid, revoked or expired API key. */
|
|
23
|
+
export declare class AuthenticationError extends VouchlineError {
|
|
24
|
+
}
|
|
25
|
+
/** 402: plan limit, credit exhausted (`details.buy_credit`) or budget reached. */
|
|
26
|
+
export declare class PaymentRequiredError extends VouchlineError {
|
|
27
|
+
}
|
|
28
|
+
/** 403: the key lacks a scope (`requiredScope`), is test-mode, or the plan blocks the API. */
|
|
29
|
+
export declare class PermissionDeniedError extends VouchlineError {
|
|
30
|
+
get requiredScope(): string | undefined;
|
|
31
|
+
}
|
|
32
|
+
export declare class NotFoundError extends VouchlineError {
|
|
33
|
+
}
|
|
34
|
+
/** 409: conflict, e.g. `run_not_finished`, `idempotency_in_progress`, `already_tracked`. */
|
|
35
|
+
export declare class ConflictError extends VouchlineError {
|
|
36
|
+
}
|
|
37
|
+
/** 429: per-key rate limit; `retryAfter` is in seconds. */
|
|
38
|
+
export declare class RateLimitError extends VouchlineError {
|
|
39
|
+
readonly retryAfter: number | undefined;
|
|
40
|
+
constructor(status: number, details: ErrorDetails, retryAfter: number | undefined);
|
|
41
|
+
}
|
|
42
|
+
export declare class ServerError extends VouchlineError {
|
|
43
|
+
}
|
|
44
|
+
/** The request never got an HTTP answer (DNS, TLS, reset, timeout). */
|
|
45
|
+
export declare class ConnectionError extends Error {
|
|
46
|
+
constructor(message: string, options?: {
|
|
47
|
+
cause?: unknown;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export declare function parseErrorBody(status: number, body: unknown): ErrorDetails;
|
|
51
|
+
export declare function errorFor(status: number, body: unknown, retryAfter?: number): VouchlineError;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/** Typed errors for every non-2xx answer, read from the API's one error envelope. */
|
|
2
|
+
export class VouchlineError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
code;
|
|
5
|
+
requestId;
|
|
6
|
+
field;
|
|
7
|
+
details;
|
|
8
|
+
constructor(status, details) {
|
|
9
|
+
super(details.message);
|
|
10
|
+
this.name = new.target.name;
|
|
11
|
+
this.status = status;
|
|
12
|
+
this.code = details.code;
|
|
13
|
+
this.requestId = details.request_id;
|
|
14
|
+
this.field = details.field;
|
|
15
|
+
this.details = details;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** 400: a field or the body is invalid (`field`, `details.errors`). */
|
|
19
|
+
export class ValidationError extends VouchlineError {
|
|
20
|
+
}
|
|
21
|
+
/** 401: missing, invalid, revoked or expired API key. */
|
|
22
|
+
export class AuthenticationError extends VouchlineError {
|
|
23
|
+
}
|
|
24
|
+
/** 402: plan limit, credit exhausted (`details.buy_credit`) or budget reached. */
|
|
25
|
+
export class PaymentRequiredError extends VouchlineError {
|
|
26
|
+
}
|
|
27
|
+
/** 403: the key lacks a scope (`requiredScope`), is test-mode, or the plan blocks the API. */
|
|
28
|
+
export class PermissionDeniedError extends VouchlineError {
|
|
29
|
+
get requiredScope() {
|
|
30
|
+
return typeof this.details.required_scope === "string" ? this.details.required_scope : undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export class NotFoundError extends VouchlineError {
|
|
34
|
+
}
|
|
35
|
+
/** 409: conflict, e.g. `run_not_finished`, `idempotency_in_progress`, `already_tracked`. */
|
|
36
|
+
export class ConflictError extends VouchlineError {
|
|
37
|
+
}
|
|
38
|
+
/** 429: per-key rate limit; `retryAfter` is in seconds. */
|
|
39
|
+
export class RateLimitError extends VouchlineError {
|
|
40
|
+
retryAfter;
|
|
41
|
+
constructor(status, details, retryAfter) {
|
|
42
|
+
super(status, details);
|
|
43
|
+
this.retryAfter = retryAfter;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export class ServerError extends VouchlineError {
|
|
47
|
+
}
|
|
48
|
+
/** The request never got an HTTP answer (DNS, TLS, reset, timeout). */
|
|
49
|
+
export class ConnectionError extends Error {
|
|
50
|
+
constructor(message, options) {
|
|
51
|
+
super(message, options);
|
|
52
|
+
this.name = "ConnectionError";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function isRecord(value) {
|
|
56
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
57
|
+
}
|
|
58
|
+
export function parseErrorBody(status, body) {
|
|
59
|
+
const inner = isRecord(body) && isRecord(body.error) ? body.error : undefined;
|
|
60
|
+
if (inner && typeof inner.code === "string" && typeof inner.message === "string") {
|
|
61
|
+
return { ...inner, code: inner.code, message: inner.message };
|
|
62
|
+
}
|
|
63
|
+
return { code: `http_${status}`, message: `HTTP ${status}` };
|
|
64
|
+
}
|
|
65
|
+
export function errorFor(status, body, retryAfter) {
|
|
66
|
+
const details = parseErrorBody(status, body);
|
|
67
|
+
if (status === 400)
|
|
68
|
+
return new ValidationError(status, details);
|
|
69
|
+
if (status === 401)
|
|
70
|
+
return new AuthenticationError(status, details);
|
|
71
|
+
if (status === 402)
|
|
72
|
+
return new PaymentRequiredError(status, details);
|
|
73
|
+
if (status === 403)
|
|
74
|
+
return new PermissionDeniedError(status, details);
|
|
75
|
+
if (status === 404)
|
|
76
|
+
return new NotFoundError(status, details);
|
|
77
|
+
if (status === 409)
|
|
78
|
+
return new ConflictError(status, details);
|
|
79
|
+
if (status === 429)
|
|
80
|
+
return new RateLimitError(status, details, retryAfter);
|
|
81
|
+
if (status >= 500)
|
|
82
|
+
return new ServerError(status, details);
|
|
83
|
+
return new VouchlineError(status, details);
|
|
84
|
+
}
|