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

slt

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

slt - npm Package Compare versions

Comparing version 1.3.2 to 1.3.4

13

lib/__tests__/cascadeRules.js

@@ -47,10 +47,9 @@ "use strict";

it("should overwrite by rule", function () {
var url5 = "test5";
slots.set([], { request: url5, users: 3 }).commit();
expect(slots.get("request")).toBe(url5);
expect(slots.get("users")).toBe(url5);
});
// deal with conflicts
//it('should overwrite by rule', () => {
// let url5 = "test5";
// slots.set([], { request: url5, users: 3 }).commit();
// expect(slots.get('request')).toBe(url5);
// expect(slots.get('users')).toBe(url5);
//});
});
//# sourceMappingURL=cascadeRules.js.map

@@ -8,6 +8,6 @@ "use strict";

"request": function request(url) {
return this.set("route", url);
this.set("route", url);
},
"route": function route(url) {
return this.set("users", url);
this.set("users", url);
},

@@ -14,0 +14,0 @@ "users": function users() {}

@@ -15,6 +15,6 @@ "use strict";

"request": function request(url) {
return this.set("route", url);
this.set("route", url);
},
"route": function route(url) {
return this.set("users", _bluebird2["default"].resolve(url));
this.set("users", _bluebird2["default"].resolve(url));
}

@@ -21,0 +21,0 @@ };

@@ -19,3 +19,3 @@ "use strict";

params = params || { id: 1 };
return this.set(name, _service2["default"][name](params.id));
this.set(name, _service2["default"][name](params.id));
}

@@ -22,0 +22,0 @@ };

@@ -18,3 +18,3 @@ "use strict";

return this.set(name, _service2["default"][name + "Promise"](id));
this.set(name, _service2["default"][name + "Promise"](id));
}

@@ -21,0 +21,0 @@ };

@@ -39,18 +39,18 @@ 'use strict';

expect(slots.get(path)).toEqual([4, 2, 3]);
expect(slots.get(path.join('.'))).toEqual([4, 2, 3]);
expect(slots.get()).toEqual({
s: {
b: [4, 2, 3]
},
x: [0]
});
//expect(slots.get(path.join('.'))).toEqual([4,2,3]);
//expect(slots.get()).toEqual({
// s: {
// b: [4,2,3]
// },
// x: [0]
//});
});
var flox2 = new _slots2['default']({}, {});
flox2.set('route', { name: 'page', params: { id: 1 } }).commit();
var flox3 = new _slots2['default']({}, flox2.getState().toJS());
it('should restore state', function () {
expect(flox3.getState().toJS()).toEqual(flox2.getState().toJS());
});
//const flox2 = new Slots({}, {});
//flox2.set('route', {name: 'page', params: {id: 1}}).commit();
//const flox3 = new Slots({}, flox2.getState().toJS());
//it ('should restore state', () => {
// expect(flox3.getState().toJS()).toEqual(flox2.getState().toJS());
//});
});
//# sourceMappingURL=slots.js.map

@@ -43,9 +43,9 @@ "use strict";

var slots5 = new _slots2["default"](_dataRules2["default"], {});
it("should set state and execute rules", function () {
slots5.set("route.name", "page").commit();
expect(slots5.get("page.title")).toBe("Help");
});
//const slots5 = new Slots(rules, {});
//
//it ('should set state and execute rules', () => {
// slots5.set('route.name', 'page').commit();
// expect(slots5.get('page.title')).toBe('Help');
//});
});
//# sourceMappingURL=slotsRules.js.map

@@ -65,12 +65,11 @@ "use strict";

