"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.scheduleAsyncBwaMem = exports.makeReference = exports.writeSequence = exports.asyncBwaMem = void 0; const child_process_1 = require("child_process"); const os_1 = require("os"); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const fastq = __importStar(require("fastq")); const async_exec = (prog, args, onData) => { return new Promise((resolve, reject) => { const child = (0, child_process_1.spawn)(prog, args, { shell: true }); child.stdout.on('data', data => onData(data.toString().trim())); child.stderr.on('data', data => onData(data.toString().trim())); child.on('error', err => reject(err)); child.on('exit', code => resolve(code)); }); }; const invReplace = (regex, string, by = '_') => string.split('').map(letter => letter.match(regex) ? letter : by).join(''); const writeSequence = (sequenceName, sequence, filePath, lineN = 80) => __awaiter(void 0, void 0, void 0, function* () { return new Promise((resolve, reject) => __awaiter(void 0, void 0, void 0, function* () { var _a; try { const r = new RegExp(".{1," + lineN + "}", "g"); const regex_sam_restriction = /[>0-9A-Za-z!#$%&+\./:;?@^_|~-]|[\n\t]/g; const nSeqName = invReplace(regex_sam_restriction, sequenceName); yield fs_1.default.promises.writeFile(filePath, '>' + nSeqName + '\n' + ((_a = sequence.match(r)) === null || _a === void 0 ? void 0 : _a.join('\n').toUpperCase())); resolve(true); } catch (error) { console.log(error); reject(false); } })); }); exports.writeSequence = writeSequence; const makeReference = (sequenceName, sequence, filePath, lineN = 80) => __awaiter(void 0, void 0, void 0, function* () { if (yield writeSequence(sequenceName, sequence, filePath, lineN)) yield async_exec('bwa', ['index', filePath], () => console.log); }); exports.makeReference = makeReference; const asyncBwaMem = (refPath, reads, // R1 : string | Array, // R2 : string | Array, runName, libName, outputDir, onData, options) => { return new Promise((resolve, reject) => __awaiter(void 0, void 0, void 0, function* () { try { const defaultOptions = { output_discordant: true, output_splitted: true, output_unmapped: true }; if (typeof options === 'undefined') { options = defaultOptions; } else { options = Object.assign(Object.assign({}, defaultOptions), options); } const refName = path_1.default.parse(refPath).name; const bwa = 'bwa'; const samblaster = 'samblaster'; const samtools = 'samtools'; const sambamba = 'sambamba'; let readsIn; let isPairedEnd = false; if (Array.isArray(reads)) { isPairedEnd = true; console.log('Assuming paired end reads'); const [R1, R2] = reads; const R1_arr = Array.isArray(R1) ? R1.join(' ') : R1; const R2_arr = Array.isArray(R2) ? R2.join(' ') : R2; const R1_kitty = R1_arr.slice(-2) === 'gz' ? 'zcat' : 'cat'; const R2_kitty = R2_arr.slice(-2) === 'gz' ? 'zcat' : 'cat'; const R1_in = `'< ${R1_kitty} ${R1_arr}'`; const R2_in = `'< ${R2_kitty} ${R2_arr}'`; readsIn = R1_in + ' ' + R2_in; } else { readsIn = reads; } let bam = path_1.default.join(outputDir, `bwa_mem_properly_on_${refName}.bam`); let bamSorted = path_1.default.join(outputDir, `bwa_mem_properly_on_${refName}.sorted.bam`); let retObj = { bamSorted }; if (options === null || options === void 0 ? void 0 : options.remove_mapped) { bam = '/dev/null'; delete retObj.bamSorted; } const threads = String((0, os_1.cpus)().length); let samblasterCmd = []; // https://github.com/GregoryFaust/samblaster samblasterCmd = ['|', samblaster, '--addMateTags', '-a', '-e', // Exclude reads marked as duplicates from discordant, splitter, and/or unmapped ]; if ((options === null || options === void 0 ? void 0 : options.output_discordant) || (options === null || options === void 0 ? void 0 : options.output_splitted)) { console.log('Using samblaster'); if (options === null || options === void 0 ? void 0 : options.output_discordant) { if (!isPairedEnd) { console.log('Discordant reads can be found only in paired reads, skipping'); } else { const discordantFile = path_1.default.join(outputDir, `bwa_mem_discordants_on_${refName}.sam`); console.log('Discordant reads file path: ', discordantFile); samblasterCmd = [...samblasterCmd, '-d', discordantFile]; retObj = Object.assign(Object.assign({}, retObj), { discordantFile }); } } if (!isPairedEnd) { samblasterCmd = [...samblasterCmd, '--ignoreUnmated']; } if (options === null || options === void 0 ? void 0 : options.output_splitted) { const splitterFile = path_1.default.join(outputDir, `bwa_mem_splitters_on_${refName}.sam`); console.log('Splitted reads file path: ', splitterFile); samblasterCmd = [...samblasterCmd, '-s', splitterFile]; retObj = Object.assign(Object.assign({}, retObj), { splitterFile }); } } if (options === null || options === void 0 ? void 0 : options.output_unmapped) { const unmappedFile = path_1.default.join(outputDir, `bwa_mem_unmapped_on_${refName}.fq`); console.log('Unmapped reads file path: ', unmappedFile); samblasterCmd = [...samblasterCmd, '-u', unmappedFile]; retObj = Object.assign(Object.assign({}, retObj), { unmappedFile }); } if (!fs_1.default.existsSync(refPath + '.amb')) { yield async_exec(bwa, ['index', refPath], (message) => onData('[BWA-INDEX] ' + message)); } console.log(options, samblasterCmd); const code = yield async_exec(bwa, ['mem', '-t', threads, '-R', `"@RG\\tPL:Illumina\\tID:${+(new Date)}\\tSM:${runName}\\tLB:${libName}"`, refPath, readsIn, ...samblasterCmd, '|', samtools, 'view', '-Sb', '-', '>', bam ], (message) => onData('[BWA-MEM] ' + message)); onData('[BWA-MEM][EXIT CODE] ' + code); if (retObj.bamSorted) { const code_sort = yield async_exec(sambamba, ['sort', '-t', threads, bam ], (message) => onData('[SAMBAMBA-SORT] ' + message)); onData('[SAMBAMBA-SORT][EXIT CODE] ' + code_sort); fs_1.default.unlinkSync(bam); } resolve(retObj); } catch (err) { reject(err); } })); }; exports.asyncBwaMem = asyncBwaMem; const asyncBwaMemWorker = (args) => __awaiter(void 0, void 0, void 0, function* () { const { refPath, reads, runName, libName, outputDir, onData } = args; yield asyncBwaMem(refPath, reads, runName, libName, outputDir, onData); }); const scheduleAsyncBwaMem = (allReads, refPath, runName, libName, allOutputDir, onData) => __awaiter(void 0, void 0, void 0, function* () { const q = fastq.promise(asyncBwaMemWorker, 1); yield Promise.all(allReads.map((reads, i) => q.push({ refPath, reads, runName, libName, outputDir: allOutputDir[i], onData }))); }); exports.scheduleAsyncBwaMem = scheduleAsyncBwaMem;