Comparing version 0.1.0 to 0.2.0
13
api.js
// The changes_couchdb API | ||
// | ||
// Copyright 2011 Iris Couch | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
@@ -4,0 +17,0 @@ var feed = require('./feed'); |
107
cli.js
#!/usr/bin/env node | ||
// The changes_couchdb command-line interface. | ||
// The follow command-line interface. | ||
// | ||
// Copyright 2011 Iris Couch | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
@@ -9,46 +22,70 @@ var lib = require('./lib') | ||
function usage() { | ||
console.log([ 'usage: changes_couchdb <URL>' | ||
, '' | ||
].join("\n")); | ||
function puts(str) { | ||
process.stdout.write(str + "\n"); | ||
} | ||
var db = process.argv[2]; | ||
if(! /^https?:\/\//.test(db)) | ||
db = 'http://' + db; | ||
function main() { | ||
var db = require.isBrowser ? (process.env.db || '/_users') : process.argv[2]; | ||
puts('Watching: ' + db); | ||
console.log('Watching:', db); | ||
var feed = new couch_changes.Feed(); | ||
feed.db = db; | ||
feed.since = (process.env.since === 'now') ? 'now' : parseInt(process.env.since || '0'); | ||
var feed = new couch_changes.Feed(); | ||
feed.db = db; | ||
feed.since = (process.env.since === 'now') ? 'now' : parseInt(process.env.since || '0'); | ||
feed.heartbeat = parseInt(process.env.heartbeat || '3000'); | ||
feed.heartbeat = (process.env.heartbeat || '3000').replace(/s$/, '000'); | ||
feed.heartbeat = parseInt(feed.heartbeat); | ||
if(process.env.host) | ||
feed.headers.host = process.env.host; | ||
if(require.isBrowser) | ||
feed.feed = 'longpoll'; | ||
if(process.env.host) | ||
feed.headers.host = process.env.host; | ||
if(process.env.inactivity) | ||
feed.inactivity_ms = parseInt(process.env.inactivity); | ||
if(process.env.filter) | ||
feed.filter = process.env.filter; | ||
if(process.env.limit) | ||
feed.limit = parseInt(process.env.limit); | ||
if(process.env.inactivity) | ||
feed.inactivity_ms = parseInt(process.env.inactivity); | ||
feed.on('confirm', function() { | ||
puts('Database confirmed: ' + db); | ||
}) | ||
feed.filter = function(doc, req) { | ||
// This is a local filter. It runs on the client side. | ||
return true; | ||
} | ||
feed.on('change', function(change) { | ||
puts('Change:' + JSON.stringify(change)); | ||
}) | ||
feed.on('change', function(change) { | ||
console.log('Change:' + JSON.stringify(change)); | ||
}) | ||
feed.on('timeout', function(state) { | ||
var seconds = state.elapsed_ms / 1000; | ||
var hb = state.heartbeat / 1000; | ||
puts('Timeout after ' + seconds + 's inactive, heartbeat=' + hb + 's'); | ||
}) | ||
feed.on('error', function(er) { | ||
//console.error(er); | ||
console.error('Changes error ============\n' + er.stack); | ||
setTimeout(function() { process.exit(0) }, 100); | ||
}) | ||
feed.on('retry', function(state) { | ||
if(require.isBrowser) | ||
puts('Long polling since ' + state.since); | ||
else | ||
puts('Retry since ' + state.since + ' after ' + state.after + 'ms'); | ||
}) | ||
process.on('uncaughtException', function(er) { | ||
console.log('========= UNCAUGHT EXCEPTION; This is bad'); | ||
console.log(er.stack); | ||
setTimeout(function() { process.exit(1) }, 100); | ||
}) | ||
feed.on('response', function() { | ||
puts('Streaming response:'); | ||
}) | ||
feed.follow(); | ||
feed.on('error', function(er) { | ||
//console.error(er); | ||
console.error('Changes error ============\n' + er.stack); | ||
setTimeout(function() { process.exit(0) }, 100); | ||
}) | ||
process.on('uncaughtException', function(er) { | ||
puts('========= UNCAUGHT EXCEPTION; This is bad'); | ||
puts(er.stack); | ||
setTimeout(function() { process.exit(1) }, 100); | ||
}) | ||
feed.follow(); | ||
} | ||
exports.main = main; | ||
if(!require.isBrowser && process.argv[1] == module.filename) | ||
main(); |
154
feed.js
// Core routines for event emitters | ||
// | ||
// Copyright 2011 Iris Couch | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
var lib = require('./lib') | ||
, url = require('url') | ||
, util = require('util') | ||
, events = require('events') | ||
, request = require('request') | ||
@@ -18,8 +31,7 @@ , querystring = require('querystring') | ||
var SUPER_CLASS = require('events').EventEmitter; | ||
//var SUPER_CLASS = require('stream').Stream; | ||
var EventEmitter = events.EventEmitter2 || events.EventEmitter; | ||
function Feed (opts) { | ||
var self = this; | ||
SUPER_CLASS.call(self); | ||
EventEmitter.call(self); | ||
@@ -63,3 +75,3 @@ self.feed = 'continuous'; | ||
} // Feed | ||
util.inherits(Feed, SUPER_CLASS); | ||
util.inherits(Feed, EventEmitter); | ||
@@ -73,4 +85,4 @@ Feed.prototype.start = | ||
if(self.feed !== 'continuous') | ||
throw new Error('The only valid feed option is "continuous"'); | ||
if(self.feed !== 'continuous' && self.feed !== 'longpoll') | ||
throw new Error('The only valid feed options are "continuous" and "longpoll"'); | ||
@@ -80,6 +92,6 @@ if(typeof self.heartbeat !== 'number') | ||
var parsed = url.parse(self.db); | ||
self.log = lib.log4js().getLogger(parsed.hostname + parsed.pathname); | ||
self.log.setLevel(process.env.changes_level || "info"); | ||
self.log = lib.log4js().getLogger(self.db); | ||
self.log.setLevel(process.env.log || "info"); | ||
self.emit('start'); | ||
return self.confirm(); | ||
@@ -94,3 +106,2 @@ } | ||
self.log.debug('Checking database: ' + self.db_safe); | ||
self.emit('confirm'); | ||
@@ -128,2 +139,3 @@ var confirm_timeout = self.heartbeat * 3; // Give it time to look up the name, connect, etc. | ||
self.emit('confirm'); | ||
return self.query(); | ||
@@ -145,2 +157,12 @@ }) | ||
if(typeof self.filter === 'function' && !query_params.include_docs) { | ||
self.log.debug('Enabling include_docs for client-side filter'); | ||
query_params.include_docs = true; | ||
} | ||
// Limit the response size for longpoll. | ||
var poll_size = 100; | ||
if(query_params.feed == 'longpoll' && (!query_params.limit || query_params.limit > poll_size)) | ||
query_params.limit = poll_size; | ||
var feed_url = self.db + '/_changes?' + querystring.stringify(query_params); | ||
@@ -173,3 +195,3 @@ | ||
function on_response(er, resp) { | ||
function on_response(er, resp, body) { | ||
clearTimeout(timeout_id); | ||
@@ -196,3 +218,5 @@ | ||
self.retry_delay = INITIAL_RETRY_DELAY; | ||
return self.prep(in_flight); | ||
self.emit('response'); | ||
return self.prep(in_flight, body); | ||
} | ||
@@ -212,3 +236,3 @@ | ||
Feed.prototype.prep = function prep_request(req) { | ||
Feed.prototype.prep = function prep_request(req, body) { | ||
var self = this; | ||
@@ -222,2 +246,36 @@ | ||
// The inactivity timer is for time between *changes*, or time between the | ||
// initial connection and the first change. Therefore it goes here. | ||
self.change_at = now; | ||
if(self.inactivity_ms) { | ||
clearTimeout(self.inactivity_timer); | ||
self.inactivity_timer = setTimeout(function() { self.on_inactivity() }, self.inactivity_ms); | ||
} | ||
var a, change; | ||
if(body) { | ||
// Some changes are already in the body. | ||
try { | ||
body = JSON.parse(body); | ||
} catch(er) { | ||
return self.die(er); | ||
} | ||
body.results = body.results || []; | ||
for(a = 0; a < body.results.length; a++) { | ||
change = body.results[a]; | ||
if(!change.seq) | ||
return self.die(new Error('Change has no .seq field: ' + json)); | ||
self.on_change(change); | ||
} | ||
return self.retry(); | ||
} | ||
var handlers = ['data', 'end', 'error']; | ||
handlers.forEach(function(ev) { | ||
req.on(ev, handler_for(ev)); | ||
}) | ||
return self.wait(); | ||
function handler_for(ev) { | ||
@@ -227,2 +285,3 @@ var name = 'on_couch_' + ev; | ||
return handle_confirmed_req_event; | ||
function handle_confirmed_req_event() { | ||
@@ -254,18 +313,3 @@ if(self.pending.request === req) | ||
} | ||
return handle_confirmed_req_event; | ||
} | ||
var handlers = ['data', 'end', 'error']; | ||
handlers.forEach(function(ev) { | ||
req.on(ev, handler_for(ev)); | ||
}) | ||
// The inactivity timer is for time between *changes*, or time between the | ||
// initial connection and the first change. Therefore it goes here. | ||
self.change_at = now; | ||
if(self.inactivity_ms) | ||
self.inactivity_timer = setTimeout(function() { self.on_inactivity() }, self.inactivity_ms); | ||
return self.wait(); | ||
} | ||
@@ -339,6 +383,14 @@ | ||
var elapsed_ms = now - self.pending.activity_at; | ||
self.log.warn('Closing req ' + self.pending.request.id() + ' for timeout after ' + elapsed_ms + 'ms; heartbeat=' + self.heartbeat); | ||
self.emit('timeout', {elapsed_ms:elapsed_ms, heartbeat:self.heartbeat, id:self.pending.request.id}); | ||
/* | ||
var msg = ' for timeout after ' + elapsed_ms + 'ms; heartbeat=' + self.heartbeat; | ||
if(!self.pending.request.id) | ||
self.log.warn('Closing req (no id) ' + msg + ' req=' + util.inspect(self.pending.request)); | ||
else | ||
self.log.warn('Closing req ' + self.pending.request.id() + msg); | ||
*/ | ||
return destroy_req(self.pending.request); | ||
//return self.retry(); | ||
} | ||
@@ -352,4 +404,4 @@ | ||
self.log.info('Retrying since=' + self.since + ' after ' + self.retry_delay + 'ms: ' + self.db_safe); | ||
self.emit('retry'); | ||
self.log.debug('Retrying since=' + self.since + ' after ' + self.retry_delay + 'ms: ' + self.db_safe); | ||
self.emit('retry', {since:self.since, after:self.retry_delay, db:self.db_safe}); | ||
@@ -379,8 +431,8 @@ setTimeout(function() { self.query() }, self.retry_delay); | ||
Feed.prototype.stop = | ||
Feed.prototype.die = function(er) { | ||
var self = this; | ||
self.log.fatal('Fatal error: ' + er.stack); | ||
self.emit('error', er); | ||
if(er) | ||
self.log.fatal('Fatal error: ' + er.stack); | ||
@@ -394,3 +446,5 @@ var req = self.pending.request; | ||
//throw er; | ||
self.emit('stop', er); | ||
if(er) | ||
self.emit('error', er); | ||
} | ||
@@ -412,5 +466,16 @@ | ||
var req = { 'query': lib.JDUP(self.pending.request.changes_query) }; | ||
var f_change = lib.JDUP(change); // Don't let the filter mutate the real data. | ||
var result = self.filter.apply(null, [f_change, req]); | ||
if(!change.doc) | ||
return self.die(new Error('Internal filter needs .doc in change ' + change.seq)); | ||
// Don't let the filter mutate the real data. | ||
var doc = lib.JDUP(change.doc); | ||
var req = lib.JDUP({'query': self.pending.request.changes_query}); | ||
var result = false; | ||
try { | ||
result = self.filter.apply(null, [doc, req]); | ||
} catch (er) { | ||
self.log.debug('Filter error', er); | ||
} | ||
result = (result && true) || false; | ||
@@ -466,4 +531,9 @@ if(result) { | ||
response.connection.end(); | ||
response.connection.destroy(); | ||
if(typeof response.abort === 'function') | ||
response.abort(); | ||
if(response.connection) { | ||
response.connection.end(); | ||
response.connection.destroy(); | ||
} | ||
} |
25
lib.js
@@ -0,1 +1,15 @@ | ||
// Copyright 2011 Iris Couch | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
exports.scrub_creds = function scrub_creds(url) { | ||
@@ -10,3 +24,7 @@ return url.replace(/^(https?:\/\/)[^:]+:[^@]+@(.*)$/, '$1$2'); // Scrub username and password | ||
// Wrap log4js so it will not be a dependency. | ||
var VERBOSE = (process.env.verbose === 'true'); | ||
var VERBOSE; | ||
if(require.isBrowser) | ||
VERBOSE = true; | ||
else | ||
verbose = (process.env.verbose === 'true'); | ||
@@ -27,2 +45,6 @@ var noop = function() {}; | ||
} catch(e) { | ||
exports.log4js = null; | ||
} | ||
if(typeof exports.log4js !== 'function') | ||
exports.log4js = function() { | ||
@@ -32,2 +54,1 @@ return { 'getLogger': function() { return noops } | ||
} | ||
} |
{ "name": "follow" | ||
, "version": "0.1.0" | ||
, "version": "0.2.0" | ||
, "author": { "name": "Jason Smith" | ||
@@ -13,2 +13,3 @@ , "email": "jhs@iriscouch.com" } | ||
, "main": "api" | ||
, "bin": {"follow": "./cli.js"} | ||
} |
@@ -32,4 +32,4 @@ # Follow: CouchDB changes notifier for NodeJS | ||
var follow_couchdb = require('follow_couchdb'); | ||
follow_couchdb("https://example.iriscouch.com/boogie", function(error, change) { | ||
var follow = require('follow'); | ||
follow("https://example.iriscouch.com/boogie", function(error, change) { | ||
if(!error) { | ||
@@ -44,3 +44,3 @@ console.log("Got change number " + change.seq + ": " + change.id); | ||
follow_couchdb({db:"https://example.iriscouch.com/boogie", include_docs:true}, function(error, change) { | ||
follow({db:"https://example.iriscouch.com/boogie", include_docs:true}, function(error, change) { | ||
if(!error) { | ||
@@ -51,5 +51,5 @@ console.log("Change " + change.seq + " has " + Object.keys(change.doc).length + " fields"); | ||
### follow_couchdb(options, callback) | ||
### follow(options, callback) | ||
The first argument is an options object. The only required option is `db`. Instead of an object, you can use a string to indicate the ``db` value. | ||
The first argument is an options object. The only required option is `db`. Instead of an object, you can use a string to indicate the `db` value. | ||
@@ -75,6 +75,6 @@ All of the CouchDB _changes options are allowed. See http://guide.couchdb.org/draft/notifications.html. | ||
var follow_couchdb = require('follow_couchdb'); | ||
var follow = require('follow'); | ||
var opts = {}; // Same options paramters as before | ||
var feed = new follow_couchdb.Feed(opts); | ||
var feed = new follow.Feed(opts); | ||
@@ -81,0 +81,0 @@ // You can also set values directly. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
No License Found
License(Experimental) License information could not be found.
Found 1 instance in 1 package
168759
19
0
1651
2
17
2