craterview 0.3.16 → 0.3.19
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 +30 -18
- package/index.ts +90 -38
- package/package.json +2 -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
|
|
|
@@ -155,16 +164,17 @@ Every field the API publishes on a job is exposed here.
|
|
|
155
164
|
| `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 |
|
|
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
|
-
| `
|
|
159
|
-
| `inputUrl` | The
|
|
167
|
+
| `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
|
+
| `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 |
|
|
163
173
|
|
|
164
|
-
`result` is the whole of what the job produced, and the
|
|
165
|
-
file are getters onto `result.output` rather than separate fields — the API states
|
|
166
|
-
once. A model with no file to hand back returns its answer in `result` and leaves
|
|
167
|
-
them null.
|
|
174
|
+
`result` is the whole of what the job produced, and the rows above it that describe the
|
|
175
|
+
result's file are getters onto `result.output` rather than separate fields — the API states
|
|
176
|
+
those links once. A model with no file to hand back returns its answer in `result` and leaves
|
|
177
|
+
every one of 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
|
|
@@ -188,11 +198,13 @@ a longer one.
|
|
|
188
198
|
is signed in, so the second cannot be derived from the first. Both expire, so fetch the
|
|
189
199
|
result rather than storing the link.
|
|
190
200
|
|
|
191
|
-
`
|
|
192
|
-
page of
|
|
193
|
-
it.
|
|
194
|
-
|
|
195
|
-
|
|
201
|
+
`thumbnailUrl` and `inputUrl` are for building a job listing: a few-hundred-pixel preview so a
|
|
202
|
+
page of jobs 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 job's other pictures. A model that produces no file has no
|
|
207
|
+
result to preview, so `thumbnailUrl` is a preview of 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.19";
|
|
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
|
}
|
|
@@ -367,18 +395,17 @@ export interface JobData {
|
|
|
367
395
|
* read them out of `result.output`.
|
|
368
396
|
*/
|
|
369
397
|
result?: Record<string, unknown> | null;
|
|
370
|
-
|
|
398
|
+
/**
|
|
399
|
+
* A small JPEG of the job's picture, for a listing: the result, or the image the model
|
|
400
|
+
* worked from when it produced no file. Absent until the job succeeds, where none could be
|
|
401
|
+
* drawn, and once the job's pictures are deleted.
|
|
402
|
+
*/
|
|
403
|
+
thumbnail_url?: string | null;
|
|
371
404
|
/** Whole credits. TypeScript cannot say integer, but the API only ever sends one. */
|
|
372
405
|
credits?: number | null;
|
|
373
406
|
eta_seconds?: number | null;
|
|
374
407
|
/** Retained past the ordinary expiry because its owner asked, links and all. */
|
|
375
408
|
kept?: boolean;
|
|
376
|
-
/**
|
|
377
|
-
* Whether the kept copy includes the image you sent as well as the result. It does when
|
|
378
|
-
* you kept the job while the original was still there; false when only the result is
|
|
379
|
-
* kept, and when nothing is.
|
|
380
|
-
*/
|
|
381
|
-
kept_original?: boolean;
|
|
382
409
|
/**
|
|
383
410
|
* Where this job stands with the public gallery, when you have offered it: present while
|
|
384
411
|
* it is being reviewed or shown, absent or null when it is not offered — including after
|
|
@@ -402,18 +429,18 @@ export interface JobData {
|
|
|
402
429
|
* by the same names.
|
|
403
430
|
*
|
|
404
431
|
* `result` is the whole of what the job produced. Where the model wrote a file,
|
|
405
|
-
* `result.output` carries its links, and `outputUrl` / `downloadUrl` /
|
|
432
|
+
* `result.output` carries its links, and `outputUrl` / `downloadUrl` /
|
|
406
433
|
* `contentType` / `outputBytes` read out of it. `outputUrl` and `downloadUrl` are the same
|
|
407
434
|
* object signed two ways: one to display, one to hand a person as a file. The disposition
|
|
408
435
|
* is signed in, so the second cannot be derived from the first without the storage
|
|
409
436
|
* credential. Both are presigned and expire — fetch the result rather than storing the link.
|
|
410
437
|
*
|
|
411
|
-
* `
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
* `
|
|
416
|
-
*
|
|
438
|
+
* `thumbnailUrl` is a small JPEG of the job's picture — the result, or the image the model
|
|
439
|
+
* worked from when it produced no file — for showing a page of jobs without downloading a
|
|
440
|
+
* page of full-size pictures. `inputUrl` is the picture the model worked from, so a result
|
|
441
|
+
* can be shown against what it was made from. Both expire with the job's other pictures.
|
|
442
|
+
* `alphaUrl` is there only when you asked for a JPEG of a picture with transparency: the
|
|
443
|
+
* transparency, as a file of its own.
|
|
417
444
|
*
|
|
418
445
|
* `etaSeconds` is the whole of what is reported about waiting: how long until the result,
|
|
419
446
|
* counting time spent waiting for a GPU as well as time spent on one. An estimate and never
|
|
@@ -448,8 +475,8 @@ export class Job {
|
|
|
448
475
|
get flagged() { return this.data.flagged ?? null; }
|
|
449
476
|
/** Retained past the ordinary expiry because you asked. */
|
|
450
477
|
get kept() { return this.data.kept ?? false; }
|
|
451
|
-
/**
|
|
452
|
-
get
|
|
478
|
+
/** A small JPEG of the job's picture, for a listing. Null where there is none. */
|
|
479
|
+
get thumbnailUrl() { return this.data.thumbnail_url ?? null; }
|
|
453
480
|
/** Where this job stands with the public gallery, or null when it is not offered. */
|
|
454
481
|
get gallery() { return this.data.gallery ?? null; }
|
|
455
482
|
/** The whole answer, including where the file is when there is one. */
|
|
@@ -473,8 +500,33 @@ export class Job {
|
|
|
473
500
|
* the disposition is signed in, so re-signing needs the storage credential.
|
|
474
501
|
*/
|
|
475
502
|
get downloadUrl() { return (this.output["download_url"] as string) ?? null; }
|
|
476
|
-
|
|
477
|
-
|
|
503
|
+
/**
|
|
504
|
+
* A link to the picture the model worked from. Where you named a region, this is that
|
|
505
|
+
* region — so what was used is something you can look at rather than something to take on
|
|
506
|
+
* trust. It is not the file you uploaded: yours stays yours and is removed on its own
|
|
507
|
+
* schedule.
|
|
508
|
+
*
|
|
509
|
+
* Null for a job that has not finished. A model that answers about a picture rather than
|
|
510
|
+
* producing one has it too: it is the picture that answer is about.
|
|
511
|
+
*/
|
|
512
|
+
get inputUrl() {
|
|
513
|
+
const given = (this.data.result ?? {})["input"];
|
|
514
|
+
return (given && typeof given === "object")
|
|
515
|
+
? ((given as Record<string, unknown>)["url"] as string) ?? null
|
|
516
|
+
: null;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* The transparency of a result you asked for as JPEG, which cannot hold it: a grayscale
|
|
520
|
+
* JPEG the size of the result, white where it is opaque and black where it is transparent.
|
|
521
|
+
* The result is then the colour alone, not placed on any background, so the two together
|
|
522
|
+
* are the picture. Null for every other result.
|
|
523
|
+
*/
|
|
524
|
+
get alphaUrl() {
|
|
525
|
+
const mask = (this.data.result ?? {})["alpha"];
|
|
526
|
+
return (mask && typeof mask === "object")
|
|
527
|
+
? ((mask as Record<string, unknown>)["url"] as string) ?? null
|
|
528
|
+
: null;
|
|
529
|
+
}
|
|
478
530
|
get contentType() { return (this.output["content_type"] as string) ?? null; }
|
|
479
531
|
get outputBytes() { return (this.output["bytes"] as number) ?? null; }
|
|
480
532
|
|
|
@@ -714,9 +766,9 @@ export class CraterView {
|
|
|
714
766
|
/**
|
|
715
767
|
* The account's credit balance, what it has spent, and what is in flight.
|
|
716
768
|
*
|
|
717
|
-
* All-time, not monthly: credits are granted and deplete rather than renewing.
|
|
718
|
-
*
|
|
719
|
-
*
|
|
769
|
+
* All-time, not monthly: credits are granted and deplete rather than renewing. A job is
|
|
770
|
+
* charged when it succeeds, so queued and running work is not in the figures yet, and a
|
|
771
|
+
* failed job costs nothing.
|
|
720
772
|
*
|
|
721
773
|
* `credits_remaining` is always a number, floored at zero. `jobs_in_flight` and
|
|
722
774
|
* `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.19",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"image-size": "^2.0.4"
|
|
6
6
|
},
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^26.2.0",
|
|
29
29
|
"esbuild": "^0.25.0",
|
|
30
|
+
"tsx": "^4.23.15",
|
|
30
31
|
"typescript": "^7.0.2",
|
|
31
32
|
"vitest": "^4.1.11"
|
|
32
33
|
}
|