Socket
Socket
Sign inDemoInstall

taskgroup

Package Overview
Dependencies
Maintainers
1
Versions
135
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

taskgroup - npm Package Compare versions

Comparing version 3.2.4 to 3.3.0

20

cyclic.js

@@ -1,9 +0,13 @@

// v1.3.0 October 26, 2013
// v1.3.7 November 1, 2013
// https://github.com/bevry/base
if ( require('fs').existsSync('.git') ) {
require('child_process').spawn(
process.platform.indexOf('win') === 0 ? process.execPath.replace('node.exe', 'npm.cmd') : 'npm',
['install', '--force', require('./package.json').name],
{env:process.env, cwd:process.cwd(), stdio:'inherit'}
).on('error', console.log).on('close', console.log);
}
(function(){
var fsUtil = require('fs'),
name = require('./package.json').name;
if ( fsUtil.existsSync('.git') === true && fsUtil.existsSync('./node_modules/'+name) === false ) {
require('child_process').spawn(
process.platform.indexOf('win') === 0 ? process.execPath.replace('node.exe', 'npm.cmd') : 'npm',
['install', '--force', name],
{env:process.env, cwd:process.cwd(), stdio:'inherit'}
).on('error', console.log).on('close', console.log);
}
})()
## History
- v3.3.0 November 1, 2013
- Bindings are now more explicit
- Improved configuration parsing
- Configuration is now accessed via `getConfig()`
- Dropped component.io and bower support, just use ender or browserify
- v3.2.4 October 27, 2013

@@ -4,0 +10,0 @@ - Re-packaged

3

LICENSE.md

@@ -6,3 +6,4 @@

