craterview 0.1.3 → 0.3.8
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 +55 -17
- package/index.ts +57 -53
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ client installs without dragging a transitive tree behind it.
|
|
|
23
23
|
import { CraterView } from "craterview";
|
|
24
24
|
|
|
25
25
|
const cv = new CraterView({ apiKey: "cv_..." });
|
|
26
|
-
const job = await cv.run(file, {
|
|
26
|
+
const job = await cv.run(file, { scale: 4 });
|
|
27
27
|
const blob = await job.blob();
|
|
28
28
|
```
|
|
29
29
|
|
|
@@ -31,12 +31,30 @@ const blob = await job.blob();
|
|
|
31
31
|
API's shape rather than an accident, but it is not something every caller should have to
|
|
32
32
|
reimplement.
|
|
33
33
|
|
|
34
|
+
Build against `echo` first. It costs no credits, needs no GPU and returns a real result — a
|
|
35
|
+
plain upscale — so the request, the parameters and the response are the ones a paid model
|
|
36
|
+
gives, and your integration needs no change when you switch. When you are ready, swap the
|
|
37
|
+
model name for the one you want.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
const job = await cv.run(file, {
|
|
41
|
+
// echo is free, for building against. Swap in a paid model when you are ready —
|
|
42
|
+
// cv-enhance-v3 to enlarge, cv-restore-v1 to repair, cv-headshot-v1 for portraits.
|
|
43
|
+
model: "echo",
|
|
44
|
+
scale: 2, wait: 30,
|
|
45
|
+
});
|
|
46
|
+
const blob = await job.blob();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
A parameter one model publishes is not one another accepts — `cv.models()` says which — and
|
|
50
|
+
a value a model does not publish is refused at submit rather than ignored.
|
|
51
|
+
|
|
34
52
|
## An API key
|
|
35
53
|
|
|
36
54
|
Keys begin with `cv_` and are issued from your dashboard.
|
|
37
55
|
|
|
38
56
|
```ts
|
|
39
|
-
const cv = new CraterView({ apiKey: process.env.
|
|
57
|
+
const cv = new CraterView({ apiKey: process.env.CV_API_KEY });
|
|
40
58
|
```
|
|
41
59
|
|
|
42
60
|
A key carries your whole allowance and does not expire. **Do not ship one to a browser** —
|
|
@@ -47,10 +65,10 @@ browser a session instead.
|
|
|
47
65
|
|
|
48
66
|
```ts
|
|
49
67
|
const job = await cv.run(image, { // Blob | ArrayBuffer | Uint8Array
|
|
50
|
-
model: "cv-
|
|
68
|
+
model: "cv-enhance-v3", // default
|
|
51
69
|
wait: 30, // seconds to hold the connection open
|
|
52
70
|
timeoutMs: 600_000, // total before giving up
|
|
53
|
-
|
|
71
|
+
scale: 4, // model parameters pass straight through
|
|
54
72
|
});
|
|
55
73
|
```
|
|
56
74
|
|
|
@@ -66,7 +84,7 @@ Useful when you want to hold the key, submit later, or fan out.
|
|
|
66
84
|
|
|
67
85
|
```ts
|
|
68
86
|
const inputKey = await cv.upload(file); // → "inputs/..."
|
|
69
|
-
let job = await cv.submit(inputKey, {
|
|
87
|
+
let job = await cv.submit(inputKey, { scale: 4 });
|
|
70
88
|
job = await cv.waitFor(job, 600_000);
|
|
71
89
|
const blob = await job.blob();
|
|
72
90
|
```
|
|
@@ -90,7 +108,7 @@ const key = newIdempotencyKey(); // once, before the first attempt
|
|
|
90
108
|
let job;
|
|
91
109
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
92
110
|
try {
|
|
93
|
-
job = await cv.submit(inputKey, { idempotencyKey: key,
|
|
111
|
+
job = await cv.submit(inputKey, { idempotencyKey: key, scale: 4 });
|
|
94
112
|
break;
|
|
95
113
|
} catch (e) {
|
|
96
114
|
if (e instanceof CraterViewError) throw e; // the server answered; do not retry
|
|
@@ -102,7 +120,8 @@ for (let attempt = 0; attempt < 3; attempt++) {
|
|
|
102
120
|
Generating a fresh key per attempt defeats the point entirely — the server has nothing to
|
|
103
121
|
match against and every attempt starts its own job. Reusing a key with a *different* body
|
|
104
122
|
is rejected with 409 rather than quietly handing back the earlier result. Claims are kept
|
|
105
|
-
for
|
|
123
|
+
for as long as the job's record is, which is not deleted; the same key always returns
|
|
124
|
+
the same job.
|
|
106
125
|
|
|
107
126
|
## Errors
|
|
108
127
|
|
|
@@ -111,7 +130,7 @@ Everything thrown by this client extends `CraterViewError`, so one `catch` handl
|
|
|
111
130
|
|
|
112
131
|
| Class | Meaning |
|
|
113
132
|
|---|---|
|
|
114
|
-
| `RateLimited` | 429. `.retryAfter` is seconds until the window rolls over. |
|
|
133
|
+
| `RateLimited` | 429. `.retryAfter` is seconds to wait: until the window rolls over for the request rate, or a short fixed interval to poll on for the in-flight cap. |
|
|
115
134
|
| `JobFailed` | The job ran and did not succeed. `.message` says what you can do about it; `.errorCode` is the half to branch on. |
|
|
116
135
|
| `CraterViewError` | Everything else, including 4xx and 5xx from the API. |
|
|
117
136
|
|
|
@@ -131,8 +150,8 @@ Every field the API publishes on a job is exposed here.
|
|
|
131
150
|
| `error` | Set when the job failed. Safe to show a user |
|
|
132
151
|
| `errorCode` | The same fact, as a stable identifier. Branch on this, show the other |
|
|
133
152
|
| `credits` | **What you were billed** |
|
|
134
|
-
| `etaSeconds` |
|
|
135
|
-
| `community` | True when the job is on the community queue: served after priority work, taking a
|
|
153
|
+
| `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 |
|
|
154
|
+
| `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 |
|
|
136
155
|
| `outputUrl`, `downloadUrl` | The result, presigned. One to display, one to save |
|
|
137
156
|
| `thumbUrl` | A small JPEG of the result, for listings. Null when none was drawn |
|
|
138
157
|
| `inputUrl` | The file you sent. Null once it has expired — inputs go after a day |
|
|
@@ -155,9 +174,11 @@ accepts exactly that length and nothing else. You do not pass it — it is read
|
|
|
155
174
|
in hand, which is what makes it impossible to get wrong.
|
|
156
175
|
|
|
157
176
|
**Running out of credit does not stop you.** A job submitted against a balance of zero is
|
|
158
|
-
accepted, charged and run — it simply waits in the community queue
|
|
159
|
-
work
|
|
160
|
-
|
|
177
|
+
accepted, charged and run — it simply waits in the community queue, which is served after
|
|
178
|
+
paid work and always takes a share of it, so it never stalls behind paid work. It comes back
|
|
179
|
+
with `community` set. There is no payment error to handle:
|
|
180
|
+
paying — with credit, or with a subscription — buys a place at the front of the queue rather
|
|
181
|
+
than the right to submit.
|
|
161
182
|
`etaSeconds` covers the whole wait, queue time included, so a community job simply reports
|
|
162
183
|
a longer one.
|
|
163
184
|
|
|
@@ -204,7 +225,7 @@ Every copy of a delivery states the same thing, so the first one you accept is t
|
|
|
204
225
|
answer — later copies of an `id` you have already handled can be dropped rather than
|
|
205
226
|
reconciled.
|
|
206
227
|
|
|
207
|
-
**Pass the raw body.** Most frameworks parse JSON for you, and re-
|
|
228
|
+
**Pass the raw body.** Most frameworks parse JSON for you, and re-serializing it changes the
|
|
208
229
|
bytes the signature was computed over — hence `express.raw` above.
|
|
209
230
|
|
|
210
231
|
`verifyWebhook` is async because it uses WebCrypto, which is what lets it run unchanged in
|
|
@@ -245,7 +266,7 @@ old one. You cannot revoke the key you are calling with.
|
|
|
245
266
|
## Everything else
|
|
246
267
|
|
|
247
268
|
```ts
|
|
248
|
-
await cv.models(); // models, parameter schemas, queue
|
|
269
|
+
await cv.models(); // models, parameter schemas, which queue you are on
|
|
249
270
|
await cv.job("job_..."); // one job by id
|
|
250
271
|
await cv.usage(); // credit balance, spend and job counts
|
|
251
272
|
|
|
@@ -254,6 +275,10 @@ for await (const job of cv.jobs({ limit: 50, status: "succeeded" })) {
|
|
|
254
275
|
}
|
|
255
276
|
```
|
|
256
277
|
|
|
278
|
+
`limit` is how many jobs one request fetches, not how many you get: the loop keeps going
|
|
279
|
+
until your history runs out. 200 is the largest page the API serves, and a bigger number is
|
|
280
|
+
fetched as 200.
|
|
281
|
+
|
|
257
282
|
## Configuration
|
|
258
283
|
|
|
259
284
|
```ts
|
|
@@ -263,7 +288,20 @@ new CraterView({
|
|
|
263
288
|
});
|
|
264
289
|
```
|
|
265
290
|
|
|
266
|
-
`baseUrl` is what you change to point at a local
|
|
291
|
+
`baseUrl` is what you change to point at a local server.
|
|
292
|
+
|
|
293
|
+
## From Claude Code
|
|
294
|
+
|
|
295
|
+
This repository is also a Claude Code marketplace. Two plugins: `craterview` connects
|
|
296
|
+
CraterView's hosted tools so the assistant enhances images in the conversation, and
|
|
297
|
+
`craterview-api` teaches an agent to call the API from code with this client. See
|
|
298
|
+
[`plugins/`](plugins/) for what each does and how it finds a key.
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
/plugin marketplace add craterviewai/craterview-ts
|
|
302
|
+
/plugin install craterview@craterviewai
|
|
303
|
+
/plugin install craterview-api@craterviewai
|
|
304
|
+
```
|
|
267
305
|
|
|
268
306
|
## Versioning
|
|
269
307
|
|
|
@@ -272,7 +310,7 @@ or `ModelInfo`, or changes the type of one, gets a minor bump while this is `0.x
|
|
|
272
310
|
bump after `1.0.0`; anything purely additive gets a patch. Pin what you depend on.
|
|
273
311
|
|
|
274
312
|
The API this wraps adds fields to its responses without warning, so treat an unfamiliar key in
|
|
275
|
-
`result` or a `status` you do not
|
|
313
|
+
`result` or a `status` you do not recognize as something to ignore rather than to fail on.
|
|
276
314
|
|
|
277
315
|
## License
|
|
278
316
|
|
package/index.ts
CHANGED
|
@@ -9,21 +9,19 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Zero dependencies: fetch and Blob are standard in Node 18+ and every browser, so the
|
|
11
11
|
* client stays installable anywhere without dragging a transitive tree behind it.
|
|
12
|
-
*
|
|
13
|
-
* **This file is published.** It goes to GitHub and npm, and `main` points at the source, so
|
|
14
|
-
* every comment here ships and a doc-comment on an exported member appears in a consumer's
|
|
15
|
-
* editor. Write for someone who can see this package and nothing else: what the client does
|
|
16
|
-
* and what a caller has to know, never how the service behind it is built.
|
|
17
12
|
*/
|
|
18
13
|
|
|
19
14
|
// Mirrored from package.json, which is the number a release bumps. It cannot be imported
|
|
20
15
|
// from there — this ships as TypeScript, so the import would have to resolve in the
|
|
21
16
|
// consumer's toolchain — so test/version.test.ts asserts the two agree.
|
|
22
|
-
export const VERSION = "0.
|
|
17
|
+
export const VERSION = "0.3.8";
|
|
23
18
|
const DEFAULT_BASE_URL = "https://api.craterview.ai";
|
|
24
|
-
// The server rejects a longer wait outright, so asking for one costs a 422 rather than
|
|
25
|
-
//
|
|
19
|
+
// The server rejects a longer wait outright, so asking for one costs a 422 rather than the
|
|
20
|
+
// wait you asked for. `run()` clamps to this rather than letting that happen.
|
|
26
21
|
const MAX_SERVER_WAIT = 30;
|
|
22
|
+
// The largest page the job history serves. Same reasoning: a bigger `limit` is refused, so
|
|
23
|
+
// `jobs()` clamps rather than spending a request to be told no.
|
|
24
|
+
const MAX_PAGE = 200;
|
|
27
25
|
|
|
28
26
|
/**
|
|
29
27
|
* A random key for safely repeating a submission.
|
|
@@ -72,11 +70,11 @@ export class CraterViewError extends Error {
|
|
|
72
70
|
* request rate, and the account's cap on jobs queued or running at the same time, which
|
|
73
71
|
* exists so one caller cannot occupy the whole fleet.
|
|
74
72
|
*
|
|
75
|
-
* `retryAfter` is seconds to wait, and
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
73
|
+
* `retryAfter` is seconds to wait, and what it means depends on which limit you hit: for
|
|
74
|
+
* the rate limit it is when the window rolls over; for the in-flight cap it is a fixed
|
|
75
|
+
* short interval to poll on, since a slot frees when one of your own jobs finishes and
|
|
76
|
+
* nothing here predicts that. `usage()` reports the cap and what you currently hold
|
|
77
|
+
* against it.
|
|
80
78
|
*/
|
|
81
79
|
export class RateLimited extends CraterViewError {
|
|
82
80
|
constructor(message: string, readonly retryAfter?: number) {
|
|
@@ -132,7 +130,7 @@ export class InvalidSignature extends CraterViewError {}
|
|
|
132
130
|
*
|
|
133
131
|
* Throws {@link InvalidSignature} if it does not check out. **Verify before you parse**, and
|
|
134
132
|
* pass the raw body exactly as received — most frameworks parse JSON for you, and
|
|
135
|
-
* re-
|
|
133
|
+
* re-serializing it changes the bytes the signature was computed over. In Express that means
|
|
136
134
|
* `express.raw({ type: "application/json" })` on this route.
|
|
137
135
|
*
|
|
138
136
|
* Async because it uses WebCrypto, which is what makes this work unchanged in Node and in a
|
|
@@ -220,8 +218,8 @@ function timingSafeEqual(a: string, b: string): boolean {
|
|
|
220
218
|
}
|
|
221
219
|
|
|
222
220
|
/**
|
|
223
|
-
* One entry from the model
|
|
224
|
-
* with `JobData` — there is no wrapper class here because there is no
|
|
221
|
+
* One entry from the model catalog, exactly as it arrives. Snake case is the wire's, as
|
|
222
|
+
* with `JobData` — there is no wrapper class here because there is no behavior to add.
|
|
225
223
|
*/
|
|
226
224
|
export interface ModelInfo {
|
|
227
225
|
/** The public name, and what you pass as `model` when submitting. */
|
|
@@ -248,6 +246,11 @@ export interface ModelInfo {
|
|
|
248
246
|
* look identical in `accepts` alone.
|
|
249
247
|
*/
|
|
250
248
|
video_coming_soon?: boolean;
|
|
249
|
+
/**
|
|
250
|
+
* How long this model is for. `fixed` is a lasting part of the service; `comet` is a
|
|
251
|
+
* featured model that may be withdrawn at short notice, so build on it knowing that.
|
|
252
|
+
*/
|
|
253
|
+
tenure?: "fixed" | "comet";
|
|
251
254
|
max_input_bytes: number;
|
|
252
255
|
/**
|
|
253
256
|
* The ceilings **your key** is held to, not the model's widest. Community work is bounded
|
|
@@ -272,22 +275,17 @@ export interface ModelInfo {
|
|
|
272
275
|
credits_per_video_second?: number | null;
|
|
273
276
|
reference_fps: number;
|
|
274
277
|
params_schema: Record<string, unknown>;
|
|
275
|
-
/**
|
|
276
|
-
* Work ahead of you **on the queue your key would use** — not a platform-wide total.
|
|
277
|
-
* Read it with `community`, which says which queue that is.
|
|
278
|
-
*/
|
|
279
|
-
queue_depth: number;
|
|
280
278
|
/**
|
|
281
279
|
* Whether your key's work goes to the community queue, which is served after priority
|
|
282
|
-
* work and takes a
|
|
280
|
+
* work and always takes a share of it, so it never stalls behind paid work. False while
|
|
281
|
+
* the account is paying — holding credit, or on a subscription.
|
|
283
282
|
*
|
|
284
|
-
* The same field, meaning the same thing, as `Job.community
|
|
285
|
-
*
|
|
283
|
+
* The same field, meaning the same thing, as `Job.community`. How long a wait will be
|
|
284
|
+
* is answered on the job, once you have one — `Job.eta_seconds` — and nowhere else.
|
|
286
285
|
*/
|
|
287
286
|
community: boolean;
|
|
288
287
|
/** Of `params_schema`, the ones that apply to still images only. */
|
|
289
288
|
image_only_params: string[];
|
|
290
|
-
/** A representative job, for sizing a progress indicator before any eta arrives. */
|
|
291
289
|
}
|
|
292
290
|
|
|
293
291
|
/** One key on the account. Never the key itself — only the prefix, which identifies it. */
|
|
@@ -300,7 +298,11 @@ export interface KeyInfo {
|
|
|
300
298
|
/** When it stopped working, or null while it still does. */
|
|
301
299
|
revoked_at: string | null;
|
|
302
300
|
rate_limit_per_minute: number;
|
|
303
|
-
/**
|
|
301
|
+
/**
|
|
302
|
+
* Whether this is the key you are calling with. It cannot be revoked by id while it is;
|
|
303
|
+
* `endCurrentKey()` (`DELETE /v1/keys/current`) ends it on request, unless it is the
|
|
304
|
+
* account's only way back in.
|
|
305
|
+
*/
|
|
304
306
|
current: boolean;
|
|
305
307
|
}
|
|
306
308
|
|
|
@@ -314,20 +316,14 @@ export interface NewKey {
|
|
|
314
316
|
rate_limit_per_minute: number;
|
|
315
317
|
}
|
|
316
318
|
|
|
317
|
-
/**
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
community: boolean;
|
|
326
|
-
credits: number | null;
|
|
327
|
-
created_at: string | null;
|
|
328
|
-
finished_at: string | null;
|
|
329
|
-
result: Record<string, unknown> | null;
|
|
330
|
-
}
|
|
319
|
+
/**
|
|
320
|
+
* The body of a callback, once {@link verifyWebhook} has checked it came from us.
|
|
321
|
+
*
|
|
322
|
+
* A job, delivered rather than fetched: the same fields a read of the job states, under the
|
|
323
|
+
* same names, every one present and null where it does not apply — so a receiver parses one
|
|
324
|
+
* shape rather than branching on which keys arrived.
|
|
325
|
+
*/
|
|
326
|
+
export interface WebhookEvent extends Required<JobData> {}
|
|
331
327
|
|
|
332
328
|
export type JobStatus = "queued" | "running" | "succeeded" | "failed";
|
|
333
329
|
|
|
@@ -400,10 +396,12 @@ export interface JobData {
|
|
|
400
396
|
* counting time spent waiting for a GPU as well as time spent on one. An estimate and never
|
|
401
397
|
* a promise — read it as guidance, not a deadline. Absent once a job has settled.
|
|
402
398
|
*
|
|
403
|
-
* `community` says the job
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
399
|
+
* `community` says the job is on the queue served after priority work, which always takes
|
|
400
|
+
* a share of it rather than only what is left over — so it waits longer at busy times and
|
|
401
|
+
* never stalls behind paid work. That is where an account goes when it has not
|
|
402
|
+
* paid for priority — by holding credit or by subscribing. Nothing is refused for want of
|
|
403
|
+
* either: paying buys a place at the front of the queue, not the right to submit, so an
|
|
404
|
+
* account that has not paid means a longer wait and never an error.
|
|
407
405
|
*/
|
|
408
406
|
export class Job {
|
|
409
407
|
constructor(private readonly data: JobData) {}
|
|
@@ -418,7 +416,7 @@ export class Job {
|
|
|
418
416
|
get etaSeconds() { return this.data.eta_seconds ?? null; }
|
|
419
417
|
/** Your own name for this job, or null if you did not send one. */
|
|
420
418
|
get customId() { return this.data.custom_id ?? null; }
|
|
421
|
-
// Defaulted rather than nulled: a
|
|
419
|
+
// Defaulted rather than nulled: a server too old to send the field is not running a
|
|
422
420
|
// community queue at all, so its jobs are paid work.
|
|
423
421
|
get community() { return this.data.community ?? false; }
|
|
424
422
|
// Nulled rather than defaulted, unlike `community` above: absent means the image was not
|
|
@@ -547,17 +545,17 @@ export class CraterView {
|
|
|
547
545
|
}
|
|
548
546
|
|
|
549
547
|
/**
|
|
550
|
-
* Available models: parameter schemas, prices, limits, and
|
|
548
|
+
* Available models: parameter schemas, prices, limits, and which queue you are on.
|
|
551
549
|
*
|
|
552
550
|
* Prices are published here, so the cost of a job is knowable before submitting it.
|
|
553
551
|
*
|
|
554
|
-
*
|
|
555
|
-
*
|
|
556
|
-
*
|
|
557
|
-
*
|
|
552
|
+
* `community` and the limits are answered **for the queue your key would use**. The API
|
|
553
|
+
* looks up the account's standing and reports the queue a job from this key would land
|
|
554
|
+
* in — so two keys asking at the same moment can get different answers, and paying —
|
|
555
|
+
* credit or a subscription — changes yours.
|
|
558
556
|
*
|
|
559
557
|
* A model that is available is not always listed — a model in trial, or being retired,
|
|
560
|
-
* stays usable by name while absent from this
|
|
558
|
+
* stays usable by name while absent from this catalog.
|
|
561
559
|
*/
|
|
562
560
|
async models(): Promise<ModelInfo[]> {
|
|
563
561
|
return await this.request("GET", "/v1/models") as ModelInfo[];
|
|
@@ -594,7 +592,7 @@ export class CraterView {
|
|
|
594
592
|
* job and is billed for both.
|
|
595
593
|
*/
|
|
596
594
|
async submit(inputKey: string, options: SubmitOptions = {}): Promise<Job> {
|
|
597
|
-
const { wait = 0, idempotencyKey, webhookUrl, customId, model = "cv-
|
|
595
|
+
const { wait = 0, idempotencyKey, webhookUrl, customId, model = "cv-enhance-v3",
|
|
598
596
|
...params } = options;
|
|
599
597
|
// Annotated, not inferred: without this the two branches unify to a type whose key
|
|
600
598
|
// may be `undefined`, which is not assignable to Record<string, string>.
|
|
@@ -618,11 +616,17 @@ export class CraterView {
|
|
|
618
616
|
*
|
|
619
617
|
* Scoped to the account rather than to this key, so a key sees every job the account has
|
|
620
618
|
* run and not only the ones it submitted itself.
|
|
619
|
+
*
|
|
620
|
+
* `limit` is how many jobs one request fetches, not how many you get: iteration continues
|
|
621
|
+
* until the history runs out. 200 is the largest page the API serves, and a bigger number
|
|
622
|
+
* is fetched as 200 rather than sent and refused.
|
|
621
623
|
*/
|
|
622
624
|
async *jobs(options: { limit?: number; status?: JobStatus } = {}): AsyncGenerator<Job> {
|
|
623
625
|
let before: string | undefined;
|
|
624
626
|
for (;;) {
|
|
625
|
-
const params = new URLSearchParams({
|
|
627
|
+
const params = new URLSearchParams({
|
|
628
|
+
limit: String(Math.min(options.limit ?? 50, MAX_PAGE)),
|
|
629
|
+
});
|
|
626
630
|
if (options.status) params.set("status", options.status);
|
|
627
631
|
if (before) params.set("before", before);
|
|
628
632
|
|