craterview 0.3.18 → 0.4.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 +27 -19
- package/index.ts +86 -33
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -136,12 +136,16 @@ the same job.
|
|
|
136
136
|
|
|
137
137
|
## Errors
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
`.status` carries the HTTP status where there was one
|
|
139
|
+
Every answer the API or storage gives that is not a success is thrown as a
|
|
140
|
+
`CraterViewError`. `.status` carries the HTTP status where there was one, and `.traceId` the
|
|
141
|
+
API's `X-Trace-Id` for a request it answered and refused — the id to quote when you ask us
|
|
142
|
+
about it. A request that got
|
|
143
|
+
no answer at all throws the runtime's own error from `fetch`, which is how the retry loop
|
|
144
|
+
above tells the two apart.
|
|
141
145
|
|
|
142
146
|
| Class | Meaning |
|
|
143
147
|
|---|---|
|
|
144
|
-
| `RateLimited` | 429 — one of
|
|
148
|
+
| `RateLimited` | 429 — one of five limits, and the message says which: the key's request rate, the account's upload URLs a minute, the account's new keys an hour, the account's jobs in flight, or a full queue. `.retryAfter` is seconds to wait: until the window rolls over for the two per-minute limits, until a new key can be made for the hourly one, or an interval to poll on for the other two. |
|
|
145
149
|
| `JobFailed` | The job ran and did not succeed. `.message` says what you can do about it; `.errorCode` is the half to branch on. |
|
|
146
150
|
| `CraterViewError` | Everything else, including 4xx and 5xx from the API. |
|
|
147
151
|
|
|
@@ -162,19 +166,19 @@ Every field the API publishes on a job is exposed here.
|
|
|
162
166
|
| `errorCode` | The same fact, as a stable identifier. Branch on this, show the other |
|
|
163
167
|
| `credits` | **What you were billed** |
|
|
164
168
|
| `etaSeconds` | Seconds until the job is expected to finish, recomputed on every read — it counts down while the job runs. Absent once the job has settled. Estimated for your image's size when `upload()` could read it (or when you pass `inputMegapixels` to `submit()`), for a typical image otherwise |
|
|
165
|
-
| `community` | True when the job is on the community queue
|
|
169
|
+
| `community` | True when the job is on the community queue, which runs on shared, free capacity and can wait longer at busy times |
|
|
166
170
|
| `outputUrl`, `downloadUrl` | The result, presigned. One to display, one to save |
|
|
167
|
-
| `
|
|
171
|
+
| `thumbnailUrl` | A small JPEG of the job's picture, for listings — the result, or what the model worked from when it produced no file. Null when none was drawn |
|
|
168
172
|
| `inputUrl` | The picture the model worked from — the region, where you named one |
|
|
169
173
|
| `alphaUrl` | Only for a JPEG result of a picture with transparency, which JPEG cannot hold: the transparency as a grayscale JPEG, white where opaque. The result is then the colour alone |
|
|
170
174
|
| `contentType` | The result's media type |
|
|
171
175
|
| `outputBytes` | The result's size in bytes |
|
|
172
176
|
| `blob()`, `arrayBuffer()` | Download the result |
|
|
173
177
|
|
|
174
|
-
`result` is the whole of what the job produced, and the
|
|
175
|
-
file are getters onto `result.output` rather than separate fields — the API states
|
|
176
|
-
once. A model with no file to hand back returns its answer in `result` and leaves
|
|
177
|
-
them null; the fields it answers with are its `result_schema` in `cv.models()`.
|
|
178
|
+
`result` is the whole of what the job produced, and the rows above it that describe the
|
|
179
|
+
result's file are getters onto `result.output` rather than separate fields — the API states
|
|
180
|
+
those links once. A model with no file to hand back returns its answer in `result` and leaves
|
|
181
|
+
every one of them null; the fields it answers with are its `result_schema` in `cv.models()`.
|
|
178
182
|
|
|
179
183
|
`credits` is the only figure about cost the API states, and the price is fixed and published
|
|
180
184
|
per model, so an invoice reconciles against `credits` alone. For how long a job took, subtract
|
|
@@ -186,11 +190,11 @@ accepts exactly that length and nothing else. You do not pass it — it is read
|
|
|
186
190
|
in hand, which is what makes it impossible to get wrong.
|
|
187
191
|
|
|
188
192
|
**Running out of credit does not stop you.** A job submitted against a balance of zero is
|
|
189
|
-
accepted and run, and charged when it succeeds — it
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
193
|
+
accepted and run, and charged when it succeeds — it goes to the community queue, which runs
|
|
194
|
+
on shared, free capacity and can wait longer at busy times. It comes back with `community`
|
|
195
|
+
set. There is no payment error to handle: paying — with credit, or with a subscription —
|
|
196
|
+
moves work onto paid compute that scales with demand, rather than buying the right to
|
|
197
|
+
submit.
|
|
194
198
|
`etaSeconds` covers the whole wait, queue time included, so a community job simply reports
|
|
195
199
|
a longer one.
|
|
196
200
|
|
|
@@ -198,13 +202,13 @@ a longer one.
|
|
|
198
202
|
is signed in, so the second cannot be derived from the first. Both expire, so fetch the
|
|
199
203
|
result rather than storing the link.
|
|
200
204
|
|
|
201
|
-
`
|
|
202
|
-
page of
|
|
205
|
+
`thumbnailUrl` and `inputUrl` are for building a job listing: a few-hundred-pixel preview so a
|
|
206
|
+
page of jobs costs kilobytes, and the picture the model worked from so a result can be
|
|
203
207
|
shown against it. Where you named a region, `inputUrl` is that region — so a before-and-after
|
|
204
208
|
is a true pair, and what was used is something you can look at rather than something to take
|
|
205
209
|
on trust. It is not the file you uploaded: yours stays yours and is removed on its own
|
|
206
|
-
schedule. Both expire with the
|
|
207
|
-
|
|
210
|
+
schedule. Both expire with the job's other pictures. A model that produces no file has no
|
|
211
|
+
result to preview, so `thumbnailUrl` is a preview of the picture it answered about.
|
|
208
212
|
|
|
209
213
|
## Webhooks
|
|
210
214
|
|
|
@@ -299,10 +303,14 @@ fetched as 200.
|
|
|
299
303
|
new CraterView({
|
|
300
304
|
apiKey: undefined,
|
|
301
305
|
baseUrl: "https://api.craterview.ai",
|
|
306
|
+
timeoutMs: 60_000,
|
|
302
307
|
});
|
|
303
308
|
```
|
|
304
309
|
|
|
305
|
-
`baseUrl` is what you change to point at a local server.
|
|
310
|
+
`baseUrl` is what you change to point at a local server. `timeoutMs` is how long to wait for
|
|
311
|
+
the API to answer before giving up with the runtime's `TimeoutError` — nothing answered, so
|
|
312
|
+
it is a request to repeat with the same idempotency key. Uploads and downloads go to storage
|
|
313
|
+
and have five minutes.
|
|
306
314
|
|
|
307
315
|
## From Claude Code
|
|
308
316
|
|
package/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { imageSize } from "image-size";
|
|
|
18
18
|
// Mirrored from package.json, which is the number a release bumps. It cannot be imported
|
|
19
19
|
// from there — this ships as TypeScript, so the import would have to resolve in the
|
|
20
20
|
// consumer's toolchain — so test/version.test.ts asserts the two agree.
|
|
21
|
-
export const VERSION = "0.
|
|
21
|
+
export const VERSION = "0.4.0";
|
|
22
22
|
const DEFAULT_BASE_URL = "https://api.craterview.ai";
|
|
23
23
|
// The server rejects a longer wait outright, so asking for one costs a 422 rather than the
|
|
24
24
|
// wait you asked for. `run()` clamps to this rather than letting that happen.
|
|
@@ -62,18 +62,25 @@ export function newIdempotencyKey(): string {
|
|
|
62
62
|
return `idem_${base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")}`;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* `status` is the HTTP status where there was one. `traceId` is the API's `X-Trace-Id` for a
|
|
67
|
+
* request it answered and refused — the id to quote when asking about it. Absent when the
|
|
68
|
+
* refusal came from storage rather than the API.
|
|
69
|
+
*/
|
|
65
70
|
export class CraterViewError extends Error {
|
|
66
|
-
constructor(message: string, readonly status?: number) {
|
|
71
|
+
constructor(message: string, readonly status?: number, readonly traceId?: string) {
|
|
67
72
|
super(message);
|
|
68
73
|
this.name = "CraterViewError";
|
|
69
74
|
}
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
/**
|
|
73
|
-
* 429 — too fast, or too much at once.
|
|
78
|
+
* 429 — too fast, or too much at once. Five different limits answer with this, and the
|
|
74
79
|
* message says which:
|
|
75
80
|
*
|
|
76
81
|
* - the key's request rate, per minute;
|
|
82
|
+
* - the account's new keys, per hour — counted across all its keys, and only from
|
|
83
|
+
* `createKey`;
|
|
77
84
|
* - the account's upload URLs, per minute — counted across all its keys;
|
|
78
85
|
* - the account's cap on jobs queued or running at the same time, which exists so one
|
|
79
86
|
* caller cannot occupy the whole fleet. `usage()` reports the cap and what you currently
|
|
@@ -82,12 +89,13 @@ export class CraterViewError extends Error {
|
|
|
82
89
|
* an account with credit or a subscription submits on a queue that fills separately.
|
|
83
90
|
*
|
|
84
91
|
* `retryAfter` is seconds to wait, and what it means depends on which limit you hit: for
|
|
85
|
-
* the two per-minute limits it is when the window rolls over; for
|
|
86
|
-
*
|
|
92
|
+
* the two per-minute limits it is when the window rolls over; for new keys, when the account
|
|
93
|
+
* may be given another; for the in-flight cap and the full queue it is an interval to poll
|
|
94
|
+
* on, since nothing here predicts when a slot frees.
|
|
87
95
|
*/
|
|
88
96
|
export class RateLimited extends CraterViewError {
|
|
89
|
-
constructor(message: string, readonly retryAfter?: number) {
|
|
90
|
-
super(message, 429);
|
|
97
|
+
constructor(message: string, readonly retryAfter?: number, traceId?: string) {
|
|
98
|
+
super(message, 429, traceId);
|
|
91
99
|
this.name = "RateLimited";
|
|
92
100
|
}
|
|
93
101
|
}
|
|
@@ -308,9 +316,9 @@ export interface ModelInfo {
|
|
|
308
316
|
*/
|
|
309
317
|
result_schema: Record<string, unknown>;
|
|
310
318
|
/**
|
|
311
|
-
* Whether your key's work goes to the community queue, which
|
|
312
|
-
*
|
|
313
|
-
*
|
|
319
|
+
* Whether your key's work goes to the community queue, which runs on shared, free capacity
|
|
320
|
+
* and can wait longer at busy times. False while the account is paying — holding credit, or
|
|
321
|
+
* on a subscription — which puts its work on paid compute that scales with demand.
|
|
314
322
|
*
|
|
315
323
|
* The same field, meaning the same thing, as `Job.community`. How long a wait will be
|
|
316
324
|
* is answered on the job, once you have one — `Job.eta_seconds` — and nowhere else.
|
|
@@ -328,7 +336,7 @@ export interface KeyInfo {
|
|
|
328
336
|
name: string;
|
|
329
337
|
created_at: string | null;
|
|
330
338
|
/** When it stopped working, or null while it still does. */
|
|
331
|
-
revoked_at
|
|
339
|
+
revoked_at?: string;
|
|
332
340
|
rate_limit_per_minute: number;
|
|
333
341
|
/**
|
|
334
342
|
* Whether this is the key you are calling with. It cannot be revoked by id while it is;
|
|
@@ -395,6 +403,12 @@ export interface JobData {
|
|
|
395
403
|
* read them out of `result.output`.
|
|
396
404
|
*/
|
|
397
405
|
result?: Record<string, unknown> | null;
|
|
406
|
+
/**
|
|
407
|
+
* A small JPEG of the job's picture, for a listing: the result, or the image the model
|
|
408
|
+
* worked from when it produced no file. Absent until the job succeeds, where none could be
|
|
409
|
+
* drawn, and once the job's pictures are deleted.
|
|
410
|
+
*/
|
|
411
|
+
thumbnail_url?: string | null;
|
|
398
412
|
/** Whole credits. TypeScript cannot say integer, but the API only ever sends one. */
|
|
399
413
|
credits?: number | null;
|
|
400
414
|
eta_seconds?: number | null;
|
|
@@ -423,28 +437,30 @@ export interface JobData {
|
|
|
423
437
|
* by the same names.
|
|
424
438
|
*
|
|
425
439
|
* `result` is the whole of what the job produced. Where the model wrote a file,
|
|
426
|
-
* `result.output` carries its links, and `outputUrl` / `downloadUrl` /
|
|
440
|
+
* `result.output` carries its links, and `outputUrl` / `downloadUrl` /
|
|
427
441
|
* `contentType` / `outputBytes` read out of it. `outputUrl` and `downloadUrl` are the same
|
|
428
442
|
* object signed two ways: one to display, one to hand a person as a file. The disposition
|
|
429
443
|
* is signed in, so the second cannot be derived from the first without the storage
|
|
430
444
|
* credential. Both are presigned and expire — fetch the result rather than storing the link.
|
|
431
445
|
*
|
|
432
|
-
* `
|
|
433
|
-
*
|
|
434
|
-
*
|
|
435
|
-
*
|
|
436
|
-
* a
|
|
446
|
+
* `thumbnailUrl` is a small JPEG of the job's picture — the result, or the image the model
|
|
447
|
+
* worked from when it produced no file — for showing a page of jobs without downloading a
|
|
448
|
+
* page of full-size pictures. `inputUrl` is the picture the model worked from, so a result
|
|
449
|
+
* can be shown against what it was made from. Both expire with the job's other pictures.
|
|
450
|
+
* `alphaUrl` is there only when you asked for a JPEG of a picture with transparency: the
|
|
451
|
+
* transparency, as a file of its own.
|
|
437
452
|
*
|
|
438
453
|
* `etaSeconds` is the whole of what is reported about waiting: how long until the result,
|
|
439
454
|
* counting time spent waiting for a GPU as well as time spent on one. An estimate and never
|
|
440
455
|
* a promise — read it as guidance, not a deadline. Absent once a job has settled.
|
|
441
456
|
*
|
|
442
|
-
* `community` says the job is on the queue
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
457
|
+
* `community` says the job is on the community queue, which runs on shared, free capacity —
|
|
458
|
+
* so it waits longer at busy times, and never stops. That is where an account's work goes
|
|
459
|
+
* when it is not paying, by holding credit or by subscribing. An empty balance is never itself
|
|
460
|
+
* a reason to refuse a job — paying moves work onto paid compute that scales with demand, not
|
|
461
|
+
* the right to submit — but on some models the community queue takes smaller files.
|
|
462
|
+
* `models()` lists each model's limits for your key, and a file over them is refused with
|
|
463
|
+
* the limit named.
|
|
448
464
|
*/
|
|
449
465
|
export class Job {
|
|
450
466
|
constructor(private readonly data: JobData) {}
|
|
@@ -468,6 +484,8 @@ export class Job {
|
|
|
468
484
|
get flagged() { return this.data.flagged ?? null; }
|
|
469
485
|
/** Retained past the ordinary expiry because you asked. */
|
|
470
486
|
get kept() { return this.data.kept ?? false; }
|
|
487
|
+
/** A small JPEG of the job's picture, for a listing. Null where there is none. */
|
|
488
|
+
get thumbnailUrl() { return this.data.thumbnail_url ?? null; }
|
|
471
489
|
/** Where this job stands with the public gallery, or null when it is not offered. */
|
|
472
490
|
get gallery() { return this.data.gallery ?? null; }
|
|
473
491
|
/** The whole answer, including where the file is when there is one. */
|
|
@@ -491,7 +509,6 @@ export class Job {
|
|
|
491
509
|
* the disposition is signed in, so re-signing needs the storage credential.
|
|
492
510
|
*/
|
|
493
511
|
get downloadUrl() { return (this.output["download_url"] as string) ?? null; }
|
|
494
|
-
get thumbUrl() { return (this.output["thumbnail_url"] as string) ?? null; }
|
|
495
512
|
/**
|
|
496
513
|
* A link to the picture the model worked from. Where you named a region, this is that
|
|
497
514
|
* region — so what was used is something you can look at rather than something to take on
|
|
@@ -543,8 +560,10 @@ export class Job {
|
|
|
543
560
|
if (!url) {
|
|
544
561
|
throw new CraterViewError(`job ${this.id} has no result (status ${this.status})`);
|
|
545
562
|
}
|
|
546
|
-
const resp = await fetch(url);
|
|
547
|
-
if (!resp.ok)
|
|
563
|
+
const resp = await fetch(url, { signal: AbortSignal.timeout(TRANSFER_TIMEOUT_MS) });
|
|
564
|
+
if (!resp.ok) {
|
|
565
|
+
throw new CraterViewError(`downloading result failed: ${resp.status}`, resp.status);
|
|
566
|
+
}
|
|
548
567
|
return await resp.blob();
|
|
549
568
|
}
|
|
550
569
|
|
|
@@ -556,8 +575,18 @@ export class Job {
|
|
|
556
575
|
export interface CraterViewOptions {
|
|
557
576
|
apiKey?: string;
|
|
558
577
|
baseUrl?: string;
|
|
578
|
+
/**
|
|
579
|
+
* Milliseconds to wait for the API to answer a request before giving up, 60 000 by default.
|
|
580
|
+
* A request that times out throws the runtime's `TimeoutError`, not a `CraterViewError`:
|
|
581
|
+
* nothing answered, so it is one to repeat with the same idempotency key. Uploads and
|
|
582
|
+
* downloads go to storage and are given five minutes whatever this says.
|
|
583
|
+
*/
|
|
584
|
+
timeoutMs?: number;
|
|
559
585
|
}
|
|
560
586
|
|
|
587
|
+
/** How long a transfer to or from storage may take, however large the picture. */
|
|
588
|
+
const TRANSFER_TIMEOUT_MS = 300_000;
|
|
589
|
+
|
|
561
590
|
/**
|
|
562
591
|
* Width × height ÷ 10⁶ from the file's header, or `undefined` for a file the reader does not
|
|
563
592
|
* know. Reads the first megabyte only — every format's dimensions sit near the front, and a
|
|
@@ -605,6 +634,7 @@ export interface SubmitOptions {
|
|
|
605
634
|
export class CraterView {
|
|
606
635
|
private readonly baseUrl: string;
|
|
607
636
|
private readonly headers: Record<string, string>;
|
|
637
|
+
private readonly timeoutMs: number;
|
|
608
638
|
// What `upload()` read from each file's header, by the key it came back with, so a later
|
|
609
639
|
// `submit()` of that key declares the size without the caller carrying it. Bounded: a
|
|
610
640
|
// long-lived client uploading thousands of files keeps the most recent few hundred.
|
|
@@ -612,6 +642,7 @@ export class CraterView {
|
|
|
612
642
|
|
|
613
643
|
constructor(options: CraterViewOptions = {}) {
|
|
614
644
|
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
645
|
+
this.timeoutMs = options.timeoutMs ?? 60_000;
|
|
615
646
|
this.headers = { "User-Agent": `craterview-ts/${VERSION}` };
|
|
616
647
|
if (options.apiKey) this.headers["Authorization"] = `Bearer ${options.apiKey}`;
|
|
617
648
|
}
|
|
@@ -619,6 +650,7 @@ export class CraterView {
|
|
|
619
650
|
private async request<T>(method: string, path: string, body?: unknown,
|
|
620
651
|
extraHeaders: Record<string, string> = {}): Promise<T> {
|
|
621
652
|
const resp = await fetch(`${this.baseUrl}${path}`, {
|
|
653
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
622
654
|
method,
|
|
623
655
|
headers: { ...this.headers, ...extraHeaders,
|
|
624
656
|
...(body ? { "Content-Type": "application/json" } : {}) },
|
|
@@ -627,10 +659,11 @@ export class CraterView {
|
|
|
627
659
|
|
|
628
660
|
if (!resp.ok) {
|
|
629
661
|
const detail = await this.detail(resp);
|
|
662
|
+
const traceId = resp.headers.get("X-Trace-Id") ?? undefined;
|
|
630
663
|
if (resp.status === 429) {
|
|
631
|
-
throw new RateLimited(detail, Number(resp.headers.get("Retry-After") ?? 0));
|
|
664
|
+
throw new RateLimited(detail, Number(resp.headers.get("Retry-After") ?? 0), traceId);
|
|
632
665
|
}
|
|
633
|
-
throw new CraterViewError(detail, resp.status);
|
|
666
|
+
throw new CraterViewError(detail, resp.status, traceId);
|
|
634
667
|
}
|
|
635
668
|
// A 204 carries no body, so asking for JSON throws on a call that succeeded.
|
|
636
669
|
if (resp.status === 204) return undefined as T;
|
|
@@ -685,9 +718,10 @@ export class CraterView {
|
|
|
685
718
|
"POST", "/v1/uploads", { content_type: type, content_length: blob.size });
|
|
686
719
|
|
|
687
720
|
const put = await fetch(slot.upload_url, {
|
|
721
|
+
signal: AbortSignal.timeout(TRANSFER_TIMEOUT_MS),
|
|
688
722
|
method: "PUT", body: blob, headers: { "Content-Type": type },
|
|
689
723
|
});
|
|
690
|
-
if (!put.ok) throw new CraterViewError(`upload failed: ${put.status}
|
|
724
|
+
if (!put.ok) throw new CraterViewError(`upload failed: ${put.status}`, put.status);
|
|
691
725
|
const megapixels = await megapixelsOf(blob);
|
|
692
726
|
if (megapixels) {
|
|
693
727
|
if (this.sizes.size >= 512) this.sizes.delete(this.sizes.keys().next().value!);
|
|
@@ -795,9 +829,24 @@ export class CraterView {
|
|
|
795
829
|
return body.secret;
|
|
796
830
|
}
|
|
797
831
|
|
|
798
|
-
/**
|
|
832
|
+
/**
|
|
833
|
+
* Every key on the account, including revoked ones, by prefix rather than value, newest
|
|
834
|
+
* first. The API serves the list a page at a time; this fetches every page and returns them
|
|
835
|
+
* together.
|
|
836
|
+
*/
|
|
799
837
|
async keys(): Promise<KeyInfo[]> {
|
|
800
|
-
|
|
838
|
+
const found: KeyInfo[] = [];
|
|
839
|
+
let before: string | undefined;
|
|
840
|
+
for (;;) {
|
|
841
|
+
const params = new URLSearchParams({ limit: String(MAX_PAGE) });
|
|
842
|
+
if (before) params.set("before", before);
|
|
843
|
+
const page = await this.request<{ data: KeyInfo[]; has_more: boolean; next_before?: string }>(
|
|
844
|
+
"GET", `/v1/keys?${params}`);
|
|
845
|
+
found.push(...page.data);
|
|
846
|
+
// As in `jobs`: a missing or non-advancing cursor stops rather than spinning.
|
|
847
|
+
if (!page.has_more || !page.next_before || page.next_before === before) return found;
|
|
848
|
+
before = page.next_before;
|
|
849
|
+
}
|
|
801
850
|
}
|
|
802
851
|
|
|
803
852
|
/**
|
|
@@ -807,6 +856,9 @@ export class CraterView {
|
|
|
807
856
|
* Several keys on an account is the ordinary arrangement, one per service or environment.
|
|
808
857
|
* They share the account's credits and history. This is also how you rotate without
|
|
809
858
|
* downtime: create the new key, move your clients onto it, then revoke the old one.
|
|
859
|
+
*
|
|
860
|
+
* An account is given a limited number of new keys an hour, whichever of its keys asks:
|
|
861
|
+
* past it this throws `RateLimited`, whose `retryAfter` says when to ask again.
|
|
810
862
|
*/
|
|
811
863
|
async createKey(name = "api"): Promise<NewKey> {
|
|
812
864
|
return await this.request("POST", "/v1/keys", { name }) as NewKey;
|
|
@@ -815,8 +867,9 @@ export class CraterView {
|
|
|
815
867
|
/**
|
|
816
868
|
* Stop a key working. Immediate, and not reversible.
|
|
817
869
|
*
|
|
818
|
-
* You cannot revoke the key this client is authenticating with
|
|
819
|
-
*
|
|
870
|
+
* You cannot revoke the key this client is authenticating with: it is refused with status
|
|
871
|
+
* 409, because it would leave you unable to make another. Create a replacement, move onto
|
|
872
|
+
* it, then revoke this one.
|
|
820
873
|
*/
|
|
821
874
|
async revokeKey(keyId: string): Promise<void> {
|
|
822
875
|
await this.request("DELETE", `/v1/keys/${encodeURIComponent(keyId)}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "craterview",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"image-size": "^2.0.4"
|
|
6
6
|
},
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
"test": "vitest run"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@types/node": "^26.
|
|
29
|
-
"esbuild": "^0.
|
|
28
|
+
"@types/node": "^26.6.3",
|
|
29
|
+
"esbuild": "^0.28.2",
|
|
30
30
|
"tsx": "^4.23.15",
|
|
31
31
|
"typescript": "^7.0.2",
|
|
32
|
-
"vitest": "^
|
|
32
|
+
"vitest": "^5.0.2"
|
|
33
33
|
}
|
|
34
34
|
}
|