@blaxel/core 0.2.96-preview.203 → 0.2.96-preview.205
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 +37 -2
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/client/sdk.gen.js +113 -2
- package/dist/cjs/common/pagination.js +92 -0
- package/dist/cjs/common/pagination.test.js +81 -0
- package/dist/cjs/common/settings.js +3 -3
- package/dist/cjs/common/settings.test.js +3 -3
- package/dist/cjs/drive/index.js +39 -4
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/jobs/jobs.js +42 -10
- package/dist/cjs/sandbox/client/sdk.gen.js +2 -2
- package/dist/cjs/sandbox/index.js +1 -0
- package/dist/cjs/sandbox/preview.js +8 -3
- package/dist/cjs/sandbox/sandbox.js +43 -33
- package/dist/cjs/sandbox/schedule.js +91 -0
- package/dist/cjs/types/client/sdk.gen.d.ts +31 -1
- package/dist/cjs/types/client/types.gen.d.ts +347 -0
- package/dist/cjs/types/common/pagination.d.ts +35 -0
- package/dist/cjs/types/common/pagination.test.d.ts +1 -0
- package/dist/cjs/types/drive/index.d.ts +35 -5
- package/dist/cjs/types/index.d.ts +1 -0
- package/dist/cjs/types/jobs/jobs.d.ts +33 -3
- package/dist/cjs/types/sandbox/client/sdk.gen.d.ts +2 -2
- package/dist/cjs/types/sandbox/client/types.gen.d.ts +28 -1
- package/dist/cjs/types/sandbox/index.d.ts +1 -0
- package/dist/cjs/types/sandbox/preview.d.ts +2 -2
- package/dist/cjs/types/sandbox/sandbox.d.ts +38 -3
- package/dist/cjs/types/sandbox/schedule.d.ts +16 -0
- package/dist/cjs/types/volume/index.d.ts +37 -4
- package/dist/cjs/volume/index.js +41 -4
- package/dist/cjs-browser/.tsbuildinfo +1 -1
- package/dist/cjs-browser/client/sdk.gen.js +113 -2
- package/dist/cjs-browser/common/pagination.js +92 -0
- package/dist/cjs-browser/common/pagination.test.js +81 -0
- package/dist/cjs-browser/common/settings.js +3 -3
- package/dist/cjs-browser/common/settings.test.js +3 -3
- package/dist/cjs-browser/drive/index.js +39 -4
- package/dist/cjs-browser/index.js +1 -0
- package/dist/cjs-browser/jobs/jobs.js +42 -10
- package/dist/cjs-browser/sandbox/client/sdk.gen.js +2 -2
- package/dist/cjs-browser/sandbox/index.js +1 -0
- package/dist/cjs-browser/sandbox/preview.js +8 -3
- package/dist/cjs-browser/sandbox/sandbox.js +43 -33
- package/dist/cjs-browser/sandbox/schedule.js +91 -0
- package/dist/cjs-browser/types/client/sdk.gen.d.ts +31 -1
- package/dist/cjs-browser/types/client/types.gen.d.ts +347 -0
- package/dist/cjs-browser/types/common/pagination.d.ts +35 -0
- package/dist/cjs-browser/types/common/pagination.test.d.ts +1 -0
- package/dist/cjs-browser/types/drive/index.d.ts +35 -5
- package/dist/cjs-browser/types/index.d.ts +1 -0
- package/dist/cjs-browser/types/jobs/jobs.d.ts +33 -3
- package/dist/cjs-browser/types/sandbox/client/sdk.gen.d.ts +2 -2
- package/dist/cjs-browser/types/sandbox/client/types.gen.d.ts +28 -1
- package/dist/cjs-browser/types/sandbox/index.d.ts +1 -0
- package/dist/cjs-browser/types/sandbox/preview.d.ts +2 -2
- package/dist/cjs-browser/types/sandbox/sandbox.d.ts +38 -3
- package/dist/cjs-browser/types/sandbox/schedule.d.ts +16 -0
- package/dist/cjs-browser/types/volume/index.d.ts +37 -4
- package/dist/cjs-browser/volume/index.js +41 -4
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/client/sdk.gen.js +104 -0
- package/dist/esm/common/pagination.js +88 -0
- package/dist/esm/common/pagination.test.js +79 -0
- package/dist/esm/common/settings.js +3 -3
- package/dist/esm/common/settings.test.js +3 -3
- package/dist/esm/drive/index.js +39 -4
- package/dist/esm/index.js +1 -0
- package/dist/esm/jobs/jobs.js +42 -10
- package/dist/esm/sandbox/client/sdk.gen.js +2 -2
- package/dist/esm/sandbox/index.js +1 -0
- package/dist/esm/sandbox/preview.js +8 -3
- package/dist/esm/sandbox/sandbox.js +43 -33
- package/dist/esm/sandbox/schedule.js +87 -0
- package/dist/esm/volume/index.js +41 -4
- package/dist/esm-browser/.tsbuildinfo +1 -1
- package/dist/esm-browser/client/sdk.gen.js +104 -0
- package/dist/esm-browser/common/pagination.js +88 -0
- package/dist/esm-browser/common/pagination.test.js +79 -0
- package/dist/esm-browser/common/settings.js +3 -3
- package/dist/esm-browser/common/settings.test.js +3 -3
- package/dist/esm-browser/drive/index.js +39 -4
- package/dist/esm-browser/index.js +1 -0
- package/dist/esm-browser/jobs/jobs.js +42 -10
- package/dist/esm-browser/sandbox/client/sdk.gen.js +2 -2
- package/dist/esm-browser/sandbox/index.js +1 -0
- package/dist/esm-browser/sandbox/preview.js +8 -3
- package/dist/esm-browser/sandbox/sandbox.js +43 -33
- package/dist/esm-browser/sandbox/schedule.js +87 -0
- package/dist/esm-browser/volume/index.js +41 -4
- package/package.json +1 -1
|
@@ -1678,6 +1678,110 @@ export const deleteSandboxPreviewToken = (options) => {
|
|
|
1678
1678
|
...options
|
|
1679
1679
|
});
|
|
1680
1680
|
};
|
|
1681
|
+
/**
|
|
1682
|
+
* List Sandbox Schedule Executions
|
|
1683
|
+
* Returns the execution history of a Sandbox's schedules (across all schedules of the sandbox), newest first. Cursor-paginated via the `cursor` and `limit` query parameters. Each item records the HTTP status of submitting the scheduled command and the process name for log lookup.
|
|
1684
|
+
*/
|
|
1685
|
+
export const listSandboxScheduleExecutions = (options) => {
|
|
1686
|
+
return (options.client ?? _heyApiClient).get({
|
|
1687
|
+
security: [
|
|
1688
|
+
{
|
|
1689
|
+
scheme: 'bearer',
|
|
1690
|
+
type: 'http'
|
|
1691
|
+
}
|
|
1692
|
+
],
|
|
1693
|
+
url: '/sandboxes/{sandboxName}/schedule-executions',
|
|
1694
|
+
...options
|
|
1695
|
+
});
|
|
1696
|
+
};
|
|
1697
|
+
/**
|
|
1698
|
+
* List Sandbox Schedules
|
|
1699
|
+
* Returns the schedules configured on a Sandbox. Starting with API version 2026-04-28 the response is wrapped in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older versions return a bare array.
|
|
1700
|
+
*/
|
|
1701
|
+
export const listSandboxSchedules = (options) => {
|
|
1702
|
+
return (options.client ?? _heyApiClient).get({
|
|
1703
|
+
security: [
|
|
1704
|
+
{
|
|
1705
|
+
scheme: 'bearer',
|
|
1706
|
+
type: 'http'
|
|
1707
|
+
}
|
|
1708
|
+
],
|
|
1709
|
+
url: '/sandboxes/{sandboxName}/schedules',
|
|
1710
|
+
...options
|
|
1711
|
+
});
|
|
1712
|
+
};
|
|
1713
|
+
/**
|
|
1714
|
+
* Create Sandbox Schedule
|
|
1715
|
+
* Adds a schedule to a Sandbox.
|
|
1716
|
+
*/
|
|
1717
|
+
export const createSandboxSchedule = (options) => {
|
|
1718
|
+
return (options.client ?? _heyApiClient).post({
|
|
1719
|
+
security: [
|
|
1720
|
+
{
|
|
1721
|
+
scheme: 'bearer',
|
|
1722
|
+
type: 'http'
|
|
1723
|
+
}
|
|
1724
|
+
],
|
|
1725
|
+
url: '/sandboxes/{sandboxName}/schedules',
|
|
1726
|
+
...options,
|
|
1727
|
+
headers: {
|
|
1728
|
+
'Content-Type': 'application/json',
|
|
1729
|
+
...options?.headers
|
|
1730
|
+
}
|
|
1731
|
+
});
|
|
1732
|
+
};
|
|
1733
|
+
/**
|
|
1734
|
+
* Delete Sandbox Schedule
|
|
1735
|
+
* Deletes a Sandbox Schedule by id.
|
|
1736
|
+
*/
|
|
1737
|
+
export const deleteSandboxSchedule = (options) => {
|
|
1738
|
+
return (options.client ?? _heyApiClient).delete({
|
|
1739
|
+
security: [
|
|
1740
|
+
{
|
|
1741
|
+
scheme: 'bearer',
|
|
1742
|
+
type: 'http'
|
|
1743
|
+
}
|
|
1744
|
+
],
|
|
1745
|
+
url: '/sandboxes/{sandboxName}/schedules/{scheduleId}',
|
|
1746
|
+
...options
|
|
1747
|
+
});
|
|
1748
|
+
};
|
|
1749
|
+
/**
|
|
1750
|
+
* Get Sandbox Schedule
|
|
1751
|
+
* Returns a Sandbox Schedule by id.
|
|
1752
|
+
*/
|
|
1753
|
+
export const getSandboxSchedule = (options) => {
|
|
1754
|
+
return (options.client ?? _heyApiClient).get({
|
|
1755
|
+
security: [
|
|
1756
|
+
{
|
|
1757
|
+
scheme: 'bearer',
|
|
1758
|
+
type: 'http'
|
|
1759
|
+
}
|
|
1760
|
+
],
|
|
1761
|
+
url: '/sandboxes/{sandboxName}/schedules/{scheduleId}',
|
|
1762
|
+
...options
|
|
1763
|
+
});
|
|
1764
|
+
};
|
|
1765
|
+
/**
|
|
1766
|
+
* Update Sandbox Schedule
|
|
1767
|
+
* Updates a Sandbox Schedule by id.
|
|
1768
|
+
*/
|
|
1769
|
+
export const updateSandboxSchedule = (options) => {
|
|
1770
|
+
return (options.client ?? _heyApiClient).put({
|
|
1771
|
+
security: [
|
|
1772
|
+
{
|
|
1773
|
+
scheme: 'bearer',
|
|
1774
|
+
type: 'http'
|
|
1775
|
+
}
|
|
1776
|
+
],
|
|
1777
|
+
url: '/sandboxes/{sandboxName}/schedules/{scheduleId}',
|
|
1778
|
+
...options,
|
|
1779
|
+
headers: {
|
|
1780
|
+
'Content-Type': 'application/json',
|
|
1781
|
+
...options?.headers
|
|
1782
|
+
}
|
|
1783
|
+
});
|
|
1784
|
+
};
|
|
1681
1785
|
/**
|
|
1682
1786
|
* Get sandbox by external ID
|
|
1683
1787
|
* Returns the most recent non-terminated sandbox matching the given external ID. If no active sandbox is found, returns 404.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export function unwrapListData(response) {
|
|
2
|
+
if (!response) {
|
|
3
|
+
return [];
|
|
4
|
+
}
|
|
5
|
+
if (Array.isArray(response)) {
|
|
6
|
+
return response;
|
|
7
|
+
}
|
|
8
|
+
return response.data ?? [];
|
|
9
|
+
}
|
|
10
|
+
function unwrapListMeta(response) {
|
|
11
|
+
if (!response || Array.isArray(response)) {
|
|
12
|
+
return { hasMore: false };
|
|
13
|
+
}
|
|
14
|
+
return response.meta ?? { hasMore: false };
|
|
15
|
+
}
|
|
16
|
+
export async function createPaginatedList({ response, fetchPage, mapItem, query, seenCursors, }) {
|
|
17
|
+
const meta = unwrapListMeta(response);
|
|
18
|
+
const data = await Promise.all(unwrapListData(response).map(mapItem));
|
|
19
|
+
const cursors = new Set(seenCursors);
|
|
20
|
+
if (query?.cursor) {
|
|
21
|
+
cursors.add(query.cursor);
|
|
22
|
+
}
|
|
23
|
+
const list = {
|
|
24
|
+
data,
|
|
25
|
+
meta,
|
|
26
|
+
// Derive `hasMore` from cursor presence, not `meta.hasMore`: `nextPage()`
|
|
27
|
+
// can only advance when there is a `nextCursor`, so a consumer doing
|
|
28
|
+
// `if (page.hasMore) await page.nextPage()` must never see `hasMore: true`
|
|
29
|
+
// while `nextPage()` returns null. Keeping the two in sync prevents silent
|
|
30
|
+
// truncation if the API ever sends `hasMore: true` without a cursor.
|
|
31
|
+
get hasMore() {
|
|
32
|
+
return Boolean(list.nextCursor);
|
|
33
|
+
},
|
|
34
|
+
get nextCursor() {
|
|
35
|
+
return meta.nextCursor || undefined;
|
|
36
|
+
},
|
|
37
|
+
async nextPage() {
|
|
38
|
+
const cursor = list.nextCursor;
|
|
39
|
+
if (!cursor) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (cursors.has(cursor)) {
|
|
43
|
+
throw new Error("Pagination returned a repeated cursor");
|
|
44
|
+
}
|
|
45
|
+
const nextQuery = { ...(query ?? {}), cursor };
|
|
46
|
+
const nextSeenCursors = new Set(cursors);
|
|
47
|
+
nextSeenCursors.add(cursor);
|
|
48
|
+
return createPaginatedList({
|
|
49
|
+
response: await fetchPage(nextQuery),
|
|
50
|
+
fetchPage,
|
|
51
|
+
mapItem,
|
|
52
|
+
query: nextQuery,
|
|
53
|
+
seenCursors: nextSeenCursors,
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
async autoPagingEach(onItem) {
|
|
57
|
+
for await (const item of list) {
|
|
58
|
+
const shouldContinue = await onItem(item);
|
|
59
|
+
if (shouldContinue === false) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
async autoPagingToArray(options) {
|
|
65
|
+
if (!options || !Number.isFinite(options.limit) || options.limit <= 0) {
|
|
66
|
+
throw new Error("autoPagingToArray requires a positive limit");
|
|
67
|
+
}
|
|
68
|
+
const items = [];
|
|
69
|
+
for await (const item of list) {
|
|
70
|
+
items.push(item);
|
|
71
|
+
if (items.length >= options.limit) {
|
|
72
|
+
return items;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return items;
|
|
76
|
+
},
|
|
77
|
+
async *[Symbol.asyncIterator]() {
|
|
78
|
+
let page = list;
|
|
79
|
+
while (page) {
|
|
80
|
+
for (const item of page.data) {
|
|
81
|
+
yield item;
|
|
82
|
+
}
|
|
83
|
+
page = await page.nextPage();
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
return list;
|
|
88
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createPaginatedList, unwrapListData } from "./pagination.js";
|
|
3
|
+
describe("pagination helpers", () => {
|
|
4
|
+
it("unwraps legacy array and paginated list responses", () => {
|
|
5
|
+
expect(unwrapListData([{ name: "legacy" }])).toEqual([{ name: "legacy" }]);
|
|
6
|
+
expect(unwrapListData({
|
|
7
|
+
data: [{ name: "paginated" }],
|
|
8
|
+
meta: { hasMore: false },
|
|
9
|
+
})).toEqual([{ name: "paginated" }]);
|
|
10
|
+
});
|
|
11
|
+
it("returns page data and lets callers request the next page", async () => {
|
|
12
|
+
const cursors = [];
|
|
13
|
+
const fetchPage = async (query) => {
|
|
14
|
+
await Promise.resolve();
|
|
15
|
+
cursors.push(query?.cursor);
|
|
16
|
+
if (!query?.cursor) {
|
|
17
|
+
return {
|
|
18
|
+
data: ["first"],
|
|
19
|
+
meta: { hasMore: true, nextCursor: "next-page" },
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
data: ["second"],
|
|
24
|
+
meta: { hasMore: false },
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
const page = await createPaginatedList({
|
|
28
|
+
response: await fetchPage(),
|
|
29
|
+
fetchPage,
|
|
30
|
+
mapItem: (item) => item.toUpperCase(),
|
|
31
|
+
});
|
|
32
|
+
expect(page.data).toEqual(["FIRST"]);
|
|
33
|
+
expect(page.hasMore).toBe(true);
|
|
34
|
+
expect(page.nextCursor).toBe("next-page");
|
|
35
|
+
const nextPage = await page.nextPage();
|
|
36
|
+
expect(nextPage?.data).toEqual(["SECOND"]);
|
|
37
|
+
expect(cursors).toEqual([undefined, "next-page"]);
|
|
38
|
+
});
|
|
39
|
+
it("keeps hasMore consistent with nextPage when the API omits a cursor", async () => {
|
|
40
|
+
const fetchPage = async () => {
|
|
41
|
+
await Promise.resolve();
|
|
42
|
+
// API reports more data but gives no cursor to fetch it.
|
|
43
|
+
return {
|
|
44
|
+
data: ["only"],
|
|
45
|
+
meta: { hasMore: true },
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
const page = await createPaginatedList({
|
|
49
|
+
response: await fetchPage(),
|
|
50
|
+
fetchPage,
|
|
51
|
+
mapItem: (item) => item,
|
|
52
|
+
});
|
|
53
|
+
// hasMore must reflect what nextPage() can actually deliver.
|
|
54
|
+
expect(page.hasMore).toBe(false);
|
|
55
|
+
await expect(page.nextPage()).resolves.toBeNull();
|
|
56
|
+
await expect(page.autoPagingToArray({ limit: 10 })).resolves.toEqual(["only"]);
|
|
57
|
+
});
|
|
58
|
+
it("supports auto paging with an explicit limit", async () => {
|
|
59
|
+
const fetchPage = async (query) => {
|
|
60
|
+
await Promise.resolve();
|
|
61
|
+
if (!query?.cursor) {
|
|
62
|
+
return {
|
|
63
|
+
data: ["first"],
|
|
64
|
+
meta: { hasMore: true, nextCursor: "next-page" },
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
data: ["second"],
|
|
69
|
+
meta: { hasMore: false },
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
const page = await createPaginatedList({
|
|
73
|
+
response: await fetchPage(),
|
|
74
|
+
fetchPage,
|
|
75
|
+
mapItem: (item) => item,
|
|
76
|
+
});
|
|
77
|
+
await expect(page.autoPagingToArray({ limit: 2 })).resolves.toEqual(["first", "second"]);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -22,10 +22,10 @@ function missingCredentialsMessage() {
|
|
|
22
22
|
return "No Blaxel credentials found. Set the BL_API_KEY and BL_WORKSPACE environment variables, or run `bl login`.";
|
|
23
23
|
}
|
|
24
24
|
// Build info - these placeholders are replaced at build time by build:replace-imports
|
|
25
|
-
const BUILD_VERSION = "0.2.96-preview.
|
|
26
|
-
const BUILD_COMMIT = "
|
|
25
|
+
const BUILD_VERSION = "0.2.96-preview.205";
|
|
26
|
+
const BUILD_COMMIT = "b2439e6cde8dee15ff0ff166366670a4f48d8ade";
|
|
27
27
|
const BUILD_SENTRY_DSN = "https://fd5e60e1c9820e1eef5ccebb84a07127@o4508714045276160.ingest.us.sentry.io/4510465864564736";
|
|
28
|
-
const BLAXEL_API_VERSION = "2026-04-
|
|
28
|
+
const BLAXEL_API_VERSION = "2026-04-28";
|
|
29
29
|
// Cache for config.yaml tracking value
|
|
30
30
|
let configTrackingValue = null;
|
|
31
31
|
let configTrackingLoaded = false;
|
|
@@ -8,10 +8,10 @@ describe('Settings.apiVersion', () => {
|
|
|
8
8
|
afterEach(() => {
|
|
9
9
|
delete env.BL_API_VERSION;
|
|
10
10
|
});
|
|
11
|
-
it('defaults to 2026-04-
|
|
11
|
+
it('defaults to 2026-04-28 when BL_API_VERSION is not set', async () => {
|
|
12
12
|
delete env.BL_API_VERSION;
|
|
13
13
|
const { settings } = await import('./settings.js');
|
|
14
|
-
expect(settings.apiVersion).toBe('2026-04-
|
|
14
|
+
expect(settings.apiVersion).toBe('2026-04-28');
|
|
15
15
|
});
|
|
16
16
|
it('headers include Blaxel-Version set to the default', async () => {
|
|
17
17
|
delete env.BL_API_VERSION;
|
|
@@ -20,7 +20,7 @@ describe('Settings.apiVersion', () => {
|
|
|
20
20
|
const previous = settings.credentials;
|
|
21
21
|
settings.credentials = new ApiKey({ apiKey: 'test-key', workspace: 'test-ws' });
|
|
22
22
|
try {
|
|
23
|
-
expect(settings.headers['Blaxel-Version']).toBe('2026-04-
|
|
23
|
+
expect(settings.headers['Blaxel-Version']).toBe('2026-04-28');
|
|
24
24
|
}
|
|
25
25
|
finally {
|
|
26
26
|
settings.credentials = previous;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { v4 as uuidv4 } from "uuid";
|
|
2
2
|
import { createDrive, deleteDrive, getDrive, listDrives, updateDrive } from "../client/index.js";
|
|
3
|
+
import { createPaginatedList } from "../common/pagination.js";
|
|
3
4
|
import { settings } from "../common/settings.js";
|
|
4
5
|
export class DriveInstance {
|
|
5
6
|
drive;
|
|
@@ -87,10 +88,44 @@ export class DriveInstance {
|
|
|
87
88
|
});
|
|
88
89
|
return new DriveInstance(data);
|
|
89
90
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
/**
|
|
92
|
+
* List one page of drives.
|
|
93
|
+
*
|
|
94
|
+
* The returned page exposes `data` for the current page, `meta` for cursor
|
|
95
|
+
* metadata, and helpers to fetch more pages only when you need them.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* const page = await DriveInstance.list({ limit: 50 });
|
|
100
|
+
*
|
|
101
|
+
* for (const drive of page.data) {
|
|
102
|
+
* console.log(drive.name);
|
|
103
|
+
* }
|
|
104
|
+
*
|
|
105
|
+
* const nextPage = await page.nextPage();
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* const allDrives = await (await DriveInstance.list()).autoPagingToArray({
|
|
111
|
+
* limit: 1000,
|
|
112
|
+
* });
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
static async list(query) {
|
|
116
|
+
const fetchPage = async (pageQuery) => {
|
|
117
|
+
const { data } = await listDrives({
|
|
118
|
+
query: pageQuery,
|
|
119
|
+
throwOnError: true,
|
|
120
|
+
});
|
|
121
|
+
return data;
|
|
122
|
+
};
|
|
123
|
+
return createPaginatedList({
|
|
124
|
+
response: await fetchPage(query),
|
|
125
|
+
fetchPage,
|
|
126
|
+
mapItem: (drive) => new DriveInstance(drive),
|
|
127
|
+
query,
|
|
128
|
+
});
|
|
94
129
|
}
|
|
95
130
|
static async delete(driveName) {
|
|
96
131
|
const { data } = await deleteDrive({
|
|
@@ -7,6 +7,7 @@ export * from "./common/node.js";
|
|
|
7
7
|
export * from "./common/errors.js";
|
|
8
8
|
export * from "./common/internal.js";
|
|
9
9
|
export * from "./common/logger.js";
|
|
10
|
+
export * from "./common/pagination.js";
|
|
10
11
|
export * from "./common/settings.js";
|
|
11
12
|
export * from "./common/webhook.js";
|
|
12
13
|
export * from "./drive/index.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createJobExecution, deleteJobExecution, getJobExecution, listJobExecutions, } from "../client/index.js";
|
|
2
2
|
import { logger } from "../common/logger.js";
|
|
3
|
+
import { createPaginatedList } from "../common/pagination.js";
|
|
3
4
|
import { settings } from "../common/settings.js";
|
|
4
5
|
import { startSpan } from "../telemetry/telemetry.js";
|
|
5
6
|
class BlJob {
|
|
@@ -75,19 +76,50 @@ class BlJob {
|
|
|
75
76
|
return data;
|
|
76
77
|
}
|
|
77
78
|
/**
|
|
78
|
-
* List
|
|
79
|
+
* List one page of executions for this job.
|
|
80
|
+
*
|
|
81
|
+
* The returned page exposes `data` for the current page, `meta` for cursor
|
|
82
|
+
* metadata, and helpers to fetch more pages only when you need them.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* const job = blJob("daily-import");
|
|
87
|
+
* const page = await job.listExecutions({ limit: 50 });
|
|
88
|
+
*
|
|
89
|
+
* for (const execution of page.data) {
|
|
90
|
+
* console.log(execution.status);
|
|
91
|
+
* }
|
|
92
|
+
*
|
|
93
|
+
* const nextPage = await page.nextPage();
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* const job = blJob("daily-import");
|
|
99
|
+
* const executions = await (await job.listExecutions()).autoPagingToArray({
|
|
100
|
+
* limit: 1000,
|
|
101
|
+
* });
|
|
102
|
+
* ```
|
|
79
103
|
*/
|
|
80
|
-
async listExecutions() {
|
|
104
|
+
async listExecutions(query) {
|
|
81
105
|
logger.debug(`Listing executions for job: ${this.jobName}`);
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
106
|
+
const fetchPage = async (pageQuery) => {
|
|
107
|
+
const { data } = await listJobExecutions({
|
|
108
|
+
path: {
|
|
109
|
+
jobId: this.jobName,
|
|
110
|
+
},
|
|
111
|
+
query: pageQuery,
|
|
112
|
+
headers: settings.headers,
|
|
113
|
+
throwOnError: true,
|
|
114
|
+
});
|
|
115
|
+
return data;
|
|
116
|
+
};
|
|
117
|
+
return createPaginatedList({
|
|
118
|
+
response: await fetchPage(query),
|
|
119
|
+
fetchPage,
|
|
120
|
+
mapItem: (execution) => execution,
|
|
121
|
+
query,
|
|
88
122
|
});
|
|
89
|
-
const items = Array.isArray(data) ? data : (data?.data ?? []);
|
|
90
|
-
return items;
|
|
91
123
|
}
|
|
92
124
|
/**
|
|
93
125
|
* Get the status of a specific execution
|
|
@@ -86,7 +86,7 @@ export const getDrivesMount = (options) => {
|
|
|
86
86
|
};
|
|
87
87
|
/**
|
|
88
88
|
* Attach a drive to a local path
|
|
89
|
-
* Mounts an agent drive using the blfs binary to a local path, optionally mounting a subpath within the drive
|
|
89
|
+
* Mounts an agent drive using the blfs binary to a local path, optionally mounting a subpath within the drive. Supports optional UID/GID mapping to remap file ownership between the local sandbox and the filer (always mapped to filer UID/GID 0). Mapping values can be set per-request via uidMap/gidMap fields, or globally via BLFS_UID_MAP/BLFS_GID_MAP environment variables (request values take precedence).
|
|
90
90
|
*/
|
|
91
91
|
export const postDrivesMount = (options) => {
|
|
92
92
|
return (options.client ?? _heyApiClient).post({
|
|
@@ -607,7 +607,7 @@ export const getProcessByIdentifierLogsStream = (options) => {
|
|
|
607
607
|
* Triggers an upgrade of the sandbox-api process. Returns 200 immediately before upgrading.
|
|
608
608
|
* The upgrade will: download the specified binary from GitHub releases, validate it, and restart.
|
|
609
609
|
* All running processes will be preserved across the upgrade.
|
|
610
|
-
* Available versions: "
|
|
610
|
+
* Available versions: "latest" (default, most recent release), "develop", "main", or specific tag like "v1.0.0"
|
|
611
611
|
* You can also specify a custom baseUrl for forks (defaults to https://github.com/blaxel-ai/sandbox/releases)
|
|
612
612
|
*/
|
|
613
613
|
export const postUpgrade = (options) => {
|
|
@@ -5,6 +5,7 @@ export * from "./filesystem/index.js";
|
|
|
5
5
|
export * from "./codegen/index.js";
|
|
6
6
|
export { SandboxDrive } from "./drive/index.js";
|
|
7
7
|
export * from "./preview.js";
|
|
8
|
+
export * from "./schedule.js";
|
|
8
9
|
export * from "./session.js";
|
|
9
10
|
export * from "./sandbox.js";
|
|
10
11
|
export * from "./system.js";
|
|
@@ -99,24 +99,29 @@ export class SandboxPreviews {
|
|
|
99
99
|
});
|
|
100
100
|
return data.map((preview) => new SandboxPreview(preview));
|
|
101
101
|
}
|
|
102
|
-
async create(preview) {
|
|
102
|
+
async create(preview, force) {
|
|
103
|
+
const query = {};
|
|
104
|
+
if (force) {
|
|
105
|
+
query['force'] = 'true';
|
|
106
|
+
}
|
|
103
107
|
const { data } = await createSandboxPreview({
|
|
104
108
|
path: {
|
|
105
109
|
sandboxName: this.sandboxName,
|
|
106
110
|
},
|
|
111
|
+
query,
|
|
107
112
|
body: preview,
|
|
108
113
|
throwOnError: true,
|
|
109
114
|
});
|
|
110
115
|
return new SandboxPreview(data);
|
|
111
116
|
}
|
|
112
|
-
async createIfNotExists(preview) {
|
|
117
|
+
async createIfNotExists(preview, force) {
|
|
113
118
|
try {
|
|
114
119
|
const previewInstance = await this.get(preview.metadata.name);
|
|
115
120
|
return previewInstance;
|
|
116
121
|
}
|
|
117
122
|
catch (e) {
|
|
118
123
|
if (typeof e === "object" && e !== null && "code" in e && e.code === 404) {
|
|
119
|
-
return this.create(preview);
|
|
124
|
+
return this.create(preview, force);
|
|
120
125
|
}
|
|
121
126
|
throw e;
|
|
122
127
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { v4 as uuidv4 } from "uuid";
|
|
2
2
|
import { createSandbox, deleteSandbox, getSandbox, getSandboxByExternalId, listSandboxes, updateSandbox } from "../client/index.js";
|
|
3
3
|
import { logger } from "../common/logger.js";
|
|
4
|
+
import { createPaginatedList } from "../common/pagination.js";
|
|
4
5
|
import { settings } from "../common/settings.js";
|
|
5
6
|
import { SandboxCodegen } from "./codegen/index.js";
|
|
6
7
|
import { SandboxDrive } from "./drive/index.js";
|
|
@@ -8,6 +9,7 @@ import { SandboxFileSystem } from "./filesystem/index.js";
|
|
|
8
9
|
import { SandboxNetwork } from "./network/index.js";
|
|
9
10
|
import { SandboxPreviews } from "./preview.js";
|
|
10
11
|
import { SandboxProcess } from "./process/index.js";
|
|
12
|
+
import { SandboxSchedules } from "./schedule.js";
|
|
11
13
|
import { SandboxSessions } from "./session.js";
|
|
12
14
|
import { SandboxSystem } from "./system.js";
|
|
13
15
|
import { normalizeEnvs, normalizePorts, normalizeVolumes } from "./types.js";
|
|
@@ -41,6 +43,7 @@ export class SandboxInstance {
|
|
|
41
43
|
network;
|
|
42
44
|
process;
|
|
43
45
|
previews;
|
|
46
|
+
schedules;
|
|
44
47
|
sessions;
|
|
45
48
|
codegen;
|
|
46
49
|
system;
|
|
@@ -52,6 +55,7 @@ export class SandboxInstance {
|
|
|
52
55
|
this.fs = new SandboxFileSystem(sandbox, this.process);
|
|
53
56
|
this.network = new SandboxNetwork(sandbox);
|
|
54
57
|
this.previews = new SandboxPreviews(sandbox);
|
|
58
|
+
this.schedules = new SandboxSchedules(sandbox);
|
|
55
59
|
this.sessions = new SandboxSessions(sandbox);
|
|
56
60
|
this.codegen = new SandboxCodegen(sandbox);
|
|
57
61
|
this.system = new SandboxSystem(sandbox);
|
|
@@ -244,40 +248,46 @@ export class SandboxInstance {
|
|
|
244
248
|
const instance = new SandboxInstance(data);
|
|
245
249
|
return SandboxInstance.attachH2Session(instance);
|
|
246
250
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
+
/**
|
|
252
|
+
* List one page of sandboxes.
|
|
253
|
+
*
|
|
254
|
+
* The returned page exposes `data` for the current page, `meta` for cursor
|
|
255
|
+
* metadata, and helpers to fetch more pages only when you need them.
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* ```ts
|
|
259
|
+
* const page = await SandboxInstance.list({ limit: 50 });
|
|
260
|
+
*
|
|
261
|
+
* for (const sandbox of page.data) {
|
|
262
|
+
* console.log(sandbox.metadata.name);
|
|
263
|
+
* }
|
|
264
|
+
*
|
|
265
|
+
* const nextPage = await page.nextPage();
|
|
266
|
+
* ```
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```ts
|
|
270
|
+
* const page = await SandboxInstance.list({ limit: 100 });
|
|
271
|
+
*
|
|
272
|
+
* for await (const sandbox of page) {
|
|
273
|
+
* console.log(sandbox.metadata.name);
|
|
274
|
+
* }
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
static async list(query) {
|
|
278
|
+
const fetchPage = async (pageQuery) => {
|
|
279
|
+
const { data } = await listSandboxes({
|
|
280
|
+
query: pageQuery,
|
|
281
|
+
throwOnError: true,
|
|
282
|
+
});
|
|
283
|
+
return data;
|
|
284
|
+
};
|
|
285
|
+
return createPaginatedList({
|
|
286
|
+
response: await fetchPage(query),
|
|
287
|
+
fetchPage,
|
|
288
|
+
mapItem: (sandbox) => SandboxInstance.attachH2Session(new SandboxInstance(sandbox)),
|
|
289
|
+
query,
|
|
251
290
|
});
|
|
252
|
-
const items = (Array.isArray(raw) ? raw : (raw?.data ?? []));
|
|
253
|
-
const instances = items.map((sb) => new SandboxInstance(sb));
|
|
254
|
-
if (!settings.disableH2) {
|
|
255
|
-
const { h2Pool } = await import("../common/h2pool.js");
|
|
256
|
-
const uniqueDomains = [
|
|
257
|
-
...new Set(instances
|
|
258
|
-
.map((i) => SandboxInstance.edgeDomainForRegion(i.spec?.region))
|
|
259
|
-
.filter((d) => d !== null)),
|
|
260
|
-
];
|
|
261
|
-
const sessionByDomain = new Map();
|
|
262
|
-
await Promise.all(uniqueDomains.map(async (domain) => {
|
|
263
|
-
try {
|
|
264
|
-
sessionByDomain.set(domain, await h2Pool.get(domain));
|
|
265
|
-
}
|
|
266
|
-
catch {
|
|
267
|
-
sessionByDomain.set(domain, null);
|
|
268
|
-
}
|
|
269
|
-
}));
|
|
270
|
-
for (const instance of instances) {
|
|
271
|
-
const domain = SandboxInstance.edgeDomainForRegion(instance.spec?.region);
|
|
272
|
-
if (domain) {
|
|
273
|
-
const session = sessionByDomain.get(domain) ?? null;
|
|
274
|
-
instance.h2Session = session;
|
|
275
|
-
instance.sandbox.h2Session = session;
|
|
276
|
-
instance.sandbox.h2Domain = domain;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
return instances;
|
|
281
291
|
}
|
|
282
292
|
static async delete(sandboxName) {
|
|
283
293
|
const { data } = await deleteSandbox({
|