@blaxel/core 0.2.47-preview.106 → 0.2.48-preview.107

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.
@@ -1,4 +1,5 @@
1
1
  // This file is auto-generated by @hey-api/openapi-ts
2
+ import { formDataBodySerializer } from '@hey-api/client-fetch';
2
3
  import { client as _heyApiClient } from "./client.gen.js";
3
4
  /**
4
5
  * Welcome message
@@ -163,6 +164,115 @@ export const getCodegenRerankingByPath = (options) => {
163
164
  ...options
164
165
  });
165
166
  };
167
+ /**
168
+ * List multipart uploads
169
+ * List all active multipart uploads
170
+ */
171
+ export const getFilesystemMultipart = (options) => {
172
+ return (options?.client ?? _heyApiClient).get({
173
+ security: [
174
+ {
175
+ scheme: 'bearer',
176
+ type: 'http'
177
+ }
178
+ ],
179
+ url: '/filesystem-multipart',
180
+ ...options
181
+ });
182
+ };
183
+ /**
184
+ * Abort multipart upload
185
+ * Abort a multipart upload and clean up all parts
186
+ */
187
+ export const deleteFilesystemMultipartByUploadIdAbort = (options) => {
188
+ return (options.client ?? _heyApiClient).delete({
189
+ security: [
190
+ {
191
+ scheme: 'bearer',
192
+ type: 'http'
193
+ }
194
+ ],
195
+ url: '/filesystem-multipart/{uploadId}/abort',
196
+ ...options
197
+ });
198
+ };
199
+ /**
200
+ * Complete multipart upload
201
+ * Complete a multipart upload by assembling all parts
202
+ */
203
+ export const postFilesystemMultipartByUploadIdComplete = (options) => {
204
+ return (options.client ?? _heyApiClient).post({
205
+ security: [
206
+ {
207
+ scheme: 'bearer',
208
+ type: 'http'
209
+ }
210
+ ],
211
+ url: '/filesystem-multipart/{uploadId}/complete',
212
+ ...options,
213
+ headers: {
214
+ 'Content-Type': 'application/json',
215
+ ...options?.headers
216
+ }
217
+ });
218
+ };
219
+ /**
220
+ * Upload part
221
+ * Upload a single part of a multipart upload
222
+ */
223
+ export const putFilesystemMultipartByUploadIdPart = (options) => {
224
+ return (options.client ?? _heyApiClient).put({
225
+ ...formDataBodySerializer,
226
+ security: [
227
+ {
228
+ scheme: 'bearer',
229
+ type: 'http'
230
+ }
231
+ ],
232
+ url: '/filesystem-multipart/{uploadId}/part',
233
+ ...options,
234
+ headers: {
235
+ 'Content-Type': null,
236
+ ...options?.headers
237
+ }
238
+ });
239
+ };
240
+ /**
241
+ * List parts
242
+ * List all uploaded parts for a multipart upload
243
+ */
244
+ export const getFilesystemMultipartByUploadIdParts = (options) => {
245
+ return (options.client ?? _heyApiClient).get({
246
+ security: [
247
+ {
248
+ scheme: 'bearer',
249
+ type: 'http'
250
+ }
251
+ ],
252
+ url: '/filesystem-multipart/{uploadId}/parts',
253
+ ...options
254
+ });
255
+ };
256
+ /**
257
+ * Initiate multipart upload
258
+ * Initiate a multipart upload session for a file
259
+ */
260
+ export const postFilesystemMultipartInitiateByPath = (options) => {
261
+ return (options.client ?? _heyApiClient).post({
262
+ security: [
263
+ {
264
+ scheme: 'bearer',
265
+ type: 'http'
266
+ }
267
+ ],
268
+ url: '/filesystem-multipart/initiate/{path}',
269
+ ...options,
270
+ headers: {
271
+ 'Content-Type': 'application/json',
272
+ ...options?.headers
273
+ }
274
+ });
275
+ };
166
276
  /**
167
277
  * Delete file or directory
168
278
  * Delete a file or directory
@@ -1,7 +1,11 @@
1
1
  import { settings } from "../../common/settings.js";
2
+ import { fs } from "../../common/node.js";
2
3
  import { SandboxAction } from "../action.js";
3
- import { deleteFilesystemByPath, getFilesystemByPath, getWatchFilesystemByPath, putFilesystemByPath } from "../client/index.js";
4
- import { readFile, writeFile } from "fs/promises";
4
+ import { deleteFilesystemByPath, getFilesystemByPath, getWatchFilesystemByPath, putFilesystemByPath, postFilesystemMultipartInitiateByPath, putFilesystemMultipartByUploadIdPart, postFilesystemMultipartByUploadIdComplete, deleteFilesystemMultipartByUploadIdAbort } from "../client/index.js";
5
+ // Multipart upload constants
6
+ const MULTIPART_THRESHOLD = 5 * 1024 * 1024; // 5MB
7
+ const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB per part
8
+ const MAX_PARALLEL_UPLOADS = 3; // Number of parallel part uploads
5
9
  export class SandboxFileSystem extends SandboxAction {
6
10
  process;
7
11
  constructor(sandbox, process) {
@@ -22,6 +26,14 @@ export class SandboxFileSystem extends SandboxAction {
22
26
  }
23
27
  async write(path, content) {
24
28
  path = this.formatPath(path);
29
+ // Calculate content size in bytes
30
+ const contentSize = new Blob([content]).size;
31
+ // Use multipart upload for large files
32
+ if (contentSize > MULTIPART_THRESHOLD) {
33
+ const blob = new Blob([content]);
34
+ return await this.uploadWithMultipart(path, blob, "0644");
35
+ }
36
+ // Use regular upload for small files
25
37
  const { response, data, error } = await putFilesystemByPath({
26
38
  path: { path },
27
39
  body: { content },
@@ -33,12 +45,20 @@ export class SandboxFileSystem extends SandboxAction {
33
45
  }
34
46
  async writeBinary(path, content) {
35
47
  path = this.formatPath(path);
36
- const formData = new FormData();
37
48
  // Convert content to Blob regardless of input type
38
49
  let fileBlob;
50
+ // Check if it's already a Blob or File (including duck-typing for cross-realm Blobs)
39
51
  if (content instanceof Blob || content instanceof File) {
40
52
  fileBlob = content;
41
53
  }
54
+ else if (typeof content === 'object' && content !== null &&
55
+ 'size' in content && 'type' in content &&
56
+ 'arrayBuffer' in content &&
57
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
58
+ typeof content.arrayBuffer === 'function') {
59
+ // Handle Blob-like objects (cross-realm Blobs)
60
+ fileBlob = content;
61
+ }
42
62
  else if (Buffer.isBuffer(content)) {
43
63
  // Convert Buffer to Blob
44
64
  fileBlob = new Blob([content]);
@@ -47,14 +67,29 @@ export class SandboxFileSystem extends SandboxAction {
47
67
  // Convert Uint8Array to Blob
48
68
  fileBlob = new Blob([content]);
49
69
  }
70
+ else if (ArrayBuffer.isView(content)) {
71
+ // Handle other TypedArray views
72
+ fileBlob = new Blob([content]);
73
+ }
50
74
  else if (typeof content === 'string') {
51
- const buffer = await readFile(content);
75
+ // Read file from local filesystem (Node.js only)
76
+ if (!fs) {
77
+ throw new Error("File path upload is only supported in Node.js environments");
78
+ }
79
+ const buffer = fs.readFileSync(content);
52
80
  fileBlob = new Blob([buffer]);
53
81
  }
54
82
  else {
55
- throw new Error("Unsupported content type");
83
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
84
+ const typeName = content?.constructor?.name ?? typeof content;
85
+ throw new Error(`Unsupported content type: ${typeName}`);
86
+ }
87
+ // Use multipart upload for large files
88
+ if (fileBlob.size > MULTIPART_THRESHOLD) {
89
+ return await this.uploadWithMultipart(path, fileBlob, "0644");
56
90
  }
57
- // Append the file as a Blob
91
+ // Use regular upload for small files
92
+ const formData = new FormData();
58
93
  formData.append("file", fileBlob, "test-binary.bin");
59
94
  formData.append("permissions", "0644");
60
95
  formData.append("path", path);
@@ -129,10 +164,13 @@ export class SandboxFileSystem extends SandboxAction {
129
164
  return data;
130
165
  }
131
166
  async download(src, destinationPath, { mode = 0o644 } = {}) {
167
+ if (!fs) {
168
+ throw new Error("File download to local filesystem is only supported in Node.js environments");
169
+ }
132
170
  const blob = await this.readBinary(src);
133
171
  const arrayBuffer = await blob.arrayBuffer();
134
172
  const buffer = Buffer.from(arrayBuffer);
135
- await writeFile(destinationPath, buffer, { mode: mode ?? 0o644 });
173
+ fs.writeFileSync(destinationPath, buffer, { mode: mode ?? 0o644 });
136
174
  }
137
175
  async rm(path, recursive = false) {
138
176
  path = this.formatPath(path);
@@ -260,4 +298,99 @@ export class SandboxFileSystem extends SandboxAction {
260
298
  formatPath(path) {
261
299
  return path;
262
300
  }
301
+ // Multipart upload helper methods
302
+ async initiateMultipartUpload(path, permissions = "0644") {
303
+ path = this.formatPath(path);
304
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
305
+ const { data } = await postFilesystemMultipartInitiateByPath({
306
+ path: { path },
307
+ body: { permissions },
308
+ baseUrl: this.url,
309
+ client: this.client,
310
+ throwOnError: true,
311
+ });
312
+ return data;
313
+ }
314
+ async uploadPart(uploadId, partNumber, fileBlob) {
315
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
316
+ const { data } = await putFilesystemMultipartByUploadIdPart({
317
+ path: { uploadId },
318
+ query: { partNumber },
319
+ body: { file: fileBlob },
320
+ baseUrl: this.url,
321
+ client: this.client,
322
+ throwOnError: true,
323
+ });
324
+ return data;
325
+ }
326
+ async completeMultipartUpload(uploadId, parts) {
327
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
328
+ const { data } = await postFilesystemMultipartByUploadIdComplete({
329
+ path: { uploadId },
330
+ body: { parts },
331
+ baseUrl: this.url,
332
+ client: this.client,
333
+ throwOnError: true,
334
+ });
335
+ return data;
336
+ }
337
+ async abortMultipartUpload(uploadId) {
338
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
339
+ await deleteFilesystemMultipartByUploadIdAbort({
340
+ path: { uploadId },
341
+ baseUrl: this.url,
342
+ client: this.client,
343
+ throwOnError: true,
344
+ });
345
+ }
346
+ async uploadWithMultipart(path, blob, permissions = "0644") {
347
+ // Initiate multipart upload
348
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
349
+ const initResponse = await this.initiateMultipartUpload(path, permissions);
350
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
351
+ const uploadId = initResponse.uploadId;
352
+ if (!uploadId) {
353
+ throw new Error("Failed to get upload ID from initiate response");
354
+ }
355
+ try {
356
+ const size = blob.size;
357
+ const numParts = Math.ceil(size / CHUNK_SIZE);
358
+ const parts = [];
359
+ // Upload parts in batches for parallel processing
360
+ for (let i = 0; i < numParts; i += MAX_PARALLEL_UPLOADS) {
361
+ const batch = [];
362
+ for (let j = 0; j < MAX_PARALLEL_UPLOADS && i + j < numParts; j++) {
363
+ const partNumber = i + j + 1;
364
+ const start = (partNumber - 1) * CHUNK_SIZE;
365
+ const end = Math.min(start + CHUNK_SIZE, size);
366
+ const chunk = blob.slice(start, end);
367
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
368
+ batch.push(this.uploadPart(uploadId, partNumber, chunk));
369
+ }
370
+ // Wait for batch to complete
371
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
372
+ const batchResults = await Promise.all(batch);
373
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument
374
+ parts.push(...batchResults.map((r) => ({ partNumber: r.partNumber, etag: r.etag })));
375
+ }
376
+ // Sort parts by partNumber to ensure correct order
377
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
378
+ parts.sort((a, b) => (a.partNumber ?? 0) - (b.partNumber ?? 0));
379
+ // Complete the upload
380
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
381
+ return await this.completeMultipartUpload(uploadId, parts);
382
+ }
383
+ catch (error) {
384
+ // Abort the upload on failure
385
+ try {
386
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
387
+ await this.abortMultipartUpload(uploadId);
388
+ }
389
+ catch (abortError) {
390
+ // Log but don't throw - we want to throw the original error
391
+ console.error('Failed to abort multipart upload:', abortError);
392
+ }
393
+ throw error;
394
+ }
395
+ }
263
396
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blaxel/core",
3
- "version": "0.2.47-preview.106",
3
+ "version": "0.2.48-preview.107",
4
4
  "description": "Blaxel Core SDK for TypeScript",
5
5
  "license": "MIT",
6
6
  "author": "Blaxel, INC (https://blaxel.ai)",
@@ -74,7 +74,7 @@
74
74
  "vite": "^5.2.0",
75
75
  "vitest": "^1.5.0"
76
76
  },
77
- "commit": "f20dd9f81dfa23f4609933362ed8f0cd1fa133f6",
77
+ "commit": "64cc47af878d72bbb58982333bcae814e3878759",
78
78
  "scripts": {
79
79
  "lint": "eslint src/",
80
80
  "dev": "tsc --watch",