vulcan 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. data/lib/vulcan/cli.rb +20 -10
  2. data/lib/vulcan/version.rb +1 -1
  3. data/server/Procfile +1 -1
  4. data/server/bin/web +10 -0
  5. data/server/lib/cloudant.coffee +20 -0
  6. data/server/lib/logger.coffee +17 -0
  7. data/server/lib/on.coffee +10 -0
  8. data/server/lib/spawner.coffee +65 -0
  9. data/server/node_modules/coffee-script/CNAME +1 -0
  10. data/server/node_modules/coffee-script/LICENSE +22 -0
  11. data/server/node_modules/coffee-script/README +51 -0
  12. data/server/node_modules/coffee-script/Rakefile +78 -0
  13. data/server/node_modules/coffee-script/bin/cake +7 -0
  14. data/server/node_modules/coffee-script/bin/coffee +7 -0
  15. data/server/node_modules/coffee-script/extras/jsl.conf +44 -0
  16. data/server/node_modules/coffee-script/lib/coffee-script/browser.js +92 -0
  17. data/server/node_modules/coffee-script/lib/coffee-script/cake.js +111 -0
  18. data/server/node_modules/coffee-script/lib/coffee-script/coffee-script.js +167 -0
  19. data/server/node_modules/coffee-script/lib/coffee-script/command.js +500 -0
  20. data/server/node_modules/coffee-script/lib/coffee-script/grammar.js +606 -0
  21. data/server/node_modules/coffee-script/lib/coffee-script/helpers.js +77 -0
  22. data/server/node_modules/coffee-script/lib/coffee-script/index.js +11 -0
  23. data/server/node_modules/coffee-script/lib/coffee-script/lexer.js +788 -0
  24. data/server/node_modules/coffee-script/lib/coffee-script/nodes.js +2986 -0
  25. data/server/node_modules/coffee-script/lib/coffee-script/optparse.js +138 -0
  26. data/server/node_modules/coffee-script/lib/coffee-script/parser.js +683 -0
  27. data/server/node_modules/coffee-script/lib/coffee-script/repl.js +261 -0
  28. data/server/node_modules/coffee-script/lib/coffee-script/rewriter.js +349 -0
  29. data/server/node_modules/coffee-script/lib/coffee-script/scope.js +146 -0
  30. data/server/node_modules/coffee-script/package.json +55 -0
  31. data/server/node_modules/connect-form/History.md +0 -6
  32. data/server/node_modules/connect-form/lib/connect-form.js +2 -4
  33. data/server/node_modules/connect-form/node_modules/formidable/Readme.md +64 -36
  34. data/server/node_modules/connect-form/node_modules/formidable/lib/incoming_form.js +5 -1
  35. data/server/node_modules/connect-form/node_modules/formidable/package.json +20 -6
  36. data/server/node_modules/connect-form/node_modules/formidable/test/common.js +5 -6
  37. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/file/funkyfilename.txt +1 -0
  38. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/js/special-chars-in-filename.js +1 -1
  39. data/server/node_modules/connect-form/node_modules/formidable/test/{slow → integration}/test-fixtures.js +38 -33
  40. data/server/node_modules/connect-form/node_modules/formidable/test/legacy/simple/test-incoming-form.js +11 -0
  41. data/server/node_modules/connect-form/node_modules/formidable/test/run.js +1 -6
  42. data/server/node_modules/connect-form/node_modules/formidable/test/unit/test-incoming-form.js +63 -0
  43. data/server/node_modules/connect-form/package.json +27 -5
  44. data/server/node_modules/cradle/README.md +10 -10
  45. data/server/node_modules/cradle/lib/cradle.js +117 -523
  46. data/server/node_modules/cradle/lib/cradle/database/attachments.js +120 -0
  47. data/server/node_modules/cradle/lib/cradle/database/changes.js +56 -0
  48. data/server/node_modules/cradle/lib/cradle/database/documents.js +215 -0
  49. data/server/node_modules/cradle/lib/cradle/database/index.js +65 -0
  50. data/server/node_modules/cradle/lib/cradle/database/views.js +125 -0
  51. data/server/node_modules/cradle/node_modules/follow/LICENSE +202 -0
  52. data/server/node_modules/cradle/node_modules/follow/README.md +164 -0
  53. data/server/node_modules/cradle/node_modules/follow/Rakefile +54 -0
  54. data/server/node_modules/cradle/node_modules/follow/api.js +35 -0
  55. data/server/node_modules/cradle/node_modules/follow/browser/eventemitter2.js +453 -0
  56. data/server/node_modules/cradle/node_modules/follow/browser/export.js +78 -0
  57. data/server/node_modules/cradle/node_modules/follow/browser/index.html +14 -0
  58. data/server/node_modules/cradle/node_modules/follow/browser/jquery-1.6.1.min.js +18 -0
  59. data/server/node_modules/cradle/node_modules/follow/browser/log4js.js +46 -0
  60. data/server/node_modules/cradle/node_modules/follow/browser/main.js +92 -0
  61. data/server/node_modules/cradle/node_modules/follow/browser/querystring.js +28 -0
  62. data/server/node_modules/cradle/node_modules/follow/browser/request.jquery.js +237 -0
  63. data/server/node_modules/cradle/node_modules/follow/browser/require.js +33 -0
  64. data/server/node_modules/cradle/node_modules/follow/browser/util.js +28 -0
  65. data/server/node_modules/cradle/node_modules/follow/cli.js +101 -0
  66. data/server/node_modules/cradle/node_modules/follow/lib/feed.js +556 -0
  67. data/server/node_modules/cradle/node_modules/follow/lib/index.js +66 -0
  68. data/server/node_modules/cradle/node_modules/follow/lib/stream.js +305 -0
  69. data/server/node_modules/cradle/node_modules/follow/node_modules/request/LICENSE +55 -0
  70. data/server/node_modules/cradle/node_modules/follow/node_modules/request/README.md +285 -0
  71. data/server/node_modules/cradle/node_modules/follow/node_modules/request/main.js +618 -0
  72. data/server/node_modules/cradle/node_modules/follow/node_modules/request/mimetypes.js +146 -0
  73. data/server/node_modules/cradle/node_modules/follow/node_modules/request/oauth.js +34 -0
  74. data/server/node_modules/cradle/node_modules/follow/node_modules/request/package.json +42 -0
  75. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/googledoodle.png +0 -0
  76. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/run.sh +6 -0
  77. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/server.js +57 -0
  78. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/test-body.js +90 -0
  79. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/test-cookie.js +29 -0
  80. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/test-cookiejar.js +90 -0
  81. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/test-errors.js +30 -0
  82. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/test-oauth.js +109 -0
  83. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/test-pipes.js +167 -0
  84. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/test-proxy.js +39 -0
  85. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/test-timeout.js +87 -0
  86. data/server/node_modules/cradle/node_modules/follow/node_modules/request/uuid.js +19 -0
  87. data/server/node_modules/cradle/node_modules/follow/node_modules/request/vendor/cookie/index.js +55 -0
  88. data/server/node_modules/cradle/node_modules/follow/node_modules/request/vendor/cookie/jar.js +72 -0
  89. data/server/node_modules/cradle/node_modules/follow/package.json +45 -0
  90. data/server/node_modules/cradle/node_modules/follow/test/couch.js +153 -0
  91. data/server/node_modules/cradle/node_modules/follow/test/follow.js +136 -0
  92. data/server/node_modules/cradle/node_modules/follow/test/issues.js +178 -0
  93. data/server/node_modules/cradle/node_modules/follow/test/issues/10.js +24 -0
  94. data/server/node_modules/cradle/node_modules/follow/test/stream.js +493 -0
  95. data/server/node_modules/cradle/node_modules/request/LICENSE +55 -0
  96. data/server/node_modules/cradle/node_modules/request/README.md +287 -0
  97. data/server/node_modules/cradle/node_modules/request/forever.js +103 -0
  98. data/server/node_modules/cradle/node_modules/request/main.js +913 -0
  99. data/server/node_modules/cradle/node_modules/request/mimetypes.js +152 -0
  100. data/server/node_modules/cradle/node_modules/request/oauth.js +34 -0
  101. data/server/node_modules/cradle/node_modules/request/package.json +42 -0
  102. data/server/node_modules/cradle/node_modules/request/tests/googledoodle.png +0 -0
  103. data/server/node_modules/cradle/node_modules/request/tests/run.js +38 -0
  104. data/server/node_modules/cradle/node_modules/request/tests/server.js +82 -0
  105. data/server/node_modules/cradle/node_modules/request/tests/squid.conf +77 -0
  106. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/ca.cnf +20 -0
  107. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/ca.crl +0 -0
  108. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/ca.crt +17 -0
  109. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/ca.csr +13 -0
  110. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/ca.key +18 -0
  111. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/ca.srl +1 -0
  112. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/server.cnf +19 -0
  113. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/server.crt +16 -0
  114. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/server.csr +11 -0
  115. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/server.js +28 -0
  116. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/server.key +9 -0
  117. data/server/node_modules/cradle/node_modules/request/tests/ssl/npm-ca.crt +16 -0
  118. data/server/node_modules/cradle/node_modules/request/tests/ssl/test.crt +15 -0
  119. data/server/node_modules/cradle/node_modules/request/tests/ssl/test.key +15 -0
  120. data/server/node_modules/cradle/node_modules/request/tests/test-body.js +95 -0
  121. data/server/node_modules/cradle/node_modules/request/tests/test-cookie.js +29 -0
  122. data/server/node_modules/cradle/node_modules/request/tests/test-cookiejar.js +90 -0
  123. data/server/node_modules/cradle/node_modules/request/tests/test-defaults.js +68 -0
  124. data/server/node_modules/cradle/node_modules/request/tests/test-errors.js +37 -0
  125. data/server/node_modules/cradle/node_modules/request/tests/test-headers.js +52 -0
  126. data/server/node_modules/cradle/node_modules/request/tests/test-httpModule.js +94 -0
  127. data/server/node_modules/cradle/node_modules/request/tests/test-https-strict.js +97 -0
  128. data/server/node_modules/cradle/node_modules/request/tests/test-https.js +86 -0
  129. data/server/node_modules/cradle/node_modules/request/tests/test-oauth.js +117 -0
  130. data/server/node_modules/cradle/node_modules/request/tests/test-params.js +92 -0
  131. data/server/node_modules/cradle/node_modules/request/tests/test-pipes.js +202 -0
  132. data/server/node_modules/cradle/node_modules/request/tests/test-proxy.js +39 -0
  133. data/server/node_modules/cradle/node_modules/request/tests/test-qs.js +28 -0
  134. data/server/node_modules/cradle/node_modules/request/tests/test-redirect.js +154 -0
  135. data/server/node_modules/cradle/node_modules/request/tests/test-timeout.js +87 -0
  136. data/server/node_modules/cradle/node_modules/request/tests/test-toJSON.js +14 -0
  137. data/server/node_modules/cradle/node_modules/request/tests/test-tunnel.js +61 -0
  138. data/server/node_modules/cradle/node_modules/request/tunnel.js +229 -0
  139. data/server/node_modules/cradle/node_modules/request/uuid.js +19 -0
  140. data/server/node_modules/cradle/node_modules/request/vendor/cookie/index.js +65 -0
  141. data/server/node_modules/cradle/node_modules/request/vendor/cookie/jar.js +72 -0
  142. data/server/node_modules/cradle/node_modules/vargs/package.json +33 -10
  143. data/server/node_modules/cradle/package.json +50 -12
  144. data/server/node_modules/cradle/test/cache-test.js +1 -4
  145. data/server/node_modules/cradle/test/connection-test.js +179 -0
  146. data/server/node_modules/cradle/test/database-attachment-test.js +344 -0
  147. data/server/node_modules/cradle/test/database-cache-test.js +132 -0
  148. data/server/node_modules/cradle/test/database-test.js +219 -0
  149. data/server/node_modules/cradle/test/database-view-test.js +141 -0
  150. data/server/node_modules/cradle/test/fixtures/databases.json +28 -1
  151. data/server/node_modules/cradle/test/helpers/seed.js +14 -5
  152. data/server/node_modules/cradle/test/response-test.js +1 -1
  153. data/server/node_modules/express/History.md +16 -0
  154. data/server/node_modules/express/bin/express +7 -6
  155. data/server/node_modules/express/lib-cov/application.js +510 -0
  156. data/server/node_modules/express/lib-cov/express.js +65 -0
  157. data/server/node_modules/express/lib-cov/middleware.js +54 -0
  158. data/server/node_modules/express/lib-cov/request.js +225 -0
  159. data/server/node_modules/express/lib-cov/response.js +611 -0
  160. data/server/node_modules/express/lib-cov/router/collection.js +40 -0
  161. data/server/node_modules/express/lib-cov/router/index.js +515 -0
  162. data/server/node_modules/express/lib-cov/router/methods.js +9 -0
  163. data/server/node_modules/express/lib-cov/router/route.js +68 -0
  164. data/server/node_modules/express/lib-cov/utils.js +151 -0
  165. data/server/node_modules/express/lib-cov/view.js +81 -0
  166. data/server/node_modules/express/lib/express.js +1 -1
  167. data/server/node_modules/express/lib/http.js +1 -2
  168. data/server/node_modules/express/lib/request.js +2 -2
  169. data/server/node_modules/express/lib/router/methods.js +10 -1
  170. data/server/node_modules/express/node_modules/connect/lib/connect.js +1 -1
  171. data/server/node_modules/express/node_modules/connect/lib/http.js +3 -2
  172. data/server/node_modules/express/node_modules/connect/lib/middleware/limit.js +0 -2
  173. data/server/node_modules/express/node_modules/connect/lib/middleware/session.js +1 -2
  174. data/server/node_modules/express/node_modules/connect/node_modules/formidable/Readme.md +42 -25
  175. data/server/node_modules/express/node_modules/connect/node_modules/formidable/lib/incoming_form.js +1 -0
  176. data/server/node_modules/express/node_modules/connect/node_modules/formidable/package.json +14 -3
  177. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/simple/test-incoming-form.js +11 -0
  178. data/server/node_modules/express/node_modules/connect/package.json +35 -7
  179. data/server/node_modules/express/node_modules/mime/package.json +30 -8
  180. data/server/node_modules/express/node_modules/mkdirp/README.markdown +35 -2
  181. data/server/node_modules/express/node_modules/mkdirp/examples/pow.js +1 -1
  182. data/server/node_modules/express/node_modules/mkdirp/index.js +72 -13
  183. data/server/node_modules/express/node_modules/mkdirp/package.json +39 -21
  184. data/server/node_modules/express/node_modules/mkdirp/test/chmod.js +38 -0
  185. data/server/node_modules/express/node_modules/mkdirp/test/clobber.js +37 -0
  186. data/server/node_modules/express/node_modules/mkdirp/test/perm.js +32 -0
  187. data/server/node_modules/express/node_modules/mkdirp/test/perm_sync.js +39 -0
  188. data/server/node_modules/express/node_modules/mkdirp/test/sync.js +27 -0
  189. data/server/node_modules/express/node_modules/mkdirp/test/umask.js +28 -0
  190. data/server/node_modules/express/node_modules/mkdirp/test/umask_sync.js +27 -0
  191. data/server/node_modules/express/node_modules/qs/History.md +10 -0
  192. data/server/node_modules/express/node_modules/qs/Readme.md +9 -2
  193. data/server/node_modules/express/node_modules/qs/examples.js +3 -0
  194. data/server/node_modules/express/node_modules/qs/lib/querystring.js +8 -6
  195. data/server/node_modules/express/node_modules/qs/package.json +26 -8
  196. data/server/node_modules/express/node_modules/qs/test/parse.js +13 -1
  197. data/server/node_modules/express/node_modules/qs/test/stringify.js +45 -37
  198. data/server/node_modules/express/package.json +55 -16
  199. data/server/node_modules/express/test.js +41 -0
  200. data/server/node_modules/knox/package.json +26 -4
  201. data/server/node_modules/node-uuid/package.json +40 -11
  202. data/server/node_modules/node-uuid/test/test.js +1 -1
  203. data/server/node_modules/nodemon/README.md +120 -0
  204. data/server/node_modules/nodemon/nodemon.js +518 -0
  205. data/server/node_modules/nodemon/nodemonignore.example +11 -0
  206. data/server/node_modules/nodemon/package.json +49 -0
  207. data/server/node_modules/restler/README.md +144 -94
  208. data/server/node_modules/restler/lib/multipartform.js +2 -0
  209. data/server/node_modules/restler/lib/restler.js +218 -61
  210. data/server/node_modules/restler/package.json +35 -8
  211. data/server/node_modules/restler/test/all.js +6 -1
  212. data/server/node_modules/restler/test/restler.js +624 -118
  213. data/server/package.json +14 -10
  214. data/server/web.coffee +64 -0
  215. data/server/web.js +15 -3
  216. metadata +170 -57
  217. data/server/index.js +0 -14
  218. data/server/node_modules/connect-form/LICENSE +0 -22
  219. data/server/node_modules/connect-form/index.js +0 -100
  220. data/server/node_modules/connect-form/node_modules/formidable/test/fast/test-incoming-form.js +0 -45
  221. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/http/no-filename/generic.http +0 -13
  222. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-chrome-13.http +0 -26
  223. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-firefox-3.6.http +0 -24
  224. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-safari-5.http +0 -23
  225. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-chrome-12.http +0 -24
  226. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-ie-7.http +0 -22
  227. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-ie-8.http +0 -22
  228. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-safari-5.http +0 -22
  229. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/multi_video.upload +0 -0
  230. data/server/node_modules/cradle/Makefile +0 -10
  231. data/server/node_modules/cradle/test/cradle-test.js +0 -650
  232. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/no-filename/generic.http +0 -13
  233. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-chrome-13.http +0 -26
  234. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-firefox-3.6.http +0 -24
  235. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-safari-5.http +0 -23
  236. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-chrome-12.http +0 -24
  237. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-ie-7.http +0 -22
  238. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-ie-8.http +0 -22
  239. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-safari-5.http +0 -22
  240. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/multi_video.upload +0 -0
  241. data/server/node_modules/express/node_modules/connect/test.js +0 -52
  242. data/server/node_modules/express/testing/foo/app.js +0 -35
  243. data/server/node_modules/express/testing/foo/package.json +0 -9
  244. data/server/node_modules/express/testing/foo/public/stylesheets/style.css +0 -8
  245. data/server/node_modules/express/testing/foo/routes/index.js +0 -10
  246. data/server/node_modules/express/testing/foo/views/index.jade +0 -2
  247. data/server/node_modules/express/testing/foo/views/layout.jade +0 -6
  248. data/server/node_modules/express/testing/index.js +0 -43
  249. data/server/node_modules/express/testing/public/test.txt +0 -2971
  250. data/server/node_modules/express/testing/views/page.html +0 -1
  251. data/server/node_modules/express/testing/views/page.jade +0 -3
  252. data/server/node_modules/express/testing/views/test.md +0 -1
  253. data/server/node_modules/express/testing/views/user/index.jade +0 -1
  254. data/server/node_modules/express/testing/views/user/list.jade +0 -1
  255. data/server/node_modules/knox/lib/knox/mime/index.js +0 -308
  256. data/server/node_modules/knox/lib/knox/mime/test.js +0 -59
  257. data/server/node_modules/node-uuid/test/benchmark-native +0 -0
  258. data/server/node_modules/on/index.js +0 -13
  259. data/server/node_modules/restler/test/test_helper.js +0 -163
  260. data/server/node_modules/spawner/index.js +0 -106
