Skip to content

Commit f086bb8

Browse files
authored
feat: add more advanced pagination options (#39)
* feat: add more advanced pagination options * feat: pagination in list api
1 parent 1e9e19c commit f086bb8

File tree

4 files changed

+131
-36
lines changed

4 files changed

+131
-36
lines changed

src/bin/commands/sandbox/index.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import type { CommandModule } from "yargs";
22
import { forkSandbox } from "./fork";
33
import { hibernateSandbox } from "./hibernate";
4-
import { DEFAULT_LIMIT, listSandboxes } from "./list";
4+
import { listSandboxes } from "./list";
55
import { shutdownSandbox } from "./shutdown";
66

7+
const DEFAULT_LIMIT = 100;
8+
79
export const sandboxCommand: CommandModule = {
810
command: "sandbox",
911
describe: "Manage sandboxes",
@@ -68,13 +70,18 @@ export const sandboxCommand: CommandModule = {
6870
{
6971
tags: argv.tags?.split(","),
7072
status: argv.status as "running" | undefined,
71-
page: argv.page as number | undefined,
72-
pageSize: argv["page-size"] as number | undefined,
7373
orderBy: argv["order-by"] as
7474
| "inserted_at"
7575
| "updated_at"
7676
| undefined,
7777
direction: argv.direction as "asc" | "desc" | undefined,
78+
pagination:
79+
argv.page || argv["page-size"]
80+
? {
81+
page: argv.page,
82+
pageSize: argv["page-size"],
83+
}
84+
: undefined,
7885
},
7986
argv["headers"] as boolean,
8087
argv.limit as number | undefined

src/bin/commands/sandbox/list.ts

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import ora from "ora";
22
import Table from "cli-table3";
33
import { CodeSandbox } from "../../../";
4-
import type { SandboxListOpts, SandboxInfo } from "../../../sandbox-client";
5-
6-
export const DEFAULT_LIMIT = 100;
4+
import type {
5+
SandboxListOpts,
6+
SandboxInfo,
7+
PaginationOpts,
8+
} from "../../../sandbox-client";
79

810
type OutputFormat = {
911
field: string;
@@ -41,41 +43,59 @@ function formatAge(date: Date): string {
4143

4244
export async function listSandboxes(
4345
outputFields?: string,
44-
listOpts: SandboxListOpts = {},
46+
listOpts: SandboxListOpts & { pagination?: PaginationOpts } = {},
4547
showHeaders = true,
46-
limit = DEFAULT_LIMIT
48+
limit?: number
4749
) {
4850
const sdk = new CodeSandbox();
4951
const spinner = ora("Fetching sandboxes...").start();
5052

5153
try {
5254
let allSandboxes: SandboxInfo[] = [];
53-
let currentPage = listOpts.page || 1;
54-
const pageSize = listOpts.pageSize || 20;
55+
let totalCount = 0;
56+
let currentPage = 1;
57+
const pageSize = 50; // API's maximum page size
5558

56-
// Keep fetching until we hit the limit or run out of sandboxes
5759
while (true) {
58-
const { sandboxes, pagination } = await sdk.sandbox.list({
60+
const {
61+
sandboxes,
62+
totalCount: total,
63+
pagination,
64+
} = await sdk.sandbox.list({
5965
...listOpts,
60-
page: currentPage,
61-
pageSize,
66+
limit: undefined, // Force pagination so we can show progress
67+
pagination: {
68+
page: currentPage,
69+
pageSize,
70+
},
6271
});
6372

64-
allSandboxes = [...allSandboxes, ...sandboxes];
65-
66-
// Stop if we've hit the limit
67-
if (allSandboxes.length >= limit) {
68-
allSandboxes = allSandboxes.slice(0, limit);
73+
if (sandboxes.length === 0) {
6974
break;
7075
}
7176

72-
// Stop if there are no more pages
73-
if (!pagination.nextPage) {
77+
totalCount = total;
78+
const newSandboxes = sandboxes.filter(
79+
(sandbox) =>
80+
!allSandboxes.some((existing) => existing.id === sandbox.id)
81+
);
82+
allSandboxes = [...allSandboxes, ...newSandboxes];
83+
84+
spinner.text = `Fetching sandboxes... (${allSandboxes.length}${
85+
limit ? `/${Math.min(limit, totalCount)}` : `/${totalCount}`
86+
})`;
87+
88+
// Stop if we've reached the total count
89+
if (allSandboxes.length >= totalCount) {
7490
break;
7591
}
7692

77-
currentPage = pagination.nextPage;
78-
spinner.text = `Fetching sandboxes... (${allSandboxes.length}/${limit})`;
93+
currentPage++;
94+
}
95+
96+
// Apply limit after fetching all sandboxes
97+
if (limit) {
98+
allSandboxes = allSandboxes.slice(0, limit);
7999
}
80100

81101
spinner.stop();
@@ -103,6 +123,12 @@ export async function listSandboxes(
103123
// eslint-disable-next-line no-console
104124
console.log(values.join("\t"));
105125
});
126+
127+
// eslint-disable-next-line no-console
128+
console.log(
129+
`\nShowing ${allSandboxes.length} of ${totalCount} sandboxes`
130+
);
131+
106132
return;
107133
}
108134

@@ -149,6 +175,13 @@ export async function listSandboxes(
149175

150176
// eslint-disable-next-line no-console
151177
console.log(table.toString());
178+
179+
if (limit && totalCount > allSandboxes.length) {
180+
// eslint-disable-next-line no-console
181+
console.log(
182+
`\nShowing ${allSandboxes.length} of ${totalCount} sandboxes`
183+
);
184+
}
152185
} catch (error) {
153186
spinner.fail("Failed to fetch sandboxes");
154187
throw error;

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
VMTier,
99
SandboxListOpts,
1010
SandboxInfo,
11+
PaginationOpts,
1112
} from "./sandbox-client";
1213

1314
export {
@@ -17,6 +18,7 @@ export {
1718
VMTier,
1819
SandboxListOpts,
1920
SandboxInfo,
21+
PaginationOpts,
2022
};
2123
export * from "./sandbox";
2224

src/sandbox-client.ts

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,27 @@ export type SandboxInfo = {
3131

3232
export type SandboxListOpts = {
3333
tags?: string[];
34-
page?: number;
35-
pageSize?: number;
3634
orderBy?: "inserted_at" | "updated_at";
3735
direction?: "asc" | "desc";
3836
status?: "running";
3937
};
4038

39+
export interface SandboxListResponse {
40+
sandboxes: SandboxInfo[];
41+
hasMore: boolean;
42+
totalCount: number;
43+
pagination: {
44+
currentPage: number;
45+
nextPage: number | null;
46+
pageSize: number;
47+
};
48+
}
49+
50+
export type PaginationOpts = {
51+
page?: number;
52+
pageSize?: number;
53+
};
54+
4155
export const DEFAULT_SUBSCRIPTIONS = {
4256
client: {
4357
status: true,
@@ -376,19 +390,47 @@ export class SandboxClient {
376390

377391
/**
378392
* List sandboxes from the current workspace with optional filters.
379-
* By default, returns up to 100 sandboxes.
393+
*
394+
* This method supports two modes of operation:
395+
* 1. Simple limit-based fetching (default):
396+
* ```ts
397+
* // Get up to 100 sandboxes (default)
398+
* const { sandboxes, totalCount } = await client.list();
399+
*
400+
* // Get up to 200 sandboxes
401+
* const { sandboxes, totalCount } = await client.list({ limit: 200 });
402+
* ```
403+
*
404+
* 2. Manual pagination:
405+
* ```ts
406+
* // Get first page
407+
* const { sandboxes, pagination } = await client.list({
408+
* pagination: { page: 1, pageSize: 50 }
409+
* });
410+
* // pagination = { currentPage: 1, nextPage: 2, pageSize: 50 }
411+
*
412+
* // Get next page if available
413+
* if (pagination.nextPage) {
414+
* const { sandboxes, pagination: nextPagination } = await client.list({
415+
* pagination: { page: pagination.nextPage, pageSize: 50 }
416+
* });
417+
* }
418+
* ```
380419
*/
381420
async list(
382-
opts: Omit<SandboxListOpts, "page" | "pageSize"> & { limit?: number } = {}
383-
): Promise<{
384-
sandboxes: SandboxInfo[];
385-
}> {
421+
opts: SandboxListOpts & {
422+
limit?: number;
423+
pagination?: PaginationOpts;
424+
} = {}
425+
): Promise<SandboxListResponse> {
386426
const limit = opts.limit ?? 100;
387-
const pageSize = 50; // API's maximum page size
388427
let allSandboxes: SandboxInfo[] = [];
389-
let currentPage = 1;
428+
let currentPage = opts.pagination?.page ?? 1;
429+
let pageSize = opts.pagination?.pageSize ?? 50;
430+
let totalCount = 0;
431+
let nextPage: number | null = null;
390432

391-
while (allSandboxes.length < limit) {
433+
while (true) {
392434
const response = await sandboxList({
393435
client: this.apiClient,
394436
query: {
@@ -402,6 +444,8 @@ export class SandboxClient {
402444
});
403445

404446
const info = handleResponse(response, "Failed to list sandboxes");
447+
totalCount = info.pagination.total_records;
448+
nextPage = info.pagination.next_page;
405449

406450
const sandboxes = info.sandboxes.map((sandbox) => ({
407451
id: sandbox.id,
@@ -416,15 +460,24 @@ export class SandboxClient {
416460
allSandboxes = [...allSandboxes, ...sandboxes];
417461

418462
// Stop if we've hit the limit or there are no more pages
419-
if (!info.pagination.next_page || allSandboxes.length >= limit) {
463+
if (!nextPage || allSandboxes.length >= limit) {
420464
allSandboxes = allSandboxes.slice(0, limit);
421465
break;
422466
}
423467

424-
currentPage = info.pagination.next_page;
468+
currentPage = nextPage;
425469
}
426470

427-
return { sandboxes: allSandboxes };
471+
return {
472+
sandboxes: allSandboxes,
473+
hasMore: totalCount > allSandboxes.length,
474+
totalCount,
475+
pagination: {
476+
currentPage,
477+
nextPage: allSandboxes.length >= limit ? nextPage : null,
478+
pageSize,
479+
},
480+
};
428481
}
429482

430483
/**

0 commit comments

Comments
 (0)