Guest User

Untitled

a guest
Jan 23rd, 2018
84
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 22.20 KB | None | 0 0
  1. var path = require('path');
  2.  
  3. require.paths.unshift(path.join(__dirname, 'cradle'));
  4.  
  5. var sys = require("sys"),
  6. http = require("http"),
  7. https = require("https"),
  8. events = require('events'),
  9. fs = require("fs"),
  10. url = require('url'),
  11. buffer = require('buffer');
  12.  
  13. var querystring = require('querystring');
  14. var Args = require('vargs').Constructor;
  15.  
  16. var cradle = exports;
  17.  
  18. cradle.extend = require('response').extend;
  19. cradle.Response = require('response').Response;
  20. cradle.Cache = require('cache').Cache;
  21.  
  22. cradle.host = '127.0.0.1';
  23. cradle.port = 5984;
  24. cradle.auth = null;
  25. cradle.options = {
  26. cache: true,
  27. raw: false,
  28. timeout: 0,
  29. secure: false,
  30. headers: {}
  31. };
  32.  
  33. cradle.setup = function (settings) {
  34. this.host = settings.host;
  35. this.auth = settings.auth;
  36. this.port = parseInt(settings.port);
  37. cradle.merge(this.options, settings);
  38.  
  39. return this;
  40. };
  41.  
  42. var protocolPattern = /^(https?):\/\//;
  43.  
  44. cradle.Connection = function Connection(/* variable args */) {
  45. var args = Array.prototype.slice.call(arguments),
  46. host, port, remote, auth, options = {};
  47.  
  48. args.forEach(function (a) {
  49. if (typeof(a) === 'number' || (typeof(a) === 'string' && /^\d{2,5}$/.test(a))) {
  50. port = parseInt(a);
  51. } else if (typeof(a) === 'object') {
  52. options = a;
  53. host = host || options.host;
  54. port = port || options.port;
  55. auth = options.auth;
  56. } else {
  57. host = a;
  58. }
  59. });
  60.  
  61. this.host = host || cradle.host;
  62. this.port = port || cradle.port;
  63. this.auth = auth || cradle.auth;
  64. this.options = cradle.merge({}, cradle.options, options);
  65.  
  66. this.options.secure = this.options.secure || this.options.ssl;
  67.  
  68. if (protocolPattern.test(this.host)) {
  69. this.protocol = this.host.match(protocolPattern)[1];
  70. this.host = this.host.replace(protocolPattern, '');
  71. }
  72.  
  73. if (this.protocol === 'https') this.options.secure = true;
  74.  
  75. if (this.auth && this.auth.user) { // Deprecation warning
  76. console.log('Warning: "user" & "pass" parameters ignored. Use "username" & "password"');
  77. }
  78. if (this.options.ssl) { // Deprecation warning
  79. console.log('Warning: "ssl" option is deprecated. Use "secure" instead.');
  80. }
  81.  
  82. this.socket = (this.options.secure) ? https : http;
  83. };
  84.  
  85. //
  86. // Connection.rawRequest()
  87. //
  88. // This is a base wrapper around connections to CouchDB. Given that it handles
  89. // *all* requests, including those for attachments, it knows nothing about
  90. // JSON serialization and does not presuppose it is sending or receiving JSON
  91. // content
  92. //
  93. cradle.Connection.prototype.rawRequest = function (method, path, options, data, headers, callback) {
  94. var promise = new(events.EventEmitter), request, that = this;
  95.  
  96. // HTTP Headers
  97. headers = headers || {};
  98.  
  99. // Set HTTP Basic Auth
  100. if (this.auth) {
  101. headers['Authorization'] = "Basic " + new Buffer(this.auth.username + ':' + this.auth.password).toString('base64');
  102. }
  103.  
  104. // Set client-wide headers
  105. for (var h in this.options.headers) {
  106. headers[h] = this.options.headers[h];
  107. }
  108.  
  109. path = (path || '/').replace(/https?:\/\//, '').replace(/\/{2,}/g, '/');
  110. if (path.charAt(0) !== '/') { path = '/' + path }
  111.  
  112. if (options) {
  113. for (var k in options) {
  114. if (typeof(options[k]) === 'boolean') {
  115. options[k] = String(options[k]);
  116. }
  117. }
  118. path += '?' + querystring.stringify(options);
  119. }
  120.  
  121. request = this.socket.request({
  122. host: this.host,
  123. port: this.port,
  124. method: method.toUpperCase(),
  125. path: path,
  126. headers: headers
  127. });
  128.  
  129. request.on('error', function (err) {
  130. return callback(err, null);
  131. });
  132.  
  133. if (data && data.on) { headers['Transfer-Encoding'] = 'chunked' }
  134.  
  135. headers['Connection'] = 'keep-alive';
  136.  
  137. request.on('response', function (res) {
  138. promise.emit('response', res);
  139. res.on('data', function (chunk) { promise.emit('data', chunk) });
  140. res.on('end', function () { promise.emit('end') });
  141. });
  142.  
  143.  
  144. if (data) {
  145. if (data.on) {
  146. data.on('data', function (chunk) { request.write(chunk) });
  147. data.on('end', function () { request.end() });
  148. } else {
  149. request.write(data, 'utf8');
  150. request.end();
  151. }
  152. } else {
  153. request.end();
  154. }
  155.  
  156. return promise;
  157. }
  158. //
  159. // Connection.request()
  160. //
  161. // This is the entry point for all requests to CouchDB, at this point,
  162. // the database name has been embed in the url, by one of the wrappers.
  163. //
  164. cradle.Connection.prototype.request = function (method, path, /* [options], [data], [headers] */ callback) {
  165. var request, that = this, args = Array.prototype.slice.call(arguments, 2);
  166.  
  167. if (typeof(callback = args.pop()) !== 'function') {
  168. args.push(callback);
  169. callback = function () {};
  170. }
  171.  
  172. var options = args.shift() || {},
  173. data = args.shift() || null,
  174. headers = cradle.merge({ host: this.host }, args.shift() || {});
  175.  
  176. //
  177. // Handle POST/PUT data. We also convert functions to strings,
  178. // so they can be used in _design documents.
  179. //
  180. if (data) {
  181. data = JSON.stringify(data, function (k, val) {
  182. if (typeof(val) === 'function') {
  183. return val.toString();
  184. } else { return val }
  185. });
  186. headers["Content-Length"] = Buffer.byteLength(data);
  187. headers["Content-Type"] = "application/json";
  188. }
  189.  
  190. request = this.rawRequest(method, path, options, data, headers, callback /* for Error event */);
  191.  
  192. //
  193. // Initialize the request, send the body, and finally,
  194. // dispatch the request.
  195. //
  196. request.on('response', function (res) {
  197. var body = [];
  198.  
  199. res.setEncoding('utf8');
  200. res.on('data', function (chunk) {
  201. chunk && body.push(chunk);
  202. }).on('end', function () {
  203. var json, response;
  204.  
  205. if (method === 'HEAD') {
  206. callback(null, res.headers, res.statusCode);
  207. } else {
  208. try { json = JSON.parse(body.join('')) }
  209. catch (e) { return callback(e) }
  210.  
  211.  
  212. if (json.error) {
  213. cradle.extend(json, { headers: res.headers });
  214. json.headers.status = res.statusCode;
  215. callback(json);
  216. } else {
  217. // If the `raw` option was set, we return the parsed
  218. // body as-is. If not, we wrap it in a `Response` object.
  219. callback(null, that.options.raw ? json : new(cradle.Response)(json, res));
  220. }
  221. }
  222. });
  223. });
  224. };
  225.  
  226. //
  227. // The database object
  228. //
  229. // We return an object with database functions,
  230. // closing around the `name` argument.
  231. //
  232. cradle.Connection.prototype.database = function (name) {
  233. var that = this, connection = this;
  234.  
  235. return {
  236. name: name,
  237. //
  238. // The database document cache.
  239. //
  240. cache: new(cradle.Cache)(that.options),
  241.  
  242. // A wrapper around `Connection.request`,
  243. // which prepends the database name.
  244. query: function (method, path /* [options], [data], [headers], [callback] */) {
  245. var args = Array.prototype.slice.call(arguments, 2);
  246. that.request.apply(that, [method, [name, path].join('/')].concat(args));
  247. },
  248. exists: function (callback) {
  249. this.query('HEAD', '/', function (err, res, status) {
  250. if (err) {
  251. callback(err);
  252. } else {
  253. if (status === 404) {
  254. callback(null, false);
  255. } else {
  256. callback(null, true);
  257. }
  258. }
  259. });
  260. },
  261.  
  262. // Fetch either a single document from the database, or cache,
  263. // or multiple documents from the database.
  264. // If it's a single doc from the db, attempt to save it to the cache.
  265. get: function (id, rev) {
  266. var that = this, options = null,
  267. args = new(Args)(arguments);
  268.  
  269. if (Array.isArray(id)) { // Bulk GET
  270. this.query('POST', '/_all_docs', { include_docs: true }, { keys: id },
  271. function (err, res) { args.callback(err, res) });
  272. } else {
  273. if (rev && args.length === 2) {
  274. if (typeof(rev) === 'string') { options = { rev: rev } }
  275. else if (typeof(rev) === 'object') { options = rev }
  276. } else if (this.cache.has(id)) {
  277. return args.callback(null, this.cache.get(id));
  278. }
  279. this.query('GET', id, options, function (err, res) {
  280. if (! err) that.cache.save(res.id, res.json);
  281. args.callback(err, res);
  282. });
  283. }
  284. },
  285.  
  286. save: function (/* [id], [rev], doc | [doc, ...] */) {
  287. var args = new(Args)(arguments),
  288. array = args.all.slice(0), doc, id, rev;
  289.  
  290. if (Array.isArray(args.first)) {
  291. doc = args.first;
  292. } else {
  293. doc = array.pop(),
  294. id = array.shift(),
  295. rev = array.shift();
  296. }
  297. this._save(id, rev, doc, args.callback);
  298. },
  299. _save: function (id, rev, doc, callback) {
  300. var options = connection.options;
  301. var document = {}, that = this;
  302.  
  303. // Bulk Insert
  304. if (Array.isArray(doc)) {
  305. document.docs = doc;
  306. if (options.allOrNothing) { document.all_or_nothing = true }
  307. this.query('POST', '/_bulk_docs', {}, document, callback);
  308. } else {
  309. // PUT a single document, with an id (Create or Update)
  310. if (id) {
  311. // Design document
  312. if (/^_design\/(\w|%)+$/.test(id) && !('views' in doc)) {
  313. document.language = "javascript";
  314. document.views = doc;
  315. } else {
  316. document = doc;
  317. }
  318. // Try to set the '_rev' attribute of the document.
  319. // If it wasn't passed, attempt to retrieve it from the cache.
  320. rev && (document._rev = rev);
  321.  
  322. if (document._rev) {
  323. this.put(id, document, callback);
  324. } else if (this.cache.has(id)) {
  325. document._rev = this.cache.get(id)._rev;
  326. this.put(id, document, callback);
  327. } else {
  328. // Attempt to create a new document. If it fails,
  329. // because an existing document with that _id exists (409),
  330. // perform a HEAD, to get the _rev, and try to re-save.
  331. this.put(id, document, function (e, res) {
  332. if (e && e.headers.status === 409) { // Conflict
  333. that.head(id, function (e, headers) {
  334. document._rev = headers['etag'].slice(1, -1);
  335. that.put(id, document, callback);
  336. });
  337. } else { callback(e, res) }
  338. });
  339. }
  340. // POST a single document, without an id (Create)
  341. } else {
  342. this.post(doc, callback);
  343. }
  344. }
  345. },
  346.  
  347. merge: function (/* [id], doc */) {
  348. var args = Array.prototype.slice.call(arguments),
  349. callback = args.pop(),
  350. doc = args.pop(),
  351. id = args.pop() || doc._id;
  352.  
  353. this._merge(id, doc, callback);
  354. },
  355. _merge: function (id, doc, callback) {
  356. var that = this;
  357. this.get(id, function (e, res) {
  358. if (e) { return callback(e) }
  359. doc = cradle.merge({}, res.json || res, doc);
  360. that.save(id, res._rev, doc, callback);
  361. });
  362. },
  363.  
  364. //
  365. // PUT a document, and write through cache
  366. //
  367. put: function (id, doc, callback) {
  368. var cache = this.cache;
  369. if (typeof(id) !== 'string') { throw new(TypeError)("id must be a string") }
  370. this.query('PUT', id, null, doc, function (e, res) {
  371. if (! e) { cache.save(id, cradle.merge({}, doc, { _rev: res.rev })) }
  372. callback && callback(e, res);
  373. });
  374. },
  375.  
  376. //
  377. // POST a document, and write through cache
  378. //
  379. post: function (doc, callback) {
  380. var cache = this.cache;
  381. this.query('POST', '/', null, doc, function (e, res) {
  382. if (! e) { cache.save(res.id, cradle.merge({}, doc, { _rev: res.rev })) }
  383. callback && callback(e, res);
  384. });
  385. },
  386.  
  387. //
  388. // Perform a HEAD request
  389. //
  390. head: function (id, callback) {
  391. this.query('HEAD', id, null, callback);
  392. },
  393.  
  394. insert: function () {
  395. throw new(Error)("`insert` is deprecated, use `save` instead");
  396. },
  397.  
  398. replicate: function (target, options, callback) {
  399. if (typeof(options) === 'function') { callback = options, options = {} }
  400. that.replicate(cradle.merge({ source: name, target: target }, options), callback);
  401. },
  402.  
  403. // Destroys a database with 'DELETE'
  404. // we raise an exception if arguments were supplied,
  405. // as we don't want users to confuse this function with `remove`.
  406. destroy: function (callback) {
  407. if (arguments.length > 1) {
  408. throw new(Error)("destroy() doesn't take any additional arguments");
  409. } else {
  410. this.query('DELETE', '/', callback);
  411. }
  412. },
  413.  
  414. // Delete a document
  415. // if the _rev wasn't supplied, we attempt to retrieve it from the
  416. // cache. If the deletion was successful, we purge the cache.
  417. remove: function (id, rev) {
  418. var that = this, doc, args = new(Args)(arguments);
  419.  
  420. if (typeof(rev) !== 'string') {
  421. if (doc = this.cache.get(id)) { rev = doc._rev }
  422. else { throw new(Error)("rev needs to be supplied") }
  423. }
  424. this.query('DELETE', id, {rev: rev}, function (err, res) {
  425. if (! err) { that.cache.purge(id) }
  426. args.callback(err, res);
  427. });
  428. },
  429. create: function (callback) {
  430. this.query('PUT', '/', callback);
  431. },
  432. info: function (callback) {
  433. this.query('GET', '/', callback);
  434. },
  435. all: function (options, callback) {
  436. if (arguments.length === 1) { callback = options, options = {} }
  437. this.query('GET', '/_all_docs', options, callback);
  438. },
  439. compact: function (design) {
  440. var headers = {};
  441. headers['Content-Type'] = "application/json";
  442. this.query('POST', '/_compact' + (typeof(design) === 'string' ? '/' + design : ''),
  443. {}, {}, headers, Args.last(arguments));
  444. },
  445. viewCleanup: function (callback) {
  446. this.query('POST', '/_view_cleanup', callback);
  447. },
  448. allBySeq: function (options) {
  449. options = typeof(options) === 'object' ? options : {};
  450. this.query('GET', '/_all_docs_by_seq', options, Args.last(arguments));
  451. },
  452.  
  453. // Query a view, passing any options to the query string.
  454. // Some query string parameters' values have to be JSON-encoded.
  455. view: function (path, options) {
  456. var args = new(Args)(arguments);
  457.  
  458. path = path.split('/');
  459. path = ['_design', path[0], '_view', path[1]].join('/');
  460.  
  461. if (typeof(options) === 'object') {
  462. ['key', 'startkey', 'endkey'].forEach(function (k) {
  463. if (k in options) { options[k] = JSON.stringify(options[k]) }
  464. });
  465. }
  466.  
  467. if (options && options.keys) {
  468. this.query('POST', path, {}, options, args.callback);
  469. } else {
  470. this.query('GET', path, options, args.callback);
  471. }
  472. },
  473.  
  474. // Query a list, passing any options to the query string.
  475. // Some query string parameters' values have to be JSON-encoded.
  476. list: function (path, options) {
  477. var args = new(Args)(arguments);
  478. path = path.split('/');
  479.  
  480. if (typeof(options) === 'object') {
  481. ['key', 'startkey', 'endkey'].forEach(function (k) {
  482. if (k in options) { options[k] = JSON.stringify(options[k]) }
  483. });
  484. }
  485. this.query('GET', ['_design', path[0], '_list', path[1], path[2]].join('/'), options, args.callback);
  486. },
  487.  
  488. update: function(path, id, options) {
  489. var args = new(Args)(arguments);
  490. path = path.split('/');
  491.  
  492. if (id) {
  493. this.query('PUT', ['_design', path[0], '_update', path[1], id].join('/'), options, args.callback);
  494. } else {
  495. this.query('POST', ['_design', path[0], '_update', path[1]].join('/'), options, args.callback);
  496. }
  497. },
  498.  
  499. push: function (doc) {},
  500.  
  501. changes: function (options, callback) {
  502. var promise = new(events.EventEmitter);
  503.  
  504. if (typeof(options) === 'function') { callback = options, options = {}; }
  505.  
  506. if (callback) {
  507. this.query('GET', '_changes', options, callback);
  508. } else {
  509. options = options || {};
  510. options.feed = options.feed || 'continuous';
  511. options.heartbeat = options.heartbeat || 1000;
  512.  
  513. that.rawRequest('GET', [name, '_changes'].join('/'), options).on('response', function (res) {
  514. var response = new(events.EventEmitter), buffer = [];
  515. res.setEncoding('utf8');
  516.  
  517. response.statusCode = res.statusCode;
  518. response.headers = res.headers;
  519.  
  520. promise.emit('response', response);
  521.  
  522. res.on('data', function (chunk) {
  523. if (chunk.trim()) {
  524. buffer.push(chunk);
  525.  
  526. if (chunk.indexOf('\n') !== -1) {
  527. buffer.length && response.emit('data', JSON.parse(buffer.join('')));
  528. buffer = [];
  529. }
  530. }
  531. }).on('end', function () {
  532. response.emit('end');
  533. });
  534. });
  535. return promise;
  536. }
  537. },
  538.  
  539. saveAttachment: function (/* id, [rev], attachmentName, contentType, dataOrStream */) {
  540. var doc, pathname, headers = {}, response, body = [], resHeaders, error, db = this;
  541.  
  542. var args = new(Args)(arguments), params = args.all;
  543.  
  544. if (typeof(args.first) === 'object') { throw new(TypeError)("first argument must be a document id") }
  545.  
  546. var id = params.shift(),
  547. dataOrStream = params.pop(),
  548. contentType = params.pop(),
  549. attachmentName = params.pop(),
  550. rev = params.pop();
  551.  
  552. if (!rev && db.cache.has(id)) {
  553. doc = { rev: db.cache.get(id)._rev };
  554. } else if (rev) {
  555. doc = { rev: rev };
  556. } else {
  557. doc = {};
  558. }
  559.  
  560. pathname = '/' + [name, id, attachmentName].join('/');
  561. headers['Content-Type'] = contentType;
  562.  
  563. that.rawRequest('PUT', pathname, doc, dataOrStream, headers)
  564. .on('response', function (res) { resHeaders = { status: res.statusCode } })
  565. .on('data', function (chunk) { body.push(chunk) })
  566. .on('end', function () {
  567. response = JSON.parse(body.join(''));
  568. response.headers = resHeaders;
  569.  
  570. if (response.headers.status == 201) {
  571. if (db.cache.has(id)) {
  572. cached = db.cache.store[id].document;
  573. cached._rev = response.rev;
  574. cached._attachments = cached._attachments || {};
  575. cached._attachments[attachmentName] = { stub: true };
  576. }
  577. args.callback(null, response);
  578. } else {
  579. args.callback(response);
  580. }
  581. });
  582. },
  583.  
  584. getAttachment: function(docId, attachmentName) {
  585. var pathname, req;
  586. pathname = '/' + [name, docId, attachmentName].join('/');
  587. return that.rawRequest('GET', pathname);
  588. },
  589.  
  590. temporaryView: function (doc, callback) {
  591. this.query('POST', '_temp_view', null, doc, callback);
  592. }
  593. }
  594.  
  595. };
  596.  
  597. //
  598. // Wrapper functions for the server API
  599. //
  600. cradle.Connection.prototype.databases = function (c) {
  601. this.request('GET', '/_all_dbs', c);
  602. };
  603. cradle.Connection.prototype.config = function (c) {
  604. this.request('GET', '/_config', c);
  605. };
  606. cradle.Connection.prototype.info = function (c) {
  607. this.request('GET', '/', c);
  608. };
  609. cradle.Connection.prototype.stats = function (c) {
  610. this.request('GET', '/_stats', c);
  611. };
  612. cradle.Connection.prototype.activeTasks = function (c) {
  613. this.request('GET', '/_active_tasks', c);
  614. };
  615. cradle.Connection.prototype.uuids = function (count, callback) {
  616. if (typeof(count) === 'function') { callback = count, count = null }
  617. this.request('GET', '/_uuids', count ? {count: count} : {}, callback);
  618. };
  619. cradle.Connection.prototype.replicate = function (options, callback) {
  620. this.request('POST', '/_replicate', null, options, callback);
  621. };
  622.  
  623. cradle.merge = function (target) {
  624. var objs = Array.prototype.slice.call(arguments, 1);
  625. objs.forEach(function(o) {
  626. Object.keys(o).forEach(function (attr) {
  627. if (! o.__lookupGetter__(attr)) {
  628. target[attr] = o[attr];
  629. }
  630. });
  631. });
  632. return target;
  633. }
Add Comment
Please, Sign In to add comment