UNPKG

26.3 kBPlain TextView Raw
1import 'isomorphic-fetch';
2import 'isomorphic-form-data';
3
4import ReadableStream = NodeJS.ReadableStream;
5import * as Promise from 'bluebird';
6import async, { AsyncQueue } from 'async';
7import http, { STATUS_CODES, request as httpRequest } from 'http';
8import https, { request as httpsRequest } from 'https';
9import { PassThrough } from 'stream';
10import socketIoClient from 'socket.io-client';
11import moment from 'moment';
12import winston from 'winston';
13
14import { getResourceBufferPromise } from './util';
15import lruCache, { Cache } from 'lru-cache';
16
17export interface Deferred {
18 resolve: Function;
19 reject: Function;
20 promise: Promise<any>&{metadata?:any};
21}
22
23export type CommunibaseEntityType = 'Person' | 'Membership' | 'Event' | 'Invoice' | 'Contact' | 'Debtor' | 'File'
24 | string;
25
26export interface CommunibaseDocument {
27 _id: string;
28 [prop: string]: any;
29}
30
31export interface CommunibaseDocumentReference {
32 rootDocumentEntityType: CommunibaseEntityType;
33 rootDocumentId: string;
34 path: {
35 field: string
36 objectId: string;
37 }[];
38}
39
40export interface CommunibaseVersionInformation {
41 _id: string;
42 updatedAt: string;
43 updatedBy: string;
44}
45
46interface CommunibaseTask {
47 options: {
48 headers?: {
49 Host?: string,
50 'x-api-key'?: string,
51 'x-access-token'?: string,
52 Accept?: string,
53 'Content-Type'?: string,
54 },
55 query?: {
56 [key: string]: string;
57 },
58 };
59 url: string;
60 deferred: Deferred;
61}
62
63export interface CommunibaseParams {
64 fields?: string;
65 limit?: number;
66}
67
68function defer(): Deferred {
69 let resolve;
70 let reject;
71 const promise = new Promise((promiseResolve, promiseReject) => {
72 resolve = promiseResolve;
73 reject = promiseReject;
74 });
75 return {
76 resolve,
77 reject,
78 promise,
79 };
80}
81
82class CommunibaseError extends Error {
83 name: string;
84 code: number;
85 message: string;
86 errors: {};
87
88 constructor(data: { name: string, code: number, message: string, errors: {} }, task: CommunibaseTask) {
89 super(data.message);
90 this.name = 'CommunibaseError';
91 this.code = (data.code || 500);
92 this.message = (data.message || '');
93 this.errors = (data.errors || {});
94 // Error.captureStackTrace(this, CommunibaseError);
95 }
96}
97
98/**
99 * Constructor for connector.
100 *
101 * @param key - The communibase api key
102 * @constructor
103 */
104export class Connector {
105 private getByIdQueue: {
106 [objectType: string]: {
107 [objectId: string]: Deferred,
108 };
109 };
110 private getByIdPrimed: boolean;
111 private key: string;
112 private token: string;
113 private serviceUrl: string;
114 private serviceUrlIsHttps: boolean;
115 private queue: AsyncQueue<any>;
116 private cache?: {
117 objectCache: {
118 [objectType: string]: {
119 [objectId: string]: Promise<CommunibaseDocument>,
120 };
121 };
122 aggregateCaches: {
123 [objectType: string]: Cache<string, {}[]>;
124 };
125 getIdsCaches: {
126 [objectType: string]: Cache<string, string[]>;
127 };
128 isAvailable(objectType: CommunibaseEntityType, objectId: string): boolean,
129 dirtySock: SocketIOClient.Socket
130 };
131
132 constructor(key: string) {
133 this.getByIdQueue = {};
134 this.getByIdPrimed = false;
135 this.key = key;
136 this.token = '';
137 this.setServiceUrl(process.env.COMMUNIBASE_API_URL || 'https://api.communibase.nl/0.1/');
138 this.queue = async.queue(
139 (task: CommunibaseTask, callback) => {
140 function fail(error: Error): void {
141 if (error instanceof Error) {
142 task.deferred.reject(error);
143 } else {
144 task.deferred.reject(new CommunibaseError(error, task));
145 }
146
147 callback();
148 return null;
149 }
150
151 if (!this.key && !this.token) {
152 fail(new Error('Missing key or token for Communibase Connector: please set COMMUNIBASE_KEY environment' +
153 ' variable, or spawn a new instance using require(\'communibase-connector-js\').clone(\'<' +
154 'your api key>\')'));
155 return;
156 }
157
158 if (!task.options.headers) {
159 task.options.headers = {
160 Accept: 'application/json',
161 'Content-Type': 'application/json',
162 };
163 }
164 if (process.env.COMMUNIBASE_API_HOST) {
165 task.options.headers.Host = process.env.COMMUNIBASE_API_HOST;
166 }
167
168 if (this.key) {
169 task.options.headers['x-api-key'] = this.key;
170 }
171 if (this.token) {
172 task.options.headers['x-access-token'] = this.token;
173 }
174 // not support by fetch spec / whatwg-fetch
175 if (task.options.query) {
176 task.url += `?${Object.keys(task.options.query).map(
177 queryVar => `${encodeURIComponent(queryVar)}=${encodeURIComponent(task.options.query[queryVar])}`,
178 ).join('&')}`;
179 task.options.query = undefined;
180 }
181
182 let success = false;
183 Promise.resolve(fetch(task.url, task.options)).then((response) => {
184 success = (response.status === 200);
185 return response.json();
186 }).then((result) => {
187 if (success) {
188 const deferred = task.deferred;
189 let records = result;
190 if (result.metadata && result.records) {
191 deferred.promise.metadata = result.metadata;
192 records = result.records;
193 }
194 deferred.resolve(records);
195 callback();
196 return null;
197 }
198 throw result;
199 }).catch(fail);
200 },
201 8,
202 );
203 }
204
205 public setServiceUrl(newServiceUrl: string):void {
206 if (!newServiceUrl) {
207 throw new Error('Cannot set empty service-url');
208 }
209 this.serviceUrl = newServiceUrl;
210 this.serviceUrlIsHttps = (newServiceUrl.indexOf('https') === 0);
211 }
212
213 private queueSearch<T extends CommunibaseDocument = CommunibaseDocument>(
214 objectType: CommunibaseEntityType,
215 selector: {},
216 params?: CommunibaseParams,
217 ): Promise<T[]> {
218 const deferred = defer();
219 this.queue.push({
220 deferred,
221 url: `${this.serviceUrl + objectType}.json/search`,
222 options: {
223 method: 'POST',
224 body: JSON.stringify(selector),
225 query: params,
226 },
227 });
228 return deferred.promise;
229 }
230
231 /**
232 * Bare boned retrieval by objectIds
233 * @returns {Promise}
234 */
235 private privateGetByIds<T extends CommunibaseDocument = CommunibaseDocument>(
236 objectType: CommunibaseEntityType,
237 objectIds: string[],
238 params?: {},
239 ): Promise<T[]> {
240 return this.queueSearch(
241 objectType,
242 {
243 _id: { $in: objectIds },
244 },
245 params,
246 );
247 }
248
249 /**
250 * Default object retrieval: should provide cachable objects
251 */
252 private spoolQueue():void {
253 Object.keys(this.getByIdQueue).forEach((objectType) => {
254 const deferredsById = this.getByIdQueue[objectType];
255 const objectIds = Object.keys(deferredsById);
256
257 this.getByIdQueue[objectType] = {};
258 this.privateGetByIds(objectType, objectIds).then(
259 (objects) => {
260 const objectHash:{ [key: string]: CommunibaseDocument } = objects.reduce(
261 (previousValue: { [key: string]: CommunibaseDocument }, object) => {
262 previousValue[object._id] = object;
263 return previousValue;
264 },
265 {},
266 );
267 objectIds.forEach((objectId: string) => {
268 if (objectHash[objectId]) {
269 deferredsById[objectId].resolve(objectHash[objectId]);
270 return;
271 }
272 deferredsById[objectId].reject(new Error(`${objectId} is not found`));
273 });
274 },
275 (err) => {
276 objectIds.forEach((objectId) => {
277 deferredsById[objectId].reject(err);
278 });
279 },
280 );
281 });
282 this.getByIdPrimed = false;
283 }
284
285 /**
286 * Get a single object by its id
287 *
288 * @param {string} objectType - E.g. Person
289 * @param {string}objectId - E.g. 52259f95dafd757b06002221
290 * @param {object} [params={}] - key/value store for extra arguments like fields, limit, page and/or sort
291 * @param {string|null} [versionId=null] - optional versionId to retrieve
292 * @returns {Promise} - for object: a key/value object with object data
293 */
294 getById<T extends CommunibaseDocument = CommunibaseDocument>(
295 objectType: CommunibaseEntityType,
296 objectId: string,
297 params?: CommunibaseParams,
298 versionId?: string,
299 ): Promise<T> {
300 if (typeof objectId !== 'string' || objectId.length !== 24) {
301 return Promise.reject(new Error('Invalid objectId'));
302 }
303
304 // not combinable...
305 if (versionId || (params && params.fields)) {
306 const deferred = defer();
307 this.queue.push({
308 deferred,
309 url: `${this.serviceUrl + objectType}.json/${versionId ?
310 `history/${objectId}/${versionId}` :
311 `crud/${objectId}`}`,
312 options: {
313 method: 'GET',
314 query: params,
315 },
316 });
317 return deferred.promise;
318 }
319
320 // cached?
321 if (this.cache && this.cache.isAvailable(objectType, objectId)) {
322 return this.cache.objectCache[objectType][objectId] as Promise<T>;
323 }
324
325 // since we are not requesting a specific version or fields, we may combine the request..?
326 if (this.getByIdQueue[objectType] === undefined) {
327 this.getByIdQueue[objectType] = {};
328 }
329
330 if (this.getByIdQueue[objectType][objectId]) {
331 // requested twice?
332 return this.getByIdQueue[objectType][objectId].promise;
333 }
334
335 this.getByIdQueue[objectType][objectId] = defer();
336
337 if (this.cache) {
338 if (this.cache.objectCache[objectType] === undefined) {
339 this.cache.objectCache[objectType] = {};
340 }
341 this.cache.objectCache[objectType][objectId] = this.getByIdQueue[objectType][objectId].promise;
342 }
343
344 if (!this.getByIdPrimed) {
345 process.nextTick(() => {
346 this.spoolQueue();
347 });
348 this.getByIdPrimed = true;
349 }
350 return this.getByIdQueue[objectType][objectId].promise;
351 }
352
353 /**
354 * Get an array of objects by their ids
355 * If one or more entries are found, they are returned as an array of values
356 *
357 * @param {string} objectType - E.g. Person
358 * @param {Array} objectIds - objectIds - E.g. ['52259f95dafd757b06002221']
359 * @param {object} [params={}] - key/value store for extra arguments like fields, limit, page and/or sort
360 * @returns {Promise} - for array of key/value objects
361 */
362 public getByIds<T extends CommunibaseDocument = CommunibaseDocument>(
363 objectType: CommunibaseEntityType,
364 objectIds: string[],
365 params?: CommunibaseParams,
366 ):Promise<T[]> {
367 if (objectIds.length === 0) {
368 return Promise.resolve([]);
369 }
370
371 // not combinable...
372 if (params && params.fields) {
373 return this.privateGetByIds<T>(objectType, objectIds, params);
374 }
375
376 return Promise.map(
377 objectIds,
378 objectId => this.getById<T>(objectType, objectId, params).reflect(),
379 ).then((inspections) => {
380 const result: T[] = [];
381 let error = null;
382 inspections.forEach((inspection) => {
383 if (inspection.isRejected()) {
384 error = inspection.reason();
385 return;
386 }
387 result.push(inspection.value());
388 });
389 if (result.length) {
390 return result;
391 }
392
393 if (error) {
394 throw new Error(error);
395 }
396
397 // return the empty array, if no results and no error
398 return result;
399 });
400 }
401
402 /**
403 * Get all objects of a certain type
404 *
405 * @param {string} objectType - E.g. Person
406 * @param {object} [params={}] - key/value store for extra arguments like fields, limit, page and/or sort
407 * @returns {Promise} - for array of key/value objects
408 */
409 public getAll<T extends CommunibaseDocument = CommunibaseDocument>
410 (objectType: CommunibaseEntityType, params?: CommunibaseParams):Promise<T[]> {
411 if (this.cache && !(params && params.fields)) {
412 return this.search<T>(objectType, {}, params);
413 }
414
415 const deferred = defer();
416 this.queue.push({
417 deferred,
418 url: `${this.serviceUrl + objectType}.json/crud`,
419 options: {
420 method: 'GET',
421 query: params,
422 },
423 });
424 return deferred.promise;
425 }
426
427 /**
428 * Get result objectIds of a certain search
429 *
430 * @param {string} objectType - E.g. Person
431 * @param {object} selector - { firstName: "Henk" }
432 * @param {object} [params={}] - key/value store for extra arguments like fields, limit, page and/or sort
433 * @returns {Promise} - for array of key/value objects
434 */
435 public getIds(objectType: CommunibaseEntityType, selector?: {}, params?: CommunibaseParams):Promise<string[]> {
436 let hash: string;
437 let result;
438
439 if (this.cache) {
440 hash = JSON.stringify([objectType, selector, params]);
441 if (!this.cache.getIdsCaches[objectType]) {
442 this.cache.getIdsCaches[objectType] = new lruCache(1000); // 1000 getIds are this.cached, per entityType
443 }
444 result = this.cache.getIdsCaches[objectType].get(hash);
445 if (result) {
446 return Promise.resolve(result);
447 }
448 }
449
450 const resultPromise = this.search(
451 objectType,
452 selector,
453 Object.assign({ fields: '_id' }, params),
454 ).then(results => results.map(obj => obj._id));
455
456 if (this.cache) {
457 return resultPromise.then((ids) => {
458 this.cache.getIdsCaches[objectType].set(hash, ids);
459 return ids;
460 });
461 }
462
463 return resultPromise;
464 }
465
466 /**
467 * Get the id of an object based on a search
468 *
469 * @param {string} objectType - E.g. Person
470 * @param {object} selector - { firstName: "Henk" }
471 * @returns {Promise} - for a string OR undefined if not found
472 */
473 public getId(objectType: CommunibaseEntityType, selector?: {}) : Promise<string> {
474 return this.getIds(objectType, selector, { limit: 1 }).then(ids => ids.pop());
475 }
476
477 /**
478 *
479 * @param objectType
480 * @param selector - mongodb style
481 * @param params
482 * @returns {Promise} for objects
483 */
484 public search<T extends CommunibaseDocument = CommunibaseDocument>(
485 objectType: CommunibaseEntityType,
486 selector: {},
487 params?: CommunibaseParams,
488 ): Promise<T[]> {
489 if (this.cache && !(params && params.fields)) {
490 return this.getIds(objectType, selector, params).then(ids => this.getByIds<T>(objectType, ids));
491 }
492
493 if (selector && (typeof selector === 'object') && Object.keys(selector).length) {
494 return this.queueSearch<T>(objectType, selector, params);
495 }
496
497 return this.getAll<T>(objectType, params);
498 }
499
500 /**
501 * This will save a document in Communibase. When a _id-field is found, this document will be updated
502 *
503 * @param objectType
504 * @param object - the to-be-saved object data
505 * @returns promise for object (the created or updated object)
506 */
507 public update<T extends CommunibaseDocument = CommunibaseDocument>
508 (objectType: CommunibaseEntityType, object: T): Promise<T> {
509 const deferred = defer();
510 const operation = ((object._id && (object._id.length > 0)) ? 'PUT' : 'POST');
511
512 if (object._id && this.cache && this.cache.objectCache && this.cache.objectCache[objectType] &&
513 this.cache.objectCache[objectType][object._id]) {
514 this.cache.objectCache[objectType][object._id] = null;
515 }
516
517 this.queue.push({
518 deferred,
519 url: `${this.serviceUrl + objectType}.json/crud${(operation === 'PUT') ? `/${object._id}` : ''}`,
520 options: {
521 method: operation,
522 body: JSON.stringify(object),
523 },
524 });
525
526 return deferred.promise;
527 }
528
529 /**
530 * Delete something from Communibase
531 *
532 * @param objectType
533 * @param objectId
534 * @returns promise (for null)
535 */
536 public destroy(objectType: CommunibaseEntityType, objectId: string):Promise<null> {
537 const deferred = defer();
538
539 if (this.cache && this.cache.objectCache && this.cache.objectCache[objectType] &&
540 this.cache.objectCache[objectType][objectId]) {
541 this.cache.objectCache[objectType][objectId] = null;
542 }
543
544 this.queue.push({
545 deferred,
546 url: `${this.serviceUrl + objectType}.json/crud/${objectId}`,
547 options: {
548 method: 'DELETE',
549 },
550 });
551
552 return deferred.promise;
553 }
554
555 /**
556 * Undelete something from Communibase
557 *
558 * @param objectType
559 * @param objectId
560 * @returns promise (for null)
561 */
562 public undelete(objectType: CommunibaseEntityType, objectId: string):Promise<CommunibaseDocument> {
563 const deferred = defer();
564
565 this.queue.push({
566 deferred,
567 url: `${this.serviceUrl + objectType}.json/history/undelete/${objectId}`,
568 options: {
569 method: 'POST',
570 },
571 });
572
573 return deferred.promise;
574 }
575
576 /**
577 * Get a Promise for a Read stream for a File stored in Communibase
578 *
579 * @param fileId
580 * @returns {Stream} see http://nodejs.org/api/stream.html#stream_stream
581 */
582 public createReadStream(fileId: string):ReadableStream {
583 const request = (this.serviceUrlIsHttps ? httpsRequest : httpRequest);
584 const fileStream = new PassThrough();
585 const req = request(
586 `${this.serviceUrl}File.json/binary/${fileId}?api_key=${this.key}`,
587 (res: http.IncomingMessage) => {
588 if (res.statusCode === 200) {
589 res.pipe(fileStream);
590 return;
591 }
592 fileStream.emit('error', new Error(STATUS_CODES[res.statusCode]));
593 fileStream.emit('end');
594 },
595 );
596 if (process.env.COMMUNIBASE_API_HOST) {
597 req.setHeader('Host', process.env.COMMUNIBASE_API_HOST);
598 }
599 req.end();
600 req.on('error', (err:Error) => {
601 fileStream.emit('error', err);
602 });
603 return fileStream;
604 }
605
606 /**
607 * Uploads the contents of the resource to Communibase (updates or creates a new File)
608 *
609 * Note `File` is not versioned
610 *
611 * @param {Stream|Buffer|String} resource a stream, buffer or a content-string
612 * @param {String} name The binary name (i.e. a filename)
613 * @param {String} destinationPath The "directory location"
614 * @param {String} id The `File` id to replace the contents of (optional; if not set then creates a new File)
615 *
616 * @returns {Promise}
617 */
618 public updateBinary(
619 resource:ReadableStream|Buffer|string,
620 name:string,
621 destinationPath:string,
622 id:string,
623 ):Promise<CommunibaseDocument> {
624 const metaData = {
625 path: destinationPath,
626 };
627
628 return getResourceBufferPromise(resource).then((buffer:any) => {
629 if (id) { // TODO check is valid id? entails extra dependency (mongodb.ObjectID)
630 // update File identified by id
631 return this.update('File', {
632 _id: id,
633 filename: name,
634 length: buffer.length,
635 uploadDate: moment().format(),
636 metadata: metaData,
637 content: buffer,
638 });
639 }
640
641 // create a new File
642 const deferred = defer();
643 const formData:FormData = new FormData();
644 let stringOrBlob:string|Blob = buffer;
645
646 // @see https://developer.mozilla.org/en-US/docs/Web/API/FormData/append
647 // officially, formdata may contain blobs or strings. node doesn't do blobs, but when polymorphing in a browser we
648 // may cast it to one for it to work properly...
649 if (typeof window !== 'undefined' && window.Blob) {
650 stringOrBlob = new Blob([buffer]);
651 }
652
653 formData.append('File', stringOrBlob, name);
654 formData.append('metadata', JSON.stringify(metaData));
655
656 this.queue.push({
657 deferred,
658 url: `${this.serviceUrl}File.json/binary`,
659 options: {
660 method: 'POST',
661 body: formData,
662 headers: {
663 Accept: 'application/json',
664 },
665 },
666 });
667 return deferred.promise;
668 });
669 }
670
671 /**
672 * Get a new Communibase Connector, may be with a different API key
673 *
674 * @param apiKey
675 * @returns {Connector}
676 */
677 public clone(apiKey: string):Connector {
678 return new Connector(apiKey);
679 }
680
681 /**
682 * Get the history information for a certain type of object
683 *
684 * VersionInformation: {
685 * "_id": "ObjectId",
686 * "updatedAt": "Date",
687 * "updatedBy": "string"
688 * }
689 *
690 * @param {string} objectType
691 * @param {string} objectId
692 * @returns promise for VersionInformation[]
693 */
694 public getHistory(objectType:CommunibaseEntityType, objectId: string):Promise<CommunibaseVersionInformation[]> {
695 const deferred = defer();
696 this.queue.push({
697 deferred,
698 url: `${this.serviceUrl + objectType}.json/history/${objectId}`,
699 options: {
700 method: 'GET',
701 },
702 });
703 return deferred.promise;
704 }
705
706 /**
707 *
708 * @param {string} objectType
709 * @param {Object} selector
710 * @returns promise for VersionInformation[]
711 */
712 public historySearch(objectType: CommunibaseEntityType, selector: {}):Promise<CommunibaseVersionInformation[]> {
713 const deferred = defer();
714 this.queue.push({
715 deferred,
716 url: `${this.serviceUrl + objectType}.json/history/search`,
717 options: {
718 method: 'POST',
719 body: JSON.stringify(selector),
720 },
721 });
722 return deferred.promise;
723 }
724
725 /**
726 * Get a single object by a DocumentReference-object. A DocumentReference object looks like
727 * {
728 * rootDocumentId: '524aca8947bd91000600000c',
729 * rootDocumentEntityType: 'Person',
730 * path: [
731 * {
732 * field: 'addresses',
733 * objectId: '53440792463cda7161000003'
734 * }, ...
735 * ]
736 * }
737 *
738 * @param {object} ref - DocumentReference style, see above
739 * @param {object} parentDocument
740 * @return {Promise} for referred object
741 */
742 public getByRef(ref: CommunibaseDocumentReference, parentDocument: CommunibaseDocument):Promise<CommunibaseDocument> {
743 if (!(ref && ref.rootDocumentEntityType && (ref.rootDocumentId || parentDocument))) {
744 return Promise.reject(new Error('Please provide a documentReference object with a type and id'));
745 }
746
747 const rootDocumentEntityTypeParts = ref.rootDocumentEntityType.split('.');
748 let parentDocumentPromise;
749 if (rootDocumentEntityTypeParts[0] !== 'parent') {
750 parentDocumentPromise = this.getById(ref.rootDocumentEntityType, ref.rootDocumentId);
751 } else {
752 parentDocumentPromise = Promise.resolve(parentDocument);
753 }
754
755 if (!(ref.path && ref.path.length && ref.path.length > 0)) {
756 return parentDocumentPromise;
757 }
758
759 /* tslint:disable no-parameter-reassignment */
760 return parentDocumentPromise.then((result: CommunibaseDocument) => {
761 ref.path.some((pathNibble) => {
762 if (result[pathNibble.field]) {
763 if (!result[pathNibble.field].some((subDocument:CommunibaseDocument) => {
764 if (subDocument._id === pathNibble.objectId) {
765 result = subDocument;
766 return true;
767 }
768 return false;
769 })) {
770 result = null;
771 return true;
772 }
773 return false;
774 }
775 result = null;
776 return true;
777 });
778 if (result) {
779 return result;
780 }
781 throw new Error('The referred object within it\'s parent could not be found');
782 });
783 /* tslint:enable no-parameter-reassignment */
784 }
785
786 /**
787 *
788 * @param {string} objectType - E.g. Event
789 * @param {array} aggregationPipeline - E.g. A MongoDB-specific Aggregation Pipeline
790 * @see http://docs.mongodb.org/manual/core/aggregation-pipeline/
791 *
792 * E.g. [
793 * { "$match": { "_id": {"$ObjectId": "52f8fb85fae15e6d0806e7c7"} } },
794 * { "$unwind": "$participants" },
795 * { "$group": { "_id": "$_id", "participantCount": { "$sum": 1 } } }
796 * ]
797 */
798 public aggregate(objectType:CommunibaseEntityType, aggregationPipeline:{}[]):Promise<{}[]> {
799 if (!aggregationPipeline || !aggregationPipeline.length) {
800 return Promise.reject(new Error('Please provide a valid Aggregation Pipeline.'));
801 }
802
803 let hash:string;
804 if (this.cache) {
805 hash = JSON.stringify([objectType, aggregationPipeline]);
806 if (!this.cache.aggregateCaches[objectType]) {
807 this.cache.aggregateCaches[objectType] = lruCache(1000); // 1000 getIds are this.cached, per entityType
808 }
809 const result = this.cache.aggregateCaches[objectType].get(hash);
810 if (result) {
811 return Promise.resolve(result);
812 }
813 }
814
815 const deferred = defer();
816 this.queue.push({
817 deferred,
818 url: `${this.serviceUrl + objectType}.json/aggregate`,
819 options: {
820 method: 'POST',
821 body: JSON.stringify(aggregationPipeline),
822 },
823 });
824
825 const resultPromise = deferred.promise;
826
827 if (this.cache) {
828 return resultPromise.then((result) => {
829 this.cache.aggregateCaches[objectType].set(hash, result);
830 return result;
831 });
832 }
833
834 return resultPromise;
835 }
836
837 /**
838 * Finalize an invoice by its ID
839 *
840 * @param invoiceId
841 * @returns {*}
842 */
843 public finalizeInvoice(invoiceId:string) : Promise<CommunibaseDocument> {
844 const deferred = defer();
845 this.queue.push({
846 deferred,
847 url: `${this.serviceUrl}Invoice.json/finalize/${invoiceId}`,
848 options: {
849 method: 'POST',
850 },
851 });
852 return deferred.promise;
853 }
854
855 /**
856 * @param communibaseAdministrationId
857 * @param socketServiceUrl
858 */
859 public enableCache(communibaseAdministrationId: string, socketServiceUrl: string):void {
860 this.cache = {
861 getIdsCaches: {},
862 aggregateCaches: {},
863 dirtySock: socketIoClient.connect(socketServiceUrl, { port: '443' }),
864 objectCache: {},
865 isAvailable(objectType, objectId) {
866 return this.cache.objectCache[objectType] && this.cache.objectCache[objectType][objectId];
867 },
868 };
869 this.cache.dirtySock.on('connect', () => {
870 this.cache.dirtySock.emit('join', `${communibaseAdministrationId}_dirty`);
871 });
872 this.cache.dirtySock.on('message', (dirtyness:string) => {
873 const dirtyInfo = dirtyness.split('|');
874 if (dirtyInfo.length !== 2) {
875 winston.warn(`${new Date()}: Got weird dirty sock data? ${dirtyness}`);
876 return;
877 }
878 this.cache.getIdsCaches[dirtyInfo[0]] = null;
879 this.cache.aggregateCaches[dirtyInfo[0]] = null;
880 if ((dirtyInfo.length === 2) && this.cache.objectCache[dirtyInfo[0]]) {
881 this.cache.objectCache[dirtyInfo[0]][dirtyInfo[1]] = null;
882 }
883 });
884 }
885}
886
887// Backwards compatibility-ish
888export default new Connector(process.env.COMMUNIBASE_KEY);
889
\No newline at end of file