UNPKG

benchling_typescript_sdk

Version:

Typescript SDK for Benchling API

216 lines (200 loc) 8.89 kB
import * as fs from "fs"; import { BenchlingClient } from "../BenchlingClient"; import { DeleteBarcodes } from "./DeleteInventory"; import type { Container, CreatePlatesAndTransferContentsRequest, ExtendedPlateTransfers, MultipleContainersTransfer, MultipleNewPlateContainersTransfer, Plate, PlateCreate, } from "../types"; function pruneToPlate(input: Record<string, any>): PlateCreate { const plateKeys: (keyof PlateCreate)[] = [ "barcode", "fields", "name", "parentStorageId", "projectId", "schemaId", "wells", ]; // Filter the input object to include only PlateCreate keys const result: Partial<PlateCreate> = {}; for (const key of plateKeys) { if (key in input) { result[key] = input[key]; } } return result as PlateCreate; } /** * Creates plates and transfers contents into them. * - plate barcode and name are optional * - entites must be containable with "transferQuantity" and "sourceConcentration" */ export class CreatePlatesWithContainableEntities { private benchling: BenchlingClient; constructor(client: BenchlingClient) { this.benchling = client; } /** * Creates plates and transfers contents into them. * - plate barcode and name are optional * - entites must be containable with "transferQuantity" and "sourceConcentration" * @param {CreatePlatesAndTransferContentsRequest[]} - An array of requests specifying the plates to create and their transfer details. * @returns {Promise<{ plates: Plate[], filledWells: Container[] }>} A promise resolving to an object containing the plates and all the plates' filled wells. */ public async createPlatesAndTransferContents( platesToCreateWithTransfers: CreatePlatesAndTransferContentsRequest[] ): Promise<Plate[]> { // User asks to create X plates with contents to transfer // Create Plates, then fill the wells, then return the plates and the final filled wells with contents // will create or update Plates with Transfered Contents const createdPlateIds: string[] = []; // must be able to archive all created plates if anything fails!! const platesToCreate: ExtendedPlateTransfers[] = platesToCreateWithTransfers.map((d) => ({ ...d, id: "", })); try { const createdPlates = await this.injectPlateIdentifiers(platesToCreate, createdPlateIds); // create plates in benchling and then update plate.id, and possibly plate.barcode to created plates const wellTransfers: MultipleContainersTransfer[] = await this.compileTransfersForNewWells( platesToCreate ); // return containers with transfered contents // !isLambdaRuntime() && // fs.writeFileSync( // "logs/wellTransfers.json", // JSON.stringify(wellTransfers, null, 2), // "utf-8" // ); // save the well transfers to a file for debugging await this.benchling.containers.waitForAllTransfers(wellTransfers, 1000); // it is possible but unlikely return createdPlates; // return the created plates and the filled containers } catch (error) { //why is this error different suddenly? console.error(error); console.error("Error creating plates and transferring contents:", error); console.error("Archiving all previously created plates:"); console.error(createdPlateIds.join(", ")); // let deletePlates = await new DeleteBarcodes(this.benchling).deletePlates( // createdPlateIds, // ids or barcodes // "Made in error", // true // ); // TODO archive all previously created plates and throw error to user throw error; } } private async injectPlateIdentifiers( platesToCreate: ExtendedPlateTransfers[], createdPlateIds: string[] ): Promise<Plate[]> { const createdPlates: Plate[] = []; for (const plateToCreate of platesToCreate) { let onlyPlate: PlateCreate = pruneToPlate(plateToCreate); const createdPlate = await this.benchling.plates.createPlate(onlyPlate); if ("error" in createdPlate) { console.error("Error creating plate:", createdPlate); console.error("Archiving all previously created plates:"); console.error(createdPlateIds.join(", ")); throw new Error( "\nFailed to create plate. Still have not implemented archiving previous plates: " + createdPlateIds.join(", ") + ".\n" ); // r96wp175:D1 vs con_9ENpL3BU // TODO archive all previously created plates and throw error to user } else if ("id" in createdPlate) { createdPlates.push(createdPlate); createdPlateIds.push(createdPlate.id); plateToCreate.id = createdPlate.id; // update plateToCreate with the created plate id plateToCreate.name = createdPlate.name; // update plateToCreate with the created plate name plateToCreate.barcode = createdPlate.barcode; // update plateToCreate with the created plate barcode } else { throw new Error( "Internal Program Error: Contact Nicole Cannon. Failed to create a plate for unknown reason" ); } } return createdPlates; } private async compileTransfersForNewWells( platesToFill: ExtendedPlateTransfers[] ): Promise<MultipleContainersTransfer[]> { const wellTransfers: MultipleContainersTransfer[] = []; // combine all transfers from all plates // throw new Error("Not yet implemented"); for (const plate of platesToFill) { const plateId = plate.id; let plateWellTransfers = plate.transfers; let plateWellGenerator = this.benchling.containers.listContainers({ ancestorStorageId: plateId, pageSize: 500, // max page size - will always all wells of a plate in first page! important! returning: "containers.id, containers.barcode", // should be empty }); // for each page in idGenerator for await (const page of plateWellGenerator) { for (const container of page) { if (!container.barcode || !container.id) { throw new Error( `Internal Program Error: Container without barcode or id found in plate ${plateId}\nContainer: ${JSON.stringify( container )}` ); } let destinationContainerId = container.id; // this is the id of the container (aka the well) in benchling let wellId = container.barcode.split(":")[1]; // this is the wellId the user wants to transfer to in plateId const matchingTransfers = plateWellTransfers.filter( (pwt) => pwt.wellId.toUpperCase() === wellId.toUpperCase() ); // get all transfers into the same container // Remove all matching transfers from plateWellTransfers plateWellTransfers = plateWellTransfers.filter( (pwt) => pwt.wellId.toUpperCase() !== wellId.toUpperCase() ); // reduce search space for next iteration // Convert each matching transfer into MultipleContainersTransfer and add to wellTransfers matchingTransfers.forEach((transfer) => { const transferToAdd: MultipleContainersTransfer = this.convertToMultipleContainersTransfer(transfer, destinationContainerId); wellTransfers.push(transferToAdd); }); } } } return wellTransfers; } private convertToMultipleContainersTransfer( transfer: MultipleNewPlateContainersTransfer, destinationContainerId: string ): MultipleContainersTransfer { let transferToAdd: MultipleContainersTransfer = { destinationContainerId: destinationContainerId, }; // Conditionally add keys if they are defined in `transfer` if (transfer.transferQuantity !== undefined) { transferToAdd.transferQuantity = transfer.transferQuantity; } if (transfer.sourceContainerId !== undefined) { transferToAdd.sourceContainerId = transfer.sourceContainerId; } if (transfer.sourceEntityId !== undefined) { transferToAdd.sourceEntityId = transfer.sourceEntityId; } if (transfer.sampleOwnerIds !== undefined) { transferToAdd.sampleOwnerIds = transfer.sampleOwnerIds; } if (transfer.sourceBatchId !== undefined) { transferToAdd.sourceBatchId = transfer.sourceBatchId; } if (transfer.sourceConcentration !== undefined) { transferToAdd.sourceConcentration = transfer.sourceConcentration; } if (transfer.transferVolume !== undefined) { transferToAdd.transferVolume = transfer.transferVolume; } if (transfer.restrictedSamplePartyIds !== undefined) { transferToAdd.restrictedSamplePartyIds = transfer.restrictedSamplePartyIds; } if (transfer.restrictionStatus !== undefined) { transferToAdd.restrictionStatus = transfer.restrictionStatus; } return transferToAdd; } }