abysshub 0.1.1-dev.2 → 0.1.1-dev.3
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 +5 -3
- package/dist/client.js +38 -11
- package/dist/error.d.ts +8 -0
- package/dist/error.js +7 -0
- package/dist/http.d.ts +3 -2
- package/dist/http.js +4 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -111,6 +111,7 @@ A refusal, a failed run, a timeout and a lost connection all raise one class, `A
|
|
|
111
111
|
| `request_id` | quote it to support, else `null` |
|
|
112
112
|
| `shortfall` | with `insufficient_funds`: how much Byssium the wallet is short, else `null` |
|
|
113
113
|
| `price` | with `price_above_max`: what the run costs now, else `null` |
|
|
114
|
+
| `run_id` | the id of the run the call started or read, once the press's `Location` or a run body named it, else `null` |
|
|
114
115
|
| `run` | the run, for a failed run or a timeout, else `null` |
|
|
115
116
|
|
|
116
117
|
- **Refusals:** `invalid_api_key` (401), `not_found` (404), `invalid_request`, `invalid_input`, `held_input_*`, `input_unreachable` and `idempotency_key_reused` (422), `insufficient_funds` (402), `spend_cap_reached` and `price_above_max` (400), `idempotency_key_in_use` (409), `rate_limited` (429), `server_error` (500).
|
|
@@ -137,13 +138,14 @@ There is no timeout by default: `run()` waits as long as the run takes. `timeout
|
|
|
137
138
|
try {
|
|
138
139
|
const run = await abyss.run("widget_131", input, { timeout: 60, signal: AbortSignal.timeout(90_000) });
|
|
139
140
|
} catch (error) {
|
|
140
|
-
if (error instanceof AbyssError && error.code === "timeout") {
|
|
141
|
-
// The run goes on
|
|
141
|
+
if (error instanceof AbyssError && error.code === "timeout" && error.run_id) {
|
|
142
|
+
// The run goes on: wait for it again, without pressing (and paying) twice.
|
|
143
|
+
const run = await abyss.runs.get(error.run_id, { wait: true });
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
146
|
```
|
|
145
147
|
|
|
146
|
-
Either one raises `AbyssError` with `code: "timeout"
|
|
148
|
+
Either one raises `AbyssError` with `code: "timeout"`, `run`, the run as last seen or `null`, and `run_id`, the run's id. The press answers its `Location` at once, so `run_id` is set as soon as the press has answered, even while no run has been seen yet; it is `null` only when the waiting stopped before that. Nothing is sent after the waiting stops.
|
|
147
149
|
|
|
148
150
|
## Names
|
|
149
151
|
|
package/dist/client.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { AbyssError } from "./error.js";
|
|
2
2
|
import { uploadFiles } from "./files.js";
|
|
3
|
-
import { request } from "./http.js";
|
|
3
|
+
import { LIMITS, request } from "./http.js";
|
|
4
4
|
import { Run } from "./run.js";
|
|
5
5
|
import { VERSION } from "./version.js";
|
|
6
6
|
const API = "https://api.abysshub.com";
|
|
7
7
|
const DEV_API = "https://api.dev.abysshub.com";
|
|
8
8
|
const DEV_KEY_PREFIX = "abyss_sk_dev_";
|
|
9
|
+
/** A `Location` naming a run, `…/v1/runs/{id}`, with the id as its group. */
|
|
10
|
+
const RUN_LOCATION = /\/v1\/runs\/([^/?#]+)\/?(?:[?#]|$)/;
|
|
9
11
|
/** The pause between two re-attaches that both ended without the run's ending. */
|
|
10
12
|
const REATTACH_PAUSE_MS = 1_000;
|
|
11
13
|
/** The first pause before a retry after a drop or a 5xx; it doubles with each retry. */
|
|
@@ -67,6 +69,7 @@ export class Abyss {
|
|
|
67
69
|
code: run.error?.code ?? "platform_fault",
|
|
68
70
|
message: run.error?.message ?? "The run failed.",
|
|
69
71
|
request_id: reply.requestId,
|
|
72
|
+
run_id: run.id,
|
|
70
73
|
run,
|
|
71
74
|
});
|
|
72
75
|
}
|
|
@@ -79,7 +82,7 @@ export class Abyss {
|
|
|
79
82
|
}
|
|
80
83
|
/**
|
|
81
84
|
* Runs one call under its `timeout` and `signal`. When either stops the waiting, the
|
|
82
|
-
* call raises `AbyssError` with `code: "timeout"
|
|
85
|
+
* call raises `AbyssError` with `code: "timeout"`, the run as last seen and its id.
|
|
83
86
|
*/
|
|
84
87
|
async #call(options, work) {
|
|
85
88
|
const timeout = options.timeout ?? this.timeout;
|
|
@@ -95,14 +98,20 @@ export class Abyss {
|
|
|
95
98
|
if (options.signal?.aborted)
|
|
96
99
|
abort();
|
|
97
100
|
options.signal?.addEventListener("abort", abort, { once: true });
|
|
98
|
-
const call =
|
|
101
|
+
const call = newCall(stop.signal);
|
|
99
102
|
try {
|
|
100
103
|
return await work(call);
|
|
101
104
|
}
|
|
102
105
|
catch (error) {
|
|
103
106
|
if (!stop.signal.aborted)
|
|
104
107
|
throw error;
|
|
105
|
-
throw new AbyssError({
|
|
108
|
+
throw new AbyssError({
|
|
109
|
+
code: "timeout",
|
|
110
|
+
message,
|
|
111
|
+
request_id: call.requestId,
|
|
112
|
+
run_id: call.runId,
|
|
113
|
+
run: call.seen && this.#run(call.seen),
|
|
114
|
+
});
|
|
106
115
|
}
|
|
107
116
|
finally {
|
|
108
117
|
clearTimeout(timer);
|
|
@@ -187,7 +196,7 @@ export class Abyss {
|
|
|
187
196
|
return reply;
|
|
188
197
|
}
|
|
189
198
|
#run(data) {
|
|
190
|
-
return new Run(data, () => this.#getRun(
|
|
199
|
+
return new Run(data, () => this.#getRun(newCall(null), data.id, false));
|
|
191
200
|
}
|
|
192
201
|
#widgetURL(widget) {
|
|
193
202
|
return `${this.baseURL}/v1/widgets/${encodeURIComponent(widget)}`;
|
|
@@ -199,6 +208,7 @@ export class Abyss {
|
|
|
199
208
|
code: "unexpected_response",
|
|
200
209
|
message: "The press answered without a Location to read the run at.",
|
|
201
210
|
request_id: reply.requestId,
|
|
211
|
+
run_id: reply.runId,
|
|
202
212
|
});
|
|
203
213
|
}
|
|
204
214
|
const url = new URL(location, `${this.baseURL}/`);
|
|
@@ -219,7 +229,7 @@ export class Abyss {
|
|
|
219
229
|
for (let attempt = 0;; attempt++) {
|
|
220
230
|
let answer;
|
|
221
231
|
try {
|
|
222
|
-
answer = await request(url, init);
|
|
232
|
+
answer = await request(url, init, LIMITS, (headers) => heard(call, headers));
|
|
223
233
|
}
|
|
224
234
|
catch (error) {
|
|
225
235
|
if (call.signal?.aborted)
|
|
@@ -228,6 +238,7 @@ export class Abyss {
|
|
|
228
238
|
throw new AbyssError({
|
|
229
239
|
code: "connection_error",
|
|
230
240
|
message: "The connection dropped, or timed out, before the API answered.",
|
|
241
|
+
run_id: call.runId,
|
|
231
242
|
cause: error,
|
|
232
243
|
});
|
|
233
244
|
}
|
|
@@ -238,17 +249,31 @@ export class Abyss {
|
|
|
238
249
|
const parsed = parse(answer.text);
|
|
239
250
|
if (answer.status === 200 || answer.status === 202) {
|
|
240
251
|
const run = isRun(parsed) ? parsed : null;
|
|
241
|
-
if (run)
|
|
252
|
+
if (run) {
|
|
242
253
|
call.seen = run;
|
|
243
|
-
|
|
254
|
+
call.runId = run.id;
|
|
255
|
+
}
|
|
256
|
+
const location = answer.headers.get("location");
|
|
257
|
+
return { body: parsed, run, cut: answer.text === null, location, requestId, runId: call.runId };
|
|
244
258
|
}
|
|
245
259
|
const pause = retryPause(answer, attempt);
|
|
246
260
|
if (pause === null || attempt >= retries)
|
|
247
|
-
throw refusal(answer.status, parsed, requestId);
|
|
261
|
+
throw refusal(answer.status, parsed, requestId, call.runId);
|
|
248
262
|
await sleep(pause, call.signal);
|
|
249
263
|
}
|
|
250
264
|
}
|
|
251
265
|
}
|
|
266
|
+
/** A call's waiting before it has learned anything. */
|
|
267
|
+
function newCall(signal) {
|
|
268
|
+
return { signal, seen: null, runId: null, requestId: null };
|
|
269
|
+
}
|
|
270
|
+
/** Notes a response's headers as they arrive: its `Request-Id`, and the run its `Location` names. */
|
|
271
|
+
function heard(call, headers) {
|
|
272
|
+
call.requestId = headers.get("request-id") ?? call.requestId;
|
|
273
|
+
const runId = headers.get("location")?.match(RUN_LOCATION)?.[1];
|
|
274
|
+
if (runId)
|
|
275
|
+
call.runId = runId;
|
|
276
|
+
}
|
|
252
277
|
/** How long to wait before sending a refused request again, or null when it is not retried. */
|
|
253
278
|
function retryPause(answer, attempt) {
|
|
254
279
|
if (answer.status === 429 || answer.status === 409) {
|
|
@@ -265,9 +290,9 @@ function backoff(attempt) {
|
|
|
265
290
|
function expect(reply, is, message) {
|
|
266
291
|
if (is(reply.body))
|
|
267
292
|
return reply.body;
|
|
268
|
-
throw new AbyssError({ code: "unexpected_response", message, request_id: reply.requestId });
|
|
293
|
+
throw new AbyssError({ code: "unexpected_response", message, request_id: reply.requestId, run_id: reply.runId });
|
|
269
294
|
}
|
|
270
|
-
function refusal(status, body, requestId) {
|
|
295
|
+
function refusal(status, body, requestId, runId) {
|
|
271
296
|
const error = isObject(body) && isObject(body.error) ? body.error : null;
|
|
272
297
|
if (!error || typeof error.code !== "string") {
|
|
273
298
|
return new AbyssError({
|
|
@@ -275,6 +300,7 @@ function refusal(status, body, requestId) {
|
|
|
275
300
|
message: `The API answered ${status} without an error body.`,
|
|
276
301
|
status,
|
|
277
302
|
request_id: requestId,
|
|
303
|
+
run_id: runId,
|
|
278
304
|
});
|
|
279
305
|
}
|
|
280
306
|
return new AbyssError({
|
|
@@ -286,6 +312,7 @@ function refusal(status, body, requestId) {
|
|
|
286
312
|
shortfall: typeof error.shortfall === "number" ? error.shortfall : null,
|
|
287
313
|
price: typeof error.price === "number" ? error.price : null,
|
|
288
314
|
request_id: isObject(body) && typeof body.request_id === "string" ? body.request_id : requestId,
|
|
315
|
+
run_id: runId,
|
|
289
316
|
});
|
|
290
317
|
}
|
|
291
318
|
function parse(text) {
|
package/dist/error.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export interface AbyssErrorFields {
|
|
|
6
6
|
param?: string | null;
|
|
7
7
|
doc_url?: string | null;
|
|
8
8
|
request_id?: string | null;
|
|
9
|
+
/** The id of the run the call started or read, once the call has learned it. */
|
|
10
|
+
run_id?: string | null;
|
|
9
11
|
/** With `insufficient_funds`: how much Byssium the wallet is short. */
|
|
10
12
|
shortfall?: number | null;
|
|
11
13
|
/** With `price_above_max`: what the run costs now. */
|
|
@@ -25,6 +27,12 @@ export declare class AbyssError extends Error {
|
|
|
25
27
|
readonly param: string | null;
|
|
26
28
|
readonly doc_url: string | null;
|
|
27
29
|
readonly request_id: string | null;
|
|
30
|
+
/**
|
|
31
|
+
* The id of the run the call started or read, once the call has learned it from a
|
|
32
|
+
* `Location` header or a run body; else null. A `timeout` carries it even when no run
|
|
33
|
+
* body has arrived, so `runs.get(run_id, { wait: true })` finds the run again.
|
|
34
|
+
*/
|
|
35
|
+
readonly run_id: string | null;
|
|
28
36
|
/** With `insufficient_funds`: how much Byssium the wallet is short; else null. */
|
|
29
37
|
readonly shortfall: number | null;
|
|
30
38
|
/** With `price_above_max`: what the run costs now; else null. */
|
package/dist/error.js
CHANGED
|
@@ -9,6 +9,12 @@ export class AbyssError extends Error {
|
|
|
9
9
|
param;
|
|
10
10
|
doc_url;
|
|
11
11
|
request_id;
|
|
12
|
+
/**
|
|
13
|
+
* The id of the run the call started or read, once the call has learned it from a
|
|
14
|
+
* `Location` header or a run body; else null. A `timeout` carries it even when no run
|
|
15
|
+
* body has arrived, so `runs.get(run_id, { wait: true })` finds the run again.
|
|
16
|
+
*/
|
|
17
|
+
run_id;
|
|
12
18
|
/** With `insufficient_funds`: how much Byssium the wallet is short; else null. */
|
|
13
19
|
shortfall;
|
|
14
20
|
/** With `price_above_max`: what the run costs now; else null. */
|
|
@@ -22,6 +28,7 @@ export class AbyssError extends Error {
|
|
|
22
28
|
this.param = fields.param ?? null;
|
|
23
29
|
this.doc_url = fields.doc_url ?? null;
|
|
24
30
|
this.request_id = fields.request_id ?? null;
|
|
31
|
+
this.run_id = fields.run_id ?? null;
|
|
25
32
|
this.shortfall = fields.shortfall ?? null;
|
|
26
33
|
this.price = fields.price ?? null;
|
|
27
34
|
this.run = fields.run ?? null;
|
package/dist/http.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface Answer {
|
|
|
14
14
|
/**
|
|
15
15
|
* Sends one request and reads its whole body. A failure before the headers rejects;
|
|
16
16
|
* a cut after them answers `text: null`, so the caller can re-attach. `init.signal`
|
|
17
|
-
* stops the request at any point, and then it always rejects.
|
|
17
|
+
* stops the request at any point, and then it always rejects. `heard` is handed the
|
|
18
|
+
* headers as soon as they arrive, so a stop during the body still leaves them known.
|
|
18
19
|
*/
|
|
19
|
-
export declare function request(url: string, init: RequestInit, limits?: Limits): Promise<Answer>;
|
|
20
|
+
export declare function request(url: string, init: RequestInit, limits?: Limits, heard?: (headers: Headers) => void): Promise<Answer>;
|
package/dist/http.js
CHANGED
|
@@ -2,9 +2,10 @@ export const LIMITS = { connectMs: 10_000, silenceMs: 30_000 };
|
|
|
2
2
|
/**
|
|
3
3
|
* Sends one request and reads its whole body. A failure before the headers rejects;
|
|
4
4
|
* a cut after them answers `text: null`, so the caller can re-attach. `init.signal`
|
|
5
|
-
* stops the request at any point, and then it always rejects.
|
|
5
|
+
* stops the request at any point, and then it always rejects. `heard` is handed the
|
|
6
|
+
* headers as soon as they arrive, so a stop during the body still leaves them known.
|
|
6
7
|
*/
|
|
7
|
-
export async function request(url, init, limits = LIMITS) {
|
|
8
|
+
export async function request(url, init, limits = LIMITS, heard) {
|
|
8
9
|
const stop = init.signal;
|
|
9
10
|
stop?.throwIfAborted();
|
|
10
11
|
const controller = new AbortController();
|
|
@@ -19,6 +20,7 @@ export async function request(url, init, limits = LIMITS) {
|
|
|
19
20
|
finally {
|
|
20
21
|
clearTimeout(connect);
|
|
21
22
|
}
|
|
23
|
+
heard?.(response.headers);
|
|
22
24
|
const text = await readBody(response, controller, limits.silenceMs);
|
|
23
25
|
stop?.throwIfAborted();
|
|
24
26
|
return { status: response.status, headers: response.headers, text };
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** This package's version, written from package.json by scripts/version.js at build. */
|
|
2
|
-
export declare const VERSION = "0.1.1-dev.
|
|
2
|
+
export declare const VERSION = "0.1.1-dev.3";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** This package's version, written from package.json by scripts/version.js at build. */
|
|
2
|
-
export const VERSION = "0.1.1-dev.
|
|
2
|
+
export const VERSION = "0.1.1-dev.3";
|
package/package.json
CHANGED