Copyright &copy; Benjamin Lupton <b@lupton.cc>
Copyright &copy; 2013+ Bevry Pty Ltd <us@bevry.me> (http://bevry.me)
<br/>Copyright &copy; 2011-2012 Benjamin Lupton <b@lupton.cc> (http://balupton.com)

@@ -9,0 +10,0 @@ ## The MIT License

// Generated by CoffeeScript 1.6.3
(function() {
var EventEmitter, Task, TaskGroup, ambi, domain, events,
var EventEmitter, Task, TaskGroup, ambi, domain, err, events,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
__slice = [].slice,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
__slice = [].slice;
ambi = require('ambi');
events = typeof window !== "undefined" && window !== null ? require('events-browser') : require('events');
events = require('events');
domain = typeof window !== "undefined" && window !== null ? require('domain-browser') : require('domain');
domain = (function() {
try {
return require('domain');
} catch (_error) {
err = _error;
return null;
}
})();

@@ -28,28 +34,43 @@ EventEmitter = events.EventEmitter;

Task.prototype.parent = null;
Task.prototype.taskDomain = null;
Task.prototype.name = null;
Task.prototype.config = null;
Task.prototype.method = null;
/*
name: null
method: null
args: null
parent: null
*/
Task.prototype.args = null;
function Task() {
var args, method, name;
var arg, args, key, opts, value, _base, _i, _len;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
Task.__super__.constructor.apply(this, arguments);
name = method = null;
if (args.length) {
if (args.length === 2) {
name = args[0], method = args[1];
} else if (args.length === 1) {
method = args[0];
if (this.config == null) {
this.config = {};
}
if ((_base = this.config).run == null) {
_base.run = false;
}
opts = {};
for (_i = 0, _len = args.length; _i < _len; _i++) {
arg = args[_i];
switch (typeof arg) {
case 'string':
opts.name = arg;
break;
case 'function':
opts.method = arg;
break;
case 'object':
for (key in arg) {
if (!__hasProp.call(arg, key)) continue;
value = arg[key];
opts[key] = value;
}
}
}
this.setConfig({
name: name,
method: method
});
this.setConfig(opts);
this;

@@ -66,3 +87,11 @@ }

value = opts[key];
this[key] = value;
switch (key) {
case 'next':
if (value) {
this.once('complete', value.bind(this));
}
break;
default:
this.config[key] = value;
}
}

@@ -72,2 +101,6 @@ return this;

Task.prototype.getConfig = function() {
return this.config;
};
Task.prototype.reset = function() {

@@ -81,3 +114,3 @@ this.completed = false;

Task.prototype.uncaughtExceptionCallback = function() {
var args, err;
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];

@@ -93,3 +126,3 @@ err = args[0];

Task.prototype.completionCallback = function() {
var args, err;
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];

@@ -119,18 +152,22 @@ if (!this.completed) {

Task.prototype.fire = function() {
var args,
_this = this;
args = (this.args || []).concat([this.completionCallback.bind(this)]);
if (this.taskDomain == null) {
var args, fire, me;
me = this;
args = (this.config.args || []).concat([this.completionCallback.bind(this)]);
if ((this.taskDomain != null) === false && ((domain != null ? domain.create : void 0) != null)) {
this.taskDomain = domain.create();
this.taskDomain.on('error', this.uncaughtExceptionCallback.bind(this));
}
this.taskDomain.run(function() {
var err;
fire = function() {
try {
return ambi.apply(null, [_this.method.bind(_this)].concat(__slice.call(args)));
return ambi.apply(null, [me.config.method.bind(me)].concat(__slice.call(args)));
} catch (_error) {
err = _error;
return _this.uncaughtExceptionCallback(err);
return me.uncaughtExceptionCallback(err);
}
});
};
if (this.taskDomain != null) {
this.taskDomain.run(fire);
} else {
fire();
}
return this;

@@ -140,3 +177,2 @@ };

Task.prototype.run = function() {
var err;
if (this.completed) {

@@ -171,4 +207,2 @@ err = new Error("A task was about to run but it has already completed, this is unexpected");

TaskGroup.prototype.parent = null;
TaskGroup.prototype.paused = true;

@@ -178,17 +212,30 @@

TaskGroup.prototype.name = null;
TaskGroup.prototype.config = null;
TaskGroup.prototype.method = null;
/*
name: null
method: null
concurrency: 1 # use 0 for unlimited
pauseOnError: true
parent: null
*/
TaskGroup.prototype.concurrency = 1;
TaskGroup.prototype.pauseOnError = true;
function TaskGroup() {
var args, me, method, name;
var arg, args, key, me, opts, value, _base, _base1, _base2, _i, _len;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
this.addGroup = __bind(this.addGroup, this);
this.addTask = __bind(this.addTask, this);
me = this;
TaskGroup.__super__.constructor.apply(this, arguments);
if (this.config == null) {
this.config = {};
}
if ((_base = this.config).concurrency == null) {
_base.concurrency = 1;
}
if ((_base1 = this.config).pauseOnError == null) {
_base1.pauseOnError = true;
}
if ((_base2 = this.config).run == null) {
_base2.run = false;
}
if (this.results == null) {

@@ -203,14 +250,21 @@ this.results = [];

}
name = method = null;
if (args.length) {
if (args.length === 2) {
name = args[0], method = args[1];
} else if (args.length === 1) {
method = args[0];
opts = {};
for (_i = 0, _len = args.length; _i < _len; _i++) {
arg = args[_i];
switch (typeof arg) {
case 'string':
opts.name = arg;
break;
case 'function':
opts.method = arg;
break;
case 'object':
for (key in arg) {
if (!__hasProp.call(arg, key)) continue;
value = arg[key];
opts[key] = value;
}
}
}
this.setConfig({
name: name,
method: method
});
this.setConfig(opts);
process.nextTick(this.fire.bind(this));

@@ -233,3 +287,29 @@ this.on('item.complete', this.itemCompletionCallback.bind(this));

value = opts[key];
this[key] = value;
switch (key) {
case 'next':
if (value) {
this.once('complete', value.bind(this));
}
break;
case 'task':
case 'tasks':
if (value) {
this.addTasks(value);
}
break;
case 'group':
case 'groups':
if (value) {
this.addGroups(value);
}
break;
case 'item':
case 'items':
if (value) {
this.addItems(value);
}
break;
default:
this.config[key] = value;
}
}

@@ -239,14 +319,19 @@ return this;

TaskGroup.prototype.getConfig = function() {
return this.config;
};
TaskGroup.prototype.fire = function() {
var args;
if (this.method) {
args = [this.addGroup, this.addTask];
this.addTask(this.method.bind(this)).setConfig({
args: args,
if (this.config.method) {
this.addTask(this.config.method.bind(this), {
args: [this.addGroup.bind(this), this.addTask.bind(this)],
includeInResults: false
});
if (!this.parent) {
if (!this.config.parent) {
this.run();
}
}
if (this.config.run === true) {
this.run();
}
return this;

@@ -258,3 +343,3 @@ };

item = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
if (item.includeInResults !== false) {
if (item.config.includeInResults !== false) {
this.results.push(args);

@@ -294,2 +379,25 @@ }

me = this;
item.setConfig({
parent: this
});
if (item instanceof Task) {
this.bubbleEvents.forEach(function(bubbleEvent) {
return item.on(bubbleEvent, function() {
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
return me.emit.apply(me, ["task." + bubbleEvent, item].concat(__slice.call(args)));
});
});
this.emit('task.add', item);
}
if (item instanceof TaskGroup) {
this.bubbleEvents.forEach(function(bubbleEvent) {
return item.on(bubbleEvent, function() {
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
return me.emit.apply(me, ["group." + bubbleEvent, item].concat(__slice.call(args)));
});
});
this.emit('group.add', item);
}
this.bubbleEvents.forEach(function(bubbleEvent) {

@@ -310,6 +418,23 @@ return item.on(bubbleEvent, function() {

TaskGroup.prototype.addItems = function() {
var args, item, items;
items = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
if (!Array.isArray(items)) {
items = [items];
}
return (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = items.length; _i < _len; _i++) {
item = items[_i];
_results.push(this.addItem.apply(this, [item].concat(__slice.call(args))));
}
return _results;
}).call(this);
};
TaskGroup.prototype.createTask = function() {
var args, task;
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
task = (function(func, args, ctor) {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;

@@ -319,27 +444,31 @@ var child = new ctor, result = func.apply(child, args);

})(Task, args, function(){});
return task;
};
TaskGroup.prototype.addTask = function() {
var args, me, task;
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
me = this;
task = this.createTask.apply(this, args).setConfig({
parent: this
});
this.bubbleEvents.forEach(function(bubbleEvent) {
return task.on(bubbleEvent, function() {
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
return me.emit.apply(me, ["task." + bubbleEvent, task].concat(__slice.call(args)));
});
});
this.emit('task.add', task);
return this.addItem(task);
return this.addItem(this.createTask.apply(this, args));
};
TaskGroup.prototype.addTasks = function() {
var args, item, items;
items = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
if (!Array.isArray(items)) {
items = [items];
}
return (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = items.length; _i < _len; _i++) {
item = items[_i];
_results.push(this.addTask.apply(this, [item].concat(__slice.call(args))));
}
return _results;
}).call(this);
};
TaskGroup.prototype.createGroup = function() {
var args, group;
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
group = (function(func, args, ctor) {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;

@@ -349,24 +478,27 @@ var child = new ctor, result = func.apply(child, args);

})(TaskGroup, args, function(){});
return group;
};
TaskGroup.prototype.addGroup = function() {
var args, group, me;
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
me = this;
group = this.createGroup.apply(this, args).setConfig({
concurrency: this.concurrency,
parent: this
});
this.bubbleEvents.forEach(function(bubbleEvent) {
return group.on(bubbleEvent, function() {
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
return me.emit.apply(me, ["group." + bubbleEvent, group].concat(__slice.call(args)));
});
});
this.emit('group.add', group);
return this.addItem(group);
return this.addItem(this.createGroup.apply(this, args));
};
TaskGroup.prototype.addGroups = function() {
var args, item, items;
items = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
if (!Array.isArray(items)) {
items = [items];
}
return (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = items.length; _i < _len; _i++) {
item = items[_i];
_results.push(this.addGroup.apply(this, [item].concat(__slice.call(args))));
}
return _results;
}).call(this);
};
TaskGroup.prototype.hasItems = function() {

@@ -377,3 +509,3 @@ return this.remaining.length !== 0;

TaskGroup.prototype.isReady = function() {
return !this.concurrency || this.running < this.concurrency;
return !this.config.concurrency || this.running < this.config.concurrency;
};

@@ -411,3 +543,3 @@

var completed, empty, pause;
pause = this.pauseOnError && this.err;
pause = this.config.pauseOnError && this.err;
empty = this.hasItems() === false && this.running === 0;

@@ -414,0 +546,0 @@ completed = pause || empty;

{
"title": "TaskGroup",
"name": "taskgroup",
"version": "3.2.4",
"version": "3.3.0",
"description": "Group together synchronous and asynchronous tasks and execute them with support for concurrency, naming, and nesting.",

@@ -26,3 +26,3 @@ "homepage": "https://github.com/bevry/taskgroup",

],
"author": "Benjamin Lupton <b@lupton.cc>",
"author": "2013+ Bevry Pty Ltd <us@bevry.me> (http://bevry.me), 2011-2012 Benjamin Lupton <b@lupton.cc> (http://balupton.com)",
"maintainers": [

@@ -32,3 +32,4 @@ "Benjamin Lupton <b@lupton.cc> (https://github.com/balupton)"

"contributors": [
"Benjamin Lupton <b@lupton.cc> (https://github.com/balupton)"
"Benjamin Lupton <b@lupton.cc> (https://github.com/balupton)",
"sfrdmn (https://github.com/sfrdmn)"
],

@@ -35,0 +36,0 @@ "bugs": {

@@ -12,6 +12,6 @@

[![Build Status](http://img.shields.io/travis-ci/bevry/taskgroup.png?branch=master)](http://travis-ci.org/bevry/taskgroup "Check this project's build status on TravisCI")
[![NPM version](https://badge.fury.io/js/taskgroup.png)](https://npmjs.org/package/taskgroup "View this project on NPM")
[![NPM version](http://badge.fury.io/js/taskgroup.png)](https://npmjs.org/package/taskgroup "View this project on NPM")
[![Gittip donate button](http://img.shields.io/gittip/bevry.png)](https://www.gittip.com/bevry/ "Donate weekly to this project using Gittip")
[![Flattr donate button](https://raw.github.com/balupton/flattr-buttons/master/badge-89x18.gif)](http://flattr.com/thing/344188/balupton-on-Flattr "Donate monthly to this project using Flattr")
[![PayPayl donate button](https://www.paypalobjects.com/en_AU/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QB8GQPZAH84N6 "Donate once-off to this project using Paypal")
[![Flattr donate button](http://img.shields.io/flattr/donate.png?color=yellow)](http://flattr.com/thing/344188/balupton-on-Flattr "Donate monthly to this project using Flattr")
[![PayPayl donate button](http://img.shields.io/paypal/donate.png?color=yellow)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QB8GQPZAH84N6 "Donate once-off to this project using Paypal")

@@ -40,10 +40,2 @@ <!-- /BADGES -->

### [Component](http://github.com/component/component)
- Use: `require('taskgroup')`
- Install: `component install bevry/taskgroup`
### [Bower](http://bower.io/)
- Use: `require('taskgroup')`
- Install: `bower install taskgroup`
<!-- /INSTALL -->

@@ -64,3 +56,3 @@

// Define what should happen once the group has completed
group.once('complete', function(err,results){
group.once('complete', function(err, results){
// Log the error that has occured

@@ -98,3 +90,3 @@ console.log(err);

// Add a sub-group to our exiting group
group.addGroup(function(addGroup,addTask){
group.addGroup(function(addGroup, addTask){
// Tell this sub-group to execute in parallel (all at once) by setting its concurrency to unlimited

@@ -129,6 +121,8 @@ // by default the concurrency for all groups is set to 1

- Available methods:
- `constructor(name?,fn?)` - create our new group, the arguments `name` and `fn` are optional, refer to their entries in configuration
- `constructor(name?,fn?)` - create our new group, arguments can be a String for `name`, an Object for `config`, and a Function for `next`
- `setConfig(config)` - set the configuration for the group, returns chain
- `addTask(args...)` - create a new task item with the arguments and adds it to the group, returns the new task item
- `addGroup(args...)` - create a new group item with the arguments and adds it to the group, returns the new group item
- `getconfig()` - return the set configuration
- `addTask(args...)`, `addTasks(tasks, args..)` - create a new task item with the arguments and adds it to the group, returns the new task item(s)
- `addGroup(args...)`, `addGroups(groups, args..)` - create a new group item with the arguments and adds it to the group, returns the new group item(s)
- `addItem(item)`, `addItem(items)` - adds the items to the group, returns the item(s)
- `getTotals()` - returns counts for the following `{running,remaining,completed,total}`

@@ -144,6 +138,10 @@ - `clear()` - remove the remaining items to be executed

- `name`, no default - allows us to assign a name to the group, useful for debugging
- `fn(addGroup,addTask,complete?)`, no default - allows us to use an inline and self-executing style for defining groups, useful for nesting
- `method(addGroup, addTask, complete?)`, no default - allows us to use an inline and self-executing style for defining groups, useful for nesting
- `concurrency`, defaults to `1` - how many items shall we allow to be run at the same time, set to `0` to allow unlimited
- `pauseOnError`, defaults to `true` - if an error occurs in one of our items, should we stop executing any remaining items?
- setting to `false` will continue with execution with the other items even if an item experiences an error
- `items` - alias for `.addTasks(items)`
- `groups` - alias for `.addGroups(groups)`
- `tasks` - alias for `.addTasks(tasks)`
- `next` - alias for `.once('complete', next)`
- Available events:

@@ -167,4 +165,5 @@ - `run()` - fired just before we execute the items

- Available methods:
- `constructor(name?,fn?)` - create our new task, the arguments `name` and `fn` are optional though `fn` must be set at some point, refer to their entries in configuration
- `constructor(args...)` - create our new task, arguments can be a String for `name`, an Object for `config`, and a Function for `next`
- `setConfig(config)` - set the configuration for the group, returns chain
- `getconfig()` - return the set configuration
- `complete()` - will fire the completion event if we are already complete, useful if you're binding your listeners after run

@@ -174,4 +173,5 @@ - `run()` - execute the task

- `name`, no default - allows us to assign a name to the group, useful for debugging
- `fn(complete?)`, no default - must be set at some point, it is the function to execute for the task, if it is asynchronous it should use the completion callback provided
- `method(complete?)`, no default - must be set at some point, it is the function to execute for the task, if it is asynchronous it should use the completion callback provided
- `args`, no default - an array of arguments that you would like to precede the completion callback when executing `fn`
- `next` - alias for `.once('complete', next)`
- Available events:

@@ -182,3 +182,2 @@ - `run()` - fired just before we execute the task

## Comparison with [Async.js](https://github.com/caolan/async)

@@ -201,6 +200,9 @@

// TaskGroup
new TaskGroup().once('complete', next)
.addTask(function(){})
.addTask(function(callback){callback();})
.run();
new TaskGroup({
tasks: [
function(){},
function(callback){callback();}
],
next: next
}).run();

@@ -218,8 +220,11 @@

// TaskGroup
new TaskGroup().setConfig({concurrency:0}).once('complete', next)
.addTask(function(){})
.addTask(function(callback){callback();})
.run();
new TaskGroup({
concurrency: 0,
tasks: [
function(){},
function(callback){callback();}
],
next: next
}).run();
// ====================================

@@ -232,9 +237,11 @@ // Map

// TaskGroup
var tasks = new TaskGroup().setConfig({concurrency:0}).once('complete', next);
['file1','file2','file3'].forEach(function(file){
tasks.addTask(function(complete){
fs.stat(file,complete);
});
});
tasks.run();
new TaskGroup({
concurrency: 0,
tasks: ['file1', 'file2', 'file3'].map(function(file){
return function(complete){
fs.stat(file, complete);
}
}),
next: next
}).run();
```

@@ -247,9 +254,60 @@

<!-- HISTORY/ -->
## History
You can discover the history inside the [History.md](https://github.com/bevry/taskgroup/blob/master/History.md#files) file
[Discover the change history by heading on over to the `History.md` file.](https://github.com/bevry/taskgroup/blob/master/History.md#files)
<!-- /HISTORY -->
<!-- CONTRIBUTE/ -->
## Contribute
[Discover how you can contribute by heading on over to the `Contributing.md` file.](https://github.com/bevry/taskgroup/blob/master/Contributing.md#files)
<!-- /CONTRIBUTE -->
<!-- BACKERS/ -->
## Backers
### Maintainers
These amazing people are maintaining this project:
- Benjamin Lupton <b@lupton.cc> (https://github.com/balupton)
### Sponsors
No sponsors yet! Will you be the first?
[![Gittip donate button](http://img.shields.io/gittip/bevry.png)](https://www.gittip.com/bevry/ "Donate weekly to this project using Gittip")
[![Flattr donate button](http://img.shields.io/flattr/donate.png?color=yellow)](http://flattr.com/thing/344188/balupton-on-Flattr "Donate monthly to this project using Flattr")
[![PayPayl donate button](http://img.shields.io/paypal/donate.png?color=yellow)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QB8GQPZAH84N6 "Donate once-off to this project using Paypal")
### Contributors
These amazing people have contributed code to this project:
- Benjamin Lupton <b@lupton.cc> (https://github.com/balupton) - [view contributions](https://github.com/bevry/taskgroup/commits?author=balupton)
- sfrdmn (https://github.com/sfrdmn) - [view contributions](https://github.com/bevry/taskgroup/commits?author=sfrdmn)
[Become a contributor!](https://github.com/bevry/taskgroup/blob/master/Contributing.md#files)
<!-- /BACKERS -->
<!-- LICENSE/ -->
## License
Licensed under the incredibly [permissive](http://en.wikipedia.org/wiki/Permissive_free_software_licence) [MIT License](http://creativecommons.org/licenses/MIT/)
<br/>Copyright © 2013+ [Bevry Pty Ltd](http://bevry.me)
<br/>Copyright © 2011-2012 [Benjamin Arthur Lupton](http://balupton.com)
Licensed under the incredibly [permissive](http://en.wikipedia.org/wiki/Permissive_free_software_licence) [MIT license](http://creativecommons.org/licenses/MIT/)
Copyright &copy; 2013+ Bevry Pty Ltd <us@bevry.me> (http://bevry.me)
<br/>Copyright &copy; 2011-2012 Benjamin Lupton <b@lupton.cc> (http://balupton.com)
<!-- /LICENSE -->
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