abysshub 0.1.1 → 0.1.2-dev.2

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 CHANGED
@@ -58,7 +58,7 @@ const run = await abyss.run(widget, input, { maxPrice, idempotencyKey, timeout,
58
58
 
59
59
  The press holds until the run ends. When the hold ends early (a deploy cuts it, the network goes down, the API's holds are all taken, or an hour passes), `run()` re-attaches with `GET <Location>?wait=true` until the run ends. Re-attaching has no limit and is not a retry. Connecting has 10 s, and 30 s of silence counts as a cut.
60
60
 
61
- A **run** has `/v1`'s fields: `id`, `widget`, `status` (`queued`, `running`, `succeeded` or `failed`), `price`, `result`, `output_files`, `error`, `created_at`, `started_at` and `ended_at`.
61
+ A **run** has `/v1`'s fields: `id`, `widget`, `status` (`queued`, `running`, `succeeded` or `failed`), `price`, `result`, `output_files`, `files` (`ready`, `asleep` or `waking`, see [Old big runs](#old-big-runs)), `error`, `created_at`, `started_at` and `ended_at`.
62
62
 
63
63
  ## The routes
64
64
 
@@ -69,6 +69,7 @@ Under `run()` there is one method per route. Each takes `timeout` and `signal` i
69
69
  | `abyss.runs.create(widget, input, { maxPrice?, idempotencyKey? })` | `POST /v1/widgets/{widget}/runs` with `"wait": false` | the run, `queued`, at once |
70
70
  | `abyss.runs.get(id, { wait? })` | `GET /v1/runs/{id}`, with `?wait=true` when `wait` | the run |
71
71
  | `abyss.runs.list({ limit?, startingAfter? })` | `GET /v1/runs?limit=&starting_after=` | `{ data, has_more }`, newest first |
72
+ | `abyss.runs.wake(id)` | `POST /v1/runs/{id}/wake` | the run, `waking` when it was asleep |
72
73
  | `abyss.widgets.get(widget)` | `GET /v1/widgets/{widget}` | `{ id, name, description, url, price, free_runs, input_schema }` |
73
74
  | `abyss.uploads.create(widget, { field, filename, size? })` | `POST /v1/widgets/{widget}/uploads` | `{ id, upload: { url, fields } }` |
74
75
  | `abyss.key()` | `GET /v1/key` | `{ name, key, user, spend_cap, spent_this_month, created_at }` |
@@ -105,9 +106,27 @@ const paths = await run.save("output"); // ["output/charts/a.png", "output/outpu
105
106
  - **`await run.save(dir)`** writes every output file under `dir` at its `path`, making folders, and answers the paths it wrote.
106
107
  - **Each of `run.output_files`** has `await f.save(path)` and `await f.read()`, which answers a `Uint8Array`. An expired URL is refreshed once, by reading the run again with `GET /v1/runs/{id}`. A file of 64 MiB or more downloads as 16 parallel byte ranges of its one URL.
107
108
 
109
+ ## Old big runs
110
+
111
+ A big run (its files total 50 MiB or more) that nobody has opened for six months falls asleep: its larger files sleep in deep storage, and the run reads `files: "asleep"`. Its `result` reads as before, and reading the run never wakes it. Most callers never meet one.
112
+
113
+ ```js
114
+ import { FilesAsleepError } from "abysshub";
115
+
116
+ try {
117
+ await run.save("output");
118
+ } catch (error) {
119
+ if (!(error instanceof FilesAsleepError)) throw error;
120
+ if (error.code === "files_asleep") await run.wake(); // back within 12 hours, with an email
121
+ }
122
+ ```
123
+
124
+ - **`await run.wake()`** (or `abyss.runs.wake(id)`) calls `POST /v1/runs/{id}/wake` and answers the run, `files: "waking"`. Its files are back within 12 hours, when it reads `ready`. Waking a run that is not asleep changes nothing.
125
+ - **`await run.save(dir)`** on a run that is `asleep` or `waking` raises `FilesAsleepError`, with `code` `files_asleep` or `files_waking`, before any download.
126
+
108
127
  ## Errors
109
128
 
110
- A refusal, a failed run, a timeout and a lost connection all raise one class, `AbyssError`. There is no class per status: branch on `code`.
129
+ A refusal, a failed run, a timeout and a lost connection all raise one class, `AbyssError`. There is no class per status: branch on `code`. Its one subclass, `FilesAsleepError`, is what `run.save()` raises on an old big run (see [Old big runs](#old-big-runs)).
111
130
 
112
131
  | Field | |
113
132
  |---|---|
@@ -122,9 +141,9 @@ A refusal, a failed run, a timeout and a lost connection all raise one class, `A
122
141
  | `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` |
123
142
  | `run` | the run, for a failed run or a timeout, else `null` |
124
143
 
125
- - **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).
144
+ - **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` and `run_files_waking` (409), `rate_limited` (429), `server_error` (500).
126
145
  - **A failed run:** `widget_fault`, `platform_fault` or `budget_exceeded`, with the run as `run`.
127
- - **Library-only codes:** `timeout` (see [Timeouts](#timeouts)); `connection_error`, when the connection still drops before any answer after the retries, with the cause as `cause`; and `unexpected_response`, when an answer is not what the route sends.
146
+ - **Library-only codes:** `timeout` (see [Timeouts](#timeouts)); `connection_error`, when the connection still drops before any answer after the retries, with the cause as `cause`; `unexpected_response`, when an answer is not what the route sends; and `files_asleep` and `files_waking`, raised by `run.save()` as `FilesAsleepError`.
128
147
 
129
148
  ## Retries
130
149
 
package/dist/client.d.ts CHANGED
@@ -47,6 +47,8 @@ export interface Runs {
47
47
  get(id: string, options?: GetRunOptions): Promise<Run>;
48
48
  /** Answers a page of runs, newest first. */
49
49
  list(options?: ListRunsOptions): Promise<RunList<Run>>;
50
+ /** Wakes a run whose files are asleep, and answers it, `waking`. A run that is not asleep is answered as it is. */
51
+ wake(id: string, options?: CallOptions): Promise<Run>;
50
52
  }
51
53
  export interface Widgets {
52
54
  /** Reads a Widget: its price, Free Runs and `input_schema`. */
package/dist/client.js CHANGED
@@ -46,6 +46,10 @@ export class Abyss {
46
46
  }),
47
47
  get: (id, options = {}) => this.#call(options, async (call) => this.#run(await this.#getRun(call, id, options.wait ?? false))),
48
48
  list: (options = {}) => this.#call(options, (call) => this.#listRuns(call, options)),
49
+ wake: (id, options = {}) => this.#call(options, async (call) => {
50
+ const reply = await this.#send(call, "POST", `${this.#runURL(id)}/wake`);
51
+ return this.#run(expect(reply, isRun, "The wake answered without a run."));
52
+ }),
49
53
  };
50
54
  this.widgets = {
51
55
  get: (widget, options = {}) => this.#call(options, (call) => this.#get(call, this.#widgetURL(widget), isWidget, "The Widget answered without an id.")),
@@ -163,7 +167,7 @@ export class Abyss {
163
167
  }
164
168
  /** Reads a run with `GET /v1/runs/{id}`, holding with `?wait=true`. A cut hold is read again. */
165
169
  async #getRun(call, id, wait) {
166
- const url = new URL(`${this.baseURL}/v1/runs/${encodeURIComponent(id)}`);
170
+ const url = new URL(this.#runURL(id));
167
171
  if (wait)
168
172
  url.searchParams.set("wait", "true");
169
173
  const reply = await this.#hold(call, url.href, (reply) => !reply.cut);
@@ -196,7 +200,10 @@ export class Abyss {
196
200
  return reply;
197
201
  }
198
202
  #run(data) {
199
- return new Run(data, () => this.#getRun(newCall(null), data.id, false));
203
+ return new Run(data, () => this.#getRun(newCall(null), data.id, false), (options) => this.runs.wake(data.id, options));
204
+ }
205
+ #runURL(id) {
206
+ return `${this.baseURL}/v1/runs/${encodeURIComponent(id)}`;
200
207
  }
201
208
  #widgetURL(widget) {
202
209
  return `${this.baseURL}/v1/widgets/${encodeURIComponent(widget)}`;
package/dist/error.d.ts CHANGED
@@ -40,3 +40,11 @@ export declare class AbyssError extends Error {
40
40
  readonly run: Run | null;
41
41
  constructor(fields: AbyssErrorFields);
42
42
  }
43
+ /**
44
+ * What `run.save()` raises when the run's files are asleep, with `code: "files_asleep"`:
45
+ * `run.wake()` brings them back within 12 hours. While they come back, `files_waking`.
46
+ * It is an `AbyssError`, carrying the run as `run`.
47
+ */
48
+ export declare class FilesAsleepError extends AbyssError {
49
+ constructor(fields: AbyssErrorFields);
50
+ }
package/dist/error.js CHANGED
@@ -34,3 +34,14 @@ export class AbyssError extends Error {
34
34
  this.run = fields.run ?? null;
35
35
  }
36
36
  }
37
+ /**
38
+ * What `run.save()` raises when the run's files are asleep, with `code: "files_asleep"`:
39
+ * `run.wake()` brings them back within 12 hours. While they come back, `files_waking`.
40
+ * It is an `AbyssError`, carrying the run as `run`.
41
+ */
42
+ export class FilesAsleepError extends AbyssError {
43
+ constructor(fields) {
44
+ super(fields);
45
+ this.name = "FilesAsleepError";
46
+ }
47
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { Abyss, Abyss as default, type AbyssOptions, type CallOptions, type GetRunOptions, type ListRunsOptions, type RunOptions, type Runs, type UploadRequest, type Uploads, type Widgets, } from "./client.js";
2
- export { AbyssError, type AbyssErrorFields } from "./error.js";
2
+ export { AbyssError, FilesAsleepError, type AbyssErrorFields } from "./error.js";
3
3
  export { file, type FileData, type FileOptions, type InputFile } from "./files.js";
4
4
  export type { Run, RunFile } from "./run.js";
5
- export type { Input, Key, RunError, RunList, RunStatus, Upload, Widget } from "./types.js";
5
+ export type { Input, Key, RunError, RunFiles, RunList, RunStatus, Upload, Widget } from "./types.js";
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  export { Abyss, Abyss as default, } from "./client.js";
2
- export { AbyssError } from "./error.js";
2
+ export { AbyssError, FilesAsleepError } from "./error.js";
3
3
  export { file } from "./files.js";
package/dist/run.d.ts CHANGED
@@ -1,6 +1,9 @@
1
- import type { RunData, RunError, RunFileData, RunStatus } from "./types.js";
1
+ import type { CallOptions } from "./client.js";
2
+ import type { RunData, RunError, RunFileData, RunFiles, RunStatus } from "./types.js";
2
3
  /** Reads the run again with `GET /v1/runs/{id}`. */
3
4
  export type Reread = () => Promise<RunData>;
5
+ /** Wakes the run with `POST /v1/runs/{id}/wake`. */
6
+ export type Wake = (options?: CallOptions) => Promise<Run>;
4
7
  /** A file this many bytes or larger downloads as parallel byte ranges: 64 MiB. */
5
8
  export declare const RANGED_FROM: number;
6
9
  /** How many parallel byte ranges a large file downloads as. */
@@ -14,12 +17,22 @@ export declare class Run {
14
17
  price: number;
15
18
  result: unknown;
16
19
  output_files: RunFile[];
20
+ files: RunFiles;
17
21
  error: RunError | null;
18
22
  created_at: string;
19
23
  started_at: string | null;
20
24
  ended_at: string | null;
21
- constructor(data: RunData, reread: Reread);
22
- /** Saves every output file under `dir`, at its `path`, and answers the paths it wrote. */
25
+ constructor(data: RunData, reread: Reread, wake: Wake);
26
+ /**
27
+ * Wakes the run's files with `POST /v1/runs/{id}/wake` and answers the run, `waking` when
28
+ * it was asleep: its files are back within 12 hours. A run that is not asleep is answered
29
+ * as it is.
30
+ */
31
+ wake(options?: CallOptions): Promise<Run>;
32
+ /**
33
+ * Saves every output file under `dir`, at its `path`, and answers the paths it wrote.
34
+ * A run whose files are asleep or waking raises `FilesAsleepError` before any download.
35
+ */
23
36
  save(dir: string): Promise<string[]>;
24
37
  }
25
38
  /** One file a run wrote. Its `url` is signed fresh on every read of the run, and expires. */
package/dist/run.js CHANGED
@@ -1,4 +1,4 @@
1
- import { AbyssError } from "./error.js";
1
+ import { AbyssError, FilesAsleepError } from "./error.js";
2
2
  import { nodeFS, nodePath } from "./node.js";
3
3
  /** A file this many bytes or larger downloads as parallel byte ranges: 64 MiB. */
4
4
  export const RANGED_FROM = 64 * 1024 * 1024;
@@ -22,22 +22,26 @@ export class Run {
22
22
  price;
23
23
  result;
24
24
  output_files;
25
+ files;
25
26
  error;
26
27
  created_at;
27
28
  started_at;
28
29
  ended_at;
29
30
  #reread;
30
- constructor(data, reread) {
31
+ #wake;
32
+ constructor(data, reread, wake) {
31
33
  this.id = data.id;
32
34
  this.widget = data.widget;
33
35
  this.status = data.status;
34
36
  this.price = data.price;
35
37
  this.result = data.result;
38
+ this.files = data.files;
36
39
  this.error = data.error;
37
40
  this.created_at = data.created_at;
38
41
  this.started_at = data.started_at;
39
42
  this.ended_at = data.ended_at;
40
43
  this.#reread = reread;
44
+ this.#wake = wake;
41
45
  this.output_files = data.output_files.map((output) => new RunFile(output, () => this.#refreshURLs()));
42
46
  }
43
47
  /** Reads the run again and gives every output file its freshly signed `url`. */
@@ -49,8 +53,29 @@ export class Run {
49
53
  output.url = url;
50
54
  }
51
55
  }
52
- /** Saves every output file under `dir`, at its `path`, and answers the paths it wrote. */
56
+ /**
57
+ * Wakes the run's files with `POST /v1/runs/{id}/wake` and answers the run, `waking` when
58
+ * it was asleep: its files are back within 12 hours. A run that is not asleep is answered
59
+ * as it is.
60
+ */
61
+ wake(options) {
62
+ return this.#wake(options);
63
+ }
64
+ /**
65
+ * Saves every output file under `dir`, at its `path`, and answers the paths it wrote.
66
+ * A run whose files are asleep or waking raises `FilesAsleepError` before any download.
67
+ */
53
68
  async save(dir) {
69
+ if (this.files === "asleep" || this.files === "waking") {
70
+ throw new FilesAsleepError({
71
+ code: `files_${this.files}`,
72
+ message: this.files === "asleep"
73
+ ? `The files of run ${this.id} are asleep. Call run.wake(): they are back within 12 hours.`
74
+ : `The files of run ${this.id} are waking after run.wake(): they are back within 12 hours.`,
75
+ run_id: this.id,
76
+ run: this,
77
+ });
78
+ }
54
79
  const path = await nodePath();
55
80
  const saved = [];
56
81
  for (const output of this.output_files) {
package/dist/types.d.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  /** A run's place: `succeeded` and `failed` are final. */
2
2
  export type RunStatus = "queued" | "running" | "succeeded" | "failed";
3
+ /**
4
+ * Whether a run's files can be read now. A big run nobody has opened for six months falls
5
+ * asleep: its larger files sleep until `run.wake()`, then read `waking` until they are back,
6
+ * within 12 hours.
7
+ */
8
+ export type RunFiles = "ready" | "asleep" | "waking";
3
9
  /** Why a run failed: `widget_fault`, `platform_fault` or `budget_exceeded`. */
4
10
  export interface RunError {
5
11
  code: string;
@@ -20,6 +26,7 @@ export interface RunData {
20
26
  price: number;
21
27
  result: unknown;
22
28
  output_files: RunFileData[];
29
+ files: RunFiles;
23
30
  error: RunError | null;
24
31
  created_at: string;
25
32
  started_at: string | null;
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";
2
+ export declare const VERSION = "0.1.2-dev.2";
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";
2
+ export const VERSION = "0.1.2-dev.2";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abysshub",
3
- "version": "0.1.1",
3
+ "version": "0.1.2-dev.2",
4
4
  "description": "The official JavaScript library for the Abyss API.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/Abysshub/abysshub-sdk/tree/main/js#readme",