New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

mass-fragmentation

Package Overview
Dependencies
Maintainers
2
Versions
40
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mass-fragmentation - npm Package Compare versions

Comparing version 1.0.2 to 1.0.3

src/__tests__/getFragmentationSVG.test.js

595

lib/index.js

@@ -8,2 +8,5 @@ 'use strict';

var OCL = require('openchemlib');
var fs = require('fs');
var path = require('path');
var reactTreeSvg = require('react-tree-svg');

@@ -335,345 +338,27 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

const cid = {
positive: [
{
Label: 'Alpha cleavage',
rxnCode:
'gCa@@duPGtF@!fI@Gu` eM@bXzB#qbq a` qQp#!RtEJHC^h@GPhB !RGUi| !R@FN?Dquz_@',
},
{
Label: 'Alpha cleavage',
rxnCode:
'gJPI@DBTijhCzB@!gK`I@DJTcj`OhH@#qbqh qfUH#!Rmwwp_[]|_g|]lcp !R?`BE@[_|b@H]vgp',
},
{
Label: 'Alpha cleavage',
rxnCode:
'gJPa@eJSKTA}E@!eMH`eIxH eFHBLGtP#qbqh qbH qu#!Rw`BH?J_|bMt]|hH !Rwpq?Dquz?@ !R?`@]vgp',
},
{
Label: 'Alpha cleavage',
rxnCode:
'gJQIL@`ReMS@_PP!eMH`eIxH eFB@HcA}D@#qbqh qJh Ql#!Rm?wpwXE?y{p]lmp !Rwxq?Dquz?@ !R@NL]vgp',
},
{
Label: 'Alpha cleavage',
rxnCode:
'gJXA@IRij`OhH@!gKhAAirPFhB#qbuH qfjH#!RM{upoPD@Mwp]lgp !R@BL?x~WAwrp]Vgp',
},
{
Label: 'Alpha cleavage',
rxnCode:
'gJYADEJSKTA}E`!eMH`eIxH eF`BLGtX#qbqh qbH qu#!RO`BH?C_|bGt]|hH !Rwtq?Dquz_@ !R?`@]vgp',
},
{
Label: 'Alpha cleavage',
rxnCode:
'gC`AAJTu@P!eF@bXpP eF@HpP#qTq qa Q\\#!R@AM?DpAlGTJB !R@DQCrip !R_sL}Nmp',
},
{
Label: 'Alpha cleavage',
rxnCode:
'gChA@IRfhCzC@!eF`BLD eF@bXpP#qbq Ql qQ#!R{|L@p~~lG[K| !R@DL]Vgp !R_xp]ngp',
},
{
Label: 'Aromatic Indicutive Cleavage',
rxnCode:
'gC`AAJTu@]SP!eF@bXpP eF@HhP#qrL QX qq#!Rgzp@kI}cG]K| !R@BL]vgp !R@BL]~kp',
},
{
Label: 'Benzyl carbocation Resonance',
rxnCode:
'gOpAGJUKutkPD!gOpAMJUNusMHD#qkOfP qi{tP#!Rm?w~@Hc}mpJw@ox@GWHB !R?`BH?[_}mpJw@k_}G]k|',
},
{
Label: 'Benzyl carbocation Resonance',
rxnCode:
'gOpAMJUNusMHD!gOpAGJUKutkPD#qkyfP qkOfP#!Rb@Jw@oy?bOvH?_y?G_H| !Rb@K~_{\\BbOvH?_y?GXY|',
},
{
Label: 'Benzyl carbocation Resonance',
rxnCode:
'gOpAMJUNusMHD!gOpAMJUKua@@D#qi{tP qkOfP#!R?`BH?[_}mpJw@k_}GWK| !Rm?w~@Hc}mpJw@ox@G]k|',
},
{
Label: 'Dioxolane elimination (aromatic)',
rxnCode:
'detDB@iiRfUwJFZB@H@H!eFHBJD daDHB@RTfUVyiZPB#IaLhfGed Ql IZdDezP#!RXJhcFCx@NALlFC`Lp@[@FZJpGWK| !R@FL]vkp !R@CzbuzJppAg@Ac`LNAL]aop',
},
{
Label: 'Displacement Reaction a.',
rxnCode:
'daxHbN@HzTiuZjl@vaHpdLE\\QFHIG~p`!gKPI@DBTi\\Zj@` eFHBND#q{ENT q~Yh Qd#!Rm?vH?[\\Bm?vw@ox@?`@]ThH !R?g|_Fa}eTv\\]vcp !R@AL]Agp',
},
{
Label: 'Displacement Reaction a.',
rxnCode:
'dazHBF@IRUUzj`CZNcGPpUqDX`d_{G@!gKXA@IReqjhB eFHBND#q]|VH qOtP q]#!RO`@~@N\\Byswg@n\\}y?p]XbH !RcLbLipBe@Lh]Ngp !R_pp]~op',
},
{
Label: 'Displacement Reaction b.',
rxnCode:
'gJXA@IRUjPH!eM@fXvB eF`BLGtP#qlVH qrH ql#!R@FL@[@CC@Fp]bgp !RGtL@PqwZO@ !R@DL]~gp',
},
{
Label: 'Displacement Reaction b.',
rxnCode:
'gFpa@eJSJZuLA}E@!gBQA@IReZf@~`` eFHBND#qYuT quT qY#!RE?rQ@G\\M[xYsAAwR`` !R@Cx~Gsx_GUj| !R_pp]nop',
},
{
Label: 'Displacement Reaction b.',
rxnCode:
'gFpa@eJSJZuRA|eE`!gCaA@IRUdCzB@ eFHBND#qYuT qre qY#!RM?sQ@D}MW|YKAAwb`` !R_rq?Dw|lGYk| !R_rp]~gp',
},
{
Label: 'Displacement Reaction b.',
rxnCode:
'gFpa@eJSJZuTA|eE`!gJQA@IRUzPH fI@Gu`#qYuT qYlP Qp#!RM?sQ@D}MW|YKAAvR`` !R_`A~@M\\Buwt]fdH !RGWi|',
},
{
Label: 'Displacement Reaction b.',
rxnCode:
'gFqIL@`ReLfts@_HQX@!gC`I@DBTeY@~`` eFHBND#qrlK quY Qd#!Rsg}_qdc}Stw}mquRw@ !R_rq?Dw|lGYk| !R_rp]~gp',
},
{
Label: 'Displacement Reaction b.',
rxnCode:
'gFqIL@`ReLftt`_PP!gBPI@DBTiVi`OhH@ eFHBND#qrlK qqk Qd#!Rsg}_qdc}Stu}mqvrw@ !R@Gy~Owx?GUj| !R_rp]ngp',
},
{
Label: 'Displacement Reaction b.',
rxnCode:
'gFqIL@`ReLftu@_HQX@!gJQA@IRUzPH fH`PA}X#qrlK qbKh Qp#!RSg}_Q`a}Sxu}mqtrw@ !Ro`B~@I_|e{t]fdH !RGWi|',
},
{
Label: 'Displacement Reaction b.',
rxnCode:
'gFyADEJSJZuTA|ee`!gJQA@IRUzPH fJ@Gup#qYuT qYlP Qp#!R]?pq@B?MOrXkAAvR`` !R?`C~@K\\Bm?t]fdH !RG_i|',
},
{
Label: 'Displacement Reaction b.',
rxnCode:
'gOpiEPDBTfVKZj`OdHl@!gGQIJ@`Rdj|tHgzV@ fI@Gu`#qbqkx qbOn qX#!RU?qQ@@~MGtXYGplDG^K| !Rog~~_{B}lCrpoQtzg@ !RGPY|',
},
{
Label: 'Displacement Reaction b.',
rxnCode:
'gOqa@jIRYXmjj@~Pbp!gGPaAQJRksPb_iX@ fI@Gu`#qYuW` qYlV qx#!RU?qQ@@~MGtXYGplDG^K| !Rog~~_{B}lCrpoQtzg@ !RGPY|',
},
{
Label: 'Grob-Wharton',
rxnCode:
'gNpaAQJSOMU@_HQX@!gCa@@duPGtD@ eMH`eIXOh`#qNWeX qNW qlX#!Rb@JH_Wx@b@I~@Ha}GWJB !Ru?sW_Wx@GYjB !R_rq?Dqwz_@',
},
{
Label: 'Grob-Wharton',
rxnCode:
'gNxaLJIRU[jXCzZ@!eMH`eIxH eF`BJGtP eFHBND#q{iV` Qv qM q{#!Rm?vw?[\\|m?v~_{\\|GXJ\\ !R@Kx@oauZ_@ !R@BL]^gp !R_tp]Agp',
},
{
Label: 'Inductive cleavage',
rxnCode:
'gChA@IRVhCTB!eF``fJD eF@Hp_za@#qbq Qd qq#!R@AL@[@@SGPi| !R@AL]Vgp !R@AL]^kp',
},
{
Label: 'Inductive cleavages assisted by β-hydrogen removal',
rxnCode:
'daxHB@zTeVVjh@~ah@!gGQA@IRUVi@~`` eF@HhP#IaLJnXP qbqk IxP#!RtEsPO]A\\tCwPWCy?tCt]Bkp !R@DL@[@AC@Fp@Pquz_@ !R@DL]akp',
},
{
Label: 'Inductive cleavages assisted by β-hydrogen removal',
rxnCode:
'gFqAAYRe|Zj`OiH@!gGQA@IRezY@~``#qbqk qbqk#!R_O@T~pAYDEKp@quJ_@ !R@Fq?[@@S_qM?[AwZ_@',
},
{
Label: 'Oxonium Resonance',
rxnCode: 'eFH`fJGtP!eFHbfLGtP#QX QX#!R@Fp]rgp !R@FL]Nkp',
},
{
Label: 'Remote Hydrogen rearrangement',
rxnCode:
'gCa@@dmPGrDJ@!fI@Gu` eM@HvB#qbq a` qrH#!R@AL@[@@SGPi| !RGUi| !R_qL@Dquzo@',
},
{
Label: 'Remote Hydrogen rearrangement',
rxnCode:
'gCa@@eMPD!eFHBLGtP eF@HhP#qTq Q qa#!R@NL?xpAlG_K !R@FL]Ngp !R@FL]Akp',
},
{
Label: 'Remote Hydrogen rearrangement',
rxnCode:
'dcN@B@J\\bRbRaWCUUUL@_ITC@!gFpAEJTjZuRA@ eM`AIhH eM@HvB#IO^adkBFU qb^i IO^ IeQ#!Rm?u~_?C}|Gq~_?A}|Gu~_?A||Gwp_Atr{@ !R_g~w_K_}mwvw_AvZo@ !R@Fp@XqtF_@ !ROvL@XquFo@',
},
{
Label: 'Resonance',
rxnCode: 'eMB`HIRVB!eMB`HiRZB#Qg Qg#!R@Fp@HquJ_@ !R@Fp@Hqtz?@',
},
{
Label: 'Resonance',
rxnCode: 'eM``eIXH!eM`beIhH#Qg Qg#!R@Fp@XquJ_@ !R@Fp@Xqtz?@',
},
{
Label: 'Retro-Diels–Alder ',
rxnCode:
'daD@@DiURijj`B!gC`@Die@` gC`@DiZDC@#IZhILxP q]a ISND#!RmwvH__y??g~H_Xa}?g|]lnp !R@Fq?[@@SGWk| !Rb@K~_xc}GUj|',
},
{
Label: 'Retro-Diels–Alder ',
rxnCode:
'daDH@@RfVijZj@H!gGQ@@dsMLA@ eF@HhP#IhfpXS` IhlEN qc#!RbGw~@H`B?g~H@ox@bGt]Bgp !RmpJw?_x@?g~w@atZ?@ !R@AL]~kp',
},
{
Label: 'Retro-Diels–Alder ',
rxnCode:
'dclD@@QIeUeieujZij@H!eFHBJD deTH@@RUf\\YYif`aF#ItYyRkFMH IeP It^JlMd`#!R?g~H?[_}?g?~_{_}m?w~_xc}?g~w?QuRK@ !R_vp]~gp !RmpK~@K\\Bm?w~@Oy?m?rw@ox@G^hb',
},
{
Label: 'Retro-Diels–Alder ',
rxnCode:
'dieH@DxLbbbQ[fjZ`B!gGY@BDeUYaLp eM`BM``#IgBRnaa IgDaa qMx#!RmpJw__x@mpK~_{\\B?`Bw@atb@` !Rb@JH@hc}b@K~@Atz`` !R_qL@DqtF_@',
},
{
Label: 'Retro-Diels–Alder ',
rxnCode:
'dif@@@Ri^zZjfh@`!gGP@DjZY`H eM`BM``#IZhddp^ Ipdza IZI#!R?g~H@k\\BbGvH_X`B?g~H_Qurg@ !Rm?w~@Hc}mpK~_qvZo@ !R_qL@Dqwz_@',
},
{
Label: 'Retro-Diels–Alder ',
rxnCode:
'dclDB@{iRYeevz]ZA`b@B!gGPa@QJRmsPbX gFp@DiTt@@B#IwbFleVPx IwaeS qb^i#!RbCvH?Ky_bCv~@H`}oe~H?Hc|oe~~@Aurw@ !RbKvHoX`Bo`BHoQtz_@ !Rog~wOK_}m?vwOAwzO@',
},
{
Label: 'Retro-Diels–Alder ',
rxnCode:
'dmLDB@{irJJIQEneV`hB@B!eF@HhP didDB@hIRVY~G``@@`#IwlDdeZa` IFP IwlIEjF#!Rg`BH?H_|g`BHGYx@a?rH?Hc|bAt]\\bH !R_uP]Fop !RmuvwWYy?mrr`g[^]muvwKAtzg@',
},
{
Label: 'Retro-Diels–Alder ',
rxnCode:
'fdeA`D@\\brTfYY_m]gIZ|EhJB@@h@@~aF@!didHB@BTfUvfZVV@OhB@ didD@@iIYgxUie`B#iup`jXdibCPcXKoI iSxKhvMHP IwbkEYH#!RbOrHW]x@bOs^[xc|w`BHWXa]w`C^[xc|ipK^@Mx@i?s^@AuRx` !Ri?rgW]x@i?rg?Mx@i?rg?Aujp` !RmsvwO]y?mqrPw[_]msvwGAwzG@',
},
{
Label: 'Retro-heteroene (Claisen rearrangement)',
rxnCode:
'daxH@@Re^jf`B!daxH@@RUiZjPB#IphiFG` IrDZax`#!RbOrW?Hb}e{vH?Hb}o`@]|hH !R@BL@[@@c@FqlWrM_@BL]~cp',
},
{
Label: 'Retro-heteroene (McLafferty-type rearrangement)',
rxnCode:
'daxH@@RUiZjPB!gJP@DjYdB eMHAIdOh`#IaLJfxP IJfxP Qg#!R@FL@[@Ac@FqlOvL?@FL]Bcp !R|Grw_Gy?|Ot]vkp !R@Fp@XqtF_@',
},
{
Label: 'Tropylium rearragement',
rxnCode:
'gOpAMJUKua@@D!gOpAGJTju`@@D#qgVMX qYoTp#!Rg`BGgXb]a?rG?H^]GPhB !RN@swnix@C[AkCDJpGSkB',
},
{
Label: 'Tropylium resonance',
rxnCode:
'gOpAGJTju`@@D!gOpAGJTju`@@D#IaLkit I{MbTh#!Rh@VUDFcl?H`N@HCfGXiB !R[X~@abJwFEL@F[y_GSi\\',
},
{
Label: 'Tropylium resonance',
rxnCode:
'gOpAGJTju`@@D!gOpAGJTju`@@D#q]yfP qgSuH#!R[X?@qaJONGO@v_y?GWK\\ !RQps~_}?YQwMmc}?FG[i|',
},
{
Label: 'Tropylium resonance',
rxnCode:
'gOpAGJTju`@@D!gOpAGJTju`@@D#qrLoX qMsW`#!RN@s_qox@[[AGCAJpGWKB !R[[C@vQJpN@s@qox@G_iB',
},
{
Label: 'Tropylium resonance',
rxnCode:
'gOpAGJTju`@@D!gOpAGJTju`@@D#qMsW` q{eQp#!R[[C@vQJpN@s@qox@GPkB !Rw}dx\\vvO?g|Rct]sGP[\\',
},
{
Label: 'Tropylium resonance',
rxnCode:
'gOpAGJTju`@@D!gOpAGJTju`@@D#q{eQp qTwKh#!Rw}dx\\vvO?g|Rct]sG]K\\ !R?`AG\\t\\Lw|[_vVvpGSiB',
},
{
Label: 'Tropylium resonance',
rxnCode:
'gOpAGJTju`@@D!gOpAGJTju`@@D#qTwKh qqUcx#!R?`AG\\t\\Lw|[_vVvpG]JB !Rw|XxCFvp?`@RlD\\LG[kB',
},
{
Label: 'Tropylium resonance ',
rxnCode:
'gOpAGJTju`@@D!gOpAGJTju`@@D#qgSuH qrLoX#!RQps~_}?YQwMmc}?FGUH| !RN@s_qox@[[AGCAJpGXXb',
},
],
negative: [],
};
const ei = {
positive: [],
negative: [],
};
const esi = {
positive: [
{
Label: 'Ionization',
rxnCode: 'eF`BLGtX!eF``fLGt\\#Qd Qd#!R@FL]Rgp !R@FL]vgp',
},
{
Label: 'Ionization',
rxnCode: 'eM`BN`~b@!eM``fN`~c@#Q[ Q[#!R@AM?DquRo@ !R@AM?Dqtzo@',
},
{
Label: 'Ionization',
rxnCode: 'gCh@AGj@`!gChA@Icu@_PP#qTq qTq#!RbOvw?_x@GYK| !RbOvw?_x@GWk|',
},
{
Label: 'Ionization',
rxnCode:
'gCa@@dmPFtwhHcMCAlp!eMH`eIhOhp#Qv@ Qv#!R@AL@[@@SGSH| !R@Fp@Dqwz``',
},
{
Label: 'Ionization',
rxnCode: 'eFB@HcA}D@!eFB`HIcA}F@#QX QX#!R_vp]bgp !R_vp]vgp',
},
{
Label: 'Ionization',
rxnCode: 'eMB@HchH!eMB`HIchOh`#Q[ Q[#!R@AM?Dqtbo@ !R@AM?Dquz@`',
},
{
Label: 'Ionization',
rxnCode: 'eMHAIXMicLLG~r!eFH`fJGtP#QX QX#!R@AL@[AtbO@ !R@AL]nkp',
},
{
Label: 'Ionization',
rxnCode: 'eMHBN``!eMH`fN`~b@#Qg Qg#!R@FL?XqtJ_@ !R@FL?XqwZ_@',
},
{
Label: 'Ionization',
rxnCode: 'eFB@HcA}D@!eFB`HIcA}F@#QX QX#!R_vp]bgp !R_vp]vgp',
},
{
Label: 'Ionization',
rxnCode: 'eFHBLGtP!eFH`fLGtX#QX QX#!R@AL]Pmp !R@AL]^gp',
},
{
Label: 'Ionization',
rxnCode: 'eM`BN`~b@!eM``fN`~bOza@#Qg Qg#!R@Fq?[AuJ?@ !R@FM?Dqvz_@',
},
],
negative: [],
};
/**
* @description get fragmentation databases based on the technique used
* @param {string} databaseName "cid", "ei" or "esi"
* @returns {object} object containing the fragmentation database
* @description Get the default databases of reactions for positive and negative mode
* @param {Object} options - Options for database selection
* @param {string} [options.mode='positive'] - The mode to be used
* @param {string} [options.kind='ionization'] - The kind of database to be used
* @param {string} dwarEntry - The dwar entry to be used
* @returns
*/
function getDatabase(databaseName) {
switch (databaseName) {
case 'cid':
return cid;
case 'ei':
return ei;
default:
return esi;
function getDatabases(options = {}, dwarEntry = '') {
const { mode = 'positive', kind = 'ionization' } = options;
if (!dwarEntry) {
dwarEntry = fs.readFileSync(
path.join(__dirname, './ReactionMassFragmentation.dwar'),
'utf8',
);
}
const data = openchemlibUtils.parseDwar(dwarEntry).data;
const database = data.filter(
(entry) =>
// @ts-ignore
entry.kind === kind && (entry.mode === mode || entry?.mode === 'both'),
);
return database;
}

@@ -684,9 +369,6 @@

* @param {Array} trees Fragmentation trees
* @param {Array} products Fragmentation trees grouped by product idCode
* @returns {object} Object with the following properties:
* - masses: array of monoisotopic masses
* - trees: array of fragmentation trees
* - products: array of trees grouped by product idCode
* @returns {Array} array of mz values
*/
function getMasses(trees, products) {
function getMasses(trees) {
let masses = {};

@@ -696,18 +378,14 @@ for (const tree of trees) {

}
return {
masses: Object.keys(masses).map(Number),
trees,
products,
};
return Object.keys(masses).map(Number);
}
function getMassesFromTree(currentBranch, masses) {
for (const product of currentBranch.products) {
if (Math.abs(product.charge) > 0) {
masses[product.mz] = true;
for (const molecule of currentBranch.molecules) {
if (Math.abs(molecule.info.charge) > 0) {
masses[molecule.info.mz] = true;
}
if (product.children.length > 0) {
for (const child of product.children) {
getMassesFromTree(child, masses);
}
}
if (currentBranch.children && currentBranch.children.length > 0) {
for (const child of currentBranch.children) {
getMassesFromTree(child, masses);
}

@@ -719,125 +397,132 @@ }

* @description Fragment a molecule by applying reactions from a custom database of reactions
* @param {import('openchemlib').Molecule} molecule - The OCL molecule to be fragmented
* @param {import('openchemlib').Molecule} oclMolecule - The OCL molecule to be fragmented
* @param {Object} [options={}]
* @param {string} [options.databaseName='cid'] - The database to be used
* @param {string} [options.mode='positive'] - The mode to be used
* @param {number} [options.maxDepth=5] - The maximum depth of the fragmentation tree
* @param {boolean} [options.getProductsTrees=false] - If true, the products trees are returned else products is an empty array
* @param {number} [options.maxDepth=5] - The maximum depth of the overall fragmentation tree
* @param {number} [options.limitReactions=200] - The maximum number of reactions to be applied
* @param {string} [options.dwarEntry] - The dwar entry to be used, if not provided, the default one will be used
* @param {number} [options.maxIonizationDepth=1] - The maximum depth of the ionization tree
* @param {Object} [options.customDatabase={}] - A custom database of reactions
* @param {Array} [options.customDatabase.positive] - A custom database of reactions for positive mode
* @param {Array} [options.customDatabase.negative] - A custom database of reactions for negative mode
* @param {Object} [options.customDatabase.ionization] - A custom database ionization reactions
* @param {Array} [options.customDatabase.ionization.positive] - A custom database of ionization reactions for positive mode
* @param {Array} [options.customDatabase.ionization.negative] - A custom database of ionization reactions for negative mode
* @param {number} [options.minIonizationDepth=1] - The minimum depth of the ionization tree
* @param {number} [options.minReactionDepth=0] - The minimum depth of the reaction tree
* @param {number} [options.maxReactionDepth=3] - The maximum depth of the reaction tree
* @returns {object} In-Silico fragmentation results with the following properties:
* - masses: array of monoisotopic masses
* - trees: array of fragmentation trees
* - products: array of trees grouped by product idCode
* - validNodes: nodes without dead branches
*/
function reactionFragmentation(molecule, options = {}) {
let {
databaseName = 'cid',
function reactionFragmentation(oclMolecule, options = {}) {
const {
mode = 'positive',
dwarEntry = '',
maxDepth = 5,
limitReactions = 200,
minIonizationDepth = 1,
maxIonizationDepth = 1,
getProductsTrees = false,
customDatabase = {},
limitReactions = 200,
minReactionDepth = 0,
maxReactionDepth = 3,
} = options;
let database;
let IonizationDb;
if (customDatabase[mode] && customDatabase[mode].length > 0) {
database = customDatabase;
} else {
database = getDatabase(databaseName);
}
if (!database) {
throw new Error(`Database ${databaseName} not found`);
}
if (databaseName === 'cid') {
if (
customDatabase.ionization &&
customDatabase.ionization[mode].length > 0
) {
IonizationDb = customDatabase.ionization[mode];
} else {
IonizationDb = getDatabase('')[mode];
}
}
let results = {};
const reactions = database[mode];
if (IonizationDb) {
let ionizationFragments = {
trees: [],
products: [],
};
for (
let currentMaxIonizationDepth = 1;
currentMaxIonizationDepth <= maxIonizationDepth;
currentMaxIonizationDepth++
) {
let ionizationLevelResult = openchemlibUtils.applyReactions([molecule], IonizationDb, {
maxDepth: currentMaxIonizationDepth,
limitReactions,
});
// add array to ionizationfragments.trees
// @ts-ignore
ionizationFragments.trees.push(...ionizationLevelResult.trees);
// @ts-ignore
ionizationFragments.products.push(...ionizationLevelResult.products);
}
for (let tree of ionizationFragments.trees) {
getMoleculesToFragment(tree, reactions, maxDepth, limitReactions);
}
if (getProductsTrees) {
const reactions = new openchemlibUtils.Reactions(OCL__default["default"], {
moleculeInfoCallback: (molecule) => {
// @ts-ignore
ionizationFragments.products = openchemlibUtils.groupTreesByProducts(
ionizationFragments.trees,
);
}
results = ionizationFragments;
} else {
results = openchemlibUtils.applyReactions([molecule], reactions, {
maxDepth,
limitReactions,
});
}
const mf = openchemlibUtils.getMF(molecule).mf;
const mfInfo = new mfParser.MF(mf).getInfo();
return {
mf,
mw: mfInfo.mass,
em: mfInfo.monoisotopicMass,
mz: mfInfo.observedMonoisotopicMass,
charge: mfInfo.charge,
};
},
maxDepth,
limitReactions,
skipProcessed: true,
});
let { masses, trees, products } = getMasses(results.trees, results.products);
reactions.appendHead([oclMolecule]);
reactions.applyOneReactantReactions(
getDatabases({ kind: 'ionization', mode }, dwarEntry),
{
min: minIonizationDepth,
max: maxIonizationDepth,
},
);
reactions.applyOneReactantReactions(
getDatabases({ kind: 'reaction', mode }, dwarEntry),
{
min: minReactionDepth,
max: maxReactionDepth,
},
);
const trees = reactions.trees;
const validNodes = reactions.getValidNodes();
const masses = getMasses(trees);
return {
trees,
validNodes,
masses,
trees,
products,
};
}
function getMoleculesToFragment(tree, reactions, maxDepth, limitReactions) {
for (let product of tree.products) {
if (product.children.length === 0) {
if (product.charge !== 0) {
let molecule = OCL__default["default"].Molecule.fromIDCode(product.idCode);
function getFragmentationSVG(trees, options = {}) {
const { OCL, accuracy, masses = [] } = options;
let fragments = openchemlibUtils.applyReactions([molecule], reactions, {
maxDepth,
limitReactions,
getProductsTrees: true,
});
// @ts-ignore
product.children = fragments.trees;
}
} else {
for (let child of product.children) {
getMoleculesToFragment(child, reactions, maxDepth, limitReactions);
}
const rendererOptions = {
nodeRenderer: reactTreeSvg.moleculeRenderer,
arrowRendererOptions: {
getLabel: (node) => {
return node?.reaction?.labreel;
},
labelPosition: 'center',
},
nodeRendererOptions: {
getTopLabel: (node) => {
const mz = node?.molecules[0]?.info?.mz;
if (mz === undefined) return;
return `${mz.toFixed(4)} m/z`;
},
getBoxStyle: (node) => {
for (const molecule of node.molecules) {
if (isInRange(masses, molecule?.info?.mz, accuracy)) {
return {
fillOpacity: 0.2,
fill: 'red',
};
}
}
},
getMolecules: (node) => {
return node.molecules.map((molecule) =>
OCL.Molecule.fromIDCode(molecule.idCode),
);
},
},
positionOptions: {
spacingHorizontal: 150,
},
};
return reactTreeSvg.render(trees, rendererOptions);
}
function isInRange(masses, mass, accuracy) {
if (!mass || !masses) {
return false;
}
const massAccuracy = (accuracy * mass) / 1e6;
for (const value of masses) {
if (Math.abs(value - mass) <= massAccuracy) {
return true;
}
}
return false;
}
exports.cid = cid;
exports.ei = ei;
exports.esi = esi;
exports.fragment = fragment;
exports.getDatabases = getDatabases;
exports.getFragmentationSVG = getFragmentationSVG;
exports.reactionFragmentation = reactionFragmentation;
{
"name": "mass-fragmentation",
"version": "1.0.2",
"version": "1.0.3",
"description": "Code to fragment molecules",

@@ -24,3 +24,4 @@ "main": "lib/index.js",

"mf-parser": "^3.0.1",
"openchemlib-utils": "5.2.0"
"openchemlib-utils": "^5.3.0",
"react-tree-svg": "^0.6.1"
},

@@ -30,3 +31,3 @@ "devDependencies": {

},
"gitHead": "5c97fecfd23faf2cc4120ce5e22f0c85b8e734b1"
"gitHead": "de5e985142af9c9a5a8a7dc1b2cbb4ddf12981e1"
}

@@ -8,305 +8,9 @@ import OCL from 'openchemlib';

describe('ReactionFragmentation', async () => {
it('Alpha cleavage: MDMAH+', async () => {
const molecule = Molecule.fromSmiles('C[NH2+]C(C)Cc2ccc1OCOc1c2');
const options = {
maxDepth: 20,
maxIonizationDepth: 2,
getProductsTrees: true,
};
const { masses, trees, products } = reactionFragmentation(
molecule,
options,
);
expect(Object.keys(products[0])).toMatchInlineSnapshot(`
[
"idCode",
"mf",
"em",
"mz",
"charge",
"trees",
"reactions",
"minSteps",
]
`);
expect(masses).toHaveLength(6);
it('Alpha cleavage: MDMA', async () => {
const molecule = Molecule.fromSmiles('CNC(Cc1ccc2c(c1)OCO2)C');
const { trees, validNodes, masses } = reactionFragmentation(molecule);
expect(validNodes).toHaveLength(274);
expect(masses).toHaveLength(52);
expect(trees).toMatchSnapshot();
});
it('tropylium rearrangement: MDMA after Alpha cleavage', async () => {
const molecule = Molecule.fromSmiles('[CH2+]c2ccc1OCOc1c2');
const options = {
maxDepth: 5,
maxIonizationDepth: 2,
getProductsTrees: true,
};
const { masses, trees, products } = reactionFragmentation(
molecule,
options,
);
expect(Object.keys(products[0])).toMatchInlineSnapshot(`
[
"idCode",
"mf",
"em",
"mz",
"charge",
"trees",
"reactions",
"minSteps",
]
`);
expect(masses).toHaveLength(2);
expect(trees).toMatchSnapshot();
});
it('Full fragmentation: MDMA', async () => {
const molecule = Molecule.fromSmiles('CC(CC1=CC2=C(C=C1)OCO2)NC');
const options = {
maxDepth: 5,
getProductsTrees: true,
limitReactions: 500,
};
let { masses, trees, products } = reactionFragmentation(molecule, options);
expect(masses).toMatchInlineSnapshot(`
[
194.11810400000002,
163.075905,
135.04460500000002,
105.03404,
193.11027900000002,
58.065674,
]
`);
expect(products[0]).toMatchInlineSnapshot(`
{
"charge": 1,
"em": 194.11810400000002,
"idCode": "dg~DBMBmeJYW]gJxZB@jj@@",
"mf": "C11H16NO2(+)",
"minSteps": 1,
"mz": 194.11810400000002,
"reactions": [
"eM\`BN\`~b@!eM\`\`fN\`~c@#Q[ Q[#!R@AM?DquRo@ !R@AM?Dqtzo@",
],
"trees": [
{
"products": [
{
"charge": 1,
"children": [],
"em": 194.11810400000002,
"flag": true,
"idCode": "dg~DBMBmeJYW]gJxZB@jj@@",
"mf": "C11H16NO2(+)",
"molfile": "
Actelion Java MolfileCreator 1.0
14 15 0 0 0 0 0 0 0 0999 V2000
18.7073 -7.2671 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
19.5677 -7.7768 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
19.5565 -8.7767 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
18.6849 -9.2670 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
18.6737 -10.2669 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
17.8021 -10.7572 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
16.9417 -10.2475 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
16.9529 -9.2475 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
17.8245 -8.7573 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
16.1911 -10.9082 -0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
16.5876 -11.8263 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
17.5832 -11.7329 -0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
20.4444 -7.2836 -0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
21.3075 -7.7768 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
1 2 1 0 0 0 0
2 3 1 0 0 0 0
3 4 1 0 0 0 0
4 5 2 0 0 0 0
5 6 1 0 0 0 0
6 7 2 0 0 0 0
7 8 1 0 0 0 0
8 9 2 0 0 0 0
4 9 1 0 0 0 0
7 10 1 0 0 0 0
10 11 1 0 0 0 0
11 12 1 0 0 0 0
6 12 1 0 0 0 0
13 2 1 0 0 0 0
13 14 1 0 0 0 0
M CHG 1 13 1
M END
",
"mz": 194.11810400000002,
},
],
"reactant": {
"charge": 0,
"em": 193.11027900000002,
"idCode": "dg~D@MBdie]v\\\\kahHBjh@@",
"mf": "C11H15NO2",
"molfile": "
Actelion Java MolfileCreator 1.0
14 15 0 0 0 0 0 0 0 0999 V2000
1.7321 -1.5000 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
1.7321 -0.5000 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
2.5981 -0.0000 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
3.4641 -0.5000 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
4.3301 -0.0000 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
5.1962 -0.5000 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
5.1962 -1.5000 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
4.3301 -2.0000 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
3.4641 -1.5000 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
6.1472 -1.8090 -0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
6.7350 -1.0000 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
6.1472 -0.1910 -0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
0.8660 -0.0000 -0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
0.0000 -0.5000 -0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
1 2 1 0 0 0 0
2 3 1 0 0 0 0
3 4 1 0 0 0 0
4 5 2 0 0 0 0
5 6 1 0 0 0 0
6 7 2 0 0 0 0
7 8 1 0 0 0 0
8 9 2 0 0 0 0
4 9 1 0 0 0 0
7 10 1 0 0 0 0
10 11 1 0 0 0 0
11 12 1 0 0 0 0
6 12 1 0 0 0 0
2 13 1 0 0 0 0
13 14 1 0 0 0 0
M END
",
"mz": 193.11027900000002,
},
"reaction": {
"Label": "Ionization",
"rxnCode": "eM\`BN\`~b@!eM\`\`fN\`~c@#Q[ Q[#!R@AM?DquRo@ !R@AM?Dqtzo@",
},
},
],
}
`);
expect(trees).toMatchSnapshot();
});
it('Full fragmentation: MDMA with custom database', async () => {
const molecule = Molecule.fromSmiles('CC(CC1=CC2=C(C=C1)OCO2)NC');
const customDatabase = {
positive: [
{
Label: 'Alpha cleavage',
rxnCode:
'gCa@@duPGtF@!fI@Gu` eM@bXzB#qbq a` qQp#!RtEJHC^h@GPhB !RGUi| !R@FN?Dquz_@',
},
{
Label: 'Alpha cleavage',
rxnCode:
'gJPI@DBTijhCzB@!gK`I@DJTcj`OhH@#qbqh qfUH#!Rmwwp_[]|_g|]lcp !R?`BE@[_|b@H]vgp',
},
{
Label: 'Alpha cleavage',
rxnCode:
'gJPa@eJSKTA}E@!eMH`eIxH eFHBLGtP#qbqh qbH qu#!Rw`BH?J_|bMt]|hH !Rwpq?Dquz?@ !R?`@]vgp',
},
{
Label: 'Alpha cleavage',
rxnCode:
'gJQIL@`ReMS@_PP!eMH`eIxH eFB@HcA}D@#qbqh qJh Ql#!Rm?wpwXE?y{p]lmp !Rwxq?Dquz?@ !R@NL]vgp',
},
{
Label: 'Alpha cleavage',
rxnCode:
'gJXA@IRij`OhH@!gKhAAirPFhB#qbuH qfjH#!RM{upoPD@Mwp]lgp !R@BL?x~WAwrp]Vgp',
},
{
Label: 'Alpha cleavage',
rxnCode:
'gJYADEJSKTA}E`!eMH`eIxH eF`BLGtX#qbqh qbH qu#!RO`BH?C_|bGt]|hH !Rwtq?Dquz_@ !R?`@]vgp',
},
{
Label: 'Alpha cleavage',
rxnCode:
'gC`AAJTu@P!eF@bXpP eF@HpP#qTq qa Q\\#!R@AM?DpAlGTJB !R@DQCrip !R_sL}Nmp',
},
{
Label: 'Alpha cleavage',
rxnCode:
'gChA@IRfhCzC@!eF`BLD eF@bXpP#qbq Ql qQ#!R{|L@p~~lG[K| !R@DL]Vgp !R_xp]ngp',
},
{
Label: 'Aromatic Indicutive Cleavage',
rxnCode:
'gC`AAJTu@]SP!eF@bXpP eF@HhP#qrL QX qq#!Rgzp@kI}cG]K| !R@BL]vgp !R@BL]~kp',
},
],
ionization: {
positive: [
{
Label: 'Ionization',
rxnCode: 'eF`BLGtX!eF``fLGt\\#Qd Qd#!R@FL]Rgp !R@FL]vgp',
},
{
Label: 'Ionization',
rxnCode: 'eM`BN`~b@!eM``fN`~c@#Q[ Q[#!R@AM?DquRo@ !R@AM?Dqtzo@',
},
{
Label: 'Ionization',
rxnCode:
'gCh@AGj@`!gChA@Icu@_PP#qTq qTq#!RbOvw?_x@GYK| !RbOvw?_x@GWk|',
},
{
Label: 'Ionization',
rxnCode:
'gCa@@dmPFtwhHcMCAlp!eMH`eIhOhp#Qv@ Qv#!R@AL@[@@SGSH| !R@Fp@Dqwz``',
},
{
Label: 'Ionization',
rxnCode: 'eFB@HcA}D@!eFB`HIcA}F@#QX QX#!R_vp]bgp !R_vp]vgp',
},
{
Label: 'Ionization',
rxnCode: 'eMB@HchH!eMB`HIchOh`#Q[ Q[#!R@AM?Dqtbo@ !R@AM?Dquz@`',
},
{
Label: 'Ionization',
rxnCode: 'eMHAIXMicLLG~r!eFH`fJGtP#QX QX#!R@AL@[AtbO@ !R@AL]nkp',
},
{
Label: 'Ionization',
rxnCode: 'eMHBN``!eMH`fN`~b@#Qg Qg#!R@FL?XqtJ_@ !R@FL?XqwZ_@',
},
{
Label: 'Ionization',
rxnCode: 'eFB@HcA}D@!eFB`HIcA}F@#QX QX#!R_vp]bgp !R_vp]vgp',
},
{
Label: 'Ionization',
rxnCode: 'eFHBLGtP!eFH`fLGtX#QX QX#!R@AL]Pmp !R@AL]^gp',
},
{
Label: 'Ionization',
rxnCode: 'eM`BN`~b@!eM``fN`~bOza@#Qg Qg#!R@Fq?[AuJ?@ !R@FM?Dqvz_@',
},
],
},
};
const options = {
maxDepth: 5,
customDatabase,
getProductsTrees: true,
};
let { masses, trees, products } = reactionFragmentation(molecule, options);
expect(masses).toMatchInlineSnapshot(`
[
194.11810400000002,
163.075905,
135.04460500000002,
193.11027900000002,
]
`);
expect(products[0].mf).toMatchInlineSnapshot('"C11H16NO2(+)"');
expect(trees).toMatchSnapshot();
});
});
export * from './fragment.js';
export * from './reactionFragmentation.js';
export * from './database/getDatabase.js';
export * from './getFragmentationSVG.js';
export * from './database/getDatabases.js';

@@ -0,125 +1,79 @@

import { MF } from 'mf-parser';
import OCL from 'openchemlib';
import { applyReactions, groupTreesByProducts } from 'openchemlib-utils';
import { Reactions, getMF } from 'openchemlib-utils';
import getDatabase from './database/getDatabase';
import { getDatabases } from './database/getDatabases';
import { getMasses } from './utils/getMasses';
/**
* @description Fragment a molecule by applying reactions from a custom database of reactions
* @param {import('openchemlib').Molecule} molecule - The OCL molecule to be fragmented
* @param {import('openchemlib').Molecule} oclMolecule - The OCL molecule to be fragmented
* @param {Object} [options={}]
* @param {string} [options.databaseName='cid'] - The database to be used
* @param {string} [options.mode='positive'] - The mode to be used
* @param {number} [options.maxDepth=5] - The maximum depth of the fragmentation tree
* @param {boolean} [options.getProductsTrees=false] - If true, the products trees are returned else products is an empty array
* @param {number} [options.maxDepth=5] - The maximum depth of the overall fragmentation tree
* @param {number} [options.limitReactions=200] - The maximum number of reactions to be applied
* @param {string} [options.dwarEntry] - The dwar entry to be used, if not provided, the default one will be used
* @param {number} [options.maxIonizationDepth=1] - The maximum depth of the ionization tree
* @param {Object} [options.customDatabase={}] - A custom database of reactions
* @param {Array} [options.customDatabase.positive] - A custom database of reactions for positive mode
* @param {Array} [options.customDatabase.negative] - A custom database of reactions for negative mode
* @param {Object} [options.customDatabase.ionization] - A custom database ionization reactions
* @param {Array} [options.customDatabase.ionization.positive] - A custom database of ionization reactions for positive mode
* @param {Array} [options.customDatabase.ionization.negative] - A custom database of ionization reactions for negative mode
* @param {number} [options.minIonizationDepth=1] - The minimum depth of the ionization tree
* @param {number} [options.minReactionDepth=0] - The minimum depth of the reaction tree
* @param {number} [options.maxReactionDepth=3] - The maximum depth of the reaction tree
* @returns {object} In-Silico fragmentation results with the following properties:
* - masses: array of monoisotopic masses
* - trees: array of fragmentation trees
* - products: array of trees grouped by product idCode
* - validNodes: nodes without dead branches
*/
export function reactionFragmentation(molecule, options = {}) {
let {
databaseName = 'cid',
export function reactionFragmentation(oclMolecule, options = {}) {
const {
mode = 'positive',
dwarEntry = '',
maxDepth = 5,
limitReactions = 200,
minIonizationDepth = 1,
maxIonizationDepth = 1,
getProductsTrees = false,
customDatabase = {},
limitReactions = 200,
minReactionDepth = 0,
maxReactionDepth = 3,
} = options;
let database;
let IonizationDb;
if (customDatabase[mode] && customDatabase[mode].length > 0) {
database = customDatabase;
} else {
database = getDatabase(databaseName);
}
if (!database) {
throw new Error(`Database ${databaseName} not found`);
}
if (databaseName === 'cid') {
if (
customDatabase.ionization &&
customDatabase.ionization[mode].length > 0
) {
IonizationDb = customDatabase.ionization[mode];
} else {
IonizationDb = getDatabase('')[mode];
}
}
let results = {};
const reactions = database[mode];
if (IonizationDb) {
let ionizationFragments = {
trees: [],
products: [],
};
for (
let currentMaxIonizationDepth = 1;
currentMaxIonizationDepth <= maxIonizationDepth;
currentMaxIonizationDepth++
) {
let ionizationLevelResult = applyReactions([molecule], IonizationDb, {
maxDepth: currentMaxIonizationDepth,
limitReactions,
});
// add array to ionizationfragments.trees
// @ts-ignore
ionizationFragments.trees.push(...ionizationLevelResult.trees);
// @ts-ignore
ionizationFragments.products.push(...ionizationLevelResult.products);
}
for (let tree of ionizationFragments.trees) {
getMoleculesToFragment(tree, reactions, maxDepth, limitReactions);
}
if (getProductsTrees) {
const reactions = new Reactions(OCL, {
moleculeInfoCallback: (molecule) => {
// @ts-ignore
ionizationFragments.products = groupTreesByProducts(
ionizationFragments.trees,
);
}
results = ionizationFragments;
} else {
results = applyReactions([molecule], reactions, {
maxDepth,
limitReactions,
});
}
const mf = getMF(molecule).mf;
const mfInfo = new MF(mf).getInfo();
return {
mf,
mw: mfInfo.mass,
em: mfInfo.monoisotopicMass,
mz: mfInfo.observedMonoisotopicMass,
charge: mfInfo.charge,
};
},
maxDepth,
limitReactions,
skipProcessed: true,
});
let { masses, trees, products } = getMasses(results.trees, results.products);
reactions.appendHead([oclMolecule]);
reactions.applyOneReactantReactions(
getDatabases({ kind: 'ionization', mode }, dwarEntry),
{
min: minIonizationDepth,
max: maxIonizationDepth,
},
);
reactions.applyOneReactantReactions(
getDatabases({ kind: 'reaction', mode }, dwarEntry),
{
min: minReactionDepth,
max: maxReactionDepth,
},
);
const trees = reactions.trees;
const validNodes = reactions.getValidNodes();
const masses = getMasses(trees);
return {
trees,
validNodes,
masses,
trees,
products,
};
}
function getMoleculesToFragment(tree, reactions, maxDepth, limitReactions) {
for (let product of tree.products) {
if (product.children.length === 0) {
if (product.charge !== 0) {
let molecule = OCL.Molecule.fromIDCode(product.idCode);
let fragments = applyReactions([molecule], reactions, {
maxDepth,
limitReactions,
getProductsTrees: true,
});
// @ts-ignore
product.children = fragments.trees;
}
} else {
for (let child of product.children) {
getMoleculesToFragment(child, reactions, maxDepth, limitReactions);
}
}
}
}
/**
* @description get array of mz from fragmentation trees
* @param {Array} trees Fragmentation trees
* @param {Array} products Fragmentation trees grouped by product idCode
* @returns {object} Object with the following properties:
* - masses: array of monoisotopic masses
* - trees: array of fragmentation trees
* - products: array of trees grouped by product idCode
* @returns {Array} array of mz values
*/
export function getMasses(trees, products) {
export function getMasses(trees) {
let masses = {};

@@ -15,20 +12,16 @@ for (const tree of trees) {

}
return {
masses: Object.keys(masses).map(Number),
trees,
products,
};
return Object.keys(masses).map(Number);
}
function getMassesFromTree(currentBranch, masses) {
for (const product of currentBranch.products) {
if (Math.abs(product.charge) > 0) {
masses[product.mz] = true;
for (const molecule of currentBranch.molecules) {
if (Math.abs(molecule.info.charge) > 0) {
masses[molecule.info.mz] = true;
}
if (product.children.length > 0) {
for (const child of product.children) {
getMassesFromTree(child, masses);
}
}
if (currentBranch.children && currentBranch.children.length > 0) {
for (const child of currentBranch.children) {
getMassesFromTree(child, masses);
}
}
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc