withruntime 0.10.0 → 0.11.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/DAYTONA.md +40 -34
- package/E2B.md +56 -57
- package/VERCEL.md +21 -19
- package/compatibility-lock.json +8 -84
- package/dist/cli.js +73 -2
- package/dist/client.d.ts +12 -0
- package/dist/client.js +27 -0
- package/dist/compat/lease.d.ts +36 -0
- package/dist/compat/lease.js +87 -0
- package/dist/daytona/computer-use.d.ts +162 -0
- package/dist/daytona/computer-use.js +324 -0
- package/dist/daytona/context.d.ts +10 -0
- package/dist/daytona/context.js +24 -0
- package/dist/daytona/daytona.d.ts +20 -0
- package/dist/daytona/daytona.js +123 -16
- package/dist/daytona/filesystem.js +8 -0
- package/dist/daytona/index.d.ts +2 -26
- package/dist/daytona/index.js +2 -7
- package/dist/daytona/process.d.ts +63 -6
- package/dist/daytona/process.js +224 -23
- package/dist/daytona/sandbox.d.ts +19 -5
- package/dist/daytona/sandbox.js +39 -25
- package/dist/e2b/client.d.ts +5 -0
- package/dist/e2b/client.js +5 -1
- package/dist/e2b/commands.d.ts +15 -6
- package/dist/e2b/commands.js +83 -18
- package/dist/e2b/errors.d.ts +7 -0
- package/dist/e2b/errors.js +20 -0
- package/dist/e2b/filesystem.d.ts +2 -1
- package/dist/e2b/filesystem.js +43 -6
- package/dist/e2b/index.d.ts +2 -2
- package/dist/e2b/index.js +1 -1
- package/dist/e2b/pty.js +6 -3
- package/dist/e2b/sandbox.d.ts +15 -5
- package/dist/e2b/sandbox.js +190 -28
- package/dist/e2b/users.d.ts +30 -0
- package/dist/e2b/users.js +185 -0
- package/dist/index.d.ts +1 -0
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.js +6 -1
- package/dist/network-cli.js +28 -0
- package/dist/products/browser.d.ts +60 -0
- package/dist/products/browser.js +37 -0
- package/dist/products/desktop.d.ts +2 -2
- package/dist/products/desktop.js +2 -2
- package/dist/products/images.d.ts +3 -1
- package/dist/products/images.js +14 -2
- package/dist/products/index.d.ts +2 -0
- package/dist/products/index.js +2 -0
- package/dist/products/network-products.d.ts +7 -0
- package/dist/products/network-products.js +3 -0
- package/dist/products/private-network.d.ts +22 -0
- package/dist/products/private-network.js +10 -0
- package/dist/sandbox.d.ts +22 -0
- package/dist/sandbox.js +47 -0
- package/dist/transport.d.ts +1 -1
- package/dist/transport.js +1 -1
- package/dist/types.d.ts +5 -2
- package/dist/vercel/index.d.ts +1 -7
- package/dist/vercel/index.js +1 -3
- package/dist/vercel/sandbox-user.d.ts +70 -0
- package/dist/vercel/sandbox-user.js +206 -0
- package/dist/vercel/sandbox.d.ts +47 -12
- package/dist/vercel/sandbox.js +201 -42
- package/package.json +1 -1
package/dist/vercel/sandbox.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { extensionSeconds, LeaseKeeper, LEASE_MAX_SECONDS } from "../compat/lease.js";
|
|
1
2
|
import { RuntimeError } from "../errors.js";
|
|
2
3
|
import { clientFor, signalOf, } from "./client.js";
|
|
3
4
|
import { Command, CommandFinished, exitCodeOf } from "./command.js";
|
|
4
5
|
import { APIError, guard, NotSupportedError, translate } from "./errors.js";
|
|
5
6
|
import { FileSystem } from "./filesystem.js";
|
|
7
|
+
import { rootOrThrow, SandboxUser, SandboxUserAlreadyExistsError, validateName, } from "./sandbox-user.js";
|
|
6
8
|
import { Snapshot } from "./snapshot.js";
|
|
7
9
|
/* Vercel Sandbox's `Sandbox` over Runtime's. Every call is one or a few calls
|
|
8
10
|
of the withruntime SDK; nothing here speaks HTTP. What Runtime cannot do the
|
|
@@ -14,7 +16,8 @@ export const MEMORY_MIB_PER_VCPU = 2048;
|
|
|
14
16
|
/** Vercel's default timeout, 5 minutes. */
|
|
15
17
|
export const DEFAULT_TIMEOUT_MS = 300_000;
|
|
16
18
|
const MIN_LEASE_SECONDS = 60;
|
|
17
|
-
|
|
19
|
+
/** Extend a long timeout's lease when less than this is left. */
|
|
20
|
+
const LEASE_MARGIN_MS = 600_000;
|
|
18
21
|
/** Commands without a timeout run until the sandbox ends: Runtime's longest. */
|
|
19
22
|
const LONGEST_MS = 86_400_000;
|
|
20
23
|
/** Vercel's working directory; `/workspace` on Runtime. */
|
|
@@ -27,13 +30,14 @@ const LEGACY_RUNTIMES = new Set(["node22", "node24", "node26", "python3.13"]);
|
|
|
27
30
|
/** Vercel regions in the United States, where Runtime runs. */
|
|
28
31
|
const US_REGIONS = new Set(["iad1", "sfo1", "cle1", "pdx1"]);
|
|
29
32
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
30
|
-
function
|
|
33
|
+
function checkTimeout(timeoutMs) {
|
|
31
34
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
|
|
32
35
|
throw new RangeError(`timeout must be a positive number of milliseconds, not ${timeoutMs}.`);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
return Math.max(timeoutMs, MIN_LEASE_SECONDS * 1000);
|
|
37
|
+
}
|
|
38
|
+
/** The first lease for a timeout: all of it, or the hour a lease may run. */
|
|
39
|
+
function leaseSeconds(timeoutMs) {
|
|
40
|
+
return Math.min(Math.ceil(checkTimeout(timeoutMs) / 1000), LEASE_MAX_SECONDS);
|
|
37
41
|
}
|
|
38
42
|
function refuseCreate(params) {
|
|
39
43
|
if (params.keepLastSnapshots !== undefined)
|
|
@@ -140,6 +144,12 @@ export class Sandbox {
|
|
|
140
144
|
#persistent;
|
|
141
145
|
#routes = new Map();
|
|
142
146
|
#onResume;
|
|
147
|
+
#keeper;
|
|
148
|
+
/** Epoch ms the current session started, as the timeout counts from it. */
|
|
149
|
+
#sessionStart;
|
|
150
|
+
/** Vercel's timeout, when this object was given one or extended it. */
|
|
151
|
+
#timeoutMs;
|
|
152
|
+
#defaultUser;
|
|
143
153
|
#linked;
|
|
144
154
|
#deleted = false;
|
|
145
155
|
/** Use Sandbox.create, Sandbox.get or Sandbox.getOrCreate. */
|
|
@@ -149,6 +159,17 @@ export class Sandbox {
|
|
|
149
159
|
this.#env = { ...resolved.params.env };
|
|
150
160
|
this.#persistent = resolved.params.persistent ?? resolved.runtime.info.onLeaseEnd !== "stop";
|
|
151
161
|
this.#onResume = resolved.params.onResume;
|
|
162
|
+
const timeout = resolved.params.timeout;
|
|
163
|
+
this.#timeoutMs = timeout === undefined ? undefined : checkTimeout(timeout);
|
|
164
|
+
this.#sessionStart =
|
|
165
|
+
timeout === undefined
|
|
166
|
+
? Date.parse(this.#rt.info.expiresAt) - this.#rt.info.timeoutSeconds * 1000
|
|
167
|
+
: Date.now();
|
|
168
|
+
this.#keeper = new LeaseKeeper({
|
|
169
|
+
sandbox: () => this.#rt,
|
|
170
|
+
until: () => this.#until(),
|
|
171
|
+
marginMs: () => Math.min(LEASE_MARGIN_MS, this.timeout / 2),
|
|
172
|
+
});
|
|
152
173
|
this.fs = new FileSystem({
|
|
153
174
|
name: this.name,
|
|
154
175
|
files: async () => (await this.#live()).files,
|
|
@@ -213,9 +234,14 @@ export class Sandbox {
|
|
|
213
234
|
get status() {
|
|
214
235
|
return statusOf(this.#rt.state);
|
|
215
236
|
}
|
|
216
|
-
/** Milliseconds
|
|
237
|
+
/** Milliseconds from the start of the session: the timeout given at
|
|
238
|
+
* create, with every extension, or the lease of a sandbox found by name. */
|
|
217
239
|
get timeout() {
|
|
218
|
-
return this.#rt.info.timeoutSeconds * 1000;
|
|
240
|
+
return this.#timeoutMs ?? this.#rt.info.timeoutSeconds * 1000;
|
|
241
|
+
}
|
|
242
|
+
/** Epoch ms the session should end: its start plus the timeout. */
|
|
243
|
+
#until() {
|
|
244
|
+
return this.#sessionStart + this.timeout;
|
|
219
245
|
}
|
|
220
246
|
get expiresAt() {
|
|
221
247
|
return new Date(this.#rt.info.expiresAt);
|
|
@@ -251,11 +277,18 @@ export class Sandbox {
|
|
|
251
277
|
...params.withruntime?.create,
|
|
252
278
|
};
|
|
253
279
|
const runtime = await guard(() => client.sandboxes.create(input, signalOf(params)));
|
|
254
|
-
const sandbox = new Sandbox({
|
|
280
|
+
const sandbox = new Sandbox({
|
|
281
|
+
runtime,
|
|
282
|
+
client,
|
|
283
|
+
params: { ...params, persistent, timeout: params.timeout ?? DEFAULT_TIMEOUT_MS },
|
|
284
|
+
});
|
|
255
285
|
try {
|
|
256
286
|
await sandbox.#setUp(params);
|
|
287
|
+
// Starts renewing at once when the timeout runs past the first lease.
|
|
288
|
+
await sandbox.#keeper.check();
|
|
257
289
|
}
|
|
258
290
|
catch (error) {
|
|
291
|
+
sandbox.#keeper.end();
|
|
259
292
|
await runtime.stop({ wait: false }).catch(() => undefined);
|
|
260
293
|
throw translate(error, sandbox.name);
|
|
261
294
|
}
|
|
@@ -367,13 +400,16 @@ export class Sandbox {
|
|
|
367
400
|
});
|
|
368
401
|
}
|
|
369
402
|
/** Running and stopped (paused) sandboxes, oldest first, with Vercel's
|
|
370
|
-
* paginator: `for await (const s of await Sandbox.list())`.
|
|
403
|
+
* paginator: `for await (const s of await Sandbox.list())`. A name prefix,
|
|
404
|
+
* creation-time bounds (`since`, `until`, epoch ms), `sortBy` and
|
|
405
|
+
* `sortOrder`, and a `cursor` from an earlier page are applied here, over
|
|
406
|
+
* every sandbox the key sees. */
|
|
371
407
|
static async list(params = {}) {
|
|
372
408
|
params.signal?.throwIfAborted();
|
|
373
|
-
for (const field of ["since", "until", "cursor", "sortBy", "sortOrder", "namePrefix"])
|
|
374
|
-
if (params[field] !== undefined)
|
|
375
|
-
throw new NotSupportedError(`Listing sandboxes by ${field}`, "List them all (oldest first) and filter the result yourself.");
|
|
376
409
|
const client = clientFor(params);
|
|
410
|
+
const local = ["namePrefix", "sortBy", "sortOrder", "cursor", "since", "until"].some((field) => params[field] !== undefined);
|
|
411
|
+
if (local)
|
|
412
|
+
return listHere(client, params);
|
|
377
413
|
const page = await guard(() => client.sandboxes.list({
|
|
378
414
|
...(params.tags ? { labels: params.tags } : {}),
|
|
379
415
|
...(params.limit ? { limit: Math.min(params.limit, 100) } : {}),
|
|
@@ -393,10 +429,15 @@ export class Sandbox {
|
|
|
393
429
|
const state = this.#rt.state;
|
|
394
430
|
if (state === "paused" || state === "pausing")
|
|
395
431
|
await this.#wake(opts);
|
|
432
|
+
await guard(() => this.#keeper.check(), this.name);
|
|
396
433
|
return this.#rt;
|
|
397
434
|
}
|
|
435
|
+
/** A woken sandbox starts a new session, whose timeout counts from now,
|
|
436
|
+
* as Vercel's resumed sessions do. */
|
|
398
437
|
async #wake(opts = {}) {
|
|
399
438
|
await guard(() => this.#rt.wake(signalOf(opts)), this.name);
|
|
439
|
+
if (this.#timeoutMs !== undefined)
|
|
440
|
+
this.#sessionStart = Date.now();
|
|
400
441
|
await this.#onResume?.(this);
|
|
401
442
|
}
|
|
402
443
|
/** Runs `work` on the live sandbox; if the lease paused it meanwhile, wakes
|
|
@@ -583,6 +624,8 @@ export class Sandbox {
|
|
|
583
624
|
else
|
|
584
625
|
await guard(() => this.#rt.stop(signalOf(opts)), this.name);
|
|
585
626
|
}
|
|
627
|
+
if (!this.#persistent)
|
|
628
|
+
this.#keeper.end();
|
|
586
629
|
return { name: this.name, status: this.status };
|
|
587
630
|
}
|
|
588
631
|
/** Ends the sandbox for good. */
|
|
@@ -593,18 +636,29 @@ export class Sandbox {
|
|
|
593
636
|
if (this.#rt.state !== "stopped")
|
|
594
637
|
await guard(() => this.#rt.stop({ wait: false, ...signalOf(opts) }), this.name);
|
|
595
638
|
this.#deleted = true;
|
|
639
|
+
this.#keeper.end();
|
|
596
640
|
}
|
|
597
641
|
async [Symbol.asyncDispose]() {
|
|
598
642
|
if (this.#deleted)
|
|
599
643
|
return;
|
|
600
644
|
await this.stop().catch(() => undefined);
|
|
601
645
|
}
|
|
602
|
-
/** Moves the end `duration` milliseconds later
|
|
603
|
-
*
|
|
646
|
+
/** Moves the end `duration` milliseconds later. The lease moves at once
|
|
647
|
+
* as far as it may (an hour ahead of now); past that it is extended while
|
|
648
|
+
* this object lives. */
|
|
604
649
|
async extendTimeout(duration, opts = {}) {
|
|
605
650
|
if (!Number.isFinite(duration) || duration <= 0)
|
|
606
651
|
throw new RangeError(`duration must be a positive number of milliseconds, not ${duration}.`);
|
|
607
|
-
await this.#withResume((runtime) =>
|
|
652
|
+
await this.#withResume(async (runtime) => {
|
|
653
|
+
this.#timeoutMs = this.timeout + duration;
|
|
654
|
+
await this.#leaseToTimeout(runtime, opts);
|
|
655
|
+
}, opts);
|
|
656
|
+
}
|
|
657
|
+
/** Extends the lease now toward the timeout, an hour ahead at most. */
|
|
658
|
+
async #leaseToTimeout(runtime, opts) {
|
|
659
|
+
const seconds = extensionSeconds(Date.parse(runtime.info.expiresAt), this.#until(), Date.now(), Infinity);
|
|
660
|
+
if (seconds >= 1)
|
|
661
|
+
await runtime.extend(seconds, signalOf(opts));
|
|
608
662
|
}
|
|
609
663
|
/** Replaces the network rules; returns the policy given. */
|
|
610
664
|
async updateNetworkPolicy(networkPolicy, opts = {}) {
|
|
@@ -630,11 +684,16 @@ export class Sandbox {
|
|
|
630
684
|
validatePorts(params.ports);
|
|
631
685
|
const wanted = params.ports === undefined ? undefined : new Set(params.ports);
|
|
632
686
|
if (params.timeout !== undefined) {
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
687
|
+
const timeout = checkTimeout(params.timeout);
|
|
688
|
+
// A shorter timeout is kept when the lease has not reached it yet: the
|
|
689
|
+
// lease then stops being extended there. Runtime cannot end a lease
|
|
690
|
+
// earlier than it was given.
|
|
691
|
+
if (this.#sessionStart + timeout < Date.parse(this.#rt.info.expiresAt) - 1000)
|
|
692
|
+
throw new NotSupportedError("A timeout that ends earlier than the current lease", `The lease runs to ${this.#rt.info.expiresAt}; call stop() when the work is done, or pass a timeout that ends after it.`);
|
|
693
|
+
const longer = timeout > this.timeout;
|
|
694
|
+
this.#timeoutMs = timeout;
|
|
695
|
+
if (longer)
|
|
696
|
+
await this.#withResume((runtime) => this.#leaseToTimeout(runtime, opts), opts);
|
|
638
697
|
}
|
|
639
698
|
if (rules !== undefined)
|
|
640
699
|
await this.#withResume((runtime) => runtime.network.set(rules, signalOf(opts)), opts);
|
|
@@ -700,26 +759,68 @@ export class Sandbox {
|
|
|
700
759
|
return new Snapshot(taken, this.#client);
|
|
701
760
|
}
|
|
702
761
|
// ---- what Runtime does differently -----------------------------------
|
|
762
|
+
/** Refused: Vercel hands back a token scoped to this one shell, and
|
|
763
|
+
* Runtime's terminal opens with the account key, which must not leave the
|
|
764
|
+
* server. */
|
|
703
765
|
openInteractive() {
|
|
704
766
|
return Promise.reject(new NotSupportedError("Vercel's interactive shell (openInteractive)", "Use `await sandbox.withruntime.terminal({ cols, rows, onData })`, or `npx withruntime sandbox shell <id>`."));
|
|
705
767
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
768
|
+
/** The user commands run as, `runtime`, and its group. */
|
|
769
|
+
getDefaultUser(_opts = {}) {
|
|
770
|
+
this.#defaultUser ??= Promise.resolve({ username: "runtime", group: "runtime" });
|
|
771
|
+
return this.#defaultUser;
|
|
772
|
+
}
|
|
773
|
+
/** Makes a Linux user with its own home, as Vercel does: `useradd -m`,
|
|
774
|
+
* then the home given to the user and the sandbox's group, mode 770. */
|
|
775
|
+
async createUser(username, opts = {}) {
|
|
776
|
+
validateName(username, "username");
|
|
777
|
+
const { group } = await this.getDefaultUser(opts);
|
|
778
|
+
const signal = opts.signal;
|
|
779
|
+
const useradd = await this.runCommand({
|
|
780
|
+
cmd: "useradd",
|
|
781
|
+
args: ["-m", "-s", "/bin/bash", username],
|
|
782
|
+
sudo: true,
|
|
783
|
+
...(signal ? { signal } : {}),
|
|
784
|
+
});
|
|
785
|
+
if (useradd.exitCode !== 0) {
|
|
786
|
+
if (useradd.exitCode === 9)
|
|
787
|
+
throw new SandboxUserAlreadyExistsError(username);
|
|
788
|
+
throw new Error(`Failed to create user "${username}": ${await useradd.stderr()}`);
|
|
789
|
+
}
|
|
790
|
+
const home = `/home/${username}`;
|
|
791
|
+
await rootOrThrow(this, "chown", [`${username}:${group}`, home], signal, () => `Failed to set ownership on ${home}`);
|
|
792
|
+
await rootOrThrow(this, "chmod", ["770", home], signal, () => `Failed to set permissions on ${home}`);
|
|
793
|
+
return new SandboxUser({ sandbox: this, username });
|
|
794
|
+
}
|
|
795
|
+
/** A handle for an existing user, such as `root`. */
|
|
796
|
+
asUser(username) {
|
|
797
|
+
validateName(username, "username");
|
|
798
|
+
return new SandboxUser({ sandbox: this, username });
|
|
799
|
+
}
|
|
800
|
+
/** Makes a group and its shared directory, `/shared/<group>`, setgid 2770. */
|
|
801
|
+
async createGroup(groupname, opts = {}) {
|
|
802
|
+
validateName(groupname, "group name");
|
|
803
|
+
const { username } = await this.getDefaultUser(opts);
|
|
804
|
+
const sharedDir = `/shared/${groupname}`;
|
|
805
|
+
const steps = [
|
|
806
|
+
["groupadd", [groupname], `Failed to create group "${groupname}"`],
|
|
807
|
+
["mkdir", ["-p", sharedDir], `Failed to create shared directory ${sharedDir}`],
|
|
808
|
+
["chown", [`${username}:${groupname}`, sharedDir], `Failed to set ownership on ${sharedDir}`],
|
|
809
|
+
["chmod", ["2770", sharedDir], `Failed to set permissions on ${sharedDir}`],
|
|
810
|
+
];
|
|
811
|
+
for (const [cmd, args, what] of steps)
|
|
812
|
+
await rootOrThrow(this, cmd, args, opts.signal, () => what);
|
|
813
|
+
return { groupname, sharedDir };
|
|
717
814
|
}
|
|
718
|
-
addUserToGroup() {
|
|
719
|
-
|
|
815
|
+
async addUserToGroup(username, groupname, opts = {}) {
|
|
816
|
+
validateName(username, "username");
|
|
817
|
+
validateName(groupname, "group name");
|
|
818
|
+
await rootOrThrow(this, "usermod", ["-aG", groupname, username], opts.signal, () => `Failed to add "${username}" to group "${groupname}"`);
|
|
720
819
|
}
|
|
721
|
-
removeUserFromGroup() {
|
|
722
|
-
|
|
820
|
+
async removeUserFromGroup(username, groupname, opts = {}) {
|
|
821
|
+
validateName(username, "username");
|
|
822
|
+
validateName(groupname, "group name");
|
|
823
|
+
await rootOrThrow(this, "gpasswd", ["-d", username, groupname], opts.signal, () => `Failed to remove "${username}" from group "${groupname}"`);
|
|
723
824
|
}
|
|
724
825
|
currentSession() {
|
|
725
826
|
throw new NotSupportedError("Vercel sessions (currentSession)", "A Runtime sandbox is its own session: call the methods on the sandbox.");
|
|
@@ -731,12 +832,6 @@ export class Sandbox {
|
|
|
731
832
|
return Promise.reject(new NotSupportedError("Listing a sandbox's snapshots from the sandbox", "Use Snapshot.list({ name }) or runtime.snapshots.list({ sandboxId })."));
|
|
732
833
|
}
|
|
733
834
|
}
|
|
734
|
-
function usersError() {
|
|
735
|
-
return new NotSupportedError("Extra Linux users and groups (createUser, asUser, groups)", "Commands run as the sandbox owner with passwordless sudo; run `sudo useradd ...` and `sudo -u <user> ...` with runCommand.");
|
|
736
|
-
}
|
|
737
|
-
function usersRefused() {
|
|
738
|
-
return Promise.reject(usersError());
|
|
739
|
-
}
|
|
740
835
|
/** Milliseconds as whole days for Runtime's retention: 1 to 365, and 0 (no
|
|
741
836
|
* expiration) as 365. */
|
|
742
837
|
export function retentionDays(ms) {
|
|
@@ -791,6 +886,70 @@ function itemOf(runtime) {
|
|
|
791
886
|
tags: { ...info.labels },
|
|
792
887
|
};
|
|
793
888
|
}
|
|
889
|
+
const CURSOR = /^rt\.([0-9]+)$/;
|
|
890
|
+
/** Sandbox.list with a name prefix, time bounds, sorting or a cursor:
|
|
891
|
+
* applied here over every sandbox the key sees, since Runtime's list filters
|
|
892
|
+
* only by labels and name. The cursor is this adapter's own, an offset into
|
|
893
|
+
* the same query. */
|
|
894
|
+
async function listHere(client, params) {
|
|
895
|
+
let offset = 0;
|
|
896
|
+
if (params.cursor !== undefined) {
|
|
897
|
+
const match = CURSOR.exec(params.cursor);
|
|
898
|
+
if (!match)
|
|
899
|
+
throw new RangeError(`The cursor "${params.cursor}" was not made by Sandbox.list here; pass pagination.next from an earlier page of the same query.`);
|
|
900
|
+
offset = Number(match[1]);
|
|
901
|
+
}
|
|
902
|
+
if (params.sortBy !== undefined &&
|
|
903
|
+
!["createdAt", "name", "statusUpdatedAt"].includes(params.sortBy))
|
|
904
|
+
throw new RangeError("sortBy must be createdAt, name or statusUpdatedAt.");
|
|
905
|
+
if (params.sortOrder !== undefined && !["asc", "desc"].includes(params.sortOrder))
|
|
906
|
+
throw new RangeError("sortOrder must be asc or desc.");
|
|
907
|
+
const page = await guard(() => client.sandboxes.list(params.tags ? { labels: params.tags } : {}, signalOf(params)));
|
|
908
|
+
const all = (await guard(() => page.toArray(10_000))).map(itemOf);
|
|
909
|
+
params.signal?.throwIfAborted();
|
|
910
|
+
const time = (value) => value === undefined ? undefined : value instanceof Date ? value.getTime() : value;
|
|
911
|
+
const since = time(params.since);
|
|
912
|
+
const until = time(params.until);
|
|
913
|
+
const items = all.filter((one) => (params.namePrefix === undefined || one.name.startsWith(params.namePrefix)) &&
|
|
914
|
+
(since === undefined || one.createdAt >= since) &&
|
|
915
|
+
(until === undefined || one.createdAt <= until));
|
|
916
|
+
const by = params.sortBy ?? "createdAt";
|
|
917
|
+
const key = (one) => by === "name" ? one.name : by === "statusUpdatedAt" ? one.updatedAt : one.createdAt;
|
|
918
|
+
const direction = params.sortOrder === "desc" ? -1 : 1;
|
|
919
|
+
items.sort((a, b) => {
|
|
920
|
+
const [x, y] = [key(a), key(b)];
|
|
921
|
+
return (x < y ? -1 : x > y ? 1 : 0) * direction;
|
|
922
|
+
});
|
|
923
|
+
const size = Math.max(1, Math.min(params.limit ?? 50, 100));
|
|
924
|
+
const pageAt = (from) => {
|
|
925
|
+
const sandboxes = items.slice(from, from + size);
|
|
926
|
+
return {
|
|
927
|
+
sandboxes,
|
|
928
|
+
pagination: {
|
|
929
|
+
count: sandboxes.length,
|
|
930
|
+
next: from + size < items.length ? `rt.${from + size}` : null,
|
|
931
|
+
},
|
|
932
|
+
};
|
|
933
|
+
};
|
|
934
|
+
return Object.assign(pageAt(offset), {
|
|
935
|
+
async *pages() {
|
|
936
|
+
for (let from = offset; from === offset || from < items.length; from += size) {
|
|
937
|
+
params.signal?.throwIfAborted();
|
|
938
|
+
yield pageAt(from);
|
|
939
|
+
}
|
|
940
|
+
},
|
|
941
|
+
async *[Symbol.asyncIterator]() {
|
|
942
|
+
for (const item of items.slice(offset)) {
|
|
943
|
+
params.signal?.throwIfAborted();
|
|
944
|
+
yield item;
|
|
945
|
+
}
|
|
946
|
+
},
|
|
947
|
+
async toArray() {
|
|
948
|
+
params.signal?.throwIfAborted();
|
|
949
|
+
return items.slice(offset);
|
|
950
|
+
},
|
|
951
|
+
});
|
|
952
|
+
}
|
|
794
953
|
/** Vercel's paginator over a Runtime page: the first page's fields, async
|
|
795
954
|
* iteration over every item, `pages()` and `toArray()`. */
|
|
796
955
|
function paginate(first, signal) {
|