@@ -0,0 +1,11 @@
1
+ # .nodemonignore file
2
+ #
3
+ # This is an example of a comment
4
+ #
5
+ # You can list individual files in the ignore list, or they can be
6
+ # file patterns. For example:
7
+ #
8
+ # /vendor/*
9
+ # /public/css/styles.css
10
+ # ./server.js
11
+ # ./.git/*
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "nodemon",
3
+ "homepage": "http://github.com/remy/nodemon",
4
+ "author": {
5
+ "name": "Remy Sharp",
6
+ "url": "http://github.com/remy"
7
+ },
8
+ "bin": {
9
+ "nodemon": "./nodemon.js"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git://github.com/remy/nodemon.git"
14
+ },
15
+ "description": "Simple monitor script for use during development of a node.js app.",
16
+ "keywords": [
17
+ "monitor",
18
+ "development",
19
+ "restart",
20
+ "autoload",
21
+ "reload",
22
+ "terminal"
23
+ ],
24
+ "version": "0.6.18",
25
+ "preferGlobal": "true",
26
+ "licenses": [
27
+ {
28
+ "type": "MIT",
29
+ "url": "http://rem.mit-license.org"
30
+ }
31
+ ],
32
+ "main": "./nodemon",
33
+ "_npmUser": {
34
+ "name": "ddollar",
35
+ "email": "ddollar@gmail.com"
36
+ },
37
+ "_id": "nodemon@0.6.18",
38
+ "dependencies": {},
39
+ "devDependencies": {},
40
+ "optionalDependencies": {},
41
+ "engines": {
42
+ "node": "*"
43
+ },
44
+ "_engineSupported": true,
45
+ "_npmVersion": "1.1.21",
46
+ "_nodeVersion": "v0.6.17",
47
+ "_defaultsLoaded": true,
48
+ "_from": "nodemon@0.6.x"
49
+ }
@@ -1,9 +1,13 @@
1
- Restler 0.2.4
2
- ===========
1
+ Restler
2
+ =======
3
3
 