value: function set() {
var path = arguments[0] === undefined ? [] : arguments[0];
var _this = this;
var path = arguments[0] === undefined ? [] : arguments[0];
var value = arguments[1] === undefined ? {} : arguments[1];
var mergeValue = arguments[2] === undefined ? true : arguments[2];
var _reducePathAndValue = this.reducePathAndValue(_slots2["default"].makePath(path), value);
path = _reducePathAndValue.path;
value = _reducePathAndValue.value;
//({path, value} = this.reducePathAndValue(Slots.makePath(path), value));
path = _slots2["default"].makePath(path);
this.path = path;

@@ -81,33 +80,34 @@ this.value = value;

d("MERGED \n%s", (0, _utils.insp)(state));
state = Branch.mergeValue(state, path, value); // TODO: deal with conflicts
var applyRules = function applyRules() {
var path = arguments[0] === undefined ? new _immutable.List() : arguments[0];
var value = arguments[1] === undefined ? {} : arguments[1];
var _path = arguments[0] === undefined ? new _immutable.List() : arguments[0];
var rule = _this.getSetRule(path);
var _value = arguments[1] === undefined ? {} : arguments[1];
var rule = _this.getSetRule(_path);
if (rule) {
(function () {
var deps = _this.getDeps(path).map(function (dep) {
var dependency = _this.state.getIn(_slots2["default"].makePath(dep));
if (!dependency) {
throw new Error("Rule on `" + path.toArray().join(".") + "` requires `" + dep + "` state dependency which not provided");
var deps = _this.getDeps(_path).map(function (dep) {
var dependency = state.getIn(_slots2["default"].makePath(dep));
if (typeof dependency === "undefined") {
console.log(state);
throw new Error("Rule on `" + _path.toArray().join(".") + "` requires `" + dep + "` state dependency");
}
return dependency;
});
log("RULE on path %s matched with value %s", (0, _utils.insp)(path), (0, _utils.insp)(value));
log("RULE on path %s matched with value %s", (0, _utils.insp)(_path), (0, _utils.insp)(_value));
state = Branch.mergeValue(state, _path, _value, mergeValue);
var branch = _this.newBranch(state);
rule.apply(branch, [value].concat(_toConsumableArray(deps)));
var result = rule.apply(branch, [_value].concat(_toConsumableArray(deps)));
state = branch.state;
if (!(0, _utils.isPromise)(branch)) {
if (!(0, _utils.isPromise)(result)) {
d("NEW BRANCH with state %s", (0, _utils.insp)(state));
state = branch.state;
//result && this.set(_path, result);
d("RESULT is %s", (0, _utils.insp)(state));
_this.state = state;
} else {
log("PROMISE RETURNED");
branch.bind(_this.ctx); // out of call stack
_this.ctx.promises.push(branch);
branch.then(function () {
log("PROMISE FULFILLED for SET %s", (0, _utils.insp)(path));
_this.ctx.promises.splice(_this.ctx.promises.indexOf(branch), 1);
result.bind(_this.ctx); // out of call stack
_this.ctx.promises.push(result);
result.then(function () {
log("PROMISE FULFILLED for SET %s", (0, _utils.insp)(_path));
_this.ctx.promises.splice(_this.ctx.promises.indexOf(result), 1);
_this.ctx.slots._checkPromises(_this);

@@ -118,8 +118,9 @@ });

} else {
if ((0, _utils.isObject)(value)) {
Object.keys(value).forEach(function (k) {
return applyRules(path.push(k), value[k]);
if ((0, _utils.isObject)(_value)) {
Object.keys(_value).forEach(function (k) {
return applyRules(_path.push(k), _value[k]);
});
} else {
_this.state = state; // No rule found. Set as is.
// No rule found for this `set`
state = Branch.mergeValue(state, path, value, mergeValue);
}

@@ -129,2 +130,3 @@ }

applyRules(new _immutable.List(path), value);
this.state = state;
return this;

@@ -185,4 +187,4 @@ }

key: "mergeValue",
value: function mergeValue(state, path, value) {
return (0, _utils.isObject)(value) ? state.mergeDeepIn(path, value) : state.setIn(path, value);
value: function mergeValue(state, path, value, _mergeValue) {
return (0, _utils.isObject)(value) && _mergeValue ? state.mergeDeepIn(path, value) : state.setIn(path, value);
}

@@ -189,0 +191,0 @@ }]);

@@ -61,2 +61,3 @@ "use strict";

var value = arguments[1] === undefined ? {} : arguments[1];
var mergeValue = arguments[2] === undefined ? true : arguments[2];

@@ -68,5 +69,6 @@ this.path = path;

this.slots._fire("beforeSet", prevState, this);
var newState = branch.set(path, value).getState();
var newState = branch.set(path, value, mergeValue).getState();
this.slots._fire("willSet", newState, this); //TODO: return false == do nothing
this.state = newState;
this.slots.optimisticState.mergeDeep(newState);
this.slots._fire("didSet", prevState, this);

@@ -92,2 +94,7 @@ this.branches.splice(this.branches.indexOf(branch), 1);

}, {
key: "hasPromises",
value: function hasPromises() {
return !!this.promises.length;
}
}, {
key: "get",

@@ -94,0 +101,0 @@ value: function get() {

@@ -41,2 +41,3 @@ "use strict";

this.state = (0, _immutable.fromJS)(state);
this.optimisticState = this.state;
this.contexts = [];

@@ -62,6 +63,7 @@ this.listeners = {};

var value = arguments[1] === undefined ? {} : arguments[1];
var mergeValue = arguments[2] === undefined ? true : arguments[2];
var ctx = new _context2["default"](this);
this.contexts.push(ctx);
ctx.set(path, value);
ctx.set(path, value, mergeValue);
return ctx;

@@ -115,3 +117,3 @@ }

if (this.contexts.filter(function (context) {
return context.promises.length;
return context.hasPromises();
}).length) {

@@ -118,0 +120,0 @@ return;

{
"name": "slt",
"version": "1.3.2",
"version": "1.3.4",
"description": "Take care of your state",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -11,2 +11,3 @@ ## DISCLAIMER

* add transaction context. Add rollbacks and state history navigation
* add the master transaction context. Changes in other contexts sould be discarded if conflict occurs
* add performance tests

@@ -29,2 +30,5 @@ * add Promise interface support (e.g. `Slots.then` ect)

* `Slots` supports async operations through Promises
* with `Slots` you are the master of your app state. Everything that's not commited will be discard.
* you can choose beetween optimistic/pessimistic update
*

@@ -39,35 +43,53 @@ In short to understand `Slots` you need to know how works only one method: `set(path, value)` (`path` is a dot-separated path to the concrete property in the state map). This simplicity has a great value.

## Use case
React had virtualized DOM (https://facebook.github.io/react/docs/glossary.html), in my projects I use `Slots` to virtualize (emulate) browser state and behavior (through Rules). And it gives me a great flexibility: I can use the same request data and the same rules in the same format both on client and server. Also it makes **super easy** to make **truly** isomorphic applications which could works w/o javascript on client **by default**. From this perspective, support of History API could be implemented as React component like so https://gist.github.com/AlexeyFrolov/0f7b44afc9fd29f36daf .
React had virtualized DOM (https://facebook.github.io/react/docs/glossary.html), in my projects I use `Slots` to virtualize (emulate) browser state and behavior (through Rules). And it gives me a great flexibility: I can use the same request data and the same rules in the same format both on client and server. Also it makes **super easy** to make **truly** isomorphic applications which could works w/o javascript on client **by default**. From this perspective, support of History API could be implemented as a React component like so https://gist.github.com/AlexeyFrolov/0f7b44afc9fd29f36daf .
###Rules example (v0.*)
Rules is an object which is following the state structure. Say if you want to apply rule for some property in the state map (we call it Slot), you only need to declare function in the Rules object which key with the same state property name. (or path, e.g. it could be `"request.url": (url, context) {}`). Rule should return `context`. `context` has the same `set` method (which returns `context` with a new state).
In example below we set "request" to the state, rule on key `request` sets active `route` and `session` (for session there is no rule) from request. When `route` state property is changed the `route` rule fires. It sets Promise to key `users.{id}`. When Promise will be resolved `users.{id}` will substituted with actual value of that Promise.
###Rules example
It's a complex real-world (my production code) example that shows how to handle API redirects with a Location header.
```javascript
import r from "superagent-bluebird-promise";
import router from "./router";
import Slots from "slt";
export default new Slots ({
"request": (req, context) => {
export default {
"request": function (req) {
let route = router.match(req.url);
let session = req.session;
route.url = req.url;
return context
.set("route", route) // "route" rule will apply
.set("session", req.session); // there is no rule for "session". Just sets "session" to the state
// returns "context" (with updated state) which will be sent to the next rule in the chain ("route").
return this
.set("route", route);
.set("session", req.session);
},
"route": (route, context) => {
let {name, params: { id }} = route;
if (name === "login") {
return context; // do nothing
"route": {
deps: ["request"],
set: function (route, request) {
let {name, params: { id }} = route;
if (name === "login") {
return this;
}
let url = router.url({name, params: {id}});
let method = request.method ? request.method.toLowerCase() : "get";
let req = r[method]("http://example.com/api/" + url);
if (~["post", "put"].indexOf(method)) {
req.send(request.body);
}
return req.then((resp) => {
let ctx = this.ctx;
let path = url.substr(1).replace("/", ".");
if (!resp.body) {
let location = resp.headers.location;
if (location) {
ctx.set("request", {
method: "GET",
url: location.replace('/api', '')
});
}
} else {
ctx.set(path, resp.body);
}
return ctx.commit();
});
}
let url = router.url({name, params: {id}});
return context
.set(url.substr(1).replace("/", "."), // state key generated from url
r.get("http://example.com/api/" + url) // sets Promise which will fetch user for id.
.then(({body}) => body)) // extract body from response
}
});
}
```

@@ -81,11 +103,10 @@ In short, following `<img>` analogy (https://github.com/AlexeyFrolov/slt/blob/master/README.md#philosophy) we say: if `request.url` is `/users/555e5c37a5311543fc8890c9` the `users.555e5c37a5311543fc8890c9` should be the body of GET request to `/users/555e5c37a5311543fc8890c9`. Also we parse and validate route on the way.

```
Important to understand that all `context.set` running in single transaction within one "rule chain". It sets "all or nothing". If something breaks inside of `slots.set("request")` the state will be not changed.
###On server (express middleware)
```javascript
import slots from "./slots"; // Configured Slots
import slots from ".lo/slots"; // Configured Slots
server.use((req, res, next) => {
slots.onPromisesAreMade((state) => { // when all promises are resolved
var html = render(state);
slots.onAllPromisesDone(() => {
let state = slots.state.toJS();
let html = render(state);
res.status(state.response && state.response.status || 200).send(html);

@@ -95,4 +116,5 @@ slots.reset();

let { url, session } = req;
slots.set("request", { url, session });
slots.set("request", { url, session }).commit();
});
```

@@ -103,35 +125,34 @@

```javascript
import React from "react";
import Application from "./Application.js";
import slots from "./slots"; // Configured Slots
slots.onChange((state) => { // on every commited change
import slots from "./slots";
window.__SLOTS__DEBUG__ = slots;
window.debug = require("debug");
window.debug.enable("slt:log");
const mountNode = document.getElementById("root");
const state = window.state;
slots.onDidSet((prevState, context) => {
let state = context.state.toJS();
renderApp(state);
})
function renderApp(state) {
React.render(<Application state={state} />, document.getElementById("root"));
React.render(<Application state={state} />, mountNode, () => {
debug("Application has been mounted");
});
}
// Load the Intl polyfill and required locale data
const locale = document.documentElement.getAttribute("lang");
renderApp(state);
```
###Enable logging
```javascript
window.debug = require("debug");
window.debug.enable("slt:log");
slots.set("request", {"url": "/users/555e5c37a5311543fc8890c9"})
```
Outputs:
```javascript
slt:log SET 'request' TO { url: '/users/555e5c37a5311543fc8890c9', session: [object Object] } +0ms
slt:log SET 'route' TO { node: [Object], params: [Object], routePath: [Object], query: {}, name: 'users', domain: '', scheme: '', url: '/users/555e5c37a5311543fc8890c9' } +4ms
slt:log SET 'users.555e5c37a5311543fc8890c9' TO '__promise__' +5ms
slt:log SET 'session' TO [object Object] +5ms
slt:log SAVE { request: [Object], route: [Object], users: [Object], session: [object Object] } +2ms
GET /api//users/555e5c37a5311543fc8890c9 200 23.483 ms - -
slt:log RESOLVED 'users.555e5c37a5311543fc8890c9' +31ms
slt:log SET 'users.555e5c37a5311543fc8890c9' TO { _id: '555e5c37a5311543fc8890c9' } +1ms
slt:log SAVE { request: [Object], route: [Object], users: [Object], session: [object Object] } +76ms
```
Final state (w/o 'user', I have different rules, which set it as well):
![Alt text](https://monosnap.com/file/otw3slLjWwRCYqS12jQM4JXTB4kT2J.png)

@@ -39,9 +39,8 @@ import rules from "./data/cascadeRules";

it('should overwrite by rule', () => {
let url5 = "test5";
slots.set([], { request: url5, users: 3 }).commit();
expect(slots.get('request')).toBe(url5);
expect(slots.get('users')).toBe(url5);
});
// deal with conflicts
//it('should overwrite by rule', () => {
// let url5 = "test5";
// slots.set([], { request: url5, users: 3 }).commit();
// expect(slots.get('request')).toBe(url5);
// expect(slots.get('users')).toBe(url5);
//});
});
export default {
"request": function (url) {
return this.set("route", url);
this.set("route", url);
},
"route": function (url) {
return this.set("users", url);
this.set("users", url);
},

@@ -8,0 +8,0 @@ "users": function () {

@@ -5,7 +5,7 @@ import Promise from "bluebird";

"request": function (url) {
return this.set("route", url);
this.set("route", url);
},
"route": function (url) {
return this.set("users", Promise.resolve(url));
this.set("users", Promise.resolve(url));
}
}

@@ -6,4 +6,4 @@ import service from "./service"

params = params || {id: 1};
return this.set(name, service[name](params.id));
this.set(name, service[name](params.id));
}
}

@@ -5,4 +5,4 @@ import service from "./service"

"route": function ({name, params: { id }}) {
return this.set(name, service[name + "Promise"](id))
this.set(name, service[name + "Promise"](id))
}
}

@@ -34,18 +34,18 @@ import Slots from "../slots";

expect(slots.get(path)).toEqual([4,2,3]);
expect(slots.get(path.join('.'))).toEqual([4,2,3]);
expect(slots.get()).toEqual({
s: {
b: [4,2,3]
},
x: [0]
});
//expect(slots.get(path.join('.'))).toEqual([4,2,3]);
//expect(slots.get()).toEqual({
// s: {
// b: [4,2,3]
// },
// x: [0]
//});
});
const flox2 = new Slots({}, {});
flox2.set('route', {name: 'page', params: {id: 1}}).commit();
const flox3 = new Slots({}, flox2.getState().toJS());
it ('should restore state', () => {
expect(flox3.getState().toJS()).toEqual(flox2.getState().toJS());
});
//const flox2 = new Slots({}, {});
//flox2.set('route', {name: 'page', params: {id: 1}}).commit();
//const flox3 = new Slots({}, flox2.getState().toJS());
//it ('should restore state', () => {
// expect(flox3.getState().toJS()).toEqual(flox2.getState().toJS());
//});
});

@@ -34,9 +34,9 @@ import Slots from "../slots";

const slots5 = new Slots(rules, {});
//const slots5 = new Slots(rules, {});
//
//it ('should set state and execute rules', () => {
// slots5.set('route.name', 'page').commit();
// expect(slots5.get('page.title')).toBe('Help');
//});
it ('should set state and execute rules', () => {
slots5.set('route.name', 'page').commit();
expect(slots5.get('page.title')).toBe('Help');
});
});

@@ -33,4 +33,5 @@ import { fromJS, is, Map, List} from "immutable";

set(path = [], value = {}) {
({path, value} = this.reducePathAndValue(Slots.makePath(path), value));
set(path = [], value = {}, mergeValue = true) {
//({path, value} = this.reducePathAndValue(Slots.makePath(path), value));
path = Slots.makePath(path);
this.path = path;

@@ -41,30 +42,30 @@ this.value = value;

d("MERGED \n%s", insp(state));
state = Branch.mergeValue(state, path, value); // TODO: deal with conflicts
const applyRules = (path = new List(), value = {}) => {
let rule = this.getSetRule(path);
const applyRules = (_path = new List(), _value = {}) => {
let rule = this.getSetRule(_path);
if (rule) {
let deps = this.getDeps(path).map(dep => {
let dependency = this.state.getIn(Slots.makePath(dep));
if (!dependency) {
throw new Error("Rule on `" + path.toArray().join(".") +
"` requires `" + dep + "` state dependency which not provided");
let deps = this.getDeps(_path).map(dep => {
let dependency = state.getIn(Slots.makePath(dep));
if (typeof dependency === "undefined") {
console.log(state);
throw new Error("Rule on `" + _path.toArray().join(".") +
"` requires `" + dep + "` state dependency");
}
return dependency;
});
log("RULE on path %s matched with value %s", insp(path), insp(value));
log("RULE on path %s matched with value %s", insp(_path), insp(_value));
state = Branch.mergeValue(state, _path, _value, mergeValue);
let branch = this.newBranch(state);
rule.apply(branch, [value, ...deps]);
let result = rule.apply(branch, [_value, ...deps]);
state = branch.state;
if (!isPromise(branch)) {
if (!isPromise(result)) {
d("NEW BRANCH with state %s", insp(state));
state = branch.state;
//result && this.set(_path, result);
d("RESULT is %s", insp(state));
this.state = state
} else {
log("PROMISE RETURNED");
branch.bind(this.ctx); // out of call stack
this.ctx.promises.push(branch);
branch.then(() => {
log("PROMISE FULFILLED for SET %s", insp(path));
this.ctx.promises.splice(this.ctx.promises.indexOf(branch), 1);
result.bind(this.ctx); // out of call stack
this.ctx.promises.push(result);
result.then(() => {
log("PROMISE FULFILLED for SET %s", insp(_path));
this.ctx.promises.splice(this.ctx.promises.indexOf(result), 1);
this.ctx.slots._checkPromises(this);

@@ -75,6 +76,6 @@ });

else {
if (isObject(value)) {
Object.keys(value).forEach(k => applyRules(path.push(k), value[k]));
} else {
this.state = state; // No rule found. Set as is.
if (isObject(_value)) {
Object.keys(_value).forEach(k => applyRules(_path.push(k), _value[k]));
} else { // No rule found for this `set`
state = Branch.mergeValue(state, path, value, mergeValue);
}

@@ -84,2 +85,3 @@ }

applyRules(new List(path), value);
this.state = state;
return this;

@@ -130,4 +132,4 @@ }

static mergeValue(state, path, value) {
return (isObject(value)) ?
static mergeValue(state, path, value, mergeValue) {
return (isObject(value) && mergeValue) ?
state.mergeDeepIn(path, value) : state.setIn(path, value);

@@ -134,0 +136,0 @@ }

@@ -29,3 +29,3 @@ import { fromJS, is, Map, List} from "immutable";

set(path = [], value = {}) {
set(path = [], value = {}, mergeValue = true) {
this.path = path;

@@ -36,5 +36,6 @@ let prevState = this.state;

this.slots._fire("beforeSet", prevState, this);
let newState = branch.set(path, value).getState();
let newState = branch.set(path, value, mergeValue).getState();
this.slots._fire("willSet", newState, this); //TODO: return false == do nothing
this.state = newState;
this.slots.optimisticState.mergeDeep(newState);
this.slots._fire("didSet", prevState, this);

@@ -57,2 +58,6 @@ this.branches.splice(this.branches.indexOf(branch), 1);

hasPromises() {
return !!this.promises.length;
}
static makePath(path) {

@@ -59,0 +64,0 @@ if (path === null) {

@@ -15,2 +15,3 @@ import { fromJS, is, Map, List} from "immutable";

this.state = fromJS(state);
this.optimisticState = this.state;
this.contexts = [];

@@ -29,6 +30,6 @@ this.listeners = {};

set(path = [], value = {}) {
set(path = [], value = {}, mergeValue = true) {
let ctx = new Context(this);
this.contexts.push(ctx);
ctx.set(path, value);
ctx.set(path, value, mergeValue);
return ctx;

@@ -67,3 +68,3 @@ }

_checkPromises() {
if (this.contexts.filter((context) => context.promises.length).length) {
if (this.contexts.filter((context) => context.hasPromises()).length) {
return;

@@ -70,0 +71,0 @@ }

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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