@stinkycomputing/cachearoo 1.0.24
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/.github/workflows/build_and_test.yml +29 -0
- package/.github/workflows/npm-publish.yml +30 -0
- package/README.md +400 -0
- package/dist/cachearoo-node.d.ts +19 -0
- package/dist/cachearoo-node.js +31 -0
- package/dist/cachearoo.d.ts +19 -0
- package/dist/cachearoo.es.js +2996 -0
- package/dist/cachearoo.min.js +15 -0
- package/dist/libs/cro.connection.d.ts +71 -0
- package/dist/libs/cro.connection.js +401 -0
- package/dist/libs/cro.d.ts +82 -0
- package/dist/libs/cro.js +283 -0
- package/dist/libs/cro.msg.competing-consumers.d.ts +46 -0
- package/dist/libs/cro.msg.competing-consumers.js +204 -0
- package/dist/libs/cro.msg.request-reply.d.ts +32 -0
- package/dist/libs/cro.msg.request-reply.js +117 -0
- package/package.json +36 -0
- package/tsconfig-node.json +32 -0
package/dist/libs/cro.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Cachearoo = exports.RequestOptionsInternal = exports.RequestOptions = exports.CachearooSettings = exports.generateUUID = exports.AlreadyExistsError = exports.TimeoutError = void 0;
|
|
4
|
+
const fetch = global.fetch;
|
|
5
|
+
const cro_connection_1 = require("./cro.connection");
|
|
6
|
+
const CACHEAROO_DEFAULT_PORT = 4300;
|
|
7
|
+
const HTTP_RESPONSE_OK = 200;
|
|
8
|
+
const HTTP_RESPONSE_NOT_FOUND = 404;
|
|
9
|
+
const HTTP_RESPONSE_CREATED = 201;
|
|
10
|
+
const HTTP_RESPONSE_NOT_MODIFIED = 304;
|
|
11
|
+
const MAX_CONCURRENT = 50;
|
|
12
|
+
class TimeoutError extends Error {
|
|
13
|
+
progressTimeout;
|
|
14
|
+
constructor(message, progressTimeout) {
|
|
15
|
+
super(message);
|
|
16
|
+
Error.captureStackTrace(this, this.constructor);
|
|
17
|
+
this.progressTimeout = progressTimeout;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.TimeoutError = TimeoutError;
|
|
21
|
+
class AlreadyExistsError extends Error {
|
|
22
|
+
constructor(message) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = 'AlreadyExistsError';
|
|
25
|
+
Error.captureStackTrace(this, this.constructor);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.AlreadyExistsError = AlreadyExistsError;
|
|
29
|
+
function generateUUID() {
|
|
30
|
+
function s4() {
|
|
31
|
+
return Math.floor((1 + Math.random()) * 0x10000)
|
|
32
|
+
.toString(16)
|
|
33
|
+
.substring(1);
|
|
34
|
+
}
|
|
35
|
+
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
|
|
36
|
+
s4() + '-' + s4() + s4() + s4();
|
|
37
|
+
}
|
|
38
|
+
exports.generateUUID = generateUUID;
|
|
39
|
+
class CachearooSettings {
|
|
40
|
+
bucket = undefined;
|
|
41
|
+
host = undefined;
|
|
42
|
+
port = undefined;
|
|
43
|
+
path = '';
|
|
44
|
+
apiKey = undefined;
|
|
45
|
+
secure = false;
|
|
46
|
+
enablePing = false;
|
|
47
|
+
pingInterval = 5000;
|
|
48
|
+
clientId = undefined;
|
|
49
|
+
wsSubDomain = undefined;
|
|
50
|
+
}
|
|
51
|
+
exports.CachearooSettings = CachearooSettings;
|
|
52
|
+
;
|
|
53
|
+
class RequestOptions {
|
|
54
|
+
bucket = undefined;
|
|
55
|
+
data = undefined;
|
|
56
|
+
failIfExists = false;
|
|
57
|
+
expire = undefined;
|
|
58
|
+
async = true;
|
|
59
|
+
forceHttp = false;
|
|
60
|
+
keysOnly = false;
|
|
61
|
+
filter = undefined;
|
|
62
|
+
removeDataFromReply = false;
|
|
63
|
+
constructor(options) {
|
|
64
|
+
if (options) {
|
|
65
|
+
this.bucket = options.bucket;
|
|
66
|
+
this.data = options.data;
|
|
67
|
+
this.failIfExists = options.failIfExists;
|
|
68
|
+
this.expire = options.expire;
|
|
69
|
+
this.async = options.async;
|
|
70
|
+
this.forceHttp = options.forceHttp;
|
|
71
|
+
this.keysOnly = options.keysOnly;
|
|
72
|
+
this.filter = options.filter;
|
|
73
|
+
this.removeDataFromReply = options.removeDataFromReply;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.RequestOptions = RequestOptions;
|
|
78
|
+
class RequestOptionsInternal extends RequestOptions {
|
|
79
|
+
url = undefined;
|
|
80
|
+
method = undefined;
|
|
81
|
+
key = undefined;
|
|
82
|
+
forceHttp = false;
|
|
83
|
+
keysOnly = false;
|
|
84
|
+
filter = undefined;
|
|
85
|
+
removeDataFromReply = false;
|
|
86
|
+
isList = false;
|
|
87
|
+
}
|
|
88
|
+
exports.RequestOptionsInternal = RequestOptionsInternal;
|
|
89
|
+
class Cachearoo {
|
|
90
|
+
connection;
|
|
91
|
+
settings;
|
|
92
|
+
requestQueue;
|
|
93
|
+
pendingRequests = 0;
|
|
94
|
+
internalizeRequestOptions(key, options, isList = false) {
|
|
95
|
+
var bucket = options.bucket || this.settings.bucket || '';
|
|
96
|
+
if (options.async == null)
|
|
97
|
+
options.async = true;
|
|
98
|
+
var opt = new RequestOptionsInternal(null);
|
|
99
|
+
if (options.keysOnly !== undefined)
|
|
100
|
+
opt.keysOnly = options.keysOnly;
|
|
101
|
+
if (options.filter !== undefined)
|
|
102
|
+
opt.filter = options.filter;
|
|
103
|
+
opt.url = this.getUrl(key, bucket, opt.keysOnly, opt.filter, isList);
|
|
104
|
+
opt.bucket = bucket;
|
|
105
|
+
opt.key = key;
|
|
106
|
+
opt.async = options.async;
|
|
107
|
+
opt.failIfExists = options.failIfExists;
|
|
108
|
+
opt.forceHttp = options.forceHttp;
|
|
109
|
+
opt.expire = options.expire;
|
|
110
|
+
opt.isList = isList;
|
|
111
|
+
opt.removeDataFromReply = options.removeDataFromReply;
|
|
112
|
+
return opt;
|
|
113
|
+
}
|
|
114
|
+
checkOptions(options) {
|
|
115
|
+
if (options instanceof RequestOptions === false) {
|
|
116
|
+
options = new RequestOptions(options);
|
|
117
|
+
}
|
|
118
|
+
return options;
|
|
119
|
+
}
|
|
120
|
+
async read(key, options) {
|
|
121
|
+
let opt = this.internalizeRequestOptions(key, this.checkOptions(options));
|
|
122
|
+
opt.method = 'GET';
|
|
123
|
+
return this.request(opt);
|
|
124
|
+
}
|
|
125
|
+
async list(options) {
|
|
126
|
+
let optWithKeysOnly = { ...options };
|
|
127
|
+
if (optWithKeysOnly.keysOnly === undefined)
|
|
128
|
+
optWithKeysOnly.keysOnly = true;
|
|
129
|
+
let opt = this.internalizeRequestOptions('', this.checkOptions(optWithKeysOnly), true);
|
|
130
|
+
opt.method = 'GET';
|
|
131
|
+
return (await this.request(opt));
|
|
132
|
+
}
|
|
133
|
+
async write(key, value, options) {
|
|
134
|
+
let opt = this.internalizeRequestOptions(key, this.checkOptions(options));
|
|
135
|
+
opt.method = opt.key ? 'PUT' : 'POST';
|
|
136
|
+
opt.data = value;
|
|
137
|
+
return this.request(opt);
|
|
138
|
+
}
|
|
139
|
+
async patch(key, patch, options) {
|
|
140
|
+
let opt = this.internalizeRequestOptions(key, this.checkOptions(options));
|
|
141
|
+
opt.method = 'PATCH';
|
|
142
|
+
opt.data = patch;
|
|
143
|
+
return this.request(opt);
|
|
144
|
+
}
|
|
145
|
+
async remove(key, options) {
|
|
146
|
+
let opt = this.internalizeRequestOptions(key, this.checkOptions(options));
|
|
147
|
+
opt.method = 'DELETE';
|
|
148
|
+
return this.request(opt);
|
|
149
|
+
}
|
|
150
|
+
async repair(bucket) {
|
|
151
|
+
var protocol = this.settings.secure ? 'https://' : 'http://';
|
|
152
|
+
var url = protocol + this.settings.host + ':' + this.settings.port + this.settings.path + '_repair/' + bucket;
|
|
153
|
+
const opt = {
|
|
154
|
+
url,
|
|
155
|
+
method: 'GET',
|
|
156
|
+
};
|
|
157
|
+
return await this.requestHttp(opt);
|
|
158
|
+
}
|
|
159
|
+
getUrl(key, bucket, keysOnly, filter, isList = false) {
|
|
160
|
+
if (!key)
|
|
161
|
+
key = '';
|
|
162
|
+
var suffix = 'd=' + new Date().getTime();
|
|
163
|
+
suffix = ((key.indexOf('?') > -1) ? '&' : '?') + suffix;
|
|
164
|
+
if (keysOnly)
|
|
165
|
+
suffix = suffix + '&keysOnly=true';
|
|
166
|
+
if ((filter != null) && (filter !== ''))
|
|
167
|
+
suffix = suffix + '&filter=' + filter;
|
|
168
|
+
var protocol = this.settings.secure ? 'https://' : 'http://';
|
|
169
|
+
if (isList) {
|
|
170
|
+
return protocol + this.settings.host + ':' + this.settings.port + this.settings.path + '_data/' + encodeURIComponent(bucket) + suffix;
|
|
171
|
+
}
|
|
172
|
+
return protocol + this.settings.host + ':' + this.settings.port + this.settings.path + '_data/' + encodeURIComponent(bucket) + '/' + encodeURIComponent(key) + suffix;
|
|
173
|
+
}
|
|
174
|
+
async requestHttp(options) {
|
|
175
|
+
const method = options.method || 'GET';
|
|
176
|
+
let request = {
|
|
177
|
+
method,
|
|
178
|
+
headers: {},
|
|
179
|
+
body: undefined,
|
|
180
|
+
};
|
|
181
|
+
if (this.settings.apiKey) {
|
|
182
|
+
request.headers['API-Key'] = this.settings.apiKey;
|
|
183
|
+
request.headers['x-api-key'] = this.settings.apiKey;
|
|
184
|
+
}
|
|
185
|
+
if ((method == 'PUT') || (method == 'POST') || (method == 'PATCH')) {
|
|
186
|
+
request.body = JSON.stringify(options.data);
|
|
187
|
+
request.headers['Content-Type'] = 'application/json';
|
|
188
|
+
if (options.failIfExists) {
|
|
189
|
+
request.headers['failIfExists'] = options.failIfExists;
|
|
190
|
+
}
|
|
191
|
+
;
|
|
192
|
+
if (options.removeDataFromReply) {
|
|
193
|
+
request.headers['RemoveDataFromReply'] = options.removeDataFromReply;
|
|
194
|
+
}
|
|
195
|
+
;
|
|
196
|
+
if (options.expire) {
|
|
197
|
+
request.headers['expire'] = options.expire;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const response = await fetch(options.url, request);
|
|
201
|
+
if (((method === 'GET') || (method === 'PATCH')) && ([HTTP_RESPONSE_OK, HTTP_RESPONSE_NOT_MODIFIED].indexOf(response.status) > -1)) {
|
|
202
|
+
return await response.json();
|
|
203
|
+
}
|
|
204
|
+
else if ((['POST', 'PUT'].indexOf(method) > -1) && ([HTTP_RESPONSE_OK, HTTP_RESPONSE_CREATED].indexOf(response.status) > -1)) {
|
|
205
|
+
let key = options.key;
|
|
206
|
+
if (response.status === HTTP_RESPONSE_CREATED) {
|
|
207
|
+
key = response.headers['location'];
|
|
208
|
+
}
|
|
209
|
+
return key;
|
|
210
|
+
}
|
|
211
|
+
else if ((method === 'DELETE') && ([HTTP_RESPONSE_OK, HTTP_RESPONSE_NOT_FOUND].indexOf(response.status) > -1)) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
else if (response.status == HTTP_RESPONSE_OK) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
throw new Error(`${response.status}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async request(options) {
|
|
222
|
+
var _resolve;
|
|
223
|
+
var _reject;
|
|
224
|
+
var promise = new Promise(function (resolve, reject) {
|
|
225
|
+
_resolve = resolve;
|
|
226
|
+
_reject = reject;
|
|
227
|
+
});
|
|
228
|
+
if (options.async === undefined)
|
|
229
|
+
options.async = true;
|
|
230
|
+
var queueItem = { resolve: _resolve, reject: _reject, options: options };
|
|
231
|
+
setTimeout(() => {
|
|
232
|
+
this.requestQueue.unshift(queueItem);
|
|
233
|
+
this.processRequestQueue();
|
|
234
|
+
});
|
|
235
|
+
return promise;
|
|
236
|
+
}
|
|
237
|
+
processRequestQueue() {
|
|
238
|
+
if ((this.pendingRequests >= MAX_CONCURRENT) || (this.requestQueue.length === 0)) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const item = this.requestQueue.pop();
|
|
242
|
+
this.pendingRequests++;
|
|
243
|
+
var promise;
|
|
244
|
+
if ((this.connection) && (item.options.async) && (this.connection.connected) && (!item.options.forceHttp)) {
|
|
245
|
+
promise = this.connection.request(item.options);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
promise = this.requestHttp(item.options);
|
|
249
|
+
}
|
|
250
|
+
promise
|
|
251
|
+
.then(obj => {
|
|
252
|
+
this.pendingRequests--;
|
|
253
|
+
item.resolve(obj);
|
|
254
|
+
setTimeout(this.processRequestQueue.bind(this));
|
|
255
|
+
})
|
|
256
|
+
.catch(err => {
|
|
257
|
+
this.pendingRequests--;
|
|
258
|
+
item.reject(err);
|
|
259
|
+
setTimeout(this.processRequestQueue.bind(this));
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
close() {
|
|
263
|
+
this.connection.close();
|
|
264
|
+
}
|
|
265
|
+
constructor(settings) {
|
|
266
|
+
if (settings == null)
|
|
267
|
+
settings = new CachearooSettings();
|
|
268
|
+
settings.host = settings.host || '127.0.0.1';
|
|
269
|
+
settings.port = settings.port || CACHEAROO_DEFAULT_PORT;
|
|
270
|
+
if (typeof settings.path !== 'string')
|
|
271
|
+
settings.path = '';
|
|
272
|
+
if ((settings.path !== '') && (settings.path.charAt(0) !== '/')) {
|
|
273
|
+
settings.path = '/' + settings.path;
|
|
274
|
+
}
|
|
275
|
+
if (settings.path.charAt(settings.path.length - 1) !== '/') {
|
|
276
|
+
settings.path = settings.path + '/';
|
|
277
|
+
}
|
|
278
|
+
this.settings = settings;
|
|
279
|
+
this.connection = new cro_connection_1.PersistentConnection(settings);
|
|
280
|
+
this.requestQueue = [];
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
exports.Cachearoo = Cachearoo;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Cachearoo } from './cro';
|
|
3
|
+
import { EventEmitter } from 'events';
|
|
4
|
+
export declare const JOB_NOT_SUPPORTED = 100;
|
|
5
|
+
export declare const NO_WORKER_AVAILABLE = 200;
|
|
6
|
+
export interface WorkCallback {
|
|
7
|
+
(job: any, callback: (err: any, obj?: any) => any, progressCallback: (obj?: any) => any): any;
|
|
8
|
+
}
|
|
9
|
+
export declare class Worker {
|
|
10
|
+
id: string;
|
|
11
|
+
available: boolean;
|
|
12
|
+
constructor(id: any);
|
|
13
|
+
work?: WorkCallback;
|
|
14
|
+
release(): void;
|
|
15
|
+
}
|
|
16
|
+
export interface JobQueryResponseCallback {
|
|
17
|
+
(errno: number, worker?: Worker): void;
|
|
18
|
+
}
|
|
19
|
+
export interface JobQueryCallback {
|
|
20
|
+
(job: any, responseCallback: JobQueryResponseCallback): void;
|
|
21
|
+
}
|
|
22
|
+
export declare class Producer {
|
|
23
|
+
private statusQueue;
|
|
24
|
+
private jobQueue;
|
|
25
|
+
private timeout;
|
|
26
|
+
private progressTimeout;
|
|
27
|
+
private pendingJobs;
|
|
28
|
+
private de;
|
|
29
|
+
private jobStatusQueueListener;
|
|
30
|
+
constructor(de: Cachearoo, channel: string, timeout?: number, progressTimeout?: number);
|
|
31
|
+
destroy(): void;
|
|
32
|
+
addJob(job: any, progressCallback: any, timeout?: number, progressTimeout?: number): Promise<any>;
|
|
33
|
+
}
|
|
34
|
+
export declare class CompetingConsumer extends EventEmitter {
|
|
35
|
+
onJobQuery?: JobQueryCallback;
|
|
36
|
+
private statusQueue;
|
|
37
|
+
private jobQueue;
|
|
38
|
+
private de;
|
|
39
|
+
private jobQueueListener;
|
|
40
|
+
private clientId;
|
|
41
|
+
jobCount: number;
|
|
42
|
+
constructor(de: Cachearoo, channel: string, clientId: string);
|
|
43
|
+
destroy(): void;
|
|
44
|
+
private jobHandler;
|
|
45
|
+
private waitForWorker;
|
|
46
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CompetingConsumer = exports.Producer = exports.Worker = exports.NO_WORKER_AVAILABLE = exports.JOB_NOT_SUPPORTED = void 0;
|
|
4
|
+
const cro_1 = require("./cro");
|
|
5
|
+
const events_1 = require("events");
|
|
6
|
+
const CHANNEL_PREFIX = 'messaging.compcon';
|
|
7
|
+
const DEFAULT_TIMEOUT = 10000;
|
|
8
|
+
const DEFAULT_PROGRESS_TIMEOUT = 10000;
|
|
9
|
+
const CONSUMER_WAIT_RANGE_MS = 20;
|
|
10
|
+
exports.JOB_NOT_SUPPORTED = 100;
|
|
11
|
+
exports.NO_WORKER_AVAILABLE = 200;
|
|
12
|
+
class Worker {
|
|
13
|
+
id;
|
|
14
|
+
available = true;
|
|
15
|
+
constructor(id) {
|
|
16
|
+
this.id = id;
|
|
17
|
+
}
|
|
18
|
+
work = undefined;
|
|
19
|
+
release() {
|
|
20
|
+
this.available = true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.Worker = Worker;
|
|
24
|
+
class PendingJob {
|
|
25
|
+
callback;
|
|
26
|
+
progressCallback;
|
|
27
|
+
lastProgress;
|
|
28
|
+
publishTime;
|
|
29
|
+
constructor(callback) {
|
|
30
|
+
const now = new Date().valueOf();
|
|
31
|
+
this.lastProgress = undefined;
|
|
32
|
+
this.publishTime = now;
|
|
33
|
+
this.callback = callback;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function wait(ms) {
|
|
37
|
+
if (ms === 0)
|
|
38
|
+
return;
|
|
39
|
+
await new Promise((resolve) => {
|
|
40
|
+
setTimeout(() => resolve(null), ms);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
class Producer {
|
|
44
|
+
statusQueue;
|
|
45
|
+
jobQueue;
|
|
46
|
+
timeout;
|
|
47
|
+
progressTimeout;
|
|
48
|
+
pendingJobs = new Map();
|
|
49
|
+
de;
|
|
50
|
+
jobStatusQueueListener;
|
|
51
|
+
constructor(de, channel, timeout, progressTimeout) {
|
|
52
|
+
this.jobQueue = CHANNEL_PREFIX + '.' + channel + '.jobs';
|
|
53
|
+
this.statusQueue = CHANNEL_PREFIX + '.' + channel + '.status';
|
|
54
|
+
this.timeout = timeout || DEFAULT_TIMEOUT;
|
|
55
|
+
this.progressTimeout = progressTimeout || DEFAULT_PROGRESS_TIMEOUT;
|
|
56
|
+
this.de = de;
|
|
57
|
+
this.jobStatusQueueListener = (ev) => {
|
|
58
|
+
if (this.pendingJobs.has(ev.key)) {
|
|
59
|
+
const job = this.pendingJobs.get(ev.key);
|
|
60
|
+
job.lastProgress = new Date().valueOf();
|
|
61
|
+
job.callback(ev.value.err, ev);
|
|
62
|
+
if (ev.value.done) {
|
|
63
|
+
this.pendingJobs.delete(ev.key);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
de.connection.addListener(this.statusQueue, '*', true, this.jobStatusQueueListener);
|
|
68
|
+
}
|
|
69
|
+
destroy() {
|
|
70
|
+
if (this.de.connection) {
|
|
71
|
+
this.de.connection.removeListener(this.jobStatusQueueListener);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async addJob(job, progressCallback, timeout, progressTimeout) {
|
|
75
|
+
timeout = timeout || this.timeout;
|
|
76
|
+
progressTimeout = progressTimeout || this.progressTimeout;
|
|
77
|
+
const id = cro_1.generateUUID();
|
|
78
|
+
await this.de.connection.write(this.jobQueue, id, job);
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const intervalRef = setInterval(() => {
|
|
81
|
+
const now = new Date().valueOf();
|
|
82
|
+
let isProgressTimeout = false;
|
|
83
|
+
if (this.pendingJobs[id].lastProgress != null) {
|
|
84
|
+
isProgressTimeout = (now > this.pendingJobs[id].lastProgress + progressTimeout);
|
|
85
|
+
}
|
|
86
|
+
const isTimeout = (now > this.pendingJobs[id].publishTime + timeout);
|
|
87
|
+
if (isProgressTimeout || isTimeout) {
|
|
88
|
+
this.pendingJobs[id] = null;
|
|
89
|
+
clearInterval(intervalRef);
|
|
90
|
+
reject(new cro_1.TimeoutError(`Timeout for job id ${id}`, isProgressTimeout));
|
|
91
|
+
}
|
|
92
|
+
}, 500);
|
|
93
|
+
this.pendingJobs[id] = new PendingJob((err, ev) => {
|
|
94
|
+
if (err) {
|
|
95
|
+
clearInterval(intervalRef);
|
|
96
|
+
reject(new Error(err));
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
if (ev.value.done) {
|
|
100
|
+
clearInterval(intervalRef);
|
|
101
|
+
resolve(ev.value);
|
|
102
|
+
this.de.connection.delete(this.statusQueue, ev.key);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
if (typeof progressCallback === 'function')
|
|
106
|
+
progressCallback(ev.value);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.Producer = Producer;
|
|
114
|
+
class CompetingConsumer extends events_1.EventEmitter {
|
|
115
|
+
onJobQuery;
|
|
116
|
+
statusQueue;
|
|
117
|
+
jobQueue;
|
|
118
|
+
de;
|
|
119
|
+
jobQueueListener;
|
|
120
|
+
clientId;
|
|
121
|
+
jobCount = 0;
|
|
122
|
+
constructor(de, channel, clientId) {
|
|
123
|
+
super();
|
|
124
|
+
this.statusQueue = CHANNEL_PREFIX + '.' + channel + '.status';
|
|
125
|
+
this.jobQueue = CHANNEL_PREFIX + '.' + channel + '.jobs';
|
|
126
|
+
this.de = de;
|
|
127
|
+
this.clientId = clientId;
|
|
128
|
+
this.jobQueueListener = (ev) => {
|
|
129
|
+
if ((ev.isDeleted) || (ev.value == null))
|
|
130
|
+
return;
|
|
131
|
+
this.jobHandler(ev.key, ev.value).catch(() => this.emit('job', { id: ev.value.id, status: 'No worker' }));
|
|
132
|
+
};
|
|
133
|
+
de.connection.addListener(this.jobQueue, '*', true, this.jobQueueListener);
|
|
134
|
+
}
|
|
135
|
+
destroy() {
|
|
136
|
+
this.de.connection.removeListener(this.jobQueueListener);
|
|
137
|
+
this.removeAllListeners();
|
|
138
|
+
}
|
|
139
|
+
async jobHandler(key, job) {
|
|
140
|
+
this.emit('job', { id: job.id, status: 'Job received' });
|
|
141
|
+
await wait(Math.floor(Math.random() * CONSUMER_WAIT_RANGE_MS));
|
|
142
|
+
const worker = await this.waitForWorker(job);
|
|
143
|
+
try {
|
|
144
|
+
const obj = {
|
|
145
|
+
claim: { id: this.clientId },
|
|
146
|
+
done: false,
|
|
147
|
+
err: null,
|
|
148
|
+
data: null,
|
|
149
|
+
progress: null
|
|
150
|
+
};
|
|
151
|
+
await this.de.connection.write(this.statusQueue, key, obj, true);
|
|
152
|
+
await this.de.connection.delete(this.jobQueue, key);
|
|
153
|
+
this.jobCount += 1;
|
|
154
|
+
if (worker.work) {
|
|
155
|
+
await worker.work(job, async (err, data) => {
|
|
156
|
+
this.jobCount -= 1;
|
|
157
|
+
if (err instanceof Error)
|
|
158
|
+
err = err.message;
|
|
159
|
+
this.emit('job', { id: job.id, done: true, data });
|
|
160
|
+
await this.de.connection.write(this.statusQueue, key, { ...obj, err, data, done: true });
|
|
161
|
+
}, (progress) => {
|
|
162
|
+
this.emit('job', { id: job.id, done: false, progress });
|
|
163
|
+
this.de.connection.write(this.statusQueue, key, { ...obj, progress });
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
worker.release();
|
|
169
|
+
if (!(err instanceof cro_1.AlreadyExistsError)) {
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async waitForWorker(job) {
|
|
175
|
+
return new Promise((resolve, reject) => {
|
|
176
|
+
var interval = setInterval(() => {
|
|
177
|
+
if (this.onJobQuery == null) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
this.onJobQuery(job, (errno, worker) => {
|
|
181
|
+
if (errno == null) {
|
|
182
|
+
clearInterval(interval);
|
|
183
|
+
this.emit('job', { id: job.id, status: 'Worker assigned' });
|
|
184
|
+
resolve(worker);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
else if (errno == exports.JOB_NOT_SUPPORTED) {
|
|
188
|
+
clearInterval(interval);
|
|
189
|
+
this.emit('job', { id: job.id, error: 'Not supported' });
|
|
190
|
+
reject(new Error('Job not supported'));
|
|
191
|
+
}
|
|
192
|
+
else if (errno == exports.NO_WORKER_AVAILABLE) {
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
clearInterval(interval);
|
|
196
|
+
this.emit('job', { id: job.id, errno });
|
|
197
|
+
reject(new Error(`Error no : ${errno}`));
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}, 100);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
exports.CompetingConsumer = CompetingConsumer;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Cachearoo } from './cro';
|
|
2
|
+
export interface MessageResponseCallback {
|
|
3
|
+
(err: any, data?: any): void;
|
|
4
|
+
}
|
|
5
|
+
export interface ProgressCallback {
|
|
6
|
+
(obj: any): void;
|
|
7
|
+
}
|
|
8
|
+
export interface MessageCallback {
|
|
9
|
+
(msg: any, responseCallback: MessageResponseCallback, progressCallback: ProgressCallback): void;
|
|
10
|
+
}
|
|
11
|
+
export declare class Requestor {
|
|
12
|
+
private pendingRequests;
|
|
13
|
+
private timeout;
|
|
14
|
+
private progressTimeout;
|
|
15
|
+
private de;
|
|
16
|
+
private channel;
|
|
17
|
+
private progressChannel;
|
|
18
|
+
private progressListener;
|
|
19
|
+
private replyListener;
|
|
20
|
+
constructor(de: any, channel: any, timeout: any, progressTimeout: any);
|
|
21
|
+
destroy(): void;
|
|
22
|
+
request(msg: any, progressCallback: ProgressCallback, timeout?: number, progressTimeout?: number): Promise<any>;
|
|
23
|
+
}
|
|
24
|
+
export declare class Replier {
|
|
25
|
+
onMessage?: MessageCallback;
|
|
26
|
+
private channel;
|
|
27
|
+
private progressChannel;
|
|
28
|
+
private de;
|
|
29
|
+
private requestListener;
|
|
30
|
+
constructor(de: Cachearoo, channel: any);
|
|
31
|
+
destroy(): void;
|
|
32
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Replier = exports.Requestor = void 0;
|
|
4
|
+
const cro_1 = require("./cro");
|
|
5
|
+
const REQUESTREPLY_PREFIX = '#reqrep/';
|
|
6
|
+
const PROGRESS_PREFIX = `#reqrep-progress/`;
|
|
7
|
+
class Request {
|
|
8
|
+
lastProgress;
|
|
9
|
+
requestTime;
|
|
10
|
+
reply;
|
|
11
|
+
progress;
|
|
12
|
+
constructor(reply, progress) {
|
|
13
|
+
const now = new Date().valueOf();
|
|
14
|
+
this.lastProgress = now;
|
|
15
|
+
this.requestTime = now;
|
|
16
|
+
this.reply = reply;
|
|
17
|
+
this.progress = progress;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
class Requestor {
|
|
21
|
+
pendingRequests = new Map();
|
|
22
|
+
timeout;
|
|
23
|
+
progressTimeout;
|
|
24
|
+
de;
|
|
25
|
+
channel;
|
|
26
|
+
progressChannel;
|
|
27
|
+
progressListener;
|
|
28
|
+
replyListener;
|
|
29
|
+
constructor(de, channel, timeout, progressTimeout) {
|
|
30
|
+
this.timeout = timeout || 5000;
|
|
31
|
+
this.progressTimeout = progressTimeout || 5000;
|
|
32
|
+
this.channel = REQUESTREPLY_PREFIX + channel;
|
|
33
|
+
this.progressChannel = PROGRESS_PREFIX + channel;
|
|
34
|
+
this.de = de;
|
|
35
|
+
this.progressListener = (ev) => {
|
|
36
|
+
const req = this.pendingRequests[ev.key];
|
|
37
|
+
if (req) {
|
|
38
|
+
req.lastProgress = new Date().valueOf();
|
|
39
|
+
if (typeof req.progress === 'function') {
|
|
40
|
+
req.progress(ev.value.data);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
this.replyListener = (ev) => {
|
|
45
|
+
if (this.pendingRequests[ev.key]) {
|
|
46
|
+
this.pendingRequests[ev.key].reply(ev.value.err, ev.value.data);
|
|
47
|
+
this.pendingRequests[ev.key] = null;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
de.connection.addListener(this.channel, '*', true, this.replyListener);
|
|
51
|
+
de.connection.addListener(this.progressChannel, '*', true, this.progressListener);
|
|
52
|
+
}
|
|
53
|
+
destroy() {
|
|
54
|
+
this.de.connection.removeListener(this.progressListener);
|
|
55
|
+
this.de.connection.removeListener(this.replyListener);
|
|
56
|
+
}
|
|
57
|
+
async request(msg, progressCallback, timeout, progressTimeout) {
|
|
58
|
+
timeout = timeout || this.timeout;
|
|
59
|
+
progressTimeout = progressTimeout || this.progressTimeout;
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
var id = cro_1.generateUUID();
|
|
62
|
+
this.de.connection.signalEvent(this.channel, id, msg);
|
|
63
|
+
const intervalRef = setInterval(() => {
|
|
64
|
+
const now = new Date().valueOf();
|
|
65
|
+
const isProgressTimeout = (now > this.pendingRequests[id].lastProgress + progressTimeout);
|
|
66
|
+
const isTimeout = (now > this.pendingRequests[id].requestTime + timeout);
|
|
67
|
+
if (isProgressTimeout || isTimeout) {
|
|
68
|
+
this.pendingRequests[id] = null;
|
|
69
|
+
clearInterval(intervalRef);
|
|
70
|
+
reject(new cro_1.TimeoutError(`Timeout in request id ${id}`, isProgressTimeout));
|
|
71
|
+
}
|
|
72
|
+
}, 500);
|
|
73
|
+
this.pendingRequests[id] = new Request((err, data) => {
|
|
74
|
+
clearInterval(intervalRef);
|
|
75
|
+
if (err) {
|
|
76
|
+
reject(new Error(err));
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
resolve(data);
|
|
80
|
+
}
|
|
81
|
+
}, progressCallback);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.Requestor = Requestor;
|
|
86
|
+
class Replier {
|
|
87
|
+
onMessage;
|
|
88
|
+
channel;
|
|
89
|
+
progressChannel;
|
|
90
|
+
de;
|
|
91
|
+
requestListener;
|
|
92
|
+
constructor(de, channel) {
|
|
93
|
+
this.channel = REQUESTREPLY_PREFIX + channel;
|
|
94
|
+
this.progressChannel = PROGRESS_PREFIX + channel;
|
|
95
|
+
this.de = de;
|
|
96
|
+
this.requestListener = (ev) => {
|
|
97
|
+
if (this.onMessage === undefined) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this.onMessage(ev.value, (err, data) => {
|
|
101
|
+
if (this.de.connection.connected) {
|
|
102
|
+
const errmsg = (err instanceof Error) ? err.message : err;
|
|
103
|
+
this.de.connection.signalEvent(this.channel, ev.key, { err: errmsg, data: data });
|
|
104
|
+
}
|
|
105
|
+
}, (data) => {
|
|
106
|
+
if (this.de.connection.connected) {
|
|
107
|
+
this.de.connection.signalEvent(this.progressChannel, ev.key, { data });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
de.connection.addListener(this.channel, '*', true, this.requestListener);
|
|
112
|
+
}
|
|
113
|
+
destroy() {
|
|
114
|
+
this.de.connection.removeListener(this.requestListener);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
exports.Replier = Replier;
|