├── .gitignore
├── Makefile
├── Readme.md
├── ajaxapi.js
├── index.js
├── package.json
└── test
├── setup.js
└── test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | browserify := ./node_modules/.bin/browserify
2 | uglify := ./node_modules/.bin/uglifyjs
3 |
4 | ajaxapi.js: index.js
5 | $(browserify) -s ajaxapi $< | $(uglify) -m > $@
6 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # ajaxapi
2 |
3 | minimal ajax library.
4 |
5 | - optimized for consuming restful api's
6 | - small and compact (< 6kb gzipped)
7 | - uses promises
8 | - supports node.js and the browser
9 |
10 | ```js
11 | var ajaxapi = require('ajaxapi');
12 |
13 | var API = ajaxapi('https://api.github.com');
14 |
15 | API.get('/repo/iojs/io.js')
16 | .then(function (data) {
17 | alert("Stars: " + data.stargazers_count);
18 | })
19 |
20 | API.put('/user', { name: 'John Constantine' })
21 | .then(function (data) {
22 | alert("User data was saved");
23 | });
24 | ```
25 |
26 |
27 |
28 | ## Customization
29 |
30 | ### Before hooks
31 |
32 | Hooks before its sent
33 |
34 | ```js
35 | var API = ajaxapi('https://api.github.com');
36 |
37 | API.before(function (ctx) {
38 | ctx.headers['X-Access-Token'] = '...';
39 |
40 | ctx.headers //=> {}
41 | ctx.method //=> "GET"
42 | ctx.url //=> "https://api.github.com/foo/bar"
43 | ctx.data //=> {}
44 | });
45 | ```
46 |
47 | ### After hooks
48 |
49 | Promise stuff -- to be appended to the chain via `.then()` after the body is
50 | parsed.
51 |
52 | These hooks will be chaining each other.
53 |
54 | ```js
55 | var API = ajaxapi('https://api.github.com');
56 |
57 | API.after(function (data) {
58 | // do stuff
59 | API.response.headers
60 | API.response.statusCode
61 |
62 | return data;
63 | });
64 | ```
65 |
--------------------------------------------------------------------------------
/ajaxapi.js:
--------------------------------------------------------------------------------
1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;"undefined"!=typeof window?t=window:"undefined"!=typeof global?t=global:"undefined"!=typeof self&&(t=self),t.ajaxapi=e()}}(function(){var e,t,r;return function n(e,t,r){function i(s,f){if(!t[s]){if(!e[s]){var a=typeof require=="function"&&require;if(!f&&a)return a(s,!0);if(o)return o(s,!0);var u=new Error("Cannot find module '"+s+"'");throw u.code="MODULE_NOT_FOUND",u}var c=t[s]={exports:{}};e[s][0].call(c.exports,function(t){var r=e[s][1][t];return i(r?r:t)},c,c.exports,n,e,t,r)}return t[s].exports}var o=typeof require=="function"&&require;for(var s=0;s1){e[r[0].toLowerCase()]=r.slice(1).join(":").trim()}});n(new i(a.status,e,a.responseText))}};a.open(e,t,true);for(var p in r.headers){a.setRequestHeader(p.toLowerCase(),r.headers[p])}a.send(r.body?r.body:null)});f.getBody=function(){return f.then(function(e){return e.getBody()})};return f.nodeify(s)}},{"./lib/handle-qs.js":4,"http-response-object":5,promise:6}],4:[function(e,t,r){"use strict";var n=e("qs").parse;var i=e("qs").stringify;t.exports=o;function o(e,t){e=e.split("?");var r=e[0];var o=(e[1]||"").split("#")[0];var s=e[1]&&e[1].split("#").length>1?"#"+e[1].split("#")[1]:"";var f=n(o);for(var a in t){f[a]=t[a]}o=i(f);if(o!==""){o="?"+o}return r+o+s}},{qs:12}],5:[function(e,t,r){"use strict";t.exports=n;function n(e,t,r){if(typeof e!=="number"){throw new TypeError("statusCode must be a number but was "+typeof e)}if(t===null){throw new TypeError("headers cannot be null")}if(typeof t!=="object"){throw new TypeError("headers must be an object but was "+typeof t)}this.statusCode=e;this.headers={};for(var n in t){this.headers[n.toLowerCase()]=t[n]}this.body=r}n.prototype.getBody=function(e){if(this.statusCode>=300){var t=new Error("Server responded with status code "+this.statusCode+":\n"+this.body.toString());t.statusCode=this.statusCode;t.headers=this.headers;t.body=this.body;throw t}return e?this.body.toString(e):this.body}},{}],6:[function(e,t,r){"use strict";t.exports=e("./lib/core.js");e("./lib/done.js");e("./lib/es6-extensions.js");e("./lib/node-extensions.js")},{"./lib/core.js":7,"./lib/done.js":8,"./lib/es6-extensions.js":9,"./lib/node-extensions.js":10}],7:[function(e,t,r){"use strict";var n=e("asap");t.exports=i;function i(e){if(typeof this!=="object")throw new TypeError("Promises must be constructed via new");if(typeof e!=="function")throw new TypeError("not a function");var t=null;var r=null;var i=[];var f=this;this.then=function(e,t){return new f.constructor(function(r,n){a(new o(e,t,r,n))})};function a(e){if(t===null){i.push(e);return}n(function(){var n=t?e.onFulfilled:e.onRejected;if(n===null){(t?e.resolve:e.reject)(r);return}var i;try{i=n(r)}catch(o){e.reject(o);return}e.resolve(i)})}function u(e){try{if(e===f)throw new TypeError("A promise cannot be resolved with itself.");if(e&&(typeof e==="object"||typeof e==="function")){var n=e.then;if(typeof n==="function"){s(n.bind(e),u,c);return}}t=true;r=e;p()}catch(i){c(i)}}function c(e){t=false;r=e;p()}function p(){for(var e=0,t=i.length;et){i.pop()}i.push(function(e,t){if(e)o(e);else n(t)});var s=e.apply(r,i);if(s&&(typeof s==="object"||typeof s==="function")&&typeof s.then==="function"){n(s)}})}};n.nodeify=function(e){return function(){var t=Array.prototype.slice.call(arguments);var r=typeof t[t.length-1]==="function"?t.pop():null;var o=this;try{return e.apply(this,arguments).nodeify(r,o)}catch(s){if(r===null||typeof r=="undefined"){return new n(function(e,t){t(s)})}else{i(function(){r.call(o,s)})}}}};n.prototype.nodeify=function(e,t){if(typeof e!="function")return this;this.then(function(r){i(function(){e.call(t,null,r)})},function(r){i(function(){e.call(t,r)})})}},{"./core.js":7,asap:11}],11:[function(e,t,r){(function(e){var r={task:void 0,next:null};var n=r;var i=false;var o=void 0;var s=false;function f(){while(r.next){r=r.next;var e=r.task;r.task=void 0;var t=r.domain;if(t){r.domain=void 0;t.enter()}try{e()}catch(n){if(s){if(t){t.exit()}setTimeout(f,0);if(t){t.enter()}throw n}else{setTimeout(function(){throw n},0)}}if(t){t.exit()}}i=false}if(typeof e!=="undefined"&&e.nextTick){s=true;o=function(){e.nextTick(f)}}else if(typeof setImmediate==="function"){if(typeof window!=="undefined"){o=setImmediate.bind(window,f)}else{o=function(){setImmediate(f)}}}else if(typeof MessageChannel!=="undefined"){var a=new MessageChannel;a.port1.onmessage=f;o=function(){a.port2.postMessage(0)}}else{o=function(){setTimeout(f,0)}}function u(t){n=n.next={task:t,domain:s&&e.domain,next:null};if(!i){i=true;o()}}t.exports=u}).call(this,e("_process"))},{_process:2}],12:[function(e,t,r){t.exports=e("./lib/")},{"./lib/":13}],13:[function(e,t,r){var n=e("./stringify");var i=e("./parse");var o={};t.exports={stringify:n,parse:i}},{"./parse":14,"./stringify":15}],14:[function(e,t,r){var n=e("./utils");var i={delimiter:"&",depth:5,arrayLimit:20,parameterLimit:1e3};i.parseValues=function(e,t){var r={};var i=e.split(t.delimiter,t.parameterLimit===Infinity?undefined:t.parameterLimit);for(var o=0,s=i.length;o=0&&f<=r.arrayLimit){o=[];o[f]=i.parseObject(e,t,r)}else{o[s]=i.parseObject(e,t,r)}}return o};i.parseKeys=function(e,t,r){if(!e){return}var n=/^([^\[\]]*)/;var o=/(\[[^\[\]]*\])/g;var s=n.exec(e);if(Object.prototype.hasOwnProperty(s[1])){return}var f=[];if(s[1]){f.push(s[1])}var a=0;while((s=o.exec(e))!==null&&a "/get/john"
31 | */
32 |
33 | /*
34 | * request() : request(method, url, [data])
35 | * Performs a request.
36 | */
37 |
38 | Api.prototype.request = function (method, url, data) {
39 | var options = { headers: {}, qs: {}, json: (data || {}) };
40 |
41 | var context = {
42 | method: method,
43 | url: this.prefix(url),
44 | data: data,
45 | headers: options.headers,
46 | options: options
47 | };
48 |
49 | // apply before hooks (custom)
50 | this._before.forEach(function (fn) { fn.call(this, context); });
51 |
52 | // promise
53 | var pro = Api.request(
54 | context.method,
55 | context.url,
56 | context.options);
57 |
58 | // apply after hooks (defaults)
59 | pro = pro
60 | .then(this.catchCorsError.bind(this))
61 | .then(this.saveResponse.bind(this))
62 | .then(this.parseBody.bind(this));
63 |
64 | // apply after hooks (custom)
65 | this._after.forEach(function (callbacks) {
66 | pro = pro.then(proxy(callbacks[0]), proxy(callbacks[1]));
67 | });
68 |
69 | function proxy (fn) {
70 | return fn ? fn.bind(this) : null;
71 | }
72 |
73 | return pro;
74 | };
75 |
76 | /*
77 | * aliases
78 | */
79 |
80 | Api.prototype.get = buildAlias('GET');
81 | Api.prototype.put = buildAlias('PUT');
82 | Api.prototype.del = buildAlias('DELETE');
83 | Api.prototype.post = buildAlias('POST');
84 | Api.prototype.patch = buildAlias('PATCH');
85 |
86 | /*
87 | * currying helper to make alias methods
88 | */
89 |
90 | function buildAlias (method) {
91 | return function () {
92 | return Api.prototype.request.apply(this,
93 | [method].concat([].slice.call(arguments)));
94 | };
95 | }
96 |
97 | /*
98 | * add before hook
99 | */
100 |
101 | Api.prototype.before = function (fn) {
102 | this._before.push(fn);
103 | return this;
104 | };
105 |
106 | /*
107 | * add after hook
108 | */
109 |
110 | Api.prototype.after = function (okFn, errFn) {
111 | this._after.push([ okFn, errFn ]);
112 | return this;
113 | };
114 |
115 | /*
116 | * parses the body
117 | *
118 | * parseBody({
119 | * body: '{"name":"Joe"}',
120 | * headers: { 'content-type': 'application/json' }
121 | * })
122 | * => { name: "Joe" }
123 | */
124 |
125 | Api.prototype.parseBody = function (res) {
126 | var
127 | body = res.getBody(),
128 | type = res.headers['content-type'];
129 |
130 | if (type && type.match(/^application\/json/))
131 | return JSON.parse(body);
132 | else
133 | return body;
134 | };
135 |
136 | /*
137 | * prefixes a url with the base
138 | */
139 |
140 | Api.prototype.prefix = function (url) {
141 | if (url[0] === '/')
142 | return (this.base || '') + url;
143 | else
144 | return url;
145 | };
146 |
147 | /*
148 | * (internal) saves responses
149 | */
150 |
151 | Api.prototype.saveResponse = function (res) {
152 | this.response = this.res = res;
153 | return res;
154 | };
155 |
156 | /*
157 | * (internal) catches cross origin request errors
158 | */
159 |
160 | Api.prototype.catchCorsError = function (res) {
161 | if (res && res.statusCode === 0)
162 | throw new Error("API failed due to cross-origin error");
163 | return res;
164 | };
165 |
166 | /*
167 | * export
168 | */
169 |
170 | module.exports = Api;
171 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ajaxapi",
3 | "version": "0.0.0-pre5",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "mocha"
8 | },
9 | "author": "Rico Sta. Cruz ",
10 | "license": "MIT",
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/rstacruz/ajaxapi.git"
14 | },
15 | "dependencies": {
16 | "then-request": "2.0.3",
17 | "uri-template-lite": "0.1.10"
18 | },
19 | "devDependencies": {
20 | "browserify": "^8.1.1",
21 | "chai": "^1.10.0",
22 | "mocha": "^2.1.0",
23 | "nock": "^0.57.0",
24 | "uglify-js": "^2.4.16"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test/setup.js:
--------------------------------------------------------------------------------
1 | global.expect = require('chai').expect;
2 | global.nock = require('nock');
3 | global.Ajax = require('../index');
4 | global.Api = null;
5 |
6 | beforeEach(function () {
7 | Api = Ajax('http://api.github.com');
8 | });
9 |
10 | afterEach(function () {
11 | nock.cleanAll();
12 | });
13 |
14 | global.mockApi = function() {
15 | return nock('http://api.github.com');
16 | };
17 |
18 | global.errExpected = function () {
19 | throw new Error("Error was expected out of a promise");
20 | };
21 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | require('./setup');
2 |
3 | describe('200', function () {
4 | var data;
5 |
6 | beforeEach(function () {
7 | mockApi()
8 | .get('/repos/iojs/io.js')
9 | .reply(200, { name: 'io.js' });
10 | });
11 |
12 | beforeEach(function (next) {
13 | return Api.get('/repos/iojs/io.js')
14 | .then(function (d) { data = d; next(); });
15 | });
16 |
17 | it('works', function () {
18 | expect(data.name).eql('io.js');
19 | });
20 | });
21 |
22 | describe('404', function () {
23 | beforeEach(function () {
24 | mockApi()
25 | .get('/repos/iojs/io.js')
26 | .reply(404);
27 | });
28 |
29 | it('has statuscode', function () {
30 | return Api.get('/repos/iojs/io.js')
31 | .then(errExpected, function (err) {
32 | expect(err.statusCode).eql(404);
33 | });
34 | });
35 | });
36 |
--------------------------------------------------------------------------------