UNPKG

benchling_typescript_sdk

Version:

Typescript SDK for Benchling API

91 lines (79 loc) 3.32 kB
import crypto from "crypto"; import { BaseClient } from "../BaseClient"; import type { Blob, BlobCreate, BlobPartCreate, BlobPart, MimeType } from "../types"; import { UnexpectedBenchlingConfigError } from "../../utils/clientError"; export class Blobs { private client: BaseClient; constructor(client: BaseClient) { this.client = client; } public async startMultiPartUpload(body: { mimeType: MimeType; name: string; type: "RAW_FILE" | "VISUALIZATION"; }): Promise<Blob> { return await this.client.postData<Blob>("blobs:start-multipart-upload", body, {}); } public async uploadPart(blob_id: string, body: BlobPartCreate): Promise<BlobPart> { return await this.client.postData<BlobPart>(`blobs/${blob_id}/parts`, body, {}); } public async completeMultiPartUpload( blob_id: string, body: { parts: { eTag: string; partNumber: number }[] } ): Promise<Blob> { return await this.client.postData<Blob>(`blobs/${blob_id}:complete-upload`, body, {}); } public async abortMultipartUpload(blob_id: string): Promise<{}> { return await this.client.postData<{}>(`blobs/${blob_id}:abort-upload`, {}, {}); } public async createBlob(blobCreate: BlobCreate): Promise<Blob> { return await this.client.postData<Blob>("blobs", blobCreate, {}); } public async multiPartUploadBlob( name: string, buffer: Buffer, mimeType: MimeType, type: "RAW_FILE" | "VISUALIZATION" ): Promise<Blob> { let partSize = 6 * 1024 * 1024; // 6 MB let blob = await this.startMultiPartUpload({ mimeType, name, type }); if (blob.id === undefined) { throw new UnexpectedBenchlingConfigError({ message: `Blob ID for ${name} is undefined after starting multipart upload`, body: "", url: `multiPartUploadBlob`, }); } let totalParts = Math.ceil(buffer.length / partSize); let uploadedParts = { parts: [] } as { parts: { eTag: string; partNumber: number }[] }; for (let partNumber = 0; partNumber < totalParts; partNumber++) { try { // Extract the current chunk of the buffer let start = partNumber * partSize; let end = Math.min(start + partSize, buffer.length); let chunk = buffer.subarray(start, end); // Use subarray instead of slice // Convert chunk to base64 string let base64Chunk = chunk.toString("base64"); // Calculate MD5 hash of the chunk let md5Hash = crypto.createHash("md5").update(chunk).digest("hex"); let part: BlobPart = await this.uploadPart(blob.id, { data64: base64Chunk, md5: md5Hash, partNumber: partNumber + 1, }); uploadedParts.parts.push({ eTag: part.eTag || "", partNumber: partNumber + 1 || 0 }); } catch (error) { await this.abortMultipartUpload(blob.id); throw error; // If an error occurs, abort the multipart upload } } let completed = await this.completeMultiPartUpload(blob.id, uploadedParts); return completed; } public async downloadBlob(blob_id: string): Promise<ReadableStream<Uint8Array>> { const endpoint = `blobs/${blob_id}/download`; return await this.client.fetchData<ReadableStream<Uint8Array>>(endpoint, {}, true, true); // Return the response bodyas a readable stream } }