@splitsoftware/splitio-commons
Version:
Split JavaScript SDK common components
132 lines (131 loc) • 7.77 kB
JavaScript
import { __assign } from "tslib";
import { KeyBuilderSS } from '../KeyBuilderSS';
import { SplitsCachePluggable } from './SplitsCachePluggable';
import { SegmentsCachePluggable } from './SegmentsCachePluggable';
import { ImpressionsCachePluggable } from './ImpressionsCachePluggable';
import { EventsCachePluggable } from './EventsCachePluggable';
import { wrapperAdapter, METHODS_TO_PROMISE_WRAP } from './wrapperAdapter';
import { isObject } from '../../utils/lang';
import { getStorageHash, validatePrefix } from '../KeyBuilder';
import { CONSUMER_PARTIAL_MODE, STORAGE_PLUGGABLE } from '../../utils/constants';
import { ImpressionsCacheInMemory } from '../inMemory/ImpressionsCacheInMemory';
import { EventsCacheInMemory } from '../inMemory/EventsCacheInMemory';
import { ImpressionCountsCacheInMemory } from '../inMemory/ImpressionCountsCacheInMemory';
import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/TelemetryCacheInMemory';
import { TelemetryCachePluggable } from './TelemetryCachePluggable';
import { ImpressionCountsCachePluggable } from './ImpressionCountsCachePluggable';
import { UniqueKeysCachePluggable } from './UniqueKeysCachePluggable';
import { UniqueKeysCacheInMemory } from '../inMemory/UniqueKeysCacheInMemory';
import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
import { metadataBuilder } from '../utils';
import { LOG_PREFIX } from '../pluggable/constants';
import { RBSegmentsCachePluggable } from './RBSegmentsCachePluggable';
var NO_VALID_WRAPPER = 'Expecting pluggable storage `wrapper` in options, but no valid wrapper instance was provided.';
var NO_VALID_WRAPPER_INTERFACE = 'The provided wrapper instance doesn’t follow the expected interface. Check our docs.';
/**
* Validate pluggable storage factory options.
*
* @param options - user options
* @throws Will throw an error if the options are invalid. Example: wrapper is not provided or doesn't have some methods.
*/
function validatePluggableStorageOptions(options) {
if (!isObject(options) || !isObject(options.wrapper))
throw new Error(NO_VALID_WRAPPER);
var wrapper = options.wrapper;
var missingMethods = METHODS_TO_PROMISE_WRAP.filter(function (method) { return typeof wrapper[method] !== 'function'; });
if (missingMethods.length)
throw new Error(NO_VALID_WRAPPER_INTERFACE + " The following methods are missing or invalid: " + missingMethods);
}
// Async return type in `client.track` method on consumer partial mode
// No need to promisify impressions cache
function promisifyEventsTrack(events) {
var origTrack = events.track;
events.track = function () {
return Promise.resolve(origTrack.apply(this, arguments));
};
return events;
}
/**
* Pluggable storage factory for consumer server-side & client-side SplitFactory.
*/
export function PluggableStorage(options) {
validatePluggableStorageOptions(options);
var prefix = validatePrefix(options.prefix);
function PluggableStorageFactory(params) {
var onReadyCb = params.onReadyCb, settings = params.settings, _a = params.settings, log = _a.log, mode = _a.mode, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize;
var metadata = metadataBuilder(settings);
var keys = new KeyBuilderSS(prefix, metadata);
var wrapper = wrapperAdapter(log, options.wrapper);
var isSynchronizer = mode === undefined; // If mode is not defined, the synchronizer is running
var isPartialConsumer = mode === CONSUMER_PARTIAL_MODE;
var telemetry = shouldRecordTelemetry(params) || isSynchronizer ?
isPartialConsumer ?
new TelemetryCacheInMemory() :
new TelemetryCachePluggable(log, keys, wrapper) :
undefined;
var impressionCountsCache = isPartialConsumer ?
new ImpressionCountsCacheInMemory() :
new ImpressionCountsCachePluggable(log, keys.buildImpressionsCountKey(), wrapper);
var uniqueKeysCache = isPartialConsumer ?
settings.core.key === undefined ? new UniqueKeysCacheInMemory() : new UniqueKeysCacheInMemoryCS() :
new UniqueKeysCachePluggable(log, keys.buildUniqueKeysKey(), wrapper);
// Connects to wrapper and emits SDK_READY event on main client
var connectPromise = wrapper.connect().then(function () {
if (isSynchronizer) {
// @TODO reuse InLocalStorage::validateCache logic
// In standalone or producer mode, clear storage if SDK key, flags filter criteria or flags spec version was modified
return wrapper.get(keys.buildHashKey()).then(function (hash) {
var currentHash = getStorageHash(settings);
if (hash !== currentHash) {
log.info(LOG_PREFIX + 'Storage HASH has changed (SDK key, flags filter criteria or flags spec version was modified). Clearing cache');
return wrapper.getKeysByPrefix(keys.prefix + ".").then(function (storageKeys) {
return Promise.all(storageKeys.map(function (storageKey) { return wrapper.del(storageKey); }));
}).then(function () { return wrapper.set(keys.buildHashKey(), currentHash); });
}
}).then(function () {
onReadyCb();
});
}
else {
// Start periodic flush of async storages if not running synchronizer (producer mode)
if (impressionCountsCache.start)
impressionCountsCache.start();
if (uniqueKeysCache.start)
uniqueKeysCache.start();
if (telemetry && telemetry.recordConfig)
telemetry.recordConfig();
onReadyCb();
}
}).catch(function (e) {
e = e || new Error('Error connecting wrapper');
onReadyCb(e);
return e; // Propagate error for shared clients
});
return {
splits: new SplitsCachePluggable(log, keys, wrapper, settings.sync.__splitFiltersValidation),
rbSegments: new RBSegmentsCachePluggable(log, keys, wrapper),
segments: new SegmentsCachePluggable(log, keys, wrapper),
impressions: isPartialConsumer ? new ImpressionsCacheInMemory(impressionsQueueSize) : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
impressionCounts: impressionCountsCache,
events: isPartialConsumer ? promisifyEventsTrack(new EventsCacheInMemory(eventsQueueSize)) : new EventsCachePluggable(log, keys.buildEventsKey(), wrapper, metadata),
telemetry: telemetry,
uniqueKeys: uniqueKeysCache,
// Stop periodic flush and disconnect the underlying storage
destroy: function () {
return Promise.all(isSynchronizer ? [] : [
impressionCountsCache.stop && impressionCountsCache.stop(),
uniqueKeysCache.stop && uniqueKeysCache.stop(),
]).then(function () { return wrapper.disconnect(); });
},
// emits SDK_READY event on shared clients and returns a reference to the storage
shared: function (_, onReadyCb) {
connectPromise.then(onReadyCb);
return __assign(__assign({}, this), {
// no-op destroy, to disconnect the wrapper only when the main client is destroyed
destroy: function () { } });
}
};
}
PluggableStorageFactory.type = STORAGE_PLUGGABLE;
return PluggableStorageFactory;
}