1const Primers = new class { 2 // #region extendSequence 3 /** 4 * Extends the primer from the starting position in the specified direction on the specified strand 5 * 6 * Example: 7 * startingPos 8 * | 9 * <--- backward â¼ forward ---> 10 * fwdStrand -> 5'-GGGGAAAAAAAATTTATATATGGGGAAAAAAAATTTATATATGGGGAAAAAAAATTTATATAT-3' 11 * compStrand -> 3'-ATATATAAATTTTTTTTCCCCATATATAAATTTTTTTTCCCCATATATAAATTTTTTTTCCCC-5' 12 * <--- forward â² backward ---> 13 * 14 * 15 * Backward extension on fwdStrand, ATG as the initialSequence: 16 * startingPos 17 * | 18 * â¼ 19 * fwdStrand -> 5'-GGGGAAAAAAAATTTATATATGGGGAAAAAAAATTTATATATGGGGAAAAAAAATTTATATAT3' 20 * <---------- 21 * TTTATATATGGATG 22 * â² 23 * | 24 * Melting temperature exceeds targetTm here. 25 * 26 * 27 * @param {String} plasmidSequence - Sequence of the plasmid 28 * @param {Number} startingIndex - Initial position from where to start extending the sequence 29 * @param {Number} targetStrand - Strand onto which to work on ("top", "bottom") 30 * @param {String} direction - Extension direction ("fwd"=5'->3', "rev"=5'->3') 31 * @param {Number} targetTm - Melting temperature that should be reached 32 * @param {String} method - Melting temperature algorithm 33 * @param {Number} minimumLength - Minimum length of the sequence 34 * @param {String} initialSequence - Starting sequence for the primer 35 * @returns {String} - Output sequence 36 */ 37 extendSequence( 38 plasmidSequence, 39 startingIndex, 40 targetStrand, 41 direction, 42 targetTM, 43 tmMethod, 44 minimumLength, 45 initialSequence="" 46 ) { 47 // Select correct strand 48 plasmidSequence = (targetStrand === "top") ? plasmidSequence : Nucleotides.reverseComplementary(plasmidSequence); 49 50 // Adjust starting index 51 startingIndex = (targetStrand === 'top') ? startingIndex: plasmidSequence.length - startingIndex + 1; 52 53 //console.log(`Primers.extendSequence -> startingIndex=${startingIndex} [${plasmidSequence.slice(startingIndex, startingIndex+3)}...]`); 54 55 // Initial extension length minus the initial sequence 56 let extensionLength = minimumLength - initialSequence.length; 57 58 // Initial primer sequence, initial sequence + initial extension 59 let prevPrimerSequence = (direction === "fwd") 60 ? initialSequence + Utilities.repeatingSlice(plasmidSequence, startingIndex, startingIndex + extensionLength - 1) 61 : Utilities.repeatingSlice(plasmidSequence, startingIndex - extensionLength + 1, startingIndex) + initialSequence; 62 63 // Initial melting temperature 64 let prevTM = Nucleotides.getMeltingTemperature(prevPrimerSequence, tmMethod); 65 66 // Extend primer until target melting temperature is reached, or the maximum amount of iterations is reached 67 let primerSequence = prevPrimerSequence; 68 let currTM = prevTM; 69 const maxIter = 100; 70 71 const useGCClamp = UserPreferences.get("useGCClamp"); 72 for (let i = 0; i < maxIter; i++) { 73 /** If the melting temperature of the current primer exceeds 74 * the target temperature and the minimum length, break the loop 75 * and return either the current primer or the previous one, whichever 76 * is closest to the target temperature 77 */ 78 Nucleotides.validatePrimerSequence(primerSequence); 79 80 const primerStableEnough = currTM >= targetTM && primerSequence.length >= minimumLength; 81 82 const newestBase = (direction === "fwd") 83 ? primerSequence.charAt(primerSequence.length - 1) 84 : primerSequence.charAt(0); 85 86 const isGCClampConditionMet = useGCClamp ? ['G', 'C'].includes(newestBase) : true; 87 88 const currentPrimerCloserToTargetTm = Math.abs(currTM - targetTM) <= Math.abs(prevTM - targetTM); 89 const prevPrimerTooShort = prevPrimerSequence.length < minimumLength; 90 91 if (primerStableEnough && isGCClampConditionMet) { 92 if ( 93 (currentPrimerCloserToTargetTm && prevPrimerTooShort) || useGCClamp 94 ) { 95 return primerSequence; 96 } else { 97 return prevPrimerSequence; 98 }; 99 } else { 100 // Save current sequence and tm, then recalculate current seq and tm 101 prevPrimerSequence = primerSequence; 102 prevTM = currTM; 103 104 extensionLength += 1; 105 106 const nextBaseIndices = (direction === "fwd") 107 ? [startingIndex, startingIndex + extensionLength - 1] 108 : [startingIndex - extensionLength + 1, startingIndex]; 109 const [nextStartIndex, nextEndIndex] = nextBaseIndices; 110 111 const primerOutOfBounds = plasmidSequence.length - nextEndIndex < 0; 112 113 114 if (Session.activePlasmid().topology === "linear" && primerOutOfBounds) { 115 throw new OutOfBasesError() 116 }; 117 118 primerSequence = (direction === "fwd") 119 ? initialSequence + Utilities.repeatingSlice(plasmidSequence, nextStartIndex, nextEndIndex) 120 : Utilities.repeatingSlice(plasmidSequence, nextStartIndex, nextEndIndex) + initialSequence; 121 122 123 currTM = Nucleotides.getMeltingTemperature(primerSequence, tmMethod); 124 }; 125 }; 126 }; 127 128 129 // #region generateSet 130 /** 131 * Generate the set of primers for a given IVA Operation. 132 * 133 * @param {String} operationType - Type of IVA Operation 134 * @param {Array<Number>} operationRange - Operation range/span 135 * @param {String} plasmidSequence - DNA sequence of the plasmid 136 * @param {String} seqToInsert - DNA sequence to be inserted 137 * @returns 138 */
139 // TO DO: warn user if TBRs bind somewhere else and give the option to extend them unti they dont 140 generateSet( 141 operationType, 142 operationRange, 143 plasmidSequence, 144 seqToInsert, 145 ) { 146 // Target Tm for homologous region 147 const targetTMHR = (operationType !== "Subcloning") 148 ? UserPreferences.get("HRTm") 149 : UserPreferences.get("HRSubcloningTm"); 150 151 // Make sure indices are sorted 152 if (operationRange[1] === null) { 153 operationRange = [ 154 operationRange[0], 155 operationRange[0] - 1 156 ]; 157 } else { 158 operationRange = [ 159 Math.min(...operationRange), 160 Math.max(...operationRange) 161 ]; 162 }; 163 164 165 // #region TBR Template binding region 166 // Forward template binding region, extend forward on the forward strand from the end position 167 const tempFwd = this.extendSequence( 168 plasmidSequence, 169 operationRange[1], 170 "top", 171 "fwd", 172 UserPreferences.get("TBRTm"), 173 UserPreferences.get("TmAlgorithm"), 174 7 175 ); 176 // Reverse template binding region, extend forward on the complementary strand from the start position 177 const tempRev = this.extendSequence( 178 plasmidSequence, 179 operationRange[0], 180 "bottom", 181 "fwd", 182 UserPreferences.get("TBRTm"), 183 UserPreferences.get("TmAlgorithm"), 184 7 185 ); 186 // #endregion TBR 187 188 189 // #region HR Homologous region 190 const isShortInsertion = Nucleotides.getMeltingTemperature(seqToInsert, "oligoCalc") < UserPreferences.get("maxTmSi"); 191 const symmetricPrimers = UserPreferences.get("symmetricPrimers"); 192 193 // Find appropriate generator for the primers 194 const generator = (isShortInsertion) 195 ? (symmetricPrimers) 196 ? this.generateSymShortSet.bind(this) 197 : this.generateAsymShortSet.bind(this) 198 : (symmetricPrimers) 199 ? this.generateSymLongSet.bind(this) 200 : this.generateAsymLongSet.bind(this) 201 202 // Generate the primers 203 const primersSet = generator( 204 plasmidSequence, 205 operationRange, 206 tempFwd, 207 tempRev, 208 seqToInsert, 209 targetTMHR, 210 operationType, 211 ); 212 // #endregion HR 213 214 return primersSet; 215 }; 216 217 218 // #region Symmetric Short 219 /** 220 * Generate a set of symmetric primers for short insertions/mutations 221 * 222 * @param {String} plasmidSequence - Sequence of the plasmid 223 * @param {Array<Number>} operationRange - Operation range/span 224 * @param {String} tempFwd - DNA sequence of the template binding region of the forward primer 225 * @param {String} tempRev - DNA sequence of the template binding region of the reverse primer 226 * @param {String} seqToInsert - DNA sequence to be inserted 227 * @param {Number} targetTMHR - Targer melting temperature for the homologous region 228 * @param {String} operationType - Type of IVA operation 229 * 230 * @returns {Object} - Primers set dictionary 231 */ 232 generateSymShortSet( 233 plasmidSequence, 234 operationRange, 235 tempFwd, 236 tempRev, 237 seqToInsert, 238 targetTMHR, 239 operationType, 240 ) { 241 /** 242 * Symmetric primers, add bases to 5' and 3' end of sequence to add, or to nothing in case of deletions 243 */ 244 // Added fragment length trackers 245 let homoFragmentLength1 = 0; 246 let homoFragmentLength2 = 0; 247 // Extend more than we need 248 let homoFwd1 = this.extendSequence( 249 plasmidSequence, 250 operationRange[0] - 1, 251 "top", 252 "rev", 253 targetTMHR, 254 "oligoCalc", 255 UserPreferences.get("HRMinLength"), 256 ); 257 console.log("Primers.generateSymShortSet ->", homoFwd1) 258 let homoFwd2 = this.extendSequence( 259 plasmidSequence, 260 operationRange[1], 261 "top", 262 "fwd", 263 targetTMHR, 264 "oligoCalc", 265 UserPreferences.get("HRMinLength"), 266 ); 267 console.log("Primers.generateSymShortSet ->", homoFwd2) 268 269 let overlappingSeq = homoFwd1 + seqToInsert + homoFwd2; 270 271 // Take turns deleting bases from each end until the target melting temperature is reached 272 let turnHomoFwd1 = true; 273 while (true) { 274 // Get slice indices for the current iteration 275 const sliceIndices = (turnHomoFwd1 === true) ? [1, overlappingSeq.length]: [0, -1] 276 // Check conditions 277 const stillAboveTargetTM = Nucleotides.getMeltingTemperature(overlappingSeq.slice(...sliceIndices), "oligoCalc") > targetTMHR; 278 const slicingGetsUsCloser = Math.abs(targetTMHR - Nucleotides.getMeltingTemperature(overlappingSeq.slice(...sliceIndices), "oligoCalc")) <= Math.abs(targetTMHR - Nucleotides.getMeltingTemperature(overlappingSeq, "oligoCalc")); 279 const minimumLengthNotReached = overlappingSeq.length > UserPreferences.get("HRMinLength"); 280 281 if ((stillAboveTargetTM || slicingGetsUsCloser) && minimumLengthNotReached) { 282 // If the minimum length has not been reached, and if we are still above the target tm or 283 // slicing would get us closer, slice the string 284 overlappingSeq = overlappingSeq.slice(...sliceIndices); 285 // Increment fragment length tracker 286 if (turnHomoFwd1 === true) {homoFragmentLength1++} else {homoFragmentLength2++}; 287 // Switch turn 288 turnHomoFwd1 = !turnHomoFwd1; 289 } else { 290 // Otherwise, end the loop 291 break; 292 }; 293 }; 294 295 // Save the fragments that were added on each side 296 homoFwd1 = homoFwd1.slice(homoFragmentLength1, homoFwd1.length); 297 homoFwd2 = homoFwd2.slice(0, homoFwd2.length - homoFragmentLength2 + 1); 298 // Get reverse complementary sequences of the added fragments 299 const homoRev1 = Nucleotides.reverseComplementary(homoFwd2); 300 //const homoRev2 = Nucleotides.reverseComplementary(homoFwd1); 301 302 const hrLength = homoFwd1.length + seqToInsert.length + homoRev1.length; 303 304 const seqToInsertRevComp = Nucleotides.reverseComplementary(seqToInsert); 305 306 const primerSetTitle = (operationType !== "Deletion") ? `Short ${operationType}` : operationType; 307 308 return { 309 title: `${Session.activePlasmid().getNextOperationIndex() + 1}. ${primerSetTitle}`, 310 type: operationType, 311 hrLength: hrLength, 312 hrTm: Nucleotides.getMeltingTemperature(homoFwd1 + seqToInsert + homoRev1, "oligoCalc"), 313 symmetry: "symmetric", 314 currentPlasmidSequence: 315 Session.activePlasmid().sequence.slice(0, operationRange[0] - 1) 316 + seqToInsert 317 + Session.activePlasmid().sequence.slice(operationRange[1]), 318 primers: [ 319 { 320 name: "Forward primer", 321 label: "Forward primer", 322 regions: [ 323 {sequence: homoFwd1, type: "HR", start: operationRange[0] - 1, direction: "fwd"}, 324 {sequence: seqToInsert, type: "INS", start: null, maxLength: seqToInsert.length, direction: "fwd"}, 325 {sequence: tempFwd, type: "TBR", start: operationRange[0] + seqToInsert.length, direction: "fwd"} 326 ], 327 }, 328 { 329 name: "Reverse primer", 330 label: "Reverse primer", 331 regions: [ 332 {sequence: homoRev1, type:"HR", start: operationRange[0] + seqToInsert.length, direction: "rev"}, 333 {sequence: seqToInsertRevComp, type: "INS", start: null, maxLength: seqToInsert.length, direction: "rev"}, 334 {sequence: tempRev, type: "TBR", start: operationRange[0] - 1, direction: "rev"} 335 ], 336 }, 337 ], 338 }; 339 }; 340 341 342 // #region Asymmetric Short 343 /** 344 * Generate a set of asymmetric primers for short insertions/mutations 345 * 346 * @param {String} plasmidSequence - Sequence of the plasmid 347 * @param {Array<Number>} operationRange - Operation range/span 348 * @param {String} tempFwd - DNA sequence of the template binding region of the forward primer 349 * @param {String} tempRev - DNA sequence of the template binding region of the reverse primer 350 * @param {String} seqToInsert - DNA sequence to be inserted 351 * @param {Number} targetTMHR - Targer melting temperature for the homologous region 352 * @param {String} operationType - Type of IVA operation 353 * 354 * @returns {Object} - Primers set dictionary 355 */ 356 generateAsymShortSet( 357 plasmidSequence, 358 operationRange, 359 tempFwd, 360 tempRev, 361 seqToInsert, 362 targetTMHR, 363 operationType, 364 ) { 365 const homoFwd = this.extendSequence( 366 plasmidSequence, 367 operationRange[0] - 1, 368 "top", 369 "rev", 370 targetTMHR, 371 "oligoCalc", 372 UserPreferences.get("HRMinLength") 373 ); 374 375 const primerSetTitle = (operationType !== "Deletion") ? `Short ${operationType}` : operationType; 376 377 return { 378 title: `${Session.activePlasmid().getNextOperationIndex() + 1}. ${primerSetTitle}`, 379 type: operationType, 380 hrLength: homoFwd.length, 381 hrTm: Nucleotides.getMeltingTemperature(homoFwd, "oligoCalc"), 382 symmetry: "asymmetric", 383 currentPlasmidSequence: 384 Session.activePlasmid().sequence.slice(0, operationRange[0] - 1) 385 + seqToInsert 386 + Session.activePlasmid().sequence.slice(operationRange[1]), 387 primers: [ 388 { 389 name: "Forward primer", 390 label: "Forward primer", 391 regions: [ 392 {sequence: homoFwd, type: "HR", start: operationRange[0] - 1, direction: "fwd"}, 393 {sequence: seqToInsert, type: "INS", start: null, maxLength: seqToInsert.length, direction: "fwd"}, 394 {sequence: tempFwd, type: "TBR", start: operationRange[0] + seqToInsert.length, direction: "fwd"} 395 ], 396 }, 397 { 398 name: "Reverse primer", 399 label: "Reverse primer", 400 regions: [ 401 {sequence: "", type: "HR", start: operationRange[0] - seqToInsert.length, direction: "rev"}, 402 {sequence: "", type: "INS", start: operationRange[0], maxLength: seqToInsert.length, direction: "rev"}, 403 {sequence: tempRev, type: "TBR", start: operationRange[0] - 1, direction: "rev"}, 404 ], 405 }, 406 ], 407 }; 408 }; 409 410 411 // #region Symmetric Long 412 /** 413 * Generate a set of symmetric primers for long insertions/mutations 414 * 415 * @param {String} plasmidSequence - Sequence of the plasmid 416 * @param {Array<Number>} operationRange - Operation range/span 417 * @param {String} tempFwd - DNA sequence of the template binding region of the forward primer 418 * @param {String} tempRev - DNA sequence of the template binding region of the reverse primer 419 * @param {String} seqToInsert - DNA sequence to be inserted 420 * @param {Number} targetTMHR - Targer melting temperature for the homologous region 421 * @param {String} operationType - Type of IVA operation 422 * 423 * @returns {Object} - Primers set dictionary 424 */ 425 generateSymLongSet( 426 plasmidSequence, 427 operationRange, 428 tempFwd, 429 tempRev, 430 seqToInsert, 431 targetTMHR, 432 operationType, 433 ) { 434 let overlappingSeq = ""; 435 436 /** 437 * Symmetric primers, take turns deleting bases from each end until the target melting temperature is reached 438 */ 439 // Deleted bases trackers for the fragments 440 let homoFragmentLength1 = 0; 441 let homoFragmentLength2 = 0; 442 443 // Initial overlapping sequence is the entire insertion 444 overlappingSeq = seqToInsert; 445 446 // Take turns deleting bases from each end until the target melting temperature is reached 447 let turnHomoFwd1 = true; 448 while (true) { 449 const sliceIndices = (turnHomoFwd1 === true) ? [1, overlappingSeq.length]: [0, -1] 450 const stillAboveTargetTM = Nucleotides.getMeltingTemperature(overlappingSeq.slice(...sliceIndices), "oligoCalc") > targetTMHR; 451 const slicingGetsUsCloser = Math.abs(targetTMHR - Nucleotides.getMeltingTemperature(overlappingSeq.slice(...sliceIndices), "oligoCalc")) <= Math.abs(targetTMHR - Nucleotides.getMeltingTemperature(overlappingSeq, "oligoCalc")); 452 const minimumLengthNotReached = overlappingSeq.length > UserPreferences.get("HRMinLength"); 453 if ((stillAboveTargetTM || slicingGetsUsCloser) && minimumLengthNotReached) { 454 overlappingSeq = overlappingSeq.slice(...sliceIndices); 455 if (turnHomoFwd1 === true) {homoFragmentLength1++} else {homoFragmentLength2++}; 456 turnHomoFwd1 = !turnHomoFwd1; 457 } else { 458 break; 459 }; 460 }; 461 462 // Set homologous sequences 463 const homoFwd = seqToInsert.slice(homoFragmentLength1, seqToInsert.length); 464 const seqToInsertRevComp = Nucleotides.reverseComplementary(seqToInsert); 465 const homoRev = seqToInsertRevComp.slice(homoFragmentLength2, seqToInsertRevComp.length); 466 467 return { 468 title: (operationType !== "Deletion") ? `Long ${operationType}` : operationType, 469 type: operationType, 470 hrLength: overlappingSeq.length, 471 hrTm: Nucleotides.getMeltingTemperature(overlappingSeq, "oligoCalc"), 472 symmetry: "symmetric", 473 currentPlasmidSequence: 474 Session.activePlasmid().sequence.slice(0, operationRange[0] - 1) 475 + seqToInsert 476 + Session.activePlasmid().sequence.slice(operationRange[1]), 477 primers: [ 478 { 479 name: "Forward primer", 480 label: "Forward primer", 481 regions: [ 482 {sequence: "", type: "HR", start: operationRange[0] - 1, direction: "fwd"}, 483 {sequence: homoFwd, type: "INS", start: operationRange[0] + seqToInsert.length - 1, maxLength: seqToInsert.length, direction: "fwd"}, 484 {sequence: tempFwd, type: "TBR", start: operationRange[0] + seqToInsert.length, direction: "fwd"}, 485 ], 486 }, 487 { 488 name: "Reverse primer", 489 label: "Reverse primer", 490 regions: [ 491 {sequence: "", type: "HR", start: operationRange[0] + seqToInsert.length, direction: "rev"}, 492 {sequence: homoRev, type: "INS", start: operationRange[0], maxLength: seqToInsert.length, direction: "rev"}, 493 {sequence: tempRev, type: "TBR", start: operationRange[0] - 1, direction: "rev"}, 494 ], 495 }, 496 ], 497 }; 498 }; 499 500 501 // #region Asymmetric Long 502 /** 503 * Generate a set of asymmetric primers for long insertions/mutations 504 * 505 * @param {String} plasmidSequence - Sequence of the plasmid 506 * @param {Array<Number>} operationRange - Operation range/span 507 * @param {String} tempFwd - DNA sequence of the template binding region of the forward primer 508 * @param {String} tempRev - DNA sequence of the template binding region of the reverse primer 509 * @param {String} seqToInsert - DNA sequence to be inserted 510 * @param {Number} targetTMHR - Targer melting temperature for the homologous region 511 * @param {String} operationType - Type of IVA operation 512 * 513 * @returns {Object} - Primers set dictionary 514 */ 515 generateAsymLongSet( 516
516 plasmidSequence, 517 operationRange, 518 tempFwd, 519 tempRev, 520 seqToInsert, 521 targetTMHR, 522 operationType, 523 ) { 524 let overlappingSeq = ""; 525 526 /** 527 * Asymmetric primers, remove bases from the complementary strand until target tm is reached 528 */ 529 // Initial overlapping sequence is the entire insertion 530 overlappingSeq = Nucleotides.reverseComplementary(seqToInsert); 531 532 // Delete bases until target tm is reached 533 while (true) { 534 const stillAboveTargetTM = Nucleotides.getMeltingTemperature(overlappingSeq.slice(1), "oligoCalc") > targetTMHR; 535 const slicingGetsUsCloser = Math.abs(targetTMHR - Nucleotides.getMeltingTemperature(overlappingSeq.slice(1), "oligoCalc")) <= Math.abs(targetTMHR - Nucleotides.getMeltingTemperature(overlappingSeq, "oligoCalc")); 536 const minimumLengthNotReached = overlappingSeq.length > UserPreferences.get("HRMinLength"); 537 if ((stillAboveTargetTM || slicingGetsUsCloser) && minimumLengthNotReached) { 538 overlappingSeq = overlappingSeq.slice(1); 539 } else { 540 break; 541 }; 542 }; 543 544 // Set homologous sequences 545 const homoFwd = seqToInsert; 546 const homoRev = overlappingSeq; 547 548 const primerSetTitle = (operationType !== "Deletion") ? `Short ${operationType}` : operationType; 549 550 return { 551 title: `${Session.activePlasmid().getNextOperationIndex() + 1}. ${primerSetTitle}`, 552 type: operationType, 553 hrLength: overlappingSeq.length, 554 hrTm: Nucleotides.getMeltingTemperature(overlappingSeq, "oligoCalc"), 555 symmetry: "asymmetric", 556 currentPlasmidSequence: 557 Session.activePlasmid().sequence.slice(0, operationRange[0] - 1) 558 + seqToInsert 559 + Session.activePlasmid().sequence.slice(operationRange[1]), 560 primers: [ 561 { 562 name: "Forward primer", 563 label: "Forward primer", 564 regions: [ 565 {sequence: "", type: "HR", start: operationRange[0] - 1, direction: "fwd"}, 566 {sequence: homoFwd, type: "INS", start: null, maxLength: seqToInsert.length, direction: "fwd"}, 567 {sequence: tempFwd, type: "TBR", start: operationRange[0] + seqToInsert.length, direction: "fwd"}, 568 ], 569 }, 570 { 571 name: "Reverse primer", 572 label: "Reverse primer", 573 regions: [ 574 {sequence: "", type: "HR", start: operationRange[0] + seqToInsert.length, direction: "rev"}, 575 {sequence: homoRev, type: "INS", start: operationRange[0], maxLength: seqToInsert.length, direction: "rev"}, 576 {sequence: tempRev, type: "TBR", start: operationRange[0] - 1, direction: "rev"}, 577 ], 578 }, 579 ], 580 }; 581 }; 582 583 584 // #region Subcloning 585 /** 586 * Generate subcloning primers 587 * 588 * @param {Array<Number>} operationRange - Operation range/span 589 * @param {String} plasmidSequence - Sequence of the plasmid 590 * @param {String} seq5Prime - DNA sequence to be inserted at the 5' end 591 * @param {String} seq3Prime - DNA sequence to be inserted at the 3' end 592 * 593 * @returns {Object} - Primers set dictionary 594 */ 595 generateSubcloningSet( 596 operationRange, 597 plasmidSequence, 598 seq5Prime, 599 seq3Prime, 600 ) { 601 // Adjust range if pure insertion 602 if (operationRange[1] === null) { 603 operationRange = [ 604 operationRange[0], 605 operationRange[0] - 1 606 ]; 607 } else { 608 operationRange = [ 609 Math.min(...operationRange), 610 Math.max(...operationRange) 611 ]; 612 }; 613 614 // Get subcloning target sequence 615 const subclonignOriginPlasmid = Session.getPlasmid(Session.subcloningOriginPlasmidIndex); 616 const subcloningOriginSpan = Session.subcloningOriginSpan; 617 const subcloningTarget = subclonignOriginPlasmid.sequence.slice(subcloningOriginSpan[0] - 1, subcloningOriginSpan[1]) 618 619 // Get current plasmid sequence 620 const activePlasmidSequence = plasmidSequence; 621 /** 622 * Create pseudo plasmid sequence 623 * We pretend the subcloning target is already in the target 624 * vector and that we're doing a simple insertion at the 5' end 625 */ 626 const pseudoPlasmidSequence5Prime = activePlasmidSequence.slice(0, operationRange[0]-1) + subcloningTarget + activePlasmidSequence.slice(operationRange[1]); 627 628 // Generate first primer set using pseudo plasmid sequence 629 const primerSet5Prime = Primers.generateSet( 630 "Insertion", 631 [operationRange[0], null], 632 pseudoPlasmidSequence5Prime, 633 seq5Prime 634 ); 635 636 /** 637 * Create pseudo plasmid sequence 638 * We pretend the subcloning target is already in the target 639 * vector and that we're doing a simple insertion at the 3' end 640 */ 641 const pseudoPlasmidSequence3Prime = Nucleotides.reverseComplementary(pseudoPlasmidSequence5Prime); 642 // Adjust operation position because the 3' pseudo plasmid sequence is flipped 643 const operationPos = pseudoPlasmidSequence3Prime.length - operationRange[0] - subcloningTarget.length + 2 644 645 // Generate second primer set using pseudo plasmid sequence 646 const primerSet3Prime = Primers.generateSet( 647 "Insertion", 648 [operationPos, null], 649 pseudoPlasmidSequence3Prime, 650 Nucleotides.reverseComplementary(seq3Prime) 651 ); 652 653 // Shorthand 654 const sets = [primerSet5Prime, primerSet3Prime]; 655 656 // The full insertion sequence 657 const insertionSequenceFull = seq5Prime + subcloningTarget + seq3Prime; 658 659 // Return primer set dict 660 return { 661 title: `${Session.activePlasmid().getNextOperationIndex() + 1}. Subcloning`, 662 type: "Subcloning", 663 hrLength: [sets[0].hrLength, sets[1].hrLength], 664 hrTm: [sets[0].hrTm, sets[1].hrTm], 665 symmetry: sets[0].symmetry, 666 currentPlasmidSequence: 667 Session.activePlasmid().sequence.slice(0, operationRange[0] - 1) 668 + insertionSequenceFull 669 + Session.activePlasmid().sequence.slice(operationRange[1]), 670 primers: [ 671 { 672 name: "Forward primer", 673 label: "Forward primer", 674 regions: [ 675 {
676 sequence: sets[0].primers[0].regions[0].sequence, 677 type: "subHR", 678 start: operationRange[0] - 1, 679 direction: "fwd", 680 }, 681 { 682 sequence: sets[0].primers[0].regions[1].sequence, 683 type: "INS", 684 start: operationRange[0] - 1 + seq5Prime.length, 685 maxLength: sets[0].primers[0].regions[1].maxLength, 686 direction: "fwd", 687 }, 688 { 689 sequence: sets[0].primers[0].regions[2].sequence, 690 type: "subTBR", 691 start: operationRange[0] + seq5Prime.length, 692 direction: "fwd", 693 }, 694 ], 695 }, 696 { 697 name: "Reverse primer", 698 label: "Reverse primer", 699 regions: [ 700 { 701 sequence: sets[1].primers[0].regions[0].sequence, 702 type: "subHR", 703 start: operationRange[0] + insertionSequenceFull.length, 704 direction: "rev", 705 }, 706 { 707 sequence: sets[1].primers[0].regions[1].sequence, 708 type: "INS", 709 start: operationRange[0] + insertionSequenceFull.length - seq3Prime.length, 710 maxLength: sets[1].primers[0].regions[1].maxLength, 711 direction: "rev", 712 }, 713 { 714 sequence: sets[1].primers[0].regions[2].sequence, 715 type: "subTBR", 716 start: operationRange[0] + insertionSequenceFull.length - seq3Prime.length - 1, 717 direction: "rev", 718 }, 719 ], 720 }, 721 { 722 name: "Vector forward primer", 723 label: "Vector forward primer", 724 regions: [ 725 { 726 sequence: sets[1].primers[1].regions[0].sequence, 727 type: "subHR", 728 start: operationRange[0] + insertionSequenceFull.length - seq3Prime.length - 1, 729 direction: "fwd", 730 }, 731 { 732 sequence: sets[1].primers[1].regions[1].sequence, 733 type: "INS", 734 start: operationRange[0] + insertionSequenceFull.length - 1, 735 maxLength: sets[1].primers[1].regions[1].maxLength, 736 direction: "fwd", 737 }, 738 { 739 sequence: sets[1].primers[1].regions[2].sequence, 740 type: "subTBR", 741 start: operationRange[0] + insertionSequenceFull.length, 742 direction: "fwd", 743 }, 744 ], 745 }, 746 { 747 name: "Vector reverse primer", 748 label: "Vector reverse primer", 749 regions: [ 750 { 751 sequence: sets[0].primers[1].regions[0].sequence, 752 type: "subHR", 753 start: operationRange[0] + seq5Prime.length, 754 direction: "rev", 755 }, 756 { 757 sequence: sets[0].primers[1].regions[1].sequence, 758 type: "INS", 759 start: operationRange[0], 760 maxLength: sets[0].primers[1].regions[1].maxLength,
761 direction: "rev", 762 }, 763 { 764 sequence: sets[0].primers[1].regions[2].sequence, 765 type: "subTBR", 766 start: operationRange[0] - 1, 767 direction: "rev", 768 }, 769 ], 770 }, 771 ] 772 } 773 }; 774 775 776 // #region Linear fragment 777 insertFromLinearFragment(operationRange, plasmidSequence, seqToInsert, linFragName, translateFeature) { 778 // "Subclone" from pseudo linear fragment 779 // Adjust range if pure insertion 780 if (operationRange[1] === null) { 781 operationRange = [ 782 operationRange[0], 783 operationRange[0] - 1 784 ]; 785 } else { 786 operationRange = [ 787 Math.min(...operationRange), 788 Math.max(...operationRange) 789 ]; 790 }; 791 792 // Get current plasmid sequence 793 const activePlasmidSequence = plasmidSequence; 794 /** 795 * Create pseudo plasmid sequence 796 * We pretend the ins target is already in the target 797 * vector and that we're doing a simple insertion at the 5' end 798 */ 799 const pseudoPlasmidSequence5Prime = activePlasmidSequence.slice(0, operationRange[0]-1) + seqToInsert + activePlasmidSequence.slice(operationRange[1]); 800 801 // Generate first primer set using pseudo plasmid sequence 802 const primerSet5Prime = Primers.generateSet( 803 "Insertion", 804 [operationRange[0], null], 805 pseudoPlasmidSequence5Prime, 806 "" 807 ); 808 809 /** 810 * Create pseudo plasmid sequence 811 * We pretend the subcloning target is already in the target 812 * vector and that we're doing a simple insertion at the 3' end 813 */ 814 const pseudoPlasmidSequence3Prime = Nucleotides.reverseComplementary(pseudoPlasmidSequence5Prime); 815 // Adjust operation position because the 3' pseudo plasmid sequence is flipped 816 const operationPos = pseudoPlasmidSequence3Prime.length - operationRange[0] - seqToInsert.length + 2 817 818 // Generate second primer set using pseudo plasmid sequence 819 const primerSet3Prime = Primers.generateSet( 820 "Insertion", 821 [operationPos, null], 822 pseudoPlasmidSequence3Prime, 823 "" 824 ); 825 826 // Shorthand 827 const sets = [primerSet5Prime, primerSet3Prime]; 828 829 // The full insertion sequence 830 const insertionSequenceFull = seqToInsert; 831 832 // Return primer set dict 833 const primerSet = { 834 title: `${Session.activePlasmid().getNextOperationIndex() + 1}. Insertion from linear fragment`, 835 type: "Subcloning", 836 hrLength: [sets[0].hrLength, sets[1].hrLength], 837 hrTm: [sets[0].hrTm, sets[1].hrTm], 838 symmetry: sets[0].symmetry, 839 currentPlasmidSequence: 840 Session.activePlasmid().sequence.slice(0, operationRange[0] - 1) 841 + insertionSequenceFull 842 + Session.activePlasmid().sequence.slice(operationRange[1]), 843 primers: [ 844 { 845 name: "Vector forward primer", 846 label: "Vector forward primer", 847 regions: [ 848 { 849 sequence: sets[1].primers[1].regions[0].sequence, 850 type: "subHR", 851 start: operationRange[0] + insertionSequenceFull.length - 1, 852 direction: "fwd", 853 }, 854 { 855 sequence: sets[1].primers[1].regions[1].sequence, 856 type: "INS", 857 start: operationRange[0] + insertionSequenceFull.length - 1, 858 maxLength: 0, 859 direction: "fwd", 860 }, 861 { 862 sequence: sets[1].primers[1].regions[2].sequence, 863 type: "subTBR", 864 start: operationRange[0] + insertionSequenceFull.length, 865 direction: "fwd", 866 }, 867 ], 868 }, 869 { 870 name: "Vector reverse primer", 871 label: "Vector reverse primer", 872 regions: [ 873 { 874 sequence: sets[0].primers[1].regions[0].sequence, 875 type: "subHR", 876 start: operationRange[0], 877 direction: "rev", 878 }, 879 { 880 sequence: sets[0].primers[1].regions[1].sequence, 881 type: "INS", 882 start: operationRange[0], 883 maxLength: 0, 884 direction: "rev", 885 }, 886 { 887 sequence: sets[0].primers[1].regions[2].sequence, 888 type: "subTBR", 889 start: operationRange[0], 890 direction: "rev", 891 }, 892 ], 893 }, 894 ] 895 }; 896 897 898 /** 899 * Create new file with overhangs 900 */
901 const overhang5Prime = sets[0].primers[0].regions[0].sequence; 902 const overhang3Prime = Nucleotides.reverseComplementary(sets[1].primers[0].regions[0].sequence); 903 const linearFragmentSequence = overhang5Prime + seqToInsert + overhang3Prime; 904 905 // Initialize features dict with default 906 let newFileFeatures = { 907 "LOCUS": { 908 label: "", 909 note: "", 910 span: "" 911 } 912 }; 913 914 // Add plasmid object to project 915 const newPlasmid = new Plasmid( 916 null, // index 917 linFragName, 918 ".fasta", // extension 919 linearFragmentSequence, 920 newFileFeatures, 921 "linear", 922 null, // additional info 923 ); 924 Session.addPlasmid(newPlasmid); 925 926 // Annotate main sequence of interest 927 const sequenceSpan = [overhang5Prime.length + 1, overhang5Prime.length + seqToInsert.length]; 928 newPlasmid.newFeature( 929 sequenceSpan, 930 "fwd", 931 linFragName, 932 null, 933 null, 934 translateFeature, 935 "" 936 ); 937 938 return primerSet; 939 }; 940};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.