Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

baobab

Package Overview
Dependencies
Maintainers
1
Versions
64
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

baobab - npm Package Compare versions

Comparing version 2.1.1 to 2.1.2

CONTRIBUTING.md

11

CHANGELOG.md
# Changelog
## v2.2.0 (provisional)
* Cursors are now ES6 iterables ([@kirjs](https://github.com/kirjs)).
## v2.1.2
* Storing hashed paths using `λ` as delimiter instead of `/` to enable some edge cases ([@nivekmai](https://github.com/nivekmai)).
* Fixing an issue with cursors where a stopped history wouldn't restart correctly ([@nikvm](https://github.com/nikvm)).
* Fixing monkeys' laziness.
* Fixing an edge case when one watches over paths beneath monkeys.
## v2.1.1

@@ -4,0 +15,0 @@

19

dist/baobab.js

@@ -59,3 +59,2 @@ /**

var deepMerge = helpers.deepMerge;
var pathObject = helpers.pathObject;
var shallowClone = helpers.shallowClone;

@@ -103,5 +102,7 @@ var shallowMerge = helpers.shallowMerge;

function hashPath(path) {
return '/' + path.map(function (step) {
if (_type2['default']['function'](step) || _type2['default'].object(step)) return '#' + uniqid() + '#';else return step;
}).join('/');
return 'λ' + path.map(function (step) {
if (_type2['default']['function'](step) || _type2['default'].object(step)) return '#' + uniqid() + '#';
return step;
}).join('λ');
}

@@ -161,3 +162,3 @@

// Properties
this.root = new _cursor2['default'](this, [], '/');
this.root = new _cursor2['default'](this, [], 'λ');
delete this.root.release;

@@ -472,3 +473,3 @@

var affectedPaths = Object.keys(this._affectedPathsIndex).map(function (h) {
return h !== '/' ? h.split('/').slice(1) : [];
return h !== 'λ' ? h.split('λ').slice(1) : [];
});

@@ -573,3 +574,5 @@

if (args.length === 1) return new _monkey.MonkeyDefinition(args[0]);else return new _monkey.MonkeyDefinition(args);
if (args.length === 1) return new _monkey.MonkeyDefinition(args[0]);
return new _monkey.MonkeyDefinition(args);
};

@@ -591,3 +594,3 @@ Baobab.dynamicNode = Baobab.monkey;

Object.defineProperty(Baobab, 'version', {
value: '2.1.1'
value: '2.1.2'
});

@@ -594,0 +597,0 @@

@@ -306,3 +306,5 @@ /**

value: function up() {
if (!this.isRoot()) return this.tree.select(this.path.slice(0, -1));else return null;
if (!this.isRoot()) return this.tree.select(this.path.slice(0, -1));
return null;
}

@@ -424,2 +426,35 @@

/**
* Method used to allow iterating over cursors containing list-type data.
*
* e.g. for(let i of cursor) { ... }
*
* @returns {object} - Each item sequentially.
// */
// [Symbol.iterator]() {
// const array = this._get().data;
// if (!type.array(array))
// throw Error('baobab.Cursor.@@iterate: cannot iterate a non-list type.');
// let i = 0;
// const cursor = this,
// length = array.length;
// return {
// next: function() {
// if (i < length) {
// return {
// value: cursor.select(i++)
// };
// }
// return {
// done: true
// };
// }
// };
// }
/**
* Getter Methods

@@ -595,2 +630,4 @@ * ---------------

this.state.recording = true;
if (this.archive) return this;

@@ -602,3 +639,2 @@

this.archive = new _helpers.Archive(maxRecords);
this.state.recording = true;
return this;

@@ -605,0 +641,0 @@ }

@@ -0,1 +1,3 @@

/* eslint eqeqeq: 0 */
/**

@@ -20,3 +22,2 @@ * Baobab Helpers

exports.makeError = makeError;
exports.pathObject = pathObject;
exports.solveRelativePath = solveRelativePath;

@@ -42,2 +43,36 @@ exports.solveUpdate = solveUpdate;

/**
* Function returning the index of the first element of a list matching the
* given predicate.
*
* @param {array} a - The target array.
* @param {function} fn - The predicate function.
* @return {mixed} - The index of the first matching item or -1.
*/
function index(a, fn) {
var i = undefined,
l = undefined;
for (i = 0, l = a.length; i < l; i++) {
if (fn(a[i])) return i;
}
return -1;
}
/**
* Efficient slice function used to clone arrays or parts of them.
*
* @param {array} array - The array to slice.
* @return {array} - The sliced array.
*/
function slice(array) {
var newArray = new Array(array.length);
var i = undefined,
l = undefined;
for (i = 0, l = array.length; i < l; i++) newArray[i] = array[i];
return newArray;
}
/**
* Archive abstraction

@@ -155,5 +190,6 @@ *

function cloneRegexp(re) {
var pattern = re.source,
flags = '';
var pattern = re.source;
var flags = '';
if (re.global) flags += 'g';

@@ -183,10 +219,12 @@ if (re.multiline) flags += 'm';

if (deep) {
var a = [];
var i = undefined,
l = undefined,
a = [];
l = undefined;
for (i = 0, l = item.length; i < l; i++) a.push(cloner(true, item[i]));
return a;
} else {
return slice(item);
}
return slice(item);
}

@@ -202,7 +240,18 @@

if (_type2['default'].object(item)) {
var k = undefined,
o = {};
var o = {};
var k = undefined;
// NOTE: could be possible to erase computed properties through `null`.
for (k in item) if (item.hasOwnProperty(k)) o[k] = deep ? cloner(true, item[k]) : item[k];
for (k in item) {
if (_type2['default'].lazyGetter(item, k)) {
Object.defineProperty(o, k, {
get: Object.getOwnPropertyDescriptor(item, k).get,
enumerable: true,
configurable: true
});
} else if (item.hasOwnProperty(k)) {
o[k] = deep ? cloner(true, item[k]) : item[k];
}
}
return o;

@@ -333,4 +382,5 @@ }

var solvedPath = [],
exists = true,
var solvedPath = [];
var exists = true,
c = object,

@@ -373,19 +423,2 @@ idx = undefined,

/**
* Function returning the index of the first element of a list matching the
* given predicate.
*
* @param {array} a - The target array.
* @param {function} fn - The predicate function.
* @return {mixed} - The index of the first matching item or -1.
*/
function index(a, fn) {
var i = undefined,
l = undefined;
for (i = 0, l = a.length; i < l; i++) {
if (fn(a[i])) return i;
}
return -1;
}
/**
* Little helper returning a JavaScript error carrying some data with it.

@@ -421,4 +454,5 @@ *

var o = objects[0],
t = undefined,
var o = objects[0];
var t = undefined,
i = undefined,

@@ -453,43 +487,2 @@ l = undefined,

/**
* Function returning a nested object according to the given path and the
* given leaf.
*
* @param {array} path - The path to follow.
* @param {mixed} leaf - The leaf to append at the end of the path.
* @return {object} - The nested object.
*/
function pathObject(path, leaf) {
var l = path.length,
o = {},
c = o,
i = undefined;
if (!l) o = leaf;
for (i = 0; i < l; i++) {
c[path[i]] = i + 1 === l ? leaf : {};
c = c[path[i]];
}
return o;
}
/**
* Efficient slice function used to clone arrays or parts of them.
*
* @param {array} array - The array to slice.
* @return {array} - The sliced array.
*/
function slice(array) {
var newArray = new Array(array.length),
i = undefined,
l = undefined;
for (i = 0, l = array.length; i < l; i++) newArray[i] = array[i];
return newArray;
}
/**
* Solving a potentially relative path.

@@ -604,2 +597,3 @@ *

var i = 0;
return function () {

@@ -606,0 +600,0 @@ return i++;

@@ -31,3 +31,3 @@ /**

* Monkey Definition class
* Note: The only reason why this is a class is to be able to spot it whithin
* Note: The only reason why this is a class is to be able to spot it within
* otherwise ordinary data.

@@ -176,3 +176,5 @@ *

if (!this.isRecursive) return paths;else return paths.reduce(function (accumulatedPaths, path) {
if (!this.isRecursive) return paths;
return paths.reduce(function (accumulatedPaths, path) {
var monkeyPath = _type2['default'].monkeyPath(_this4.tree._monkeys, path);

@@ -179,0 +181,0 @@

@@ -162,4 +162,5 @@ /**

type.monkeyPath = function (data, path) {
var subpath = [],
c = data,
var subpath = [];
var c = data,
i = undefined,

@@ -205,7 +206,11 @@ l = undefined;

return type.path(definition.cursors[k]);
}))) return null;else return 'object';
}))) return null;
return 'object';
} else if (type.array(definition)) {
if (!type['function'](definition[definition.length - 1]) || !definition.slice(0, -1).every(function (p) {
return type.path(p);
})) return null;else return 'array';
})) return null;
return 'array';
}

@@ -212,0 +217,0 @@

@@ -22,4 +22,2 @@ /**

var _monkey = require('./monkey');
var _helpers = require('./helpers');

@@ -49,7 +47,7 @@

var dummy = { root: data },
dummyPath = ['root'].concat(_toConsumableArray(path));
dummyPath = ['root'].concat(_toConsumableArray(path)),
currentPath = [];
// Walking the path
var p = dummy,
currentPath = [],
i = undefined,

@@ -80,5 +78,3 @@ l = undefined,

if (opts.persistent) {
p[s] = (0, _helpers.shallowClone)(value);
} else if (value instanceof _monkey.MonkeyDefinition) {
if (_type2['default'].lazyGetter(p, s)) {
Object.defineProperty(p, s, {

@@ -89,2 +85,4 @@ value: value,

});
} else if (opts.persistent) {
p[s] = (0, _helpers.shallowClone)(value);
} else {

@@ -113,5 +111,15 @@ p[s] = value;

// Purity check
if (opts.pure && result === value) return { node: p[s] };
if (opts.pure && p[s] === result) return { node: p[s] };
p[s] = opts.persistent ? (0, _helpers.shallowClone)(result) : result;
if (_type2['default'].lazyGetter(p, s)) {
Object.defineProperty(p, s, {
value: result,
enumerable: true,
configurable: true
});
} else if (opts.persistent) {
p[s] = (0, _helpers.shallowClone)(result);
} else {
p[s] = result;
}
}

@@ -118,0 +126,0 @@

@@ -94,3 +94,5 @@ /**

// Watcher mappings can accept a cursor
if (v instanceof _cursor2['default']) return v.solvedPath;else return _this2.mapping[k];
if (v instanceof _cursor2['default']) return v.solvedPath;
return _this2.mapping[k];
});

@@ -108,3 +110,3 @@

if (monkeyPath) return cp.concat((0, _helpers.getIn)(_this2.tree._monkeys, p).data.relatedPaths());
if (monkeyPath) return cp.concat((0, _helpers.getIn)(_this2.tree._monkeys, monkeyPath).data.relatedPaths());

@@ -111,0 +113,0 @@ return cp.concat([p]);

{
"name": "baobab",
"version": "2.1.1",
"version": "2.1.2",
"description": "JavaScript persistent data tree with cursors.",

@@ -17,2 +17,3 @@ "main": "./dist/baobab.js",

"eslint": "^1.1.0",
"eslint-config-airbnb": "^1.0.0",
"gulp": "^3.8.10",

@@ -31,3 +32,3 @@ "gulp-header": "^1.2.2",

"build": "gulp build && babel ./src --out-dir dist",
"lint": "eslint ./*.js && eslint ./src",
"lint": "eslint ./src ./test",
"prepublish": "npm run build",

@@ -34,0 +35,0 @@ "test": "mocha -R spec --compilers js:babel/register ./test/endpoint.js"

@@ -80,3 +80,3 @@ [![Build Status](https://travis-ci.org/Yomguithereal/baobab.svg)](https://travis-ci.org/Yomguithereal/baobab)

The library (as a standalone) currently weights ~8ko gzipped.
The library (as a standalone) currently weighs ~8kb gzipped.

@@ -976,21 +976,5 @@ ## Usage

Contributions are obviously welcome. This project is nothing but experimental and I would cherish some feedback and advice about the library.
See [CONTRIBUTING.md](CONTRIBUTING.md).
Be sure to add unit tests if relevant and pass them all before submitting your pull request.
```bash
# Installing the dev environment
git clone git@github.com:Yomguithereal/baobab.git
cd baobab
npm install
# Running the tests
npm test
# Linting, building
npm run lint
npm run build
```
## License
MIT

@@ -22,3 +22,2 @@ /**

deepMerge,
pathObject,
shallowClone,

@@ -67,8 +66,8 @@ shallowMerge,

function hashPath(path) {
return '/' + path.map(step => {
return 'λ' + path.map(step => {
if (type.function(step) || type.object(step))
return `#${uniqid()}#`;
else
return step;
}).join('/');
return step;
}).join('λ');
}

@@ -123,3 +122,3 @@

// Properties
this.root = new Cursor(this, [], '/');
this.root = new Cursor(this, [], 'λ');
delete this.root.release;

@@ -178,15 +177,10 @@

const clean = (data, p=[]) => {
const clean = (data, p = []) => {
if (data instanceof Monkey) {
data.release();
update(
this._monkeys,
p,
{type: 'unset'},
{
immutable: false,
persistent: false,
pure: false
}
);
update(this._monkeys, p, {type: 'unset'}, {
immutable: false,
persistent: false,
pure: false
});

@@ -197,3 +191,3 @@ return;

if (type.object(data)) {
for (let k in data)
for (const k in data)
clean(data[k], p.concat(k));

@@ -205,3 +199,3 @@ }

const walk = (data, p=[]) => {
const walk = (data, p = []) => {

@@ -219,12 +213,7 @@ // Should we sit a monkey in the tree?

update(
this._monkeys,
p,
{type: 'set', value: monkey},
{
immutable: false,
persistent: false,
pure: false
}
);
update(this._monkeys, p, {type: 'set', value: monkey}, {
immutable: false,
persistent: false,
pure: false
});

@@ -236,3 +225,3 @@ return;

if (type.object(data)) {
for (let k in data)
for (const k in data)
walk(data[k], p.concat(k));

@@ -423,3 +412,3 @@ }

// If the operation is push, the affected path is slightly different
let affectedPath = solvedPath.concat(
const affectedPath = solvedPath.concat(
operation.type === 'push' ? node.length - 1 : []

@@ -471,4 +460,4 @@ );

const affectedPaths = Object.keys(this._affectedPathsIndex).map(h => {
return h !== '/' ?
h.split('/').slice(1) :
return h !== 'λ' ?
h.split('λ').slice(1) :
[];

@@ -567,4 +556,4 @@ });

return new MonkeyDefinition(args[0]);
else
return new MonkeyDefinition(args);
return new MonkeyDefinition(args);
};

@@ -586,3 +575,3 @@ Baobab.dynamicNode = Baobab.monkey;

Object.defineProperty(Baobab, 'version', {
value: '2.1.1'
value: '2.1.2'
});

@@ -589,0 +578,0 @@

@@ -267,4 +267,4 @@ /**

return this.tree.select(this.path.slice(0, -1));
else
return null;
return null;
}

@@ -373,4 +373,4 @@

let array = this._get().data,
l = arguments.length;
const array = this._get().data,
l = arguments.length;

@@ -391,2 +391,35 @@ if (!type.array(array))

/**
* Method used to allow iterating over cursors containing list-type data.
*
* e.g. for(let i of cursor) { ... }
*
* @returns {object} - Each item sequentially.
// */
// [Symbol.iterator]() {
// const array = this._get().data;
// if (!type.array(array))
// throw Error('baobab.Cursor.@@iterate: cannot iterate a non-list type.');
// let i = 0;
// const cursor = this,
// length = array.length;
// return {
// next: function() {
// if (i < length) {
// return {
// value: cursor.select(i++)
// };
// }
// return {
// done: true
// };
// }
// };
// }
/**
* Getter Methods

@@ -406,3 +439,3 @@ * ---------------

*/
_get(path=[]) {
_get(path = []) {

@@ -502,3 +535,3 @@ if (!type.path(path))

for (let k in m) {
for (const k in m) {
if (m[k] instanceof Monkey)

@@ -525,3 +558,3 @@ delete d[k];

for (let k in projection)
for (const k in projection)
data[k] = this.get(projection[k]);

@@ -565,2 +598,4 @@

this.state.recording = true;
if (this.archive)

@@ -573,3 +608,2 @@ return this;

this.archive = new Archive(maxRecords);
this.state.recording = true;
return this;

@@ -594,3 +628,3 @@ }

*/
undo(steps=1) {
undo(steps = 1) {
if (!this.state.recording)

@@ -597,0 +631,0 @@ throw new Error('Baobab.Cursor.undo: cursor is not recording.');

@@ -0,1 +1,3 @@

/* eslint eqeqeq: 0 */
/**

@@ -16,2 +18,37 @@ * Baobab Helpers

/**
* Function returning the index of the first element of a list matching the
* given predicate.
*
* @param {array} a - The target array.
* @param {function} fn - The predicate function.
* @return {mixed} - The index of the first matching item or -1.
*/
function index(a, fn) {
let i, l;
for (i = 0, l = a.length; i < l; i++) {
if (fn(a[i]))
return i;
}
return -1;
}
/**
* Efficient slice function used to clone arrays or parts of them.
*
* @param {array} array - The array to slice.
* @return {array} - The sliced array.
*/
function slice(array) {
const newArray = new Array(array.length);
let i,
l;
for (i = 0, l = array.length; i < l; i++)
newArray[i] = array[i];
return newArray;
}
/**
* Archive abstraction

@@ -112,5 +149,6 @@ *

function cloneRegexp(re) {
let pattern = re.source,
flags = '';
const pattern = re.source;
let flags = '';
if (re.global) flags += 'g';

@@ -145,3 +183,7 @@ if (re.multiline) flags += 'm';

if (deep) {
let i, l, a = [];
const a = [];
let i,
l;
for (i = 0, l = item.length; i < l; i++)

@@ -151,5 +193,4 @@ a.push(cloner(true, item[i]));

}
else {
return slice(item);
}
return slice(item);
}

@@ -167,8 +208,19 @@

if (type.object(item)) {
let k, o = {};
const o = {};
let k;
// NOTE: could be possible to erase computed properties through `null`.
for (k in item)
if (item.hasOwnProperty(k))
for (k in item) {
if (type.lazyGetter(item, k)) {
Object.defineProperty(o, k, {
get: Object.getOwnPropertyDescriptor(item, k).get,
enumerable: true,
configurable: true
});
}
else if (item.hasOwnProperty(k)) {
o[k] = deep ? cloner(true, item[k]) : item[k];
}
}
return o;

@@ -313,4 +365,5 @@ }

let solvedPath = [],
exists = true,
const solvedPath = [];
let exists = true,
c = object,

@@ -358,19 +411,2 @@ idx,

/**
* Function returning the index of the first element of a list matching the
* given predicate.
*
* @param {array} a - The target array.
* @param {function} fn - The predicate function.
* @return {mixed} - The index of the first matching item or -1.
*/
function index(a, fn) {
let i, l;
for (i = 0, l = a.length; i < l; i++) {
if (fn(a[i]))
return i;
}
return -1;
}
/**
* Little helper returning a JavaScript error carrying some data with it.

@@ -385,3 +421,3 @@ *

for (let k in data)
for (const k in data)
err[k] = data[k];

@@ -403,4 +439,5 @@

function merger(deep, ...objects) {
let o = objects[0],
t,
const o = objects[0];
let t,
i,

@@ -437,44 +474,2 @@ l,

/**
* Function returning a nested object according to the given path and the
* given leaf.
*
* @param {array} path - The path to follow.
* @param {mixed} leaf - The leaf to append at the end of the path.
* @return {object} - The nested object.
*/
export function pathObject(path, leaf) {
let l = path.length,
o = {},
c = o,
i;
if (!l)
o = leaf;
for (i = 0; i < l; i++) {
c[path[i]] = (i + 1 === l) ? leaf : {};
c = c[path[i]];
}
return o;
}
/**
* Efficient slice function used to clone arrays or parts of them.
*
* @param {array} array - The array to slice.
* @return {array} - The sliced array.
*/
function slice(array) {
let newArray = new Array(array.length),
i,
l;
for (i = 0, l = array.length; i < l; i++)
newArray[i] = array[i];
return newArray;
}
/**
* Solving a potentially relative path.

@@ -490,3 +485,3 @@ *

for (let i = 0, l = to.length; i < l; i++) {
let step = to[i];
const step = to[i];

@@ -584,3 +579,4 @@ if (step === '.') {

const uniqid = (function() {
var i = 0;
let i = 0;
return function() {

@@ -587,0 +583,0 @@ return i++;

@@ -10,3 +10,2 @@ /**

import {
arrayFrom,
deepFreeze,

@@ -21,3 +20,3 @@ getIn,

* Monkey Definition class
* Note: The only reason why this is a class is to be able to spot it whithin
* Note: The only reason why this is a class is to be able to spot it within
* otherwise ordinary data.

@@ -156,14 +155,14 @@ *

return paths;
else
return paths.reduce((accumulatedPaths, path) => {
const monkeyPath = type.monkeyPath(this.tree._monkeys, path);
if (!monkeyPath)
return accumulatedPaths.concat([path]);
return paths.reduce((accumulatedPaths, path) => {
const monkeyPath = type.monkeyPath(this.tree._monkeys, path);
// Solving recursive path
const relatedMonkey = getIn(this.tree._monkeys, monkeyPath).data;
if (!monkeyPath)
return accumulatedPaths.concat([path]);
return accumulatedPaths.concat(relatedMonkey.relatedPaths());
}, []);
// Solving recursive path
const relatedMonkey = getIn(this.tree._monkeys, monkeyPath).data;
return accumulatedPaths.concat(relatedMonkey.relatedPaths());
}, []);
}

@@ -226,3 +225,2 @@

this.tree._data = result.data;
}

@@ -229,0 +227,0 @@

@@ -57,3 +57,3 @@ /**

!(target instanceof Date) &&
!(target instanceof RegExp) &&
!(target instanceof RegExp) &&
!(typeof Map === 'function' && target instanceof Map) &&

@@ -160,4 +160,5 @@ !(typeof Set === 'function' && target instanceof Set);

type.monkeyPath = function(data, path) {
let subpath = [],
c = data,
const subpath = [];
let c = data,
i,

@@ -210,4 +211,4 @@ l;

return null;
else
return 'object';
return 'object';
}

@@ -218,4 +219,4 @@ else if (type.array(definition)) {

return null;
else
return 'array';
return 'array';
}

@@ -222,0 +223,0 @@

@@ -8,3 +8,2 @@ /**

import type from './type';
import {MonkeyDefinition} from './monkey';
import {

@@ -38,3 +37,3 @@ freeze,

*/
export default function update(data, path, operation, opts={}) {
export default function update(data, path, operation, opts = {}) {
const {type: operationType, value} = operation;

@@ -44,7 +43,7 @@

const dummy = {root: data},
dummyPath = ['root', ...path];
dummyPath = ['root', ...path],
currentPath = [];
// Walking the path
let p = dummy,
currentPath = [],
i,

@@ -77,8 +76,5 @@ l,

if (opts.persistent) {
p[s] = shallowClone(value);
}
else if (value instanceof MonkeyDefinition) {
if (type.lazyGetter(p, s)) {
Object.defineProperty(p, s, {
value: value,
value,
enumerable: true,

@@ -88,2 +84,5 @@ configurable: true

}
else if (opts.persistent) {
p[s] = shallowClone(value);
}
else {

@@ -112,6 +111,18 @@ p[s] = value;

// Purity check
if (opts.pure && result === value)
return {node: p[s]};
if (opts.pure && p[s] === result)
return {node: p[s]};
p[s] = opts.persistent ? shallowClone(result) : result;
if (type.lazyGetter(p, s)) {
Object.defineProperty(p, s, {
value: result,
enumerable: true,
configurable: true
});
}
else if (opts.persistent) {
p[s] = shallowClone(result);
}
else {
p[s] = result;
}
}

@@ -118,0 +129,0 @@

@@ -66,4 +66,4 @@ /**

return v.solvedPath;
else
return this.mapping[k];
return this.mapping[k];
});

@@ -85,3 +85,3 @@

return cp.concat(
getIn(this.tree._monkeys, p).data.relatedPaths()
getIn(this.tree._monkeys, monkeyPath).data.relatedPaths()
);

@@ -127,5 +127,5 @@

// Creating the get method
let projection = {};
const projection = {};
for (let k in mapping)
for (const k in mapping)
projection[k] = mapping[k] instanceof Cursor ?

@@ -132,0 +132,0 @@ mapping[k].path :

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