@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
@@ -6,6 +6,94 @@ import { deleteFilesystemByPath, deleteFilesystemMultipartByUploadIdAbort, getFi
6
6
  const MULTIPART_THRESHOLD = 5 * 1024 * 1024; // 5MB
7
7
  const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB per part
8
8
  const MAX_PARALLEL_UPLOADS = 3; // Number of parallel part uploads
9
+ // Base backoff between part-upload retries, in milliseconds. Grows linearly
10
+ // per attempt and is jittered to avoid synchronized retries (thundering herd)
11
+ // when several parallel parts fail against the same edge at the same time.
12
+ const RETRY_BASE_DELAY_MS = 200;
13
+ // Markers that, when present anywhere in the error chain, are unambiguous
14
+ // signals of a transient HTTP/2 stream reset or connection drop. These are
15
+ // protocol/transport level codes, not application payloads, so substring
16
+ // matching them does not over-match a server-sent error body. Each entry is
17
+ // matched case-sensitively against the error message and its cause.
18
+ //
19
+ // Deliberately excluded: bare "INTERNAL_ERROR" and "fetch failed". Both are
20
+ // too generic on their own (an application 500 body or any failed fetch would
21
+ // match), so we only treat them as transient when paired with a transport
22
+ // error code on the cause (see isTransientUploadError).
23
+ const TRANSIENT_RESET_MARKERS = [
24
+ "ENHANCE_YOUR_CALM", // H2 flow-control backpressure reset
25
+ "NGHTTP2_INTERNAL_ERROR", // H2 internal stream error (qualified form)
26
+ "ERR_HTTP2", // node http2 error code family
27
+ "GOAWAY", // peer is draining the connection
28
+ "HTTP/2 session closed before response", // thrown by our own h2 transport
29
+ "HTTP/2 session sent GOAWAY before response",
30
+ ];
31
+ // Node-level error codes (from `error.code` / `error.cause.code`) that mean
32
+ // the connection itself dropped mid-flight and the request never completed.
33
+ // These are safe to retry for an idempotent part upload.
34
+ const TRANSIENT_ERROR_CODES = new Set([
35
+ "ECONNRESET",
36
+ "ECONNREFUSED",
37
+ "ETIMEDOUT",
38
+ "EPIPE",
39
+ "ERR_HTTP2_STREAM_ERROR",
40
+ "ERR_HTTP2_GOAWAY_SESSION",
41
+ "ERR_HTTP2_SESSION_ERROR",
42
+ ]);
43
+ function collectErrorText(error) {
44
+ const messages = [];
45
+ const codes = [];
46
+ // Walk the error -> cause chain (bounded) so a transport error wrapped by a
47
+ // higher-level "fetch failed" is still classified correctly.
48
+ let current = error;
49
+ for (let depth = 0; depth < 5 && current && typeof current === "object"; depth++) {
50
+ const node = current;
51
+ if (typeof node.message === "string")
52
+ messages.push(node.message);
53
+ if (typeof node.code === "string")
54
+ codes.push(node.code);
55
+ current = node.cause;
56
+ }
57
+ return { messages, codes };
58
+ }
59
+ function isTransientUploadError(error) {
60
+ if (!error || typeof error !== "object") {
61
+ return false;
62
+ }
63
+ const { messages, codes } = collectErrorText(error);
64
+ // 1. An explicit transient transport error code anywhere in the chain.
65
+ if (codes.some((code) => TRANSIENT_ERROR_CODES.has(code))) {
66
+ return true;
67
+ }
68
+ // 2. An unambiguous protocol-level reset marker in any message.
69
+ if (messages.some((text) => TRANSIENT_RESET_MARKERS.some((marker) => text.includes(marker)))) {
70
+ return true;
71
+ }
72
+ return false;
73
+ }
74
+ function nextRetryDelayMs(attempt) {
75
+ // Linear backoff (200ms, 400ms, ...) plus up to one extra base delay of
76
+ // random jitter so concurrent part retries do not all fire on the same tick.
77
+ const base = RETRY_BASE_DELAY_MS * attempt;
78
+ const jitter = Math.floor(Math.random() * RETRY_BASE_DELAY_MS);
79
+ return base + jitter;
80
+ }
81
+ async function retryOnTransient(fn) {
82
+ const retries = settings.fsPartRetries; // 0 = off (current default behavior)
83
+ let attempt = 0;
84
+ for (;;) {
85
+ try {
86
+ return await fn();
87
+ }
88
+ catch (error) {
89
+ attempt++;
90
+ if (retries <= 0 || attempt > retries || !isTransientUploadError(error)) {
91
+ throw error;
92
+ }
93
+ await new Promise((resolve) => setTimeout(resolve, nextRetryDelayMs(attempt)));
94
+ }
95
+ }
96
+ }
9
97
  export class SandboxFileSystem extends SandboxAction {
10
98
  process;
11
99
  constructor(sandbox, process) {
@@ -423,7 +511,7 @@ export class SandboxFileSystem extends SandboxAction {
423
511
  const start = (partNumber - 1) * CHUNK_SIZE;
424
512
  const end = Math.min(start + CHUNK_SIZE, size);
425
513
  const chunk = blob.slice(start, end);
426
- batch.push(this.uploadPart(uploadId, partNumber, chunk));
514
+ batch.push(retryOnTransient(() => this.uploadPart(uploadId, partNumber, chunk)));
427
515
  }
428
516
  // Wait for batch to complete
429
517
  const batchResults = await Promise.all(batch);
@@ -1,6 +1,7 @@
1
1
  import { v4 as uuidv4 } from "uuid";
2
2
  import { createSandbox, deleteSandbox, getSandbox, 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";
@@ -219,10 +220,46 @@ export class SandboxInstance {
219
220
  const instance = new SandboxInstance(data);
220
221
  return SandboxInstance.attachH2Session(instance);
221
222
  }
222
- static async list() {
223
- const { data } = await listSandboxes({ throwOnError: true });
224
- const instances = data.map((sandbox) => new SandboxInstance(sandbox));
225
- return Promise.all(instances.map((instance) => SandboxInstance.attachH2Session(instance)));
223
+ /**
224
+ * List one page of sandboxes.
225
+ *
226
+ * The returned page exposes `data` for the current page, `meta` for cursor
227
+ * metadata, and helpers to fetch more pages only when you need them.
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * const page = await SandboxInstance.list({ limit: 50 });
232
+ *
233
+ * for (const sandbox of page.data) {
234
+ * console.log(sandbox.metadata.name);
235
+ * }
236
+ *
237
+ * const nextPage = await page.nextPage();
238
+ * ```
239
+ *
240
+ * @example
241
+ * ```ts
242
+ * const page = await SandboxInstance.list({ limit: 100 });
243
+ *
244
+ * for await (const sandbox of page) {
245
+ * console.log(sandbox.metadata.name);
246
+ * }
247
+ * ```
248
+ */
249
+ static async list(query) {
250
+ const fetchPage = async (pageQuery) => {
251
+ const { data } = await listSandboxes({
252
+ query: pageQuery,
253
+ throwOnError: true,
254
+ });
255
+ return data;
256
+ };
257
+ return createPaginatedList({
258
+ response: await fetchPage(query),
259
+ fetchPage,
260
+ mapItem: (sandbox) => SandboxInstance.attachH2Session(new SandboxInstance(sandbox)),
261
+ query,
262
+ });
226
263
  }
227
264
  static async delete(sandboxName) {
228
265
  const { data } = await deleteSandbox({
@@ -1,5 +1,6 @@
1
1
  import { v4 as uuidv4 } from "uuid";
2
2
  import { createVolume, deleteVolume, getVolume, listVolumes, updateVolume } from "../client/index.js";
3
+ import { createPaginatedList } from "../common/pagination.js";
3
4
  import { settings } from "../common/settings.js";
4
5
  export class VolumeInstance {
5
6
  volume;
@@ -85,9 +86,46 @@ export class VolumeInstance {
85
86
  });
86
87
  return new VolumeInstance(data);
87
88
  }
88
- static async list() {
89
- const { data } = await listVolumes({ throwOnError: true });
90
- return data.map((volume) => new VolumeInstance(volume));
89
+ /**
90
+ * List one page of volumes.
91
+ *
92
+ * The returned page exposes `data` for the current page, `meta` for cursor
93
+ * metadata, and helpers to fetch more pages only when you need them.
94
+ *
95
+ * @example
96
+ * ```ts
97
+ * const page = await VolumeInstance.list({ limit: 50 });
98
+ *
99
+ * for (const volume of page.data) {
100
+ * console.log(volume.name);
101
+ * }
102
+ *
103
+ * const nextPage = await page.nextPage();
104
+ * ```
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * const page = await VolumeInstance.list({ limit: 100 });
109
+ *
110
+ * for await (const volume of page) {
111
+ * console.log(volume.name);
112
+ * }
113
+ * ```
114
+ */
115
+ static async list(query) {
116
+ const fetchPage = async (pageQuery) => {
117
+ const { data } = await listVolumes({
118
+ query: pageQuery,
119
+ throwOnError: true,
120
+ });
121
+ return data;
122
+ };
123
+ return createPaginatedList({
124
+ response: await fetchPage(query),
125
+ fetchPage,
126
+ mapItem: (volume) => new VolumeInstance(volume),
127
+ query,
128
+ });
91
129
  }
92
130
  static async delete(volumeName) {
93
131
  const { data } = await deleteVolume({