benchling_typescript_sdk
Version:
Typescript SDK for Benchling API
91 lines (79 loc) • 3.32 kB
text/typescript
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
}
}