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

mysql

Package Overview
Dependencies
Maintainers
6
Versions
65
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mysql - npm Package Compare versions

Comparing version 2.0.0-alpha8 to 2.0.0-alpha9

lib/PoolCluster.js

10

Changes.md

@@ -7,2 +7,12 @@ # Changes

## v2.0.0-alpha9 (2013-08-27)
* Add query to pool to execute queries directly using the pool
* Pool option to set queue limit
* Pool sends 'connection' event when it opens a new connection
* Added stringifyObjects option to treat input as strings rather than objects (#501)
* Support for poolClusters
* Datetime improvements
* Bug fixes
## v2.0.0-alpha8 (2013-04-30)

@@ -9,0 +19,0 @@

@@ -7,2 +7,3 @@ var Connection = require('./lib/Connection');

var PoolConfig = require('./lib/PoolConfig');
var PoolCluster = require('./lib/PoolCluster');

@@ -17,2 +18,6 @@ exports.createConnection = function(config) {

exports.createPoolCluster = function(config) {
return new PoolCluster(config);
};
exports.createQuery = Connection.createQuery;

@@ -19,0 +24,0 @@

4

lib/Connection.js
var Net = require('net');
var ConnectionConfig = require('./ConnectionConfig');
var Pool = require('./Pool');
var Protocol = require('./protocol/Protocol');

@@ -108,3 +107,2 @@ var SqlString = require('./protocol/SqlString');

query.sql = this.format(query.sql, query.values || []);
delete query.values;

@@ -154,3 +152,3 @@ return this._protocol._enqueue(query);

}
return SqlString.format(sql, values, this.config.timezone);
return SqlString.format(sql, values, this.config.stringifyObjects, this.config.timezone);
};

@@ -157,0 +155,0 @@

@@ -21,2 +21,3 @@ var urlParse = require('url').parse;

this.debug = options.debug;
this.stringifyObjects = options.stringifyObjects || false;
this.timezone = options.timezone || 'local';

@@ -23,0 +24,0 @@ this.flags = options.flags || '';

@@ -1,7 +0,12 @@

