craterview 0.3.13 → 0.3.18
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 +25 -13
- package/index.ts +96 -27
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -8,10 +8,19 @@ npm install craterview
|
|
|
8
8
|
```
|
|
9
9
|
|
|
10
10
|
**The package ships TypeScript source.** `index.ts` is what npm installs — there is no build
|
|
11
|
-
step and no compiled JavaScript in the tarball — so
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
step and no compiled JavaScript in the tarball — so it needs a toolchain that compiles
|
|
12
|
+
TypeScript inside `node_modules`. Two that do, and are tested against the published package:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx tsx main.ts # run a script directly
|
|
16
|
+
|
|
17
|
+
npx esbuild main.js --bundle --platform=node --format=esm --outfile=main.bundle.mjs
|
|
18
|
+
node main.bundle.mjs # or bundle first, for plain JavaScript projects
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
A front-end bundler works the same way, provided its TypeScript step is not configured to
|
|
22
|
+
skip `node_modules`. **Node on its own cannot import it**, type stripping included: Node
|
|
23
|
+
strips types only from your own files and refuses any `.ts` file under `node_modules`.
|
|
15
24
|
|
|
16
25
|
The code itself needs Node 18 or newer, or any modern browser. **One runtime dependency**,
|
|
17
26
|
[`image-size`](https://www.npmjs.com/package/image-size), pure JavaScript that runs in
|
|
@@ -132,7 +141,7 @@ Everything thrown by this client extends `CraterViewError`, so one `catch` handl
|
|
|
132
141
|
|
|
133
142
|
| Class | Meaning |
|
|
134
143
|
|---|---|
|
|
135
|
-
| `RateLimited` | 429. `.retryAfter` is seconds to wait: until the window rolls over for the
|
|
144
|
+
| `RateLimited` | 429 — one of four limits, and the message says which: the key's request rate, the account's upload URLs a minute, 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, or an interval to poll on for the other two. |
|
|
136
145
|
| `JobFailed` | The job ran and did not succeed. `.message` says what you can do about it; `.errorCode` is the half to branch on. |
|
|
137
146
|
| `CraterViewError` | Everything else, including 4xx and 5xx from the API. |
|
|
138
147
|
|
|
@@ -156,7 +165,8 @@ Every field the API publishes on a job is exposed here.
|
|
|
156
165
|
| `community` | True when the job is on the community queue: served after priority work, always taking a share of it, so it never stalls behind paid work |
|
|
157
166
|
| `outputUrl`, `downloadUrl` | The result, presigned. One to display, one to save |
|
|
158
167
|
| `thumbUrl` | A small JPEG of the result, for listings. Null when none was drawn |
|
|
159
|
-
| `inputUrl` | The
|
|
168
|
+
| `inputUrl` | The picture the model worked from — the region, where you named one |
|
|
169
|
+
| `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 |
|
|
160
170
|
| `contentType` | The result's media type |
|
|
161
171
|
| `outputBytes` | The result's size in bytes |
|
|
162
172
|
| `blob()`, `arrayBuffer()` | Download the result |
|
|
@@ -164,7 +174,7 @@ Every field the API publishes on a job is exposed here.
|
|
|
164
174
|
`result` is the whole of what the job produced, and the five rows above it that describe the
|
|
165
175
|
file are getters onto `result.output` rather than separate fields — the API states those links
|
|
166
176
|
once. A model with no file to hand back returns its answer in `result` and leaves every one of
|
|
167
|
-
them null.
|
|
177
|
+
them null; the fields it answers with are its `result_schema` in `cv.models()`.
|
|
168
178
|
|
|
169
179
|
`credits` is the only figure about cost the API states, and the price is fixed and published
|
|
170
180
|
per model, so an invoice reconciles against `credits` alone. For how long a job took, subtract
|
|
@@ -176,7 +186,7 @@ accepts exactly that length and nothing else. You do not pass it — it is read
|
|
|
176
186
|
in hand, which is what makes it impossible to get wrong.
|
|
177
187
|
|
|
178
188
|
**Running out of credit does not stop you.** A job submitted against a balance of zero is
|
|
179
|
-
accepted, charged
|
|
189
|
+
accepted and run, and charged when it succeeds — it simply waits in the community queue, which is served after
|
|
180
190
|
paid work and always takes a share of it, so it never stalls behind paid work. It comes back
|
|
181
191
|
with `community` set. There is no payment error to handle:
|
|
182
192
|
paying — with credit, or with a subscription — buys a place at the front of the queue rather
|
|
@@ -189,10 +199,12 @@ is signed in, so the second cannot be derived from the first. Both expire, so fe
|
|
|
189
199
|
result rather than storing the link.
|
|
190
200
|
|
|
191
201
|
`thumbUrl` and `inputUrl` are for building a job listing: a few-hundred-pixel preview so a
|
|
192
|
-
page of results costs kilobytes, and the
|
|
193
|
-
it.
|
|
194
|
-
|
|
195
|
-
|
|
202
|
+
page of results costs kilobytes, and the picture the model worked from so a result can be
|
|
203
|
+
shown against it. Where you named a region, `inputUrl` is that region — so a before-and-after
|
|
204
|
+
is a true pair, and what was used is something you can look at rather than something to take
|
|
205
|
+
on trust. It is not the file you uploaded: yours stays yours and is removed on its own
|
|
206
|
+
schedule. Both expire with the result. A model that produces no file has no `thumbUrl`, but still
|
|
207
|
+
has the picture it answered about.
|
|
196
208
|
|
|
197
209
|
## Webhooks
|
|
198
210
|
|
|
@@ -268,7 +280,7 @@ old one. You cannot revoke the key you are calling with.
|
|
|
268
280
|
## Everything else
|
|
269
281
|
|
|
270
282
|
```ts
|
|
271
|
-
await cv.models(); // models,
|
|
283
|
+
await cv.models(); // models, what each takes and answers with, which queue you are on
|
|
272
284
|
await cv.job("job_..."); // one job by id
|
|
273
285
|
await cv.usage(); // credit balance, spend and job counts
|
|
274
286
|
|
package/index.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* import { CraterView } from "craterview";
|
|
5
5
|
*
|
|
6
6
|
* const cv = new CraterView({ apiKey: "cv_..." });
|
|
7
|
-
* const job = await cv.run(file, {
|
|
7
|
+
* const job = await cv.run(file, { scale: 4 });
|
|
8
8
|
* const blob = await job.blob();
|
|
9
9
|
*
|
|
10
10
|
* One dependency: `image-size`, which reads an image's dimensions from its header — no
|
|
@@ -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.3.
|
|
21
|
+
export const VERSION = "0.3.18";
|
|
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.
|
|
@@ -35,7 +35,7 @@ const MAX_PAGE = 200;
|
|
|
35
35
|
* const key = newIdempotencyKey(); // once, before the first attempt
|
|
36
36
|
* for (let attempt = 0; attempt < 3; attempt++) {
|
|
37
37
|
* try {
|
|
38
|
-
* job = await cv.submit(inputKey, { idempotencyKey: key,
|
|
38
|
+
* job = await cv.submit(inputKey, { idempotencyKey: key, scale: 4 });
|
|
39
39
|
* break;
|
|
40
40
|
* } catch (e) {
|
|
41
41
|
* if (!(e instanceof CraterViewError)) continue; // same key, at most one job
|
|
@@ -70,15 +70,20 @@ export class CraterViewError extends Error {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
|
-
* 429 — too fast, or too much at once.
|
|
74
|
-
*
|
|
75
|
-
*
|
|
73
|
+
* 429 — too fast, or too much at once. Four different limits answer with this, and the
|
|
74
|
+
* message says which:
|
|
75
|
+
*
|
|
76
|
+
* - the key's request rate, per minute;
|
|
77
|
+
* - the account's upload URLs, per minute — counted across all its keys;
|
|
78
|
+
* - the account's cap on jobs queued or running at the same time, which exists so one
|
|
79
|
+
* caller cannot occupy the whole fleet. `usage()` reports the cap and what you currently
|
|
80
|
+
* hold against it;
|
|
81
|
+
* - the queue itself, when it is full and taking no more work. Nothing you sent is wrong;
|
|
82
|
+
* an account with credit or a subscription submits on a queue that fills separately.
|
|
76
83
|
*
|
|
77
84
|
* `retryAfter` is seconds to wait, and what it means depends on which limit you hit: for
|
|
78
|
-
* the
|
|
79
|
-
*
|
|
80
|
-
* nothing here predicts that. `usage()` reports the cap and what you currently hold
|
|
81
|
-
* against it.
|
|
85
|
+
* the two per-minute limits it is when the window rolls over; for the in-flight cap and the
|
|
86
|
+
* full queue it is an interval to poll on, since nothing here predicts when a slot frees.
|
|
82
87
|
*/
|
|
83
88
|
export class RateLimited extends CraterViewError {
|
|
84
89
|
constructor(message: string, readonly retryAfter?: number) {
|
|
@@ -92,7 +97,7 @@ export class RateLimited extends CraterViewError {
|
|
|
92
97
|
*
|
|
93
98
|
* `message` says what you can do about it and `errorCode` is the half to branch on, because
|
|
94
99
|
* the prose is written for a person and gets reworded. `inference_failed` means the fault was
|
|
95
|
-
* ours,
|
|
100
|
+
* ours, nothing was charged, and the same call is worth making again.
|
|
96
101
|
*
|
|
97
102
|
* An unfamiliar code is a failure with no special handling, not an error in itself: new ones
|
|
98
103
|
* appear as new things become worth telling apart.
|
|
@@ -261,10 +266,9 @@ export interface ModelInfo {
|
|
|
261
266
|
* more tightly than paid work, so these move when `community` below does — check them
|
|
262
267
|
* before uploading rather than learning them from a rejected job.
|
|
263
268
|
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
* unbounded for you.
|
|
269
|
+
* `max_frame_megapixels` bounds one frame you send, and applies to images too since a
|
|
270
|
+
* still is one frame. `max_frames` bounds how many frames one job may carry. Zero means
|
|
271
|
+
* that axis is unbounded for you.
|
|
268
272
|
*
|
|
269
273
|
* For a duration, divide: `max_frames / reference_fps` is the longest clip you may send, in
|
|
270
274
|
* seconds. It is not published as its own field on purpose — one limit with two spellings
|
|
@@ -273,12 +277,36 @@ export interface ModelInfo {
|
|
|
273
277
|
*/
|
|
274
278
|
max_frame_megapixels: number;
|
|
275
279
|
max_frames: number;
|
|
280
|
+
/**
|
|
281
|
+
* The largest frame you may get **back**, in megapixels, and for a model that enlarges it
|
|
282
|
+
* is the ceiling that decides whether a job runs: what returns is the frame you send
|
|
283
|
+
* multiplied by the enlargement in each direction, so a modest photograph at a large
|
|
284
|
+
* enlargement is refused where the same photograph unenlarged is not.
|
|
285
|
+
*
|
|
286
|
+
* Check it before uploading:
|
|
287
|
+
*
|
|
288
|
+
* ```ts
|
|
289
|
+
* const result = megapixels * scale ** 2;
|
|
290
|
+
* if (model.max_output_megapixels && result > model.max_output_megapixels) {
|
|
291
|
+
* // send a smaller region of the image, or ask for less enlargement
|
|
292
|
+
* }
|
|
293
|
+
* ```
|
|
294
|
+
*
|
|
295
|
+
* Sending a smaller region keeps every pixel of what it covers; asking for less
|
|
296
|
+
* enlargement keeps the whole picture. Zero means this axis is unbounded for you.
|
|
297
|
+
*/
|
|
298
|
+
max_output_megapixels: number;
|
|
276
299
|
/** Whole credits. The price is flat and knowable before you send anything. */
|
|
277
300
|
credits_per_image: number;
|
|
278
301
|
/** Whole credits. Null means this model takes stills only. */
|
|
279
302
|
credits_per_video_second?: number | null;
|
|
280
303
|
reference_fps: number;
|
|
281
304
|
params_schema: Record<string, unknown>;
|
|
305
|
+
/**
|
|
306
|
+
* JSON Schema for the fields a finished job of this model states under `result`, beside
|
|
307
|
+
* any files it links. Empty for a model whose whole answer is its file.
|
|
308
|
+
*/
|
|
309
|
+
result_schema: Record<string, unknown>;
|
|
282
310
|
/**
|
|
283
311
|
* Whether your key's work goes to the community queue, which is served after priority
|
|
284
312
|
* work and always takes a share of it, so it never stalls behind paid work. False while
|
|
@@ -304,8 +332,8 @@ export interface KeyInfo {
|
|
|
304
332
|
rate_limit_per_minute: number;
|
|
305
333
|
/**
|
|
306
334
|
* Whether this is the key you are calling with. It cannot be revoked by id while it is;
|
|
307
|
-
* `
|
|
308
|
-
*
|
|
335
|
+
* `DELETE /v1/keys/current` ends it on request, unless it is the account's only way
|
|
336
|
+
* back in. This client has no method for that call.
|
|
309
337
|
*/
|
|
310
338
|
current: boolean;
|
|
311
339
|
}
|
|
@@ -331,6 +359,13 @@ export interface WebhookEvent extends Required<JobData> {}
|
|
|
331
359
|
|
|
332
360
|
export type JobStatus = "queued" | "running" | "succeeded" | "failed";
|
|
333
361
|
|
|
362
|
+
/** A job's standing with the public gallery: `pending` under review, `approved` once shown. */
|
|
363
|
+
export interface GalleryState {
|
|
364
|
+
status: "pending" | "approved";
|
|
365
|
+
/** The submission's id, which is what withdrawing it takes. */
|
|
366
|
+
post_id: string;
|
|
367
|
+
}
|
|
368
|
+
|
|
334
369
|
/** The API's job representation, exactly as it arrives. Snake case is the wire's. */
|
|
335
370
|
export interface JobData {
|
|
336
371
|
id: string;
|
|
@@ -360,12 +395,17 @@ export interface JobData {
|
|
|
360
395
|
* read them out of `result.output`.
|
|
361
396
|
*/
|
|
362
397
|
result?: Record<string, unknown> | null;
|
|
363
|
-
input_url?: string | null;
|
|
364
398
|
/** Whole credits. TypeScript cannot say integer, but the API only ever sends one. */
|
|
365
399
|
credits?: number | null;
|
|
366
400
|
eta_seconds?: number | null;
|
|
367
401
|
/** Retained past the ordinary expiry because its owner asked, links and all. */
|
|
368
402
|
kept?: boolean;
|
|
403
|
+
/**
|
|
404
|
+
* Where this job stands with the public gallery, when you have offered it: present while
|
|
405
|
+
* it is being reviewed or shown, absent or null when it is not offered — including after
|
|
406
|
+
* you withdraw it.
|
|
407
|
+
*/
|
|
408
|
+
gallery?: GalleryState | null;
|
|
369
409
|
community?: boolean;
|
|
370
410
|
/**
|
|
371
411
|
* Whether an automated check thought this image may fall outside what the service
|
|
@@ -390,11 +430,10 @@ export interface JobData {
|
|
|
390
430
|
* credential. Both are presigned and expire — fetch the result rather than storing the link.
|
|
391
431
|
*
|
|
392
432
|
* `thumbUrl` is a small JPEG of the result, for showing a page of jobs without downloading
|
|
393
|
-
* a page of full-size outputs. `inputUrl` is the
|
|
394
|
-
* against what it was made from.
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
* there.
|
|
433
|
+
* a page of full-size outputs. `inputUrl` is the picture the model worked from, so a result
|
|
434
|
+
* can be shown against what it was made from. Both expire with the result. `alphaUrl` is
|
|
435
|
+
* there only when you asked for a JPEG of a picture with transparency: the transparency, as
|
|
436
|
+
* a file of its own.
|
|
398
437
|
*
|
|
399
438
|
* `etaSeconds` is the whole of what is reported about waiting: how long until the result,
|
|
400
439
|
* counting time spent waiting for a GPU as well as time spent on one. An estimate and never
|
|
@@ -427,6 +466,10 @@ export class Job {
|
|
|
427
466
|
// checked, and `false` would say it was checked and cleared. The two are different
|
|
428
467
|
// answers and only one of them is true.
|
|
429
468
|
get flagged() { return this.data.flagged ?? null; }
|
|
469
|
+
/** Retained past the ordinary expiry because you asked. */
|
|
470
|
+
get kept() { return this.data.kept ?? false; }
|
|
471
|
+
/** Where this job stands with the public gallery, or null when it is not offered. */
|
|
472
|
+
get gallery() { return this.data.gallery ?? null; }
|
|
430
473
|
/** The whole answer, including where the file is when there is one. */
|
|
431
474
|
get result() { return this.data.result ?? null; }
|
|
432
475
|
/**
|
|
@@ -449,7 +492,33 @@ export class Job {
|
|
|
449
492
|
*/
|
|
450
493
|
get downloadUrl() { return (this.output["download_url"] as string) ?? null; }
|
|
451
494
|
get thumbUrl() { return (this.output["thumbnail_url"] as string) ?? null; }
|
|
452
|
-
|
|
495
|
+
/**
|
|
496
|
+
* A link to the picture the model worked from. Where you named a region, this is that
|
|
497
|
+
* region — so what was used is something you can look at rather than something to take on
|
|
498
|
+
* trust. It is not the file you uploaded: yours stays yours and is removed on its own
|
|
499
|
+
* schedule.
|
|
500
|
+
*
|
|
501
|
+
* Null for a job that has not finished. A model that answers about a picture rather than
|
|
502
|
+
* producing one has it too: it is the picture that answer is about.
|
|
503
|
+
*/
|
|
504
|
+
get inputUrl() {
|
|
505
|
+
const given = (this.data.result ?? {})["input"];
|
|
506
|
+
return (given && typeof given === "object")
|
|
507
|
+
? ((given as Record<string, unknown>)["url"] as string) ?? null
|
|
508
|
+
: null;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* The transparency of a result you asked for as JPEG, which cannot hold it: a grayscale
|
|
512
|
+
* JPEG the size of the result, white where it is opaque and black where it is transparent.
|
|
513
|
+
* The result is then the colour alone, not placed on any background, so the two together
|
|
514
|
+
* are the picture. Null for every other result.
|
|
515
|
+
*/
|
|
516
|
+
get alphaUrl() {
|
|
517
|
+
const mask = (this.data.result ?? {})["alpha"];
|
|
518
|
+
return (mask && typeof mask === "object")
|
|
519
|
+
? ((mask as Record<string, unknown>)["url"] as string) ?? null
|
|
520
|
+
: null;
|
|
521
|
+
}
|
|
453
522
|
get contentType() { return (this.output["content_type"] as string) ?? null; }
|
|
454
523
|
get outputBytes() { return (this.output["bytes"] as number) ?? null; }
|
|
455
524
|
|
|
@@ -689,9 +758,9 @@ export class CraterView {
|
|
|
689
758
|
/**
|
|
690
759
|
* The account's credit balance, what it has spent, and what is in flight.
|
|
691
760
|
*
|
|
692
|
-
* All-time, not monthly: credits are granted and deplete rather than renewing.
|
|
693
|
-
*
|
|
694
|
-
*
|
|
761
|
+
* All-time, not monthly: credits are granted and deplete rather than renewing. A job is
|
|
762
|
+
* charged when it succeeds, so queued and running work is not in the figures yet, and a
|
|
763
|
+
* failed job costs nothing.
|
|
695
764
|
*
|
|
696
765
|
* `credits_remaining` is always a number, floored at zero. `jobs_in_flight` and
|
|
697
766
|
* `max_jobs_in_flight` are the state of the queue and are what a 429 on submit is about.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "craterview",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.18",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"image-size": "^2.0.4"
|
|
6
6
|
},
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^26.2.0",
|
|
29
|
+
"esbuild": "^0.25.0",
|
|
30
|
+
"tsx": "^4.23.15",
|
|
29
31
|
"typescript": "^7.0.2",
|
|
30
32
|
"vitest": "^4.1.11"
|
|
31
33
|
}
|