jsforce2 1.11.1
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.
- package/LICENSE +22 -0
- package/README.md +74 -0
- package/bin/jsforce +3 -0
- package/bower.json +30 -0
- package/build/jsforce-api-analytics.js +393 -0
- package/build/jsforce-api-analytics.min.js +2 -0
- package/build/jsforce-api-analytics.min.js.map +1 -0
- package/build/jsforce-api-apex.js +183 -0
- package/build/jsforce-api-apex.min.js +2 -0
- package/build/jsforce-api-apex.min.js.map +1 -0
- package/build/jsforce-api-bulk.js +1054 -0
- package/build/jsforce-api-bulk.min.js +2 -0
- package/build/jsforce-api-bulk.min.js.map +1 -0
- package/build/jsforce-api-chatter.js +320 -0
- package/build/jsforce-api-chatter.min.js +2 -0
- package/build/jsforce-api-chatter.min.js.map +1 -0
- package/build/jsforce-api-metadata.js +3020 -0
- package/build/jsforce-api-metadata.min.js +2 -0
- package/build/jsforce-api-metadata.min.js.map +1 -0
- package/build/jsforce-api-soap.js +403 -0
- package/build/jsforce-api-soap.min.js +2 -0
- package/build/jsforce-api-soap.min.js.map +1 -0
- package/build/jsforce-api-streaming.js +3479 -0
- package/build/jsforce-api-streaming.min.js +2 -0
- package/build/jsforce-api-streaming.min.js.map +1 -0
- package/build/jsforce-api-tooling.js +319 -0
- package/build/jsforce-api-tooling.min.js +2 -0
- package/build/jsforce-api-tooling.min.js.map +1 -0
- package/build/jsforce-core.js +25250 -0
- package/build/jsforce-core.min.js +2 -0
- package/build/jsforce-core.min.js.map +1 -0
- package/build/jsforce.js +31637 -0
- package/build/jsforce.min.js +2 -0
- package/build/jsforce.min.js.map +1 -0
- package/core.js +1 -0
- package/index.js +1 -0
- package/lib/VERSION.js +2 -0
- package/lib/_required.js +29 -0
- package/lib/api/analytics.js +387 -0
- package/lib/api/apex.js +177 -0
- package/lib/api/bulk.js +862 -0
- package/lib/api/chatter.js +314 -0
- package/lib/api/index.js +8 -0
- package/lib/api/metadata.js +848 -0
- package/lib/api/soap.js +397 -0
- package/lib/api/streaming-extension.js +136 -0
- package/lib/api/streaming.js +270 -0
- package/lib/api/tooling.js +313 -0
- package/lib/browser/canvas.js +90 -0
- package/lib/browser/client.js +241 -0
- package/lib/browser/core.js +5 -0
- package/lib/browser/jsforce.js +6 -0
- package/lib/browser/jsonp.js +52 -0
- package/lib/browser/request.js +70 -0
- package/lib/cache.js +252 -0
- package/lib/cli/cli.js +431 -0
- package/lib/cli/repl.js +337 -0
- package/lib/connection.js +1881 -0
- package/lib/core.js +16 -0
- package/lib/csv.js +50 -0
- package/lib/date.js +163 -0
- package/lib/http-api.js +300 -0
- package/lib/jsforce.js +10 -0
- package/lib/logger.js +52 -0
- package/lib/oauth2.js +206 -0
- package/lib/process.js +275 -0
- package/lib/promise.js +164 -0
- package/lib/query.js +881 -0
- package/lib/quick-action.js +90 -0
- package/lib/record-stream.js +305 -0
- package/lib/record.js +107 -0
- package/lib/registry/file-registry.js +48 -0
- package/lib/registry/index.js +3 -0
- package/lib/registry/registry.js +111 -0
- package/lib/require.js +14 -0
- package/lib/soap.js +207 -0
- package/lib/sobject.js +558 -0
- package/lib/soql-builder.js +236 -0
- package/lib/transport.js +233 -0
- package/package.json +110 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Browser client connection management class
|
|
3
|
+
* @author Shinichi Tomita <shinichi.tomita@gmail.com>
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
var events = require('events'),
|
|
9
|
+
inherits = require('inherits'),
|
|
10
|
+
qs = require('querystring'),
|
|
11
|
+
_ = require('lodash/core'),
|
|
12
|
+
Connection = require('../connection'),
|
|
13
|
+
OAuth2 = require('../oauth2');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @private
|
|
17
|
+
*/
|
|
18
|
+
function popupWin(url, w, h) {
|
|
19
|
+
var left = (screen.width/2)-(w/2);
|
|
20
|
+
var top = (screen.height/2)-(h/2);
|
|
21
|
+
return window.open(url, null, 'location=yes,toolbar=no,status=no,menubar=no,width='+w+',height='+h+',top='+top+',left='+left);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function handleCallbackResponse() {
|
|
25
|
+
var res = checkCallbackResponse();
|
|
26
|
+
var state = localStorage.getItem('jsforce_state');
|
|
27
|
+
if (res && state && res.body.state === state) {
|
|
28
|
+
localStorage.removeItem('jsforce_state');
|
|
29
|
+
var states = state.split('.');
|
|
30
|
+
var prefix = states[0], promptType = states[1];
|
|
31
|
+
var cli = new Client(prefix);
|
|
32
|
+
if (res.success) {
|
|
33
|
+
cli._storeTokens(res.body);
|
|
34
|
+
location.hash = '';
|
|
35
|
+
} else {
|
|
36
|
+
cli._storeError(res.body);
|
|
37
|
+
}
|
|
38
|
+
if (promptType === 'popup') { window.close(); }
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @private
|
|
45
|
+
*/
|
|
46
|
+
function checkCallbackResponse() {
|
|
47
|
+
var params;
|
|
48
|
+
if (window.location.hash) {
|
|
49
|
+
params = qs.parse(window.location.hash.substring(1));
|
|
50
|
+
if (params.access_token) {
|
|
51
|
+
return { success: true, body: params };
|
|
52
|
+
}
|
|
53
|
+
} else if (window.location.search) {
|
|
54
|
+
params = qs.parse(window.location.search.substring(1));
|
|
55
|
+
if (params.error) {
|
|
56
|
+
return { success: false, body: params };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** @private **/
|
|
62
|
+
var clientIdx = 0;
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @class
|
|
67
|
+
* @todo add document
|
|
68
|
+
*/
|
|
69
|
+
var Client = function(prefix) {
|
|
70
|
+
this._prefix = prefix || 'jsforce' + clientIdx++;
|
|
71
|
+
this.connection = null;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
inherits(Client, events.EventEmitter);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
*
|
|
78
|
+
*/
|
|
79
|
+
Client.prototype.init = function(config) {
|
|
80
|
+
if (handleCallbackResponse()) { return; }
|
|
81
|
+
this.config = config;
|
|
82
|
+
this.connection = new Connection(config);
|
|
83
|
+
var tokens = this._getTokens();
|
|
84
|
+
if (tokens) {
|
|
85
|
+
this.connection.initialize(tokens);
|
|
86
|
+
var self = this;
|
|
87
|
+
setTimeout(function() {
|
|
88
|
+
self.emit('connect', self.connection);
|
|
89
|
+
}, 10);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
*
|
|
95
|
+
*/
|
|
96
|
+
Client.prototype.login = function(options, callback) {
|
|
97
|
+
if (_.isFunction(options)) {
|
|
98
|
+
callback = options;
|
|
99
|
+
options = {};
|
|
100
|
+
}
|
|
101
|
+
options = options || {};
|
|
102
|
+
callback = callback || function(){ };
|
|
103
|
+
_.extend(options, this.config);
|
|
104
|
+
var self = this;
|
|
105
|
+
this._prompt(options, callback);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
Client.prototype._prompt = function(options, callback) {
|
|
110
|
+
var self = this;
|
|
111
|
+
var oauth2 = new OAuth2(options);
|
|
112
|
+
var rand = Math.random().toString(36).substring(2);
|
|
113
|
+
var state = [ this._prefix, "popup", rand ].join('.');
|
|
114
|
+
localStorage.setItem("jsforce_state", state);
|
|
115
|
+
var authzUrl = oauth2.getAuthorizationUrl({
|
|
116
|
+
response_type: 'token',
|
|
117
|
+
scope : options.scope,
|
|
118
|
+
state: state
|
|
119
|
+
});
|
|
120
|
+
var size = options.size || {};
|
|
121
|
+
var pw = popupWin(authzUrl, size.width || 912, size.height || 513);
|
|
122
|
+
if (!pw) {
|
|
123
|
+
state = [ this._prefix, "redirect", rand ].join('.');
|
|
124
|
+
localStorage.setItem("jsforce_state", state);
|
|
125
|
+
authzUrl = oauth2.getAuthorizationUrl({
|
|
126
|
+
response_type: 'token',
|
|
127
|
+
scope : options.scope,
|
|
128
|
+
state: state
|
|
129
|
+
});
|
|
130
|
+
location.href = authzUrl;
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
self._removeTokens();
|
|
134
|
+
var pid = setInterval(function() {
|
|
135
|
+
try {
|
|
136
|
+
if (!pw || pw.closed) {
|
|
137
|
+
clearInterval(pid);
|
|
138
|
+
var tokens = self._getTokens();
|
|
139
|
+
if (tokens) {
|
|
140
|
+
self.connection.initialize(tokens);
|
|
141
|
+
self.emit('connect', self.connection);
|
|
142
|
+
callback(null, { status: 'connect' });
|
|
143
|
+
} else {
|
|
144
|
+
var err = self._getError();
|
|
145
|
+
if (err) {
|
|
146
|
+
callback(new Error(err.error + ": " + err.error_description));
|
|
147
|
+
} else {
|
|
148
|
+
callback(null, { status: 'cancel' });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} catch(e) {}
|
|
153
|
+
}, 1000);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
*
|
|
158
|
+
*/
|
|
159
|
+
Client.prototype.isLoggedIn = function() {
|
|
160
|
+
return !!(this.connection && this.connection.accessToken);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
*
|
|
165
|
+
*/
|
|
166
|
+
Client.prototype.logout = function() {
|
|
167
|
+
this.connection.logout();
|
|
168
|
+
this._removeTokens();
|
|
169
|
+
this.emit('disconnect');
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @private
|
|
174
|
+
*/
|
|
175
|
+
Client.prototype._getTokens = function() {
|
|
176
|
+
var regexp = new RegExp("(^|;\\s*)"+this._prefix+"_loggedin=true(;|$)");
|
|
177
|
+
if (document.cookie.match(regexp)) {
|
|
178
|
+
var issuedAt = Number(localStorage.getItem(this._prefix+'_issued_at'));
|
|
179
|
+
if (Date.now() < issuedAt + 2 * 60 * 60 * 1000) { // 2 hours
|
|
180
|
+
var userInfo;
|
|
181
|
+
var idUrl = localStorage.getItem(this._prefix + '_id');
|
|
182
|
+
if (idUrl) {
|
|
183
|
+
var ids = idUrl.split('/');
|
|
184
|
+
userInfo = { id: ids.pop(), organizationId: ids.pop(), url: idUrl };
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
accessToken: localStorage.getItem(this._prefix + '_access_token'),
|
|
188
|
+
instanceUrl: localStorage.getItem(this._prefix + '_instance_url'),
|
|
189
|
+
userInfo: userInfo
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @private
|
|
198
|
+
*/
|
|
199
|
+
Client.prototype._storeTokens = function(params) {
|
|
200
|
+
localStorage.setItem(this._prefix + '_access_token', params.access_token);
|
|
201
|
+
localStorage.setItem(this._prefix + '_instance_url', params.instance_url);
|
|
202
|
+
localStorage.setItem(this._prefix + '_issued_at', params.issued_at);
|
|
203
|
+
localStorage.setItem(this._prefix + '_id', params.id);
|
|
204
|
+
document.cookie = this._prefix + '_loggedin=true;';
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @private
|
|
209
|
+
*/
|
|
210
|
+
Client.prototype._removeTokens = function() {
|
|
211
|
+
localStorage.removeItem(this._prefix + '_access_token');
|
|
212
|
+
localStorage.removeItem(this._prefix + '_instance_url');
|
|
213
|
+
localStorage.removeItem(this._prefix + '_issued_at');
|
|
214
|
+
localStorage.removeItem(this._prefix + '_id');
|
|
215
|
+
document.cookie = this._prefix + '_loggedin=';
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* @private
|
|
220
|
+
*/
|
|
221
|
+
Client.prototype._getError = function() {
|
|
222
|
+
try {
|
|
223
|
+
var err = JSON.parse(localStorage.getItem(this._prefix + '_error'));
|
|
224
|
+
localStorage.removeItem(this._prefix + '_error');
|
|
225
|
+
return err;
|
|
226
|
+
} catch(e) {}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @private
|
|
231
|
+
*/
|
|
232
|
+
Client.prototype._storeError = function(err) {
|
|
233
|
+
localStorage.setItem(this._prefix + '_error', JSON.stringify(err));
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
*
|
|
238
|
+
*/
|
|
239
|
+
module.exports = new Client();
|
|
240
|
+
|
|
241
|
+
module.exports.Client = Client;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*global window, document */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var _index = 0;
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
|
|
8
|
+
supported: typeof window !== 'undefined' && typeof document !== 'undefined',
|
|
9
|
+
|
|
10
|
+
createRequest: function(jsonpParam, timeout) {
|
|
11
|
+
jsonpParam = jsonpParam || 'callback';
|
|
12
|
+
timeout = timeout || 10000;
|
|
13
|
+
|
|
14
|
+
return function(params, callback) {
|
|
15
|
+
if (params.method.toUpperCase() !== 'GET') {
|
|
16
|
+
return callback(new Error('JSONP only supports GET request.'));
|
|
17
|
+
}
|
|
18
|
+
var cbFuncName = '_jsforce_jsonpCallback_' + (++_index);
|
|
19
|
+
var callbacks = window;
|
|
20
|
+
var url = params.url;
|
|
21
|
+
url += url.indexOf('?')>0 ? '&' : '?';
|
|
22
|
+
url += jsonpParam + '=' + cbFuncName;
|
|
23
|
+
|
|
24
|
+
var script = document.createElement('script');
|
|
25
|
+
script.type = 'text/javascript';
|
|
26
|
+
script.src = url;
|
|
27
|
+
document.documentElement.appendChild(script);
|
|
28
|
+
|
|
29
|
+
var pid = setTimeout(function() {
|
|
30
|
+
cleanup();
|
|
31
|
+
callback(new Error("JSONP call time out."));
|
|
32
|
+
}, timeout);
|
|
33
|
+
|
|
34
|
+
callbacks[cbFuncName] = function(res) {
|
|
35
|
+
cleanup();
|
|
36
|
+
callback(null, {
|
|
37
|
+
statusCode: 200,
|
|
38
|
+
headers: { "content-type": "application/json" },
|
|
39
|
+
body: JSON.stringify(res)
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
var cleanup = function() {
|
|
44
|
+
clearTimeout(pid);
|
|
45
|
+
document.documentElement.removeChild(script);
|
|
46
|
+
delete callbacks[cbFuncName];
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var Duplex = require('readable-stream').Duplex;
|
|
4
|
+
var _ = require('lodash/core');
|
|
5
|
+
|
|
6
|
+
module.exports = function(params, callback) {
|
|
7
|
+
var xhr = new XMLHttpRequest();
|
|
8
|
+
xhr.open(params.method, params.url);
|
|
9
|
+
if (params.headers) {
|
|
10
|
+
for (var header in params.headers) {
|
|
11
|
+
xhr.setRequestHeader(header, params.headers[header]);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
xhr.setRequestHeader("Accept", "*/*");
|
|
15
|
+
var response;
|
|
16
|
+
var str = new Duplex();
|
|
17
|
+
str._read = function(size) {
|
|
18
|
+
if (response) {
|
|
19
|
+
str.push(response.body);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var bufs = [];
|
|
23
|
+
var sent = false;
|
|
24
|
+
str._write = function(chunk, encoding, callback) {
|
|
25
|
+
bufs.push(chunk.toString(encoding === "buffer" ? "binary" : encoding));
|
|
26
|
+
callback();
|
|
27
|
+
};
|
|
28
|
+
str.on('finish', function() {
|
|
29
|
+
if (!sent) {
|
|
30
|
+
xhr.send(bufs.join(''));
|
|
31
|
+
sent = true;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
if (params.body || params.body === "" || !/^(put|post|patch)$/i.test(params.method)) {
|
|
35
|
+
xhr.send(params.body);
|
|
36
|
+
sent = true;
|
|
37
|
+
}
|
|
38
|
+
xhr.onreadystatechange = function() {
|
|
39
|
+
if (xhr.readyState === 4) {
|
|
40
|
+
var headerNames = getResponseHeaderNames(xhr);
|
|
41
|
+
var headers = {};
|
|
42
|
+
_.forEach(headerNames, function(headerName) {
|
|
43
|
+
if (headerName) {
|
|
44
|
+
headers[headerName] = xhr.getResponseHeader(headerName);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
response = {
|
|
48
|
+
statusCode: xhr.status,
|
|
49
|
+
headers: headers,
|
|
50
|
+
body: xhr.response
|
|
51
|
+
};
|
|
52
|
+
if (!response.statusCode) {
|
|
53
|
+
response.statusCode = 400;
|
|
54
|
+
response.body = "Access Declined";
|
|
55
|
+
}
|
|
56
|
+
if (callback) {
|
|
57
|
+
callback(null, response, response.body);
|
|
58
|
+
}
|
|
59
|
+
str.end();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
return str;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function getResponseHeaderNames(xhr) {
|
|
66
|
+
var headerLines = (xhr.getAllResponseHeaders() || "").split(/[\r\n]+/);
|
|
67
|
+
return _.map(headerLines, function(headerLine) {
|
|
68
|
+
return headerLine.split(/\s*:/)[0].toLowerCase();
|
|
69
|
+
});
|
|
70
|
+
}
|
package/lib/cache.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Manages asynchronous method response cache
|
|
3
|
+
* @author Shinichi Tomita <shinichi.tomita@gmail.com>
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
var events = require('events'),
|
|
9
|
+
inherits = require('inherits'),
|
|
10
|
+
_ = require('lodash/core');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Class for managing cache entry
|
|
14
|
+
*
|
|
15
|
+
* @private
|
|
16
|
+
* @class
|
|
17
|
+
* @constructor
|
|
18
|
+
* @template T
|
|
19
|
+
*/
|
|
20
|
+
var CacheEntry = function() {
|
|
21
|
+
this.fetching = false;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
inherits(CacheEntry, events.EventEmitter);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get value in the cache entry
|
|
28
|
+
*
|
|
29
|
+
* @param {Callback.<T>} [callback] - Callback function callbacked the cache entry updated
|
|
30
|
+
* @returns {T|undefined}
|
|
31
|
+
*/
|
|
32
|
+
CacheEntry.prototype.get = function(callback) {
|
|
33
|
+
if (!callback) {
|
|
34
|
+
return this._value;
|
|
35
|
+
} else {
|
|
36
|
+
this.once('value', callback);
|
|
37
|
+
if (!_.isUndefined(this._value)) {
|
|
38
|
+
this.emit('value', this._value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Set value in the cache entry
|
|
45
|
+
*
|
|
46
|
+
* @param {T} [value] - A value for caching
|
|
47
|
+
*/
|
|
48
|
+
CacheEntry.prototype.set = function(value) {
|
|
49
|
+
this._value = value;
|
|
50
|
+
this.emit('value', this._value);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Clear cached value
|
|
55
|
+
*/
|
|
56
|
+
CacheEntry.prototype.clear = function() {
|
|
57
|
+
this.fetching = false;
|
|
58
|
+
delete this._value;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Caching manager for async methods
|
|
64
|
+
*
|
|
65
|
+
* @class
|
|
66
|
+
* @constructor
|
|
67
|
+
*/
|
|
68
|
+
var Cache = function() {
|
|
69
|
+
this._entries = {};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* retrive cache entry, or create if not exists.
|
|
74
|
+
*
|
|
75
|
+
* @param {String} [key] - Key of cache entry
|
|
76
|
+
* @returns {CacheEntry}
|
|
77
|
+
*/
|
|
78
|
+
Cache.prototype.get = function(key) {
|
|
79
|
+
if (key && this._entries[key]) {
|
|
80
|
+
return this._entries[key];
|
|
81
|
+
} else {
|
|
82
|
+
var entry = new CacheEntry();
|
|
83
|
+
this._entries[key] = entry;
|
|
84
|
+
return entry;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* clear cache entries prefix matching given key
|
|
90
|
+
* @param {String} [key] - Key prefix of cache entry to clear
|
|
91
|
+
*/
|
|
92
|
+
Cache.prototype.clear = function(key) {
|
|
93
|
+
for (var k in this._entries) {
|
|
94
|
+
if (!key || k.indexOf(key) === 0) {
|
|
95
|
+
this._entries[k].clear();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* create and return cache key from namespace and serialized arguments.
|
|
102
|
+
* @private
|
|
103
|
+
*/
|
|
104
|
+
function createCacheKey(namespace, args) {
|
|
105
|
+
args = Array.prototype.slice.apply(args);
|
|
106
|
+
return namespace + '(' + _.map(args, function(a){ return JSON.stringify(a); }).join(',') + ')';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Enable caching for async call fn to intercept the response and store it to cache.
|
|
111
|
+
* The original async calll fn is always invoked.
|
|
112
|
+
*
|
|
113
|
+
* @protected
|
|
114
|
+
* @param {Function} fn - Function to covert cacheable
|
|
115
|
+
* @param {Object} [scope] - Scope of function call
|
|
116
|
+
* @param {Object} [options] - Options
|
|
117
|
+
* @return {Function} - Cached version of function
|
|
118
|
+
*/
|
|
119
|
+
Cache.prototype.makeResponseCacheable = function(fn, scope, options) {
|
|
120
|
+
var cache = this;
|
|
121
|
+
options = options || {};
|
|
122
|
+
return function() {
|
|
123
|
+
var args = Array.prototype.slice.apply(arguments);
|
|
124
|
+
var callback = args.pop();
|
|
125
|
+
if (!_.isFunction(callback)) {
|
|
126
|
+
args.push(callback);
|
|
127
|
+
callback = null;
|
|
128
|
+
}
|
|
129
|
+
var keys = _.isString(options.key) ? options.key :
|
|
130
|
+
_.isFunction(options.key) ? options.key.apply(scope, args) :
|
|
131
|
+
createCacheKey(options.namespace, args);
|
|
132
|
+
if (!Array.isArray(keys)) { keys = [ keys ]; }
|
|
133
|
+
var entries = [];
|
|
134
|
+
keys.forEach(function (key) {
|
|
135
|
+
var entry = cache.get(key);
|
|
136
|
+
entry.fetching = true;
|
|
137
|
+
entries.push(entry);
|
|
138
|
+
})
|
|
139
|
+
if (callback) {
|
|
140
|
+
args.push(function(err, result) {
|
|
141
|
+
if (Array.isArray(result) && result.length == entries.length) {
|
|
142
|
+
entries.forEach(function (entry, index) {
|
|
143
|
+
entry.set({ error: err, result: result[index] });
|
|
144
|
+
})
|
|
145
|
+
} else {
|
|
146
|
+
entries.forEach(function (entry) {
|
|
147
|
+
entry.set({ error: err, result: result });
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
callback(err, result);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
var ret, error;
|
|
154
|
+
try {
|
|
155
|
+
ret = fn.apply(scope || this, args);
|
|
156
|
+
} catch(e) {
|
|
157
|
+
error = e;
|
|
158
|
+
}
|
|
159
|
+
if (ret && _.isFunction(ret.then)) { // if the returned value is promise
|
|
160
|
+
if (!callback) {
|
|
161
|
+
return ret.then(function(result) {
|
|
162
|
+
if (Array.isArray(result) && result.length == entries.length) {
|
|
163
|
+
entries.forEach(function (entry, index) {
|
|
164
|
+
entry.set({ error: undefined, result: result[index] });
|
|
165
|
+
})
|
|
166
|
+
} else {
|
|
167
|
+
entries.forEach(function (entry) {
|
|
168
|
+
entry.set({ error: undefined, result: result });
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
}, function(err) {
|
|
173
|
+
if (Array.isArray(err) && err.length == entries.length) {
|
|
174
|
+
entries.forEach(function (entry, index) {
|
|
175
|
+
entry.set({ error: err[index], result: undefined });
|
|
176
|
+
})
|
|
177
|
+
} else {
|
|
178
|
+
entries.forEach(function (entry) {
|
|
179
|
+
entry.set({ error: err, result: undefined });
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
throw err;
|
|
183
|
+
});
|
|
184
|
+
} else {
|
|
185
|
+
return ret;
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
if (Array.isArray(ret) && ret.length == entries.length) {
|
|
189
|
+
entries.forEach(function (entry, index) {
|
|
190
|
+
entry.set({ error: error, result: ret[index] });
|
|
191
|
+
})
|
|
192
|
+
} else {
|
|
193
|
+
entries.forEach(function (entry) {
|
|
194
|
+
entry.set({ error: error, result: ret });
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
if (error) { throw error; }
|
|
198
|
+
return ret;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Enable caching for async call fn to lookup the response cache first, then invoke original if no cached value.
|
|
205
|
+
*
|
|
206
|
+
* @protected
|
|
207
|
+
* @param {Function} fn - Function to covert cacheable
|
|
208
|
+
* @param {Object} [scope] - Scope of function call
|
|
209
|
+
* @param {Object} [options] - Options
|
|
210
|
+
* @return {Function} - Cached version of function
|
|
211
|
+
*/
|
|
212
|
+
Cache.prototype.makeCacheable = function(fn, scope, options) {
|
|
213
|
+
var cache = this;
|
|
214
|
+
options = options || {};
|
|
215
|
+
var $fn = function() {
|
|
216
|
+
var args = Array.prototype.slice.apply(arguments);
|
|
217
|
+
var callback = args.pop();
|
|
218
|
+
if (!_.isFunction(callback)) {
|
|
219
|
+
args.push(callback);
|
|
220
|
+
}
|
|
221
|
+
var key = _.isString(options.key) ? options.key :
|
|
222
|
+
_.isFunction(options.key) ? options.key.apply(scope, args) :
|
|
223
|
+
createCacheKey(options.namespace, args);
|
|
224
|
+
var entry = cache.get(key);
|
|
225
|
+
if (!_.isFunction(callback)) { // if callback is not given in last arg, return cached result (immediate).
|
|
226
|
+
var value = entry.get();
|
|
227
|
+
if (!value) { throw new Error('Function call result is not cached yet.'); }
|
|
228
|
+
if (value.error) { throw value.error; }
|
|
229
|
+
return value.result;
|
|
230
|
+
}
|
|
231
|
+
entry.get(function(value) {
|
|
232
|
+
callback(value.error, value.result);
|
|
233
|
+
});
|
|
234
|
+
if (!entry.fetching) { // only when no other client is calling function
|
|
235
|
+
entry.fetching = true;
|
|
236
|
+
args.push(function(err, result) {
|
|
237
|
+
entry.set({ error: err, result: result });
|
|
238
|
+
});
|
|
239
|
+
fn.apply(scope || this, args);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
$fn.clear = function() {
|
|
243
|
+
var key = _.isString(options.key) ? options.key :
|
|
244
|
+
_.isFunction(options.key) ? options.key.apply(scope, arguments) :
|
|
245
|
+
createCacheKey(options.namespace, arguments);
|
|
246
|
+
cache.clear(key);
|
|
247
|
+
};
|
|
248
|
+
return $fn;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
module.exports = Cache;
|