4
4
  (C) Dan Webb (dan@danwebb.net/@danwrong) 2011, Licensed under the MIT-LICENSE
5
5
 
6
- An HTTP client library for node.js (0.3 and up). Hides most of the complexity of creating and using http.Client. Very early days yet.
6
+ An HTTP client library for node.js (0.6.x and up). Hides most of the complexity of creating and using http.Client.
7
+
8
+ **Release 2.x.x** is dedicated to modifying how errors are handled and emitted. Currently errors are being fired as an on 'error' event but as [@ctavan](https://github.com/ctavan) pointed out on [issue #36](https://github.com/danwrong/restler/pull/36) a better approach (and more commonly in vogue now) would be to pass the error obj to the callback.
9
+
10
+ Ths change will inevitably affect those using older < 0.2.x versions of restler. Those not ready to upgrade yet are encouraged to stay on the 0.2.x version.
7
11
 
8
12
  See [Version History](https://github.com/danwrong/restler/wiki/Version-History) for changes
9
13
 
@@ -21,7 +25,8 @@ Features
21
25
  * Transparently handle SSL (just specify https in the URL)
22
26
  * Deals with basic auth for you, just provide username and password options
23
27
  * Simple service wrapper that allows you to easily put together REST API libraries
24
- * Transparently handle content-encoded responses (gzip, deflate)
28
+ * Transparently handle content-encoded responses (gzip, deflate) (requires node 0.6+)
29
+ * Transparently handle different content charsets via [iconv](https://github.com/bnoordhuis/node-iconv) (if available)
25
30
 
26
31
 
27
32
  API
@@ -29,18 +34,28 @@ API
29
34
 
30
35
  ### request(url, options)
31
36
 
32
- Basic method to make a request of any type. The function returns a RestRequest object
33
- that emits events:
37
+ Basic method to make a request of any type. The function returns a RestRequest object that emits events:
38
+
39
+ #### events
40
+
41
+ * `complete: function(result, response)` - emitted when the request has finished whether it was successful or not. Gets passed the response result and the response object as arguments. If some error has occurred, `result` is always instance of `Error`, otherwise it contains response data.
42
+ * `success: function(data, response)` - emitted when the request was successful. Gets passed the response data and the response object as arguments.
43
+ * `fail: function(data, response)` - emitted when the request was successful, but 4xx status code returned. Gets passed the response data and the response object as arguments.
44
+ * `error: function(err, response)` - emitted when some errors have occurred (eg. connection aborted, parse, encoding, decoding failed or some other unhandled errors). Gets passed the `Error` object and the response object (when available) as arguments.
45
+ * `abort: function()` - emitted when `request.abort()` is called.
46
+ * `2XX`, `3XX`, `4XX`, `5XX: function(data, response)` - emitted for all requests with response codes in the range (eg. `2XX` emitted for 200, 201, 203).
47
+ * <code><i>actual response code</i>: function(data, response)</code> - emitted for every single response code (eg. 404, 201, etc).
48
+
49
+ #### members
50
+
51
+ * `abort([error])` Cancels request. `abort` event is emitted. `request.aborted` is set to `true`. If non-falsy `error` is passed, then `error` will be additionaly emitted (with `error` passed as a param and `error.type` is set to `"abort"`). Otherwise only `complete` event will raise.
52
+ * `retry([timeout])` Re-sends request after `timeout` ms. Pending request is aborted.
53
+ * `aborted` Determines if request was aborted.
34
54
 
35
- * _complete_ emitted when the request has finished whether it was successful or not. Gets passed the response data and the response as arguments.
36
- * _success_ emitted when the request was successful. Gets passed the response data and the response as arguments.
37
- * _error_ emitted when the request was unsuccessful. Gets passed the response data and the response as arguments.
38
- * _2XX, 3XX, 4XX, 5XX etc_ emitted for all requests with response codes in the range. Eg. 2XX emitted for 200, 201, 203
39
- * _actual response code_ there is an event emitted for every single response code. eg. 404, 201, etc.
40
55
 
41
56
  ### get(url, options)
42
57
 
43
- Create a GET request.
58
+ Create a GET request.
44
59
 
45
60
  ### post(url, options)
46
61
 
@@ -54,107 +69,142 @@ Create a PUT request.
54
69
 
55
70
  Create a DELETE request.
56
71
 
57
- ### response parsers
72
+ ### head(url, options)
73
+
74
+ Create a HEAD request.
75
+
76
+ ### json(url, data, options)
77
+
78
+ Send json `data` via GET method.
79
+
80
+ ### postJson(url, data, options)
81
+
82
+ Send json `data` via POST method.
83
+
84
+
85
+ ### Parsers
58
86
 
59
87
  You can give any of these to the parsers option to specify how the response data is deserialized.
88
+ In case of malformed content, parsers emit `error` event. Original data returned by server is stored in `response.raw`.
60
89
 
61
90
  #### parsers.auto
62
91
 
63
- Checks the content-type and then uses parsers.xml, parsers.json or parsers.yaml.
92
+ Checks the content-type and then uses parsers.xml, parsers.json or parsers.yaml.
64
93
  If the content type isn't recognised it just returns the data untouched.
65
94
 
66
95
  #### parsers.json, parsers.xml, parsers.yaml
67
96
 
68
97
  All of these attempt to turn the response into a JavaScript object. In order to use the YAML and XML parsers you must have yaml and/or xml2js installed.
69
98
 
70
- ### options hash
71
-
72
- * _method_ Request method, can be get, post, put, del
73
- * _query_ Query string variables as a javascript object, will override the querystring in the URL
74
- * _data_ The data to be added to the body of the request. Can be a string or any object.
75
- Note that if you want your request body to be JSON with the Content-Type `application/json`, you need to
76
- JSON.stringify your object first. Otherwise, it will be sent as `application/x-www-form-urlencoded` and encoded accordingly.
77
- * _parser_ A function that will be called on the returned data. try parsers.auto, parsers.json etc
78
- * _encoding_ The encoding of the request body. defaults to utf8
79
- * _decoding_ The encoding of the response body. For a list of supported values see [Buffers](http://nodejs.org/docs/latest/api/buffers.html#buffers). Additionally accepts `"buffer"` - returns response as `Buffer`. Defaults to `utf8`.
80
- * _headers_ a hash of HTTP headers to be sent
81
- * _username_ Basic auth username
82
- * _password_ Basic auth password
83
- * _multipart_ If set the data passed will be formated as multipart/form-encoded. See multipart example below.
84
- * _client_ A http.Client instance if you want to reuse or implement some kind of connection pooling.
85
- * _followRedirects_ Does what it says on the tin.
99
+ ### Options
100
+
101
+ * `method` Request method, can be get, post, put, del. Defaults to `"get"`.
102
+ * `query` Query string variables as a javascript object, will override the querystring in the URL. Defaults to empty.
103
+ * `data` The data to be added to the body of the request. Can be a string or any object.
104
+ Note that if you want your request body to be JSON with the `Content-Type: application/json`, you need to
105
+ `JSON.stringify` your object first. Otherwise, it will be sent as `application/x-www-form-urlencoded` and encoded accordingly.
106
+ Also you can use `json()` and `postJson()` methods.
107
+ * `parser` A function that will be called on the returned data. Use any of predefined `restler.parsers`. See parsers section below. Defaults to `restler.parsers.auto`.
108
+ * `encoding` The encoding of the request body. Defaults to `"utf8"`.
109
+ * `decoding` The encoding of the response body. For a list of supported values see [Buffers](http://nodejs.org/docs/latest/api/buffers.html#buffers). Additionally accepts `"buffer"` - returns response as `Buffer`. Defaults to `"utf8"`.
110
+ * `headers` A hash of HTTP headers to be sent. Defaults to `{ 'Accept': '*/*', 'User-Agent': 'Restler for node.js' }`.
111
+ * `username` Basic auth username. Defaults to empty.
112
+ * `password` Basic auth password. Defaults to empty.
113
+ * `multipart` If set the data passed will be formated as `multipart/form-encoded`. See multipart example below. Defaults to `false`.
114
+ * `client` A http.Client instance if you want to reuse or implement some kind of connection pooling. Defaults to empty.
115
+ * `followRedirects` If set will recursively follow redirects. Defaults to `true`.
86
116
 
87
117
 
88
118
  Example usage
89
119
  -------------
90
120
 
91
- var sys = require('util'),
92
- rest = require('./restler');
93
-
94
- rest.get('http://google.com').on('complete', function(data) {
95
- sys.puts(data);
96
- });
97
-
98
- rest.get('http://twaud.io/api/v1/users/danwrong.json').on('complete', function(data) {
99
- sys.puts(data[0].message); // auto convert to object
100
- });
101
-
102
- rest.get('http://twaud.io/api/v1/users/danwrong.xml').on('complete', function(data) {
103
- sys.puts(data[0].sounds[0].sound[0].message); // auto convert to object
104
- });
105
-
106
- rest.post('http://user:pass@service.com/action', {
107
- data: { id: 334 },
108
- }).on('complete', function(data, response) {
109
- if (response.statusCode == 201) {
110
- // you can get at the raw response like this...
111
- }
112
- });
113
-
114
- // multipart request sending a file and using https
115
- rest.post('https://twaud.io/api/v1/upload.json', {
116
- multipart: true,
117
- username: 'danwrong',
118
- password: 'wouldntyouliketoknow',
119
- data: {
120
- 'sound[message]': 'hello from restler!',
121
- 'sound[file]': rest.file('doug-e-fresh_the-show.mp3', null, null, null, 'audio/mpeg')
122
- }
123
- }).on('complete', function(data) {
124
- sys.puts(data.audio_url);
125
- });
126
-
127
- // create a service constructor for very easy API wrappers a la HTTParty...
128
- Twitter = rest.service(function(u, p) {
129
- this.defaults.username = u;
130
- this.defaults.password = p;
131
- }, {
132
- baseURL: 'http://twitter.com'
133
- }, {
134
- update: function(message) {
135
- return this.post('/statuses/update.json', { data: { status: message } });
136
- }
137
- });
138
-
139
- var client = new Twitter('danwrong', 'password');
140
- client.update('Tweeting using a Restler service thingy').on('complete', function(data) {
141
- sys.p(data);
142
- });
143
-
144
- // the JSON post
145
- rest.post('http://example.com/action', {
146
- data: JSON.stringify({ id: 334 }),
147
- }).on('complete', function(data, response) {
148
- // you can get at the raw response like this...
149
- });
150
-
151
-
121
+ ```javascript
122
+ var sys = require('util'),
123
+ rest = require('./restler');
124
+
125
+ rest.get('http://google.com').on('complete', function(result) {
126
+ if (result instanceof Error) {
127
+ sys.puts('Error: ' + result.message);
128
+ this.retry(5000); // try again after 5 sec
129
+ } else {
130
+ sys.puts(result);
131
+ }
132
+ });
133
+
134
+ rest.get('http://twaud.io/api/v1/users/danwrong.json').on('complete', function(data) {
135
+ sys.puts(data[0].message); // auto convert to object
136
+ });
137
+
138
+ rest.get('http://twaud.io/api/v1/users/danwrong.xml').on('complete', function(data) {
139
+ sys.puts(data[0].sounds[0].sound[0].message); // auto convert to object
140
+ });
141
+
142
+ rest.post('http://user:pass@service.com/action', {
143
+ data: { id: 334 },
144
+ }).on('complete', function(data, response) {
145
+ if (response.statusCode == 201) {
146
+ // you can get at the raw response like this...
147
+ }
148
+ });
149
+
150
+ // multipart request sending a 321567 byte long file using https
151
+ rest.post('https://twaud.io/api/v1/upload.json', {
152
+ multipart: true,
153
+ username: 'danwrong',
154
+ password: 'wouldntyouliketoknow',
155
+ data: {
156
+ 'sound[message]': 'hello from restler!',
157
+ 'sound[file]': rest.file('doug-e-fresh_the-show.mp3', null, 321567, null, 'audio/mpeg')
158
+ }
159
+ }).on('complete', function(data) {
160
+ sys.puts(data.audio_url);
161
+ });
162
+
163
+ // create a service constructor for very easy API wrappers a la HTTParty...
164
+ Twitter = rest.service(function(u, p) {
165
+ this.defaults.username = u;
166
+ this.defaults.password = p;
167
+ }, {
168
+ baseURL: 'http://twitter.com'
169
+ }, {
170
+ update: function(message) {
171
+ return this.post('/statuses/update.json', { data: { status: message } });
172
+ }
173
+ });
174
+
175
+ var client = new Twitter('danwrong', 'password');
176
+ client.update('Tweeting using a Restler service thingy').on('complete', function(data) {
177
+ sys.p(data);
178
+ });
179
+
180
+ // post JSON
181
+ var jsonData = { id: 334 };
182
+ rest.postJson('http://example.com/action', jsonData).on('complete', function(data, response) {
183
+ // handle response
184
+ });
185
+
186
+ ```
187
+
152
188
  Running the tests
153
189
  -----------------
190
+ install **[nodeunit](https://github.com/caolan/nodeunit)**
191
+
192
+ ```bash
193
+ npm install nodeunit
194
+ ```
195
+
196
+ then
197
+
198
+ ```bash
199
+ node test/all.js
200
+ ```
201
+
202
+ or
203
+
204
+ ```bash
205
+ nodeunit test/restler.js
206
+ ```
154
207
 
155
- node test/restler.js
156
-
157
208
  TODO
158
209
  ----
159
- * Deal with no utf-8 response bodies
160
210
  * What do you need? Let me know or fork.
@@ -87,6 +87,8 @@ Part.prototype = {
87
87
  valueSize = this.value.fileSize;
88
88
  } else if (this.value.data) {
89
89
  valueSize = this.value.data.length;
90
+ } else if (typeof this.value === 'number') {
91
+ valueSize = this.value.toString().length;
90
92
  } else {
91
93
  valueSize = this.value.length;
92
94
  }
@@ -1,19 +1,29 @@
1
- var sys = require('util'),
2
- http = require('http'),
3
- https = require('https'),
4
- url = require('url'),
5
- qs = require('querystring'),
6
- multipart = require('./multipartform'),
7
- zlib = require('zlib');
8
-
1
+ var sys = require('util');
2
+ var http = require('http');
3
+ var https = require('https');
4
+ var url = require('url');
5
+ var qs = require('querystring');
6
+ var multipart = require('./multipartform');
7
+ var zlib = null;
8
+ var Iconv = null;
9
+
10
+ try {
11
+ zlib = require('zlib');
12
+ } catch (err) {}
13
+
14
+ try {
15
+ Iconv = require('iconv').Iconv;
16
+ } catch (err) {}
17
+
9
18
  function mixin(target, source) {
19
+ source = source || {};
10
20
  Object.keys(source).forEach(function(key) {
11
21
  target[key] = source[key];
12
22
  });
13
23
 
14
24
  return target;
15
25
  }
16
-
26
+
17
27
  function Request(uri, options) {
18
28
  this.url = url.parse(uri);
19
29
  this.options = options;
@@ -22,6 +32,10 @@ function Request(uri, options) {
22
32
  'User-Agent': 'Restler for node.js',
23
33
  'Host': this.url.host
24
34
  };
35
+
36
+ if (zlib) {
37
+ this.headers['Accept-Encoding'] = 'gzip, deflate';
38
+ }
25
39
 
26
40
  mixin(this.headers, options.headers || {});
27
41
 
@@ -41,6 +55,13 @@ function Request(uri, options) {
41
55
 
42
56
  if (this.options.multipart) {
43
57
  this.headers['Content-Type'] = 'multipart/form-data; boundary=' + multipart.defaultBoundary;
58
+ var multipart_size = multipart.sizeOf(this.options.data, multipart.defaultBoundary);
59
+ if (typeof multipart_size === 'number' && multipart_size === multipart_size) {
60
+ this.headers['Content-Length'] = multipart_size;
61
+ }
62
+ else {
63
+ console.log("Building multipart request without Content-Length header, please specify all file sizes");
64
+ }
44
65
  } else {
45
66
  if (typeof this.options.data == 'object') {
46
67
  this.options.data = qs.stringify(this.options.data);
@@ -96,14 +117,14 @@ mixin(Request.prototype, {
96
117
  _responseHandler: function(response) {
97
118
  var self = this;
98
119
 
99
- if (this._isRedirect(response) && this.options.followRedirects == true) {
120
+ if (self._isRedirect(response) && self.options.followRedirects) {
100
121
  try {
101
- var location = url.resolve(this.url, response.headers['location']);
102
- this.options.originalRequest = this;
103
-
104
- request(location, this.options);
105
- } catch(e) {
106
- self._respond('error', '', 'Failed to follow redirect');
122
+ self.url = url.parse(url.resolve(self.url.href, response.headers['location']));
123
+ self._retry();
124
+ // todo handle somehow infinite redirects
125
+ } catch(err) {
126
+ err.message = 'Failed to follow redirect: ' + err.message;
127
+ self._fireError(err, response);
107
128
  }
108
129
  } else {
109
130
  var body = '';
@@ -115,62 +136,96 @@ mixin(Request.prototype, {
115
136
  });
116
137
 
117
138
  response.on('end', function() {
139
+ response.rawEncoded = body;
118
140
  self._decode(new Buffer(body, 'binary'), response, function(err, body) {
119
141
  if (err) {
120
- self._respond('error', '', 'Failed to decode response body');
142
+ self._fireError(err, response);
121
143
  return;
122
144
  }
123
- self._encode(body, response, function(body) {
124
- self._fireEvents(body, response);
145
+ response.raw = body;
146
+ body = self._iconv(body, response);
147
+ self._encode(body, response, function(err, body) {
148
+ if (err) {
149
+ self._fireError(err, response);
150
+ } else {
151
+ self._fireSuccess(body, response);
152
+ }
125
153
  });
126
154
  });
127
155
  });
128
156
  }
129
157
  },
130
158
  _decode: function(body, response, callback) {
131
- var encoder = response.headers['content-encoding'];
132
- if (encoder in decoders) {
133
- decoders[encoder].call(response, body, callback);
159
+ var decoder = response.headers['content-encoding'];
160
+ if (decoder in decoders) {
161
+ decoders[decoder].call(response, body, callback);
134
162
  } else {
135
163
  callback(null, body);
136
164
  }
137
165
  },
166
+ _iconv: function(body, response) {
167
+ if (Iconv) {
168
+ var charset = response.headers['content-type'];
169
+ if (charset) {
170
+ charset = /\bcharset=(.+)(?:;|$)/i.exec(charset);
171
+ if (charset) {
172
+ charset = charset[1].trim().toUpperCase();
173
+ if (charset != 'UTF-8') {
174
+ try {
175
+ var iconv = new Iconv(charset, 'UTF-8//TRANSLIT//IGNORE');
176
+ return iconv.convert(body);
177
+ } catch (err) {}
178
+ }
179
+ }
180
+ }
181
+ }
182
+ return body;
183
+ },
138
184
  _encode: function(body, response, callback) {
139
185
  var self = this;
140
186
  if (self.options.decoding == 'buffer') {
141
- callback(body);
187
+ callback(null, body);
142
188
  } else {
143
189
  body = body.toString(self.options.decoding);
144
190
  if (self.options.parser) {
145
191
  self.options.parser.call(response, body, callback);
146
192
  } else {
147
- callback(body);
193
+ callback(null, body);
148
194
  }
149
195
  }
150
196
  },
151
- _respond: function(type, data, response) {
152
- if (this.options.originalRequest) {
153
- this.options.originalRequest.emit(type, data, response);
197
+ _fireError: function(err, response) {
198
+ this.emit('error', err, response);
199
+ this.emit('complete', err, response);
200
+ },
201
+ _fireSuccess: function(body, response) {
202
+ if (parseInt(response.statusCode) >= 400) {
203
+ this.emit('fail', body, response);
154
204
  } else {
155
- this.emit(type, data, response);
205
+ this.emit('success', body, response);
156
206
  }
157
- },
158
- _fireEvents: function(body, response) {
159
- if (parseInt(response.statusCode) >= 400) this._respond('error', body, response);
160
- else this._respond('success', body, response);
161
-
162
- this._respond(response.statusCode.toString().replace(/\d{2}$/, 'XX'), body, response);
163
- this._respond(response.statusCode.toString(), body, response);
164
- this._respond('complete', body, response);
207
+ this.emit(response.statusCode.toString().replace(/\d{2}$/, 'XX'), body, response);
208
+ this.emit(response.statusCode.toString(), body, response);
209
+ this.emit('complete', body, response);
165
210
  },
166
211
  _makeRequest: function() {
167
212
  var self = this;
168
-
169
213
  this.request.on('response', function(response) {
170
- self._responseHandler(response);
171
- }).on('error', function(err) {
172
- self._respond('error', null, err);
173
- });
214
+ self.emit('response', response);
215
+ self._responseHandler(response);
216
+ }).on('error', function(err) {
217
+ if (!self.aborted) {
218
+ self._fireError(err, null);
219
+ }
220
+ });
221
+ },
222
+ _retry: function() {
223
+ this.request.removeAllListeners().on('error', function() {});
224
+ if (this.request.finished) {
225
+ this.request.abort();
226
+ }
227
+ Request.call(this, this.url.href, this.options); // reusing request object to handle recursive calls and remember listeners
228
+ this.run();
174
229
  },
175
230
  run: function() {
176
231
  var self = this;
@@ -186,6 +241,44 @@ mixin(Request.prototype, {
186
241
  this.request.end();
187
242
  }
188
243
 
244
+ return this;
245
+ },
246
+ abort: function(err) {
247
+ var self = this;
248
+
249
+ if (err) {
250
+ if (typeof err == 'string') {
251
+ err = new Error(err);
252
+ } else if (!(err instanceof Error)) {
253
+ err = new Error('AbortError');
254
+ }
255
+ err.type = 'abort';
256
+ } else {
257
+ err = null;
258
+ }
259
+
260
+ self.request.on('close', function() {
261
+ if (err) {
262
+ self._fireError(err, null);
263
+ } else {
264
+ self.emit('complete', null, null);
265
+ }
266
+ });
267
+
268
+ self.aborted = true;
269
+ self.request.abort();
270
+ self.emit('abort', err);
271
+ return this;
272
+ },
273
+ retry: function(timeout) {
274
+ var self = this;
275
+ timeout = parseInt(timeout);
276
+ var fn = self._retry.bind(self);
277
+ if (!isFinite(timeout) || timeout <= 0) {
278
+ process.nextTick(fn, timeout);
279
+ } else {
280
+ setTimeout(fn, timeout);
281
+ }
189
282
  return this;
190
283
  }
191
284
  });
@@ -199,14 +292,19 @@ function shortcutOptions(options, method) {
199
292
 
200
293
  function request(url, options) {
201
294
  var request = new Request(url, options);
202
- request.on('error', function() {});
203
- return request.run();
295
+ request.on('error', function() {});
296
+ process.nextTick(request.run.bind(request));
297
+ return request;
204
298
  }
205
299
 
206
300
  function get(url, options) {
207
301
  return request(url, shortcutOptions(options, 'GET'));
208
302
  }
209
303
 
304
+ function patch(url, options) {
305
+ return request(url, shortcutOptions(options, 'PATCH'));
306
+ }
307
+
210
308
  function post(url, options) {
211
309
  return request(url, shortcutOptions(options, 'POST'));
212
310
  }
@@ -219,23 +317,66 @@ function del(url, options) {
219
317
  return request(url, shortcutOptions(options, 'DELETE'));
220
318
  }
221
319
 
320
+ function head(url, options) {
321
+ return request(url, shortcutOptions(options, 'HEAD'));
322
+ }
323
+
324
+ function json(url, data, options, method) {
325
+ options = options || {};
326
+ options.parser = (typeof options.parser !== "undefined") ? options.parser : parsers.auto;
327
+ options.headers = options.headers || {};
328
+ options.headers['content-type'] = 'application/json';
329
+ options.data = JSON.stringify(data);
330
+ options.method = method || 'GET';
331
+ return request(url, options);
332
+ }
333
+
334
+ function postJson(url, data, options) {
335
+ return json(url, data, options, 'POST');
336
+ }
337
+
222
338
  var parsers = {
223
339
  auto: function(data, callback) {
224
340
  var contentType = this.headers['content-type'];
225
-
341
+ var contentParser;
226
342
  if (contentType) {
227
- for (var matcher in parsers.auto.matchers) {
228
-
229
- if (contentType.indexOf(matcher) == 0) {
230
- return parsers.auto.matchers[matcher].call(this, data, callback);
343
+ contentType = contentType.replace(/;.+/, ''); // remove all except mime type (eg. text/html; charset=UTF-8)
344
+ if (contentType in parsers.auto.matchers) {
345
+ contentParser = parsers.auto.matchers[contentType];
346
+ } else {
347
+ // custom (vendor) mime types
348
+ var parts = contentType.match(/^([\w-]+)\/vnd((?:\.(?:[\w-]+))+)\+([\w-]+)$/i);
349
+ if (parts) {
350
+ var type = parts[1];
351
+ var vendors = parts[2].substr(1).split('.');
352
+ var subtype = parts[3];
353
+ var vendorType;
354
+ while (vendors.pop() && !(vendorType in parsers.auto.matchers)) {
355
+ vendorType = vendors.length
356
+ ? type + '/vnd.' + vendors.join('.') + '+' + subtype
357
+ : vendorType = type + '/' + subtype;
358
+ }
359
+ contentParser = parsers.auto.matchers[vendorType];
231
360
  }
232
361
  }
233
362
  }
234
-
235
- callback(data);
363
+ if (typeof contentParser == 'function') {
364
+ contentParser.call(this, data, callback);
365
+ } else {
366
+ callback(null, data);
367
+ }
236
368
  },
237
369
  json: function(data, callback) {
238
- callback(data && JSON.parse(data));
370
+ if (data && data.length) {
371
+ try {
372
+ callback(null, JSON.parse(data));
373
+ } catch (err) {
374
+ err.message = 'Failed to parse JSON body: ' + err.message;
375
+ callback(err, null);
376
+ }
377
+ } else {
378
+ callback(null, null);
379
+ }
239
380
  }
240
381
  };
241
382
 
@@ -247,7 +388,16 @@ try {
247
388
  var yaml = require('yaml');
248
389
 
249
390
  parsers.yaml = function(data, callback) {
250
- return callback(data && yaml.eval(data));
391
+ if (data) {
392
+ try {
393
+ callback(null, yaml.eval(data));
394
+ } catch (err) {
395
+ err.message = 'Failed to parse YAML body: ' + err.message;
396
+ callback(err, null);
397
+ }
398
+ } else {
399
+ callback(null, null);
400
+ }
251
401
  };
252
402
 
253
403
  parsers.auto.matchers['application/yaml'] = parsers.yaml;
@@ -259,17 +409,14 @@ try {
259
409
  parsers.xml = function(data, callback) {
260
410
  if (data) {
261
411
  var parser = new xml2js.Parser();
262
-
263
- parser.on('end', function(result) {
264
- callback(result);
412
+ parser.parseString(data, function(err, data) {
413
+ if (err) {
414
+ err.message = 'Failed to parse XML body: ' + err.message;
415
+ }
416
+ callback(err, data);
265
417
  });
266
- try {
267
- parser.parseString(data);
268
- } catch (e) {
269
- callback({error:'Oops, something went wrong.'});
270
- }
271
418
  } else {
272
- callback();
419
+ callback(null, null);
273
420
  }
274
421
  };
275
422
 
@@ -302,12 +449,18 @@ mixin(Service.prototype, {
302
449
  get: function(path, options) {
303
450
  return get(this._url(path), this._withDefaults(options));
304
451
  },
452
+ patch: function(path, options) {
453
+ return patch(this._url(path), this._withDefaults(options));
454
+ },
305
455
  put: function(path, options) {
306
456
  return put(this._url(path), this._withDefaults(options));
307
457
  },
308
458
  post: function(path, options) {
309
459
  return post(this._url(path), this._withDefaults(options));
310
460
  },
461
+ json: function(method, path, data, options) {
462
+ return json(this._url(path), data, this._withDefaults(options), method);
463
+ },
311
464
  del: function(path, options) {
312
465
  return del(this._url(path), this._withDefaults(options));
313
466
  },
@@ -333,9 +486,13 @@ mixin(exports, {
333
486
  request: request,
334
487
  service: service,
335
488
  get: get,
489
+ patch: patch,
336
490
  post: post,
337
491
  put: put,
338
492
  del: del,
493
+ head: head,
494
+ json: json,
495
+ postJson: postJson,
339
496
  parsers: parsers,
340
497
  file: multipart.file,
341
498
  data: multipart.data