var Mysql = require('../');
var mysql = require('../');
var Connection = require('./Connection');
var EventEmitter = require('events').EventEmitter;
var Util = require('util');
var PoolConnection = require('./PoolConnection');
module.exports = Pool;
Util.inherits(Pool, EventEmitter);
function Pool(options) {
EventEmitter.call(this);
this.config = options.config;

@@ -16,44 +21,63 @@ this.config.connectionConfig.pool = this;

Pool.prototype.getConnection = function(cb) {
Pool.prototype.getConnection = function (cb) {
if (this._closed) {
cb(new Error('Pool is closed.'));
return;
return process.nextTick(function(){
return cb(new Error('Pool is closed.'));
});
}
var connection;
if (this._freeConnections.length > 0) {
var connection = this._freeConnections[0];
this._freeConnections.shift();
cb(null, connection);
} else if (this.config.connectionLimit == 0 || this._allConnections.length < this.config.connectionLimit) {
var self = this;
var connection = this._createConnection();
connection = this._freeConnections.shift();
return process.nextTick(function(){
return cb(null, connection);
});
}
if (this.config.connectionLimit === 0 || this._allConnections.length < this.config.connectionLimit) {
connection = new PoolConnection(this, { config: this.config.connectionConfig });
this._allConnections.push(connection);
connection.connect(function(err) {
if (self._closed) {
cb(new Error('Pool is closed.'));
return connection.connect(function(err) {
if (this._closed) {
return cb(new Error('Pool is closed.'));
}
else if (err) {
cb(err);
} else {
cb(null, connection);
if (err) {
return cb(err);
}
this.emit('connection', connection);
return cb(null, connection);
}.bind(this));
}
if (!this.config.waitForConnections) {
return process.nextTick(function(){
return cb(new Error('No connections available.'));
});
} else if (this.config.waitForConnections) {
this._connectionQueue.push(cb);
} else {
cb(new Error('No connections available.'));
}
if (this.config.queueLimit && this._connectionQueue.length >= this.config.queueLimit) {
return cb(new Error('Queue limit reached.'));
}
this._connectionQueue.push(cb);
};
Pool.prototype.releaseConnection = function(connection) {
if (connection._poolRemoved) {
Pool.prototype.releaseConnection = function (connection) {
var cb;
if (!connection._pool) {
// The connection has been removed from the pool and is no longer good.
if (this._connectionQueue.length) {
var cb = this._connectionQueue[0];
this._connectionQueue.shift();
cb = this._connectionQueue.shift();
process.nextTick(this.getConnection.bind(this, cb));
}
} else if (this._connectionQueue.length) {
var cb = this._connectionQueue[0];
this._connectionQueue.shift();
cb = this._connectionQueue.shift();
process.nextTick(cb.bind(null, null, connection));

@@ -65,79 +89,57 @@ } else {

Pool.prototype.end = function(cb) {
Pool.prototype.end = function (cb) {
this._closed = true;
cb = cb || function(err) { if( err ) throw err; };
var self = this;
if (typeof cb != "function") {
cb = function (err) {
if (err) throw err;
};
}
var calledBack = false;
var closedConnections = 0;
var calledBack = false;
var connection;
var endCB = function(err) {
if (calledBack) {
return;
} else if (err) {
}
if (err || ++closedConnections >= this._allConnections.length) {
calledBack = true;
delete endCB;
cb(err);
} else if (++closedConnections >= self._allConnections.length) {
calledBack = true;
delete endCB;
cb();
return cb(err);
}
};
}.bind(this);
if (this._allConnections.length == 0) {
endCB();
return;
if (this._allConnections.length === 0) {
return endCB();
}
for (var i = 0; i < this._allConnections.length; ++i) {
var connection = this._allConnections[i];
connection.destroy = connection._realDestroy;
connection.end = connection._realEnd;
connection.end(endCB);
for (var i = 0; i < this._allConnections.length; i++) {
connection = this._allConnections[i];
connection._realEnd(endCB);
}
};
Pool.prototype._createConnection = function() {
var self = this;
var connection = (this.config.createConnection)
? this.config.createConnection(this.config.connectionConfig)
: Mysql.createConnection(this.config.connectionConfig);
Pool.prototype.query = function (sql, values, cb) {
if (typeof values === 'function') {
cb = values;
values = null;
}
connection._realEnd = connection.end;
connection.end = function(cb) {
self.releaseConnection(connection);
if (cb) cb();
};
this.getConnection(function (err, conn) {
if (err) return cb(err);
connection._realDestroy = connection.destroy;
connection.destroy = function() {
self._removeConnection(connection);
connection.destroy();
};
// When a fatal error occurs the connection's protocol ends, which will cause
// the connection to end as well, thus we only need to watch for the end event
// and we will be notified of disconnects.
connection.on('end', this._handleConnectionEnd.bind(this, connection));
connection.on('error', this._handleConnectionError.bind(this, connection));
return connection;
conn.query(sql, values, function () {
conn.release();
cb.apply(this, arguments);
});
});
};
Pool.prototype._handleConnectionEnd = function(connection) {
if (this._closed || connection._poolRemoved) {
return;
}
this._removeConnection(connection);
};
Pool.prototype._removeConnection = function(connection) {
var i;
Pool.prototype._handleConnectionError = function(connection) {
if (this._closed || connection._poolRemoved) {
return;
}
this._removeConnection(connection);
};
Pool.prototype._removeConnection = function(connection) {
connection._poolRemoved = true;
for (var i = 0; i < this._allConnections.length; ++i) {
for (i = 0; i < this._allConnections.length; i++) {
if (this._allConnections[i] === connection) {

@@ -148,3 +150,4 @@ this._allConnections.splice(i, 1);

}
for (var i = 0; i < this._freeConnections.length; ++i) {
for (i = 0; i < this._freeConnections.length; i++) {
if (this._freeConnections[i] === connection) {

@@ -156,5 +159,7 @@ this._freeConnections.splice(i, 1);

connection.end = connection._realEnd;
connection.destroy = connection._realDestroy;
this.releaseConnection(connection);
};
Pool.prototype.escape = function(value) {
return mysql.escape(value, this.config.connectionConfig.stringifyObjects, this.config.connectionConfig.timezone);
};

@@ -7,3 +7,2 @@

this.connectionConfig = new ConnectionConfig(options);
this.createConnection = options.createConnection || undefined;
this.waitForConnections = (options.waitForConnections === undefined)

@@ -15,2 +14,5 @@ ? true

: Number(options.connectionLimit);
this.queueLimit = (options.queueLimit === undefined)
? 0
: Number(options.queueLimit);
}

@@ -24,3 +24,3 @@ module.exports = ComChangeUserPacket;

writer.writeNullTerminatedString(this.database);
writer.writeUnsignedNumber(1, this.charsetNumber);
writer.writeUnsignedNumber(2, this.charsetNumber);
};

@@ -8,2 +8,3 @@ var Types = require('../constants/types');

this.parser = options.parser;
this.packet = options.packet;
this.db = options.packet.db;

@@ -10,0 +11,0 @@ this.table = options.packet.table;

@@ -33,3 +33,3 @@ module.exports = FieldPacket;

this.charsetNr = parser.parseUnsignedNumber(2);
this.fieldLength = parser.parseUnsignedNumber(4);
this.length = parser.parseUnsignedNumber(4);
this.type = parser.parseUnsignedNumber(1);

@@ -51,3 +51,3 @@ this.flags = parser.parseUnsignedNumber(2);

this.name = parser.parseLengthCodedString();
this.fieldLength = parser.parseUnsignedNumber(parser.parseUnsignedNumber(1));
this.length = parser.parseUnsignedNumber(parser.parseUnsignedNumber(1));
this.type = parser.parseUnsignedNumber(parser.parseUnsignedNumber(1));

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

writer.writeUnsignedNumber(2, this.charsetNr || 0);
writer.writeUnsignedNumber(4, this.fieldLength || 0);
writer.writeUnsignedNumber(4, this.length || 0);
writer.writeUnsignedNumber(1, this.type || 0);

@@ -81,3 +81,3 @@ writer.writeUnsignedNumber(2, this.flags || 0);

writer.writeUnsignedNumber(1, 0x01);
writer.writeUnsignedNumber(1, this.fieldLength);
writer.writeUnsignedNumber(1, this.length);
writer.writeUnsignedNumber(1, 0x01);

@@ -84,0 +84,0 @@ writer.writeUnsignedNumber(1, this.type);

@@ -60,3 +60,3 @@ var Types = require('../constants/types');

} else {
dateString += timeZone;
dateString += ' ' + timeZone;
}

@@ -63,0 +63,0 @@ }

@@ -34,2 +34,4 @@ var Util = require('util');

err.code = code;
err.errno = packet.errno;
err.sqlState = packet.sqlState;

@@ -59,2 +61,9 @@ return err;

// Without this we are leaking memory. This problem was introduced in
// 8189925374e7ce3819bbe88b64c7b15abac96b16. I suspect that the error object
// causes a cyclic reference that the GC does not detect properly, but I was
// unable to produce a standalone version of this leak. This would be a great
// challenge for somebody interested in difficult problems : )!
delete this._callSite;
// try...finally for exception safety

@@ -61,0 +70,0 @@ try {

@@ -21,3 +21,3 @@ var SqlString = exports;

if (val instanceof Date) {
val = SqlString.dateToString(val, timeZone || "Z");
val = SqlString.dateToString(val, timeZone || 'local');
}

@@ -57,3 +57,3 @@

return array.map(function(v) {
if (Array.isArray(v)) return '(' + SqlString.arrayToList(v) + ')';
if (Array.isArray(v)) return '(' + SqlString.arrayToList(v, timeZone) + ')';
return SqlString.escape(v, true, timeZone);

@@ -63,3 +63,3 @@ }).join(', ');

SqlString.format = function(sql, values, timeZone) {
SqlString.format = function(sql, values, stringifyObjects, timeZone) {
values = [].concat(values);

@@ -75,3 +75,3 @@

}
return SqlString.escape(values.shift(), false, timeZone);
return SqlString.escape(values.shift(), stringifyObjects, timeZone);
});

@@ -93,9 +93,10 @@ };

var year = dt.getFullYear();
var month = zeroPad(dt.getMonth() + 1);
var day = zeroPad(dt.getDate());
var hour = zeroPad(dt.getHours());
var minute = zeroPad(dt.getMinutes());
var second = zeroPad(dt.getSeconds());
var month = zeroPad(dt.getMonth() + 1, 2);
var day = zeroPad(dt.getDate(), 2);
var hour = zeroPad(dt.getHours(), 2);
var minute = zeroPad(dt.getMinutes(), 2);
var second = zeroPad(dt.getSeconds(), 2);
var millisecond = zeroPad(dt.getMilliseconds(), 3);
return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second;
return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second + '.' + millisecond;
};

@@ -132,4 +133,9 @@

function zeroPad(number) {
return (number < 10) ? '0' + number : number;
function zeroPad(number, length) {
number = number.toString();
while (number.length < length) {
number = '0' + number;
}
return number;
}

@@ -136,0 +142,0 @@

@@ -5,3 +5,3 @@ {

"description": "A node.js driver for mysql. It is written in JavaScript, does not require compiling, and is 100% MIT licensed.",
"version": "2.0.0-alpha8",
"version": "2.0.0-alpha9",
"repository": {

@@ -8,0 +8,0 @@ "type": "git",

@@ -8,3 +8,3 @@ # node-mysql

```bash
npm install mysql@2.0.0-alpha8
npm install mysql@2.0.0-alpha9
```

@@ -142,4 +142,6 @@

* `database`: Name of the database to use for this connection (Optional).
* `charset`: The charset for the connection. (Default: `'UTF8_GENERAL_CI'`)
* `charset`: The charset for the connection. (Default: `'UTF8_GENERAL_CI'`. Value needs to be all in upper case letters!)
* `timezone`: The timezone used to store local dates. (Default: `'local'`)
* `stringifyObjects`: Stringify objects instead of converting to values. See
issue [#501](https://github.com/felixge/node-mysql/issues/501). (Default: `'false'`)
* `insecureAuth`: Allow connecting to MySQL instances that ask for the old

@@ -211,3 +213,3 @@ (insecure) authentication method. (Default: `false`)

user : 'bob',
password : 'secret'
password : 'secret',
});

@@ -220,2 +222,11 @@

If you need to set session variables on the connection before it gets used,
you can listen to the `connection` event.
```js
pool.on('connection', function(err, connection) {
connection.query('SET SESSION auto_increment_increment=1')
});
```
When you are done with a connection, just call `connection.end()` and the

@@ -262,3 +273,62 @@ connection will return to the pool, ready to be used again by someone else.

(Default: `10`)
* `queueLimit`: The maximum number of connection requests the pool will queue
before returning an error from `getConnection`. If set to `0`, there is no
limit to the number of queued connection requests. (Default: `0`)
## PoolCluster
PoolCluster provides multiple hosts connection. (group & retry & selector)
```js
// create
var poolCluster = mysql.createPoolCluster();
poolCluster.add(config); // anonymous group
poolCluster.add('MASTER', masterConfig);
poolCluster.add('SLAVE1', slave1Config);
poolCluster.add('SLAVE2', slave2Config);
// Target Group : ALL(anonymous, MASTER, SLAVE1-2), Selector : round-robin(default)
poolCluster.getConnection(function (err, connection) {});
// Target Group : MASTER, Selector : round-robin
poolCluster.getConnection('MASTER', function (err, connection) {});
// Target Group : SLAVE1-2, Selector : order
// If can't connect to SLAVE1, return SLAVE2. (remove SLAVE1 in the cluster)
poolCluster.on('remove', function (nodeId) {
console.log('REMOVED NODE : ' + nodeId); // nodeId = SLAVE1
});
poolCluster.getConnection('SLAVE*', 'ORDER', function (err, connection) {});
// of namespace : of(pattern, selector)
poolCluster.of('*').getConnection(function (err, connection) {});
var pool = poolCluster.of('SLAVE*', 'RANDOM');
pool.getConnection(function (err, connection) {});
pool.getConnection(function (err, connection) {});
// destroy
poolCluster.end();
```
## PoolCluster Option
* `canRetry`: If `true`, `PoolCluster` will attempt to reconnect when connection fails. (Default: `true`)
* `removeNodeErrorCount`: If connection fails, node's `errorCount` increases.
When `errorCount` is greater than `removeNodeErrorCount`, remove a node in the `PoolCluster`. (Default: `5`)
* `defaultSelector`: The default selector. (Default: `RR`)
* `RR`: Select one alternately. (Round-Robin)
* `RANDOM`: Select the node by random function.
* `ORDER`: Select the first node available unconditionally.
```js
var clusterConfig = {
removeNodeErrorCount: 1, // Remove the node immediately when connection fails.
defaultSelector: 'ORDER',
};
var poolCluster = mysql.createPoolCluster(clusterConfig);
```
## Switching users / altering connection state

@@ -291,29 +361,41 @@

You may lose the connection to a MySQL server due to network problems, the
server timing you out, or the server crashing. All of these events are
considered fatal errors, and will have the `err.code =
server timing you out, the server being restarted, or crashing. All of these
events are considered fatal errors, and will have the `err.code =
'PROTOCOL_CONNECTION_LOST'`. See the [Error Handling](#error-handling) section
for more information.
The best way to handle such unexpected disconnects is shown below:
A good way to handle such unexpected disconnects is shown below:
```js
function handleDisconnect(connection) {
connection.on('error', function(err) {
if (!err.fatal) {
return;
}
var db_config = {
host: 'localhost',
user: 'root',
password: '',
database: 'example'
};
if (err.code !== 'PROTOCOL_CONNECTION_LOST') {
throw err;
}
var connection;
console.log('Re-connecting lost connection: ' + err.stack);
function handleDisconnect() {
connection = mysql.createConnection(db_config); // Recreate the connection, since
// the old one cannot be reused.
connection = mysql.createConnection(connection.config);
handleDisconnect(connection);
connection.connect();
connection.connect(function(err) { // The server is either down
if(err) { // or restarting (takes a while sometimes).
console.log('error when connecting to db:', err);
setTimeout(handleDisconnect, 2000); // We introduce a delay before attempting to reconnect,
} // to avoid a hot loop, and to allow our node script to
}); // process asynchronous requests in the meantime.
// If you're also serving http, display a 503 error.
connection.on('error', function(err) {
console.log('db error', err);
if(err.code === 'PROTOCOL_CONNECTION_LOST') { // Connection to the MySQL server is usually
handleDisconnect(); // lost due to either server restart, or a
} else { // connnection idle timeout (the wait_timeout
throw err; // server variable configures this)
}
});
}
handleDisconnect(connection);
handleDisconnect();
```

@@ -332,3 +414,3 @@

provided data before using it inside a SQL query. You can do so using the
`connection.escape()` method:
`connection.escape()` or `pool.escape()` methods:

@@ -623,3 +705,3 @@ ```js

table2_fieldA: '...',
table2_fieldB: '...'
table2_fieldB: '...',
}, ...]

@@ -772,3 +854,3 @@ */

}):
});
```

@@ -790,8 +872,20 @@

}
})
});
```
__WARNING: YOU MUST INVOKE the parser using one of these three field functions in your custom typeCast callback. They can only be called once.( see #539 for discussion)__
If you need a buffer there's also a `.buffer()` function and also a `.geometry()` one
both used by the default type cast that you can use.
```
field.string()
field.buffer()
field.geometry()
```
are aliases for
```
parser.parseLengthCodedString()
parser.parseLengthCodedBuffer()
parser.parseGeometryValue()
```
__You can find which field function you need to use by looking at: [RowDataPacket.prototype._typeCast](https://github.com/felixge/node-mysql/blob/master/lib/protocol/packets/RowDataPacket.js#L41)__
## Connection Flags

@@ -812,3 +906,3 @@

```js
var connection = mysql.createConnection("mysql://localhost/test?flags=-FOUND_ROWS")
var connection = mysql.createConnection("mysql://localhost/test?flags=-FOUND_ROWS");
```

@@ -869,2 +963,21 @@

## Running unit tests
Set the environment variables `MYSQL_DATABASE`, `MYSQL_HOST`, `MYSQL_PORT`, `MYSQL_USER` and `MYSQL_PASSWORD`. (You may want to put these in a `config.sh` file and source it when you run the tests). Then run `make test`.
For example, if you have an installation of mysql running on localhost:3306 and no password set for the `root` user, run:
```
mysql -u root -e "CREATE DATABASE IF NOT EXISTS node_mysql_test"
MYSQL_HOST=localhost MYSQL_PORT=3306 MYSQL_DATABASE=node_mysql_test MYSQL_USER=root MYSQL_PASSWORD= make test
```
## Running unit tests on windows
* Edit the variables in the file ```make.bat``` according to your system and mysql-settings.
* Make sure the database (e.g. 'test') you want to use exists and the user you entered has the proper rights to use the test database. (E.g. do not forget to execute the SQL-command ```FLUSH PRIVILEGES``` after you have created the user.)
* In a DOS-box (or CMD-shell) in the folder of your application run ```npm install mysql --dev``` or in the mysql folder (```node_modules\mysql```), run ```npm install --dev```. (This will install additional developer-dependencies for node-mysql.)
* Run ```npm test mysql``` in your applications folder or ```npm test``` in the mysql subfolder.
* If you want to log the output into a file use ```npm test mysql > test.log``` or ```npm test > test.log```.
## Todo

@@ -871,0 +984,0 @@

@@ -34,6 +34,12 @@ var common = exports;

config = mergeTestConfig(config);
config.createConnection = common.createConnection;
config.connectionConfig = mergeTestConfig(config.connectionConfig);
return Mysql.createPool(config);
};
common.createPoolCluster = function(config) {
config = mergeTestConfig(config);
config.createConnection = common.createConnection;
return Mysql.createPoolCluster(config);
};
common.createFakeServer = function(options) {

@@ -49,4 +55,8 @@ return new FakeServer(_.extend({}, options));

connection.query('USE ' + common.testDatabase);
}
};
common.getTestConfig = function(config) {
return mergeTestConfig(config);
};
function mergeTestConfig(config) {

@@ -53,0 +63,0 @@ if (common.isTravis()) {

@@ -0,1 +1,10 @@

/**
* This test is skipped, if the environment variable "windir" is set.
* It assumes that it runs on a windows system then.
*/
if (process.env.windir) {
return console.log('Skipping "test-unix-domain-socket.js" - Environment'
+ ' variable "windir" is set. Skipping this, because we seem to be on'
+ ' a windows system');
}
var common = require('../../common');

@@ -7,2 +16,3 @@ var connection = common.createConnection({socketPath: common.fakeServerSocket});

var didConnect = false;
server.listen(common.fakeServerSocket, function(err) {

@@ -31,4 +41,4 @@ if (err) throw err;

process.on('exit', function() {
assert.equal(didConnect, true);
assert.equal(hadConnection, true);
assert.equal(didConnect, true);
assert.equal(hadConnection, true);
});

@@ -24,4 +24,4 @@ var common = require('../../common');

shouldGetConnection = true;
connection.end();
connection.release();
});
});

@@ -12,7 +12,5 @@ var common = require('../../common');

assert.ok(pool._allConnections.length == 0);
assert.ok(connection._poolRemoved);
assert.strictEqual(connection.end, Connection.prototype.end);
assert.strictEqual(connection.destroy, Connection.prototype.destroy);
assert.ok(!connection._pool);
pool.end();
});

@@ -88,5 +88,5 @@ var common = require('../../common');

'dates are converted to YYYY-MM-DD HH:II:SS': function() {
var expected = '2012-05-07 11:42:03';
var date = new Date(Date.UTC(2012, 4, 7, 11, 42, 3));
'dates are converted to YYYY-MM-DD HH:II:SS.sss': function() {
var expected = '2012-05-07 11:42:03.002';
var date = new Date(2012, 4, 7, 11, 42, 3, 2);
var string = SqlString.escape(date);

@@ -133,2 +133,15 @@

},
'objects is converted to values': function () {
var sql = SqlString.format('?', { 'hello': 'world' }, false)
assert.equal(sql, "`hello` = 'world'")
},
'objects is not converted to values': function () {
var sql = SqlString.format('?', { 'hello': 'world' }, true)
assert.equal(sql, "'[object Object]'")
var sql = SqlString.format('?', { toString: function () { return 'hello' } }, true)
assert.equal(sql, "'hello'")
}
});

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