PageSourceSearch

https://ivaprime.com/static/js/primers.js

js ivaprime.com collected 2026-10-03 21:38:02 UTC 40,454 bytes, 940 lines download raw bytes

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.