@blaxel/core 0.2.85-preview.159 → 0.2.86-dev.162

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.
Files changed (72) hide show
  1. package/README.md +37 -2
  2. package/dist/cjs/.tsbuildinfo +1 -1
  3. package/dist/cjs/client/sdk.gen.js +63 -12
  4. package/dist/cjs/common/h2fetch.js +102 -22
  5. package/dist/cjs/common/pagination.js +87 -0
  6. package/dist/cjs/common/pagination.test.js +62 -0
  7. package/dist/cjs/common/settings.js +29 -3
  8. package/dist/cjs/common/settings.test.js +3 -3
  9. package/dist/cjs/drive/index.js +39 -3
  10. package/dist/cjs/index.js +1 -0
  11. package/dist/cjs/jobs/jobs.js +42 -9
  12. package/dist/cjs/sandbox/filesystem/filesystem.js +89 -1
  13. package/dist/cjs/sandbox/sandbox.js +41 -4
  14. package/dist/cjs/types/client/sdk.gen.d.ts +34 -19
  15. package/dist/cjs/types/client/types.gen.d.ts +614 -21
  16. package/dist/cjs/types/common/pagination.d.ts +35 -0
  17. package/dist/cjs/types/common/pagination.test.d.ts +1 -0
  18. package/dist/cjs/types/common/settings.d.ts +12 -0
  19. package/dist/cjs/types/drive/index.d.ts +34 -4
  20. package/dist/cjs/types/index.d.ts +1 -0
  21. package/dist/cjs/types/jobs/jobs.d.ts +33 -3
  22. package/dist/cjs/types/sandbox/sandbox.d.ts +35 -2
  23. package/dist/cjs/types/volume/index.d.ts +36 -4
  24. package/dist/cjs/volume/index.js +41 -3
  25. package/dist/cjs-browser/.tsbuildinfo +1 -1
  26. package/dist/cjs-browser/client/sdk.gen.js +63 -12
  27. package/dist/cjs-browser/common/pagination.js +87 -0
  28. package/dist/cjs-browser/common/pagination.test.js +62 -0
  29. package/dist/cjs-browser/common/settings.js +29 -3
  30. package/dist/cjs-browser/common/settings.test.js +3 -3
  31. package/dist/cjs-browser/drive/index.js +39 -3
  32. package/dist/cjs-browser/index.js +1 -0
  33. package/dist/cjs-browser/jobs/jobs.js +42 -9
  34. package/dist/cjs-browser/sandbox/filesystem/filesystem.js +89 -1
  35. package/dist/cjs-browser/sandbox/sandbox.js +41 -4
  36. package/dist/cjs-browser/types/client/sdk.gen.d.ts +34 -19
  37. package/dist/cjs-browser/types/client/types.gen.d.ts +614 -21
  38. package/dist/cjs-browser/types/common/pagination.d.ts +35 -0
  39. package/dist/cjs-browser/types/common/pagination.test.d.ts +1 -0
  40. package/dist/cjs-browser/types/common/settings.d.ts +12 -0
  41. package/dist/cjs-browser/types/drive/index.d.ts +34 -4
  42. package/dist/cjs-browser/types/index.d.ts +1 -0
  43. package/dist/cjs-browser/types/jobs/jobs.d.ts +33 -3
  44. package/dist/cjs-browser/types/sandbox/sandbox.d.ts +35 -2
  45. package/dist/cjs-browser/types/volume/index.d.ts +36 -4
  46. package/dist/cjs-browser/volume/index.js +41 -3
  47. package/dist/esm/.tsbuildinfo +1 -1
  48. package/dist/esm/client/sdk.gen.js +57 -9
  49. package/dist/esm/common/h2fetch.js +102 -22
  50. package/dist/esm/common/pagination.js +83 -0
  51. package/dist/esm/common/pagination.test.js +60 -0
  52. package/dist/esm/common/settings.js +29 -3
  53. package/dist/esm/common/settings.test.js +3 -3
  54. package/dist/esm/drive/index.js +39 -3
  55. package/dist/esm/index.js +1 -0
  56. package/dist/esm/jobs/jobs.js +42 -9
  57. package/dist/esm/sandbox/filesystem/filesystem.js +89 -1
  58. package/dist/esm/sandbox/sandbox.js +41 -4
  59. package/dist/esm/volume/index.js +41 -3
  60. package/dist/esm-browser/.tsbuildinfo +1 -1
  61. package/dist/esm-browser/client/sdk.gen.js +57 -9
  62. package/dist/esm-browser/common/pagination.js +83 -0
  63. package/dist/esm-browser/common/pagination.test.js +60 -0
  64. package/dist/esm-browser/common/settings.js +29 -3
  65. package/dist/esm-browser/common/settings.test.js +3 -3
  66. package/dist/esm-browser/drive/index.js +39 -3
  67. package/dist/esm-browser/index.js +1 -0
  68. package/dist/esm-browser/jobs/jobs.js +42 -9
  69. package/dist/esm-browser/sandbox/filesystem/filesystem.js +89 -1
  70. package/dist/esm-browser/sandbox/sandbox.js +41 -4
  71. package/dist/esm-browser/volume/index.js +41 -3
  72. package/package.json +1 -1
@@ -2,7 +2,7 @@
2
2
  import { client as _heyApiClient } from "./client.gen.js";
3
3
  /**
4
4
  * List all agents
5
- * Returns all AI agents deployed in the workspace. Each agent includes its deployment status, runtime configuration, and global inference endpoint URL.
5
+ * Returns AI agents deployed in the workspace. Each agent includes its deployment status, runtime configuration, and global inference endpoint URL. 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 keep returning a bare array with all agents.
6
6
  */
7
7
  export const listAgents = (options) => {
8
8
  return (options?.client ?? _heyApiClient).get({
@@ -221,7 +221,7 @@ export const verifyCustomDomain = (options) => {
221
221
  };
222
222
  /**
223
223
  * List drives
224
- * Returns all drives in the workspace. Drives provide persistent storage that can be attached to agents, functions, and sandboxes.
224
+ * Returns all drives in the workspace. Drives provide persistent storage that can be attached to agents, functions, and sandboxes. Starting with API version 2026-04-28, the response wraps items in `{data, meta}` and supports cursor pagination via the `cursor` and `limit` query parameters; older versions keep returning a bare array with all drives.
225
225
  */
226
226
  export const listDrives = (options) => {
227
227
  return (options?.client ?? _heyApiClient).get({
@@ -348,6 +348,22 @@ export const listAllEgressGateways = (options) => {
348
348
  ...options
349
349
  });
350
350
  };
351
+ /**
352
+ * Egress gateway sandbox attachments
353
+ * Returns the inverse map (gateway → sandbox names) for the workspace. Used by the egress-IPs UI to render attachment counts without fetching the sandboxes listing full client-side.
354
+ */
355
+ export const getEgressGatewayUsage = (options) => {
356
+ return (options?.client ?? _heyApiClient).get({
357
+ security: [
358
+ {
359
+ scheme: 'bearer',
360
+ type: 'http'
361
+ }
362
+ ],
363
+ url: '/egressgateways/usage',
364
+ ...options
365
+ });
366
+ };
351
367
  /**
352
368
  * List all egress IPs across all VPCs and gateways in the workspace
353
369
  */
@@ -397,7 +413,7 @@ export const testFeatureFlag = (options) => {
397
413
  };
398
414
  /**
399
415
  * List all MCP servers
400
- * Returns all MCP server functions deployed in the workspace. Each function includes its deployment status, transport protocol (websocket or http-stream), and endpoint URL.
416
+ * Returns MCP server functions deployed in the workspace. Each function includes its deployment status, transport protocol (websocket or http-stream), and endpoint URL. 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 keep returning a bare array with all functions.
401
417
  */
402
418
  export const listFunctions = (options) => {
403
419
  return (options?.client ?? _heyApiClient).get({
@@ -805,7 +821,7 @@ export const getIntegrationConnectionModel = (options) => {
805
821
  };
806
822
  /**
807
823
  * List batch jobs
808
- * Returns all batch job definitions in the workspace. Each job can be triggered to run multiple parallel tasks with configurable concurrency and retry settings.
824
+ * Returns batch job definitions in the workspace. Each job can be triggered to run multiple parallel tasks with configurable concurrency and retry settings. 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 keep returning a bare array with all jobs.
809
825
  */
810
826
  export const listJobs = (options) => {
811
827
  return (options?.client ?? _heyApiClient).get({
@@ -893,7 +909,7 @@ export const updateJob = (options) => {
893
909
  };
894
910
  /**
895
911
  * List job executions
896
- * Returns paginated list of executions for a batch job, sorted by creation time. Each execution contains status, task counts, and timing information.
912
+ * Returns executions for a batch job. 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 keep the legacy offset/limit contract and return a bare array.
897
913
  */
898
914
  export const listJobExecutions = (options) => {
899
915
  return (options.client ?? _heyApiClient).get({
@@ -959,6 +975,22 @@ export const getJobExecution = (options) => {
959
975
  ...options
960
976
  });
961
977
  };
978
+ /**
979
+ * List execution tasks
980
+ * Returns one cursor-paginated page of an execution's tasks. Tasks are derived from event history each request; only the in-memory slicing is paginated, the events scan still fetches the whole event log behind the scenes. Available starting with API version 2026-04-28.
981
+ */
982
+ export const listJobExecutionTasks = (options) => {
983
+ return (options.client ?? _heyApiClient).get({
984
+ security: [
985
+ {
986
+ scheme: 'bearer',
987
+ type: 'http'
988
+ }
989
+ ],
990
+ url: '/jobs/{jobId}/executions/{executionId}/tasks',
991
+ ...options
992
+ });
993
+ };
962
994
  /**
963
995
  * List job revisions
964
996
  * Returns revisions for a job by name.
@@ -1009,7 +1041,7 @@ export const listMcpHubDefinitions = (options) => {
1009
1041
  };
1010
1042
  /**
1011
1043
  * List model endpoints
1012
- * Returns all model gateway endpoints configured in the workspace. Each model represents a proxy to an external LLM provider (OpenAI, Anthropic, etc.) with unified access control.
1044
+ * Returns model gateway endpoints configured in the workspace. Each model represents a proxy to an external LLM provider (OpenAI, Anthropic, etc.) with unified access control. 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 keep returning a bare array with all models.
1013
1045
  */
1014
1046
  export const listModels = (options) => {
1015
1047
  return (options?.client ?? _heyApiClient).get({
@@ -1161,7 +1193,7 @@ export const declineImageShare = (options) => {
1161
1193
  };
1162
1194
  /**
1163
1195
  * List governance policies
1164
- * Returns all governance policies in the workspace. Policies control deployment locations, hardware flavors, and token limits for agents, functions, and models.
1196
+ * Returns governance policies in the workspace. Policies control deployment locations, hardware flavors, and token limits for agents, functions, and models. 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 keep returning a bare array with all policies.
1165
1197
  */
1166
1198
  export const listPolicies = (options) => {
1167
1199
  return (options?.client ?? _heyApiClient).get({
@@ -1247,6 +1279,22 @@ export const updatePolicy = (options) => {
1247
1279
  }
1248
1280
  });
1249
1281
  };
1282
+ /**
1283
+ * List resources using a policy
1284
+ * Returns the names of every resource (agent, function, model, sandbox, job) currently referencing the given policy. Replaces the client-side fan-out the policies UI used to do over the listings.
1285
+ */
1286
+ export const getPolicyUsages = (options) => {
1287
+ return (options.client ?? _heyApiClient).get({
1288
+ security: [
1289
+ {
1290
+ scheme: 'bearer',
1291
+ type: 'http'
1292
+ }
1293
+ ],
1294
+ url: '/policies/{policyName}/usages',
1295
+ ...options
1296
+ });
1297
+ };
1250
1298
  /**
1251
1299
  * List public ips
1252
1300
  * Returns a list of all public ips used in Blaxel..
@@ -1281,7 +1329,7 @@ export const listSandboxHubDefinitions = (options) => {
1281
1329
  };
1282
1330
  /**
1283
1331
  * List sandboxes
1284
- * Returns all sandboxes in the workspace. Each sandbox includes its configuration, status, and endpoint URL.
1332
+ * Returns sandboxes in the workspace. Each sandbox includes its configuration, status, and endpoint URL. Terminated sandboxes are hidden by default; pass `showTerminated=true` to include them. 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 keep returning a bare array of all sandboxes.
1285
1333
  */
1286
1334
  export const listSandboxes = (options) => {
1287
1335
  return (options?.client ?? _heyApiClient).get({
@@ -1841,7 +1889,7 @@ export const deleteVolumeTemplateVersion = (options) => {
1841
1889
  };
1842
1890
  /**
1843
1891
  * List persistent volumes
1844
- * Returns all persistent storage volumes in the workspace. Volumes can be attached to sandboxes for durable file storage that persists across sessions and sandbox deletions.
1892
+ * Returns persistent storage volumes in the workspace. Volumes can be attached to sandboxes for durable file storage that persists across sessions and sandbox deletions. 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 keep returning a bare array of volumes.
1845
1893
  */
1846
1894
  export const listVolumes = (options) => {
1847
1895
  return (options?.client ?? _heyApiClient).get({
@@ -1,6 +1,74 @@
1
+ import { settings } from "./settings.js";
1
2
  import { refH2SessionForActiveRequest } from "./h2ref.js";
2
3
  const MIN_H2_SESSION_MAX_LISTENERS = 64;
3
4
  const sessionsWithListenerBudget = new WeakSet();
5
+ /**
6
+ * Per-domain async semaphore that bounds the number of in-flight HTTP/2
7
+ * requests against a single edge domain (one H2 session). The cap is keyed
8
+ * by domain so throttling one sandbox's uploads never blocks requests to an
9
+ * unrelated sandbox served by a different edge.
10
+ *
11
+ * When `settings.maxConcurrentH2Requests` is `0`/unset the gate is a no-op
12
+ * (current default behavior: unlimited concurrency).
13
+ */
14
+ // Safety timeout for a held slot. A request that never resolves (no response,
15
+ // no error, no abort) would otherwise hold its slot forever and starve every
16
+ // queued request for the same domain. After this window we release the slot
17
+ // and let the underlying request continue or reject on its own; we never
18
+ // cancel the in-flight request here, we only stop it from blocking the queue.
19
+ // Kept deliberately conservative (well above any realistic part-upload time)
20
+ // so it does not interfere with legitimately slow but healthy requests.
21
+ const H2_SLOT_TIMEOUT_MS = 120_000;
22
+ const h2GatesByDomain = new Map();
23
+ function getH2Gate(domain) {
24
+ let gate = h2GatesByDomain.get(domain);
25
+ if (!gate) {
26
+ gate = { active: 0, queue: [] };
27
+ h2GatesByDomain.set(domain, gate);
28
+ }
29
+ return gate;
30
+ }
31
+ /**
32
+ * Acquire an in-flight slot for `domain`. Resolves with a release function
33
+ * that is idempotent and FIFO-fair: releasing wakes the longest-waiting
34
+ * queued caller for the same domain. A safety timer also releases the slot
35
+ * if the caller never does, preventing per-domain starvation.
36
+ */
37
+ async function acquireH2Slot(domain) {
38
+ const max = settings.maxConcurrentH2Requests; // 0/undefined = unlimited
39
+ if (!max || max <= 0)
40
+ return () => { };
41
+ const gate = getH2Gate(domain);
42
+ while (gate.active >= max) {
43
+ await new Promise((resolve) => gate.queue.push(resolve));
44
+ }
45
+ gate.active++;
46
+ let released = false;
47
+ // Holder so `release` can clear the safety timer that is created after it.
48
+ const timer = {};
49
+ const release = () => {
50
+ if (released)
51
+ return;
52
+ released = true;
53
+ if (timer.handle !== undefined)
54
+ clearTimeout(timer.handle);
55
+ gate.active--;
56
+ const next = gate.queue.shift();
57
+ if (next) {
58
+ next();
59
+ }
60
+ else if (gate.active === 0 && gate.queue.length === 0) {
61
+ // No active requests and nothing waiting: drop the empty gate so the
62
+ // Map does not grow unbounded across many short-lived domains.
63
+ h2GatesByDomain.delete(domain);
64
+ }
65
+ };
66
+ timer.handle = setTimeout(release, H2_SLOT_TIMEOUT_MS);
67
+ // unref() so a pending safety timer never keeps the process alive. Guarded
68
+ // because not every runtime's timer handle exposes unref().
69
+ timer.handle.unref?.();
70
+ return release;
71
+ }
4
72
  /**
5
73
  * Creates a fetch()-compatible function that sends requests over an existing
6
74
  * HTTP/2 session. Falls back to globalThis.fetch() only when the session is
@@ -26,11 +94,40 @@ export function createH2Fetch(session) {
26
94
  */
27
95
  export function createPoolBackedH2Fetch(pool, domain) {
28
96
  return async (input) => {
97
+ const rel = await acquireH2Slot(domain);
98
+ try {
99
+ const session = await pool.get(domain);
100
+ if (session) {
101
+ let h2RequestCreated = false;
102
+ try {
103
+ return await _h2Request(session, input, {
104
+ onH2RequestCreated: () => {
105
+ h2RequestCreated = true;
106
+ },
107
+ });
108
+ }
109
+ catch (err) {
110
+ if (h2RequestCreated) {
111
+ pool.evictSession(domain, session);
112
+ }
113
+ throw err;
114
+ }
115
+ }
116
+ return await globalThis.fetch(input);
117
+ }
118
+ finally {
119
+ rel();
120
+ }
121
+ };
122
+ }
123
+ export async function h2RequestDirectFromPool(pool, domain, url, init) {
124
+ const rel = await acquireH2Slot(domain);
125
+ try {
29
126
  const session = await pool.get(domain);
30
127
  if (session) {
31
128
  let h2RequestCreated = false;
32
129
  try {
33
- return await _h2Request(session, input, {
130
+ return await h2RequestDirectInternal(session, url, init, {
34
131
  onH2RequestCreated: () => {
35
132
  h2RequestCreated = true;
36
133
  },
@@ -43,28 +140,11 @@ export function createPoolBackedH2Fetch(pool, domain) {
43
140
  throw err;
44
141
  }
45
142
  }
46
- return globalThis.fetch(input);
47
- };
48
- }
49
- export async function h2RequestDirectFromPool(pool, domain, url, init) {
50
- const session = await pool.get(domain);
51
- if (session) {
52
- let h2RequestCreated = false;
53
- try {
54
- return await h2RequestDirectInternal(session, url, init, {
55
- onH2RequestCreated: () => {
56
- h2RequestCreated = true;
57
- },
58
- });
59
- }
60
- catch (err) {
61
- if (h2RequestCreated) {
62
- pool.evictSession(domain, session);
63
- }
64
- throw err;
65
- }
143
+ return await globalThis.fetch(url, init);
144
+ }
145
+ finally {
146
+ rel();
66
147
  }
67
- return globalThis.fetch(url, init);
68
148
  }
69
149
  /**
70
150
  * Low-level H2 request that takes raw URL + init, skipping Request construction.
@@ -0,0 +1,83 @@
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
+ get hasMore() {
27
+ return Boolean(meta.hasMore);
28
+ },
29
+ get nextCursor() {
30
+ return meta.nextCursor || undefined;
31
+ },
32
+ async nextPage() {
33
+ const cursor = list.nextCursor;
34
+ if (!cursor) {
35
+ return null;
36
+ }
37
+ if (cursors.has(cursor)) {
38
+ throw new Error("Pagination returned a repeated cursor");
39
+ }
40
+ const nextQuery = { ...(query ?? {}), cursor };
41
+ const nextSeenCursors = new Set(cursors);
42
+ nextSeenCursors.add(cursor);
43
+ return createPaginatedList({
44
+ response: await fetchPage(nextQuery),
45
+ fetchPage,
46
+ mapItem,
47
+ query: nextQuery,
48
+ seenCursors: nextSeenCursors,
49
+ });
50
+ },
51
+ async autoPagingEach(onItem) {
52
+ for await (const item of list) {
53
+ const shouldContinue = await onItem(item);
54
+ if (shouldContinue === false) {
55
+ return;
56
+ }
57
+ }
58
+ },
59
+ async autoPagingToArray(options) {
60
+ if (!options || !Number.isFinite(options.limit) || options.limit <= 0) {
61
+ throw new Error("autoPagingToArray requires a positive limit");
62
+ }
63
+ const items = [];
64
+ for await (const item of list) {
65
+ items.push(item);
66
+ if (items.length >= options.limit) {
67
+ return items;
68
+ }
69
+ }
70
+ return items;
71
+ },
72
+ async *[Symbol.asyncIterator]() {
73
+ let page = list;
74
+ while (page) {
75
+ for (const item of page.data) {
76
+ yield item;
77
+ }
78
+ page = await page.nextPage();
79
+ }
80
+ },
81
+ };
82
+ return list;
83
+ }
@@ -0,0 +1,60 @@
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("supports auto paging with an explicit limit", async () => {
40
+ const fetchPage = async (query) => {
41
+ await Promise.resolve();
42
+ if (!query?.cursor) {
43
+ return {
44
+ data: ["first"],
45
+ meta: { hasMore: true, nextCursor: "next-page" },
46
+ };
47
+ }
48
+ return {
49
+ data: ["second"],
50
+ meta: { hasMore: false },
51
+ };
52
+ };
53
+ const page = await createPaginatedList({
54
+ response: await fetchPage(),
55
+ fetchPage,
56
+ mapItem: (item) => item,
57
+ });
58
+ await expect(page.autoPagingToArray({ limit: 2 })).resolves.toEqual(["first", "second"]);
59
+ });
60
+ });
@@ -5,10 +5,10 @@ import { authentication } from "../authentication/index.js";
5
5
  import { env } from "../common/env.js";
6
6
  import { fs, os, path } from "../common/node.js";
7
7
  // Build info - these placeholders are replaced at build time by build:replace-imports
8
- const BUILD_VERSION = "0.2.85-preview.159";
9
- const BUILD_COMMIT = "a9c088b49239125f1b129fbc95d1887556112a33";
8
+ const BUILD_VERSION = "0.2.86-dev.162";
9
+ const BUILD_COMMIT = "67e269718e3381ddd43832b1def2532c2b218e61";
10
10
  const BUILD_SENTRY_DSN = "https://fd5e60e1c9820e1eef5ccebb84a07127@o4508714045276160.ingest.us.sentry.io/4510465864564736";
11
- const BLAXEL_API_VERSION = "2026-04-16";
11
+ const BLAXEL_API_VERSION = "2026-04-28";
12
12
  // Cache for config.yaml tracking value
13
13
  let configTrackingValue = null;
14
14
  let configTrackingLoaded = false;
@@ -226,6 +226,32 @@ class Settings {
226
226
  }
227
227
  return isDenoRuntime();
228
228
  }
229
+ get maxConcurrentH2Requests() {
230
+ if (typeof this.config.maxConcurrentH2Requests === "number") {
231
+ return this.config.maxConcurrentH2Requests;
232
+ }
233
+ const value = env.BL_MAX_H2_INFLIGHT;
234
+ if (value) {
235
+ const parsed = parseInt(value, 10);
236
+ if (!Number.isNaN(parsed)) {
237
+ return parsed;
238
+ }
239
+ }
240
+ return 0;
241
+ }
242
+ get fsPartRetries() {
243
+ if (typeof this.config.fsPartRetries === "number") {
244
+ return this.config.fsPartRetries;
245
+ }
246
+ const value = env.BL_FS_PART_RETRIES;
247
+ if (value) {
248
+ const parsed = parseInt(value, 10);
249
+ if (!Number.isNaN(parsed)) {
250
+ return parsed;
251
+ }
252
+ }
253
+ return 0;
254
+ }
229
255
  async authenticate() {
230
256
  await this.credentials.authenticate();
231
257
  }
@@ -7,14 +7,14 @@ describe('Settings.apiVersion', () => {
7
7
  afterEach(() => {
8
8
  delete env.BL_API_VERSION;
9
9
  });
10
- it('defaults to 2026-04-16 when BL_API_VERSION is not set', async () => {
10
+ it('defaults to 2026-04-28 when BL_API_VERSION is not set', async () => {
11
11
  delete env.BL_API_VERSION;
12
12
  const { settings } = await import('./settings.js');
13
- expect(settings.apiVersion).toBe('2026-04-16');
13
+ expect(settings.apiVersion).toBe('2026-04-28');
14
14
  });
15
15
  it('headers include Blaxel-Version set to the default', async () => {
16
16
  delete env.BL_API_VERSION;
17
17
  const { settings } = await import('./settings.js');
18
- expect(settings.headers['Blaxel-Version']).toBe('2026-04-16');
18
+ expect(settings.headers['Blaxel-Version']).toBe('2026-04-28');
19
19
  });
20
20
  });
@@ -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;
@@ -83,9 +84,44 @@ export class DriveInstance {
83
84
  });
84
85
  return new DriveInstance(data);
85
86
  }
86
- static async list() {
87
- const { data } = await listDrives({ throwOnError: true });
88
- return data.map((drive) => new DriveInstance(drive));
87
+ /**
88
+ * List one page of drives.
89
+ *
90
+ * The returned page exposes `data` for the current page, `meta` for cursor
91
+ * metadata, and helpers to fetch more pages only when you need them.
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * const page = await DriveInstance.list({ limit: 50 });
96
+ *
97
+ * for (const drive of page.data) {
98
+ * console.log(drive.name);
99
+ * }
100
+ *
101
+ * const nextPage = await page.nextPage();
102
+ * ```
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * const allDrives = await (await DriveInstance.list()).autoPagingToArray({
107
+ * limit: 1000,
108
+ * });
109
+ * ```
110
+ */
111
+ static async list(query) {
112
+ const fetchPage = async (pageQuery) => {
113
+ const { data } = await listDrives({
114
+ query: pageQuery,
115
+ throwOnError: true,
116
+ });
117
+ return data;
118
+ };
119
+ return createPaginatedList({
120
+ response: await fetchPage(query),
121
+ fetchPage,
122
+ mapItem: (drive) => new DriveInstance(drive),
123
+ query,
124
+ });
89
125
  }
90
126
  static async delete(driveName) {
91
127
  const { data } = await deleteDrive({
package/dist/esm/index.js CHANGED
@@ -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,18 +76,50 @@ class BlJob {
75
76
  return data;
76
77
  }
77
78
  /**
78
- * List all executions for this job
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 { data } = await listJobExecutions({
83
- path: {
84
- jobId: this.jobName,
85
- },
86
- headers: settings.headers,
87
- throwOnError: true,
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
- return data ?? [];
90
123
  }
91
124
  /**
92
125
  * Get the status of a specific execution