@launchdarkly/js-server-sdk-common
Version:
LaunchDarkly Server SDK for JavaScript - common code
216 lines • 8.95 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const TtlCache_1 = require("../cache/TtlCache");
const persistentStoreKinds_1 = require("./persistentStoreKinds");
const sortDataSet_1 = require("./sortDataSet");
const UpdateQueue_1 = require("./UpdateQueue");
function cacheKey(kind, key) {
return `${kind.namespace}:${key}`;
}
function allForKindCacheKey(kind) {
return `$all:${kind.namespace}`;
}
// This key will be set in the cache if we have checked that a store was
// initialized and found that it was not. If we do not become initialized
// within the TTL period, then the key will expire, and the next initialization
// check will pass through to the store. Once we are initialized, then the key
// will never be checked again.
const initializationCheckedKey = '$checkedInit';
// The interval to check items in the TTL cache to purge expired items.
// Expired items which are not purged, will be reactively purged when they are
// accessed.
const defaultCheckInterval = 600;
function itemIfNotDeleted(item) {
return !item || item.item.deleted ? null : item.item;
}
function deletedDescriptor(version) {
return {
version,
item: { version, deleted: true },
};
}
/**
* Deserialize a {@link SerializedItemDescriptor}
* @param kind The persistent store data kind to deserialize.
* @param descriptor The serialized descriptor we want to deserialize.
* @returns An item descriptor for the deserialized item.
*/
function deserialize(kind, descriptor) {
if (descriptor.deleted || !descriptor.serializedItem) {
return deletedDescriptor(descriptor.version);
}
const deserializedItem = kind.deserialize(descriptor.serializedItem);
if (deserializedItem === undefined) {
// This would only happen if the JSON is invalid.
return deletedDescriptor(descriptor.version);
}
if (deserializedItem.version === 0 ||
deserializedItem.version === descriptor.version ||
deserializedItem.item === undefined) {
return deserializedItem;
}
// There was a mismatch between the version of the serialized descriptor and the deserialized
// descriptor. So we are going to trust the version of the serialized descriptor.
return {
version: descriptor.version,
item: deserializedItem.item,
};
}
/**
* Internal implementation of {@link LDFeatureStore} that delegates the basic functionality to an
* instance of {@link PersistentDataStore}. It provides optional caching behavior and other logic
* that would otherwise be repeated in every data store implementation. This makes it easier to
* create new database integrations by implementing only the database-specific logic.
*/
class PersistentDataStoreWrapper {
constructor(_core, ttl) {
this._core = _core;
this._isInitialized = false;
/**
* Used to preserve order of operations of async requests.
*/
this._queue = new UpdateQueue_1.default();
if (ttl) {
this._itemCache = new TtlCache_1.default({
ttl,
checkInterval: defaultCheckInterval,
});
this._allItemsCache = new TtlCache_1.default({
ttl,
checkInterval: defaultCheckInterval,
});
}
}
init(allData, callback) {
this._queue.enqueue((cb) => {
const afterStoreInit = () => {
this._isInitialized = true;
if (this._itemCache) {
this._itemCache.clear();
this._allItemsCache.clear();
Object.keys(allData).forEach((kindNamespace) => {
const kind = persistentStoreKinds_1.persistentStoreKinds[kindNamespace];
const items = allData[kindNamespace];
this._allItemsCache.set(allForKindCacheKey(kind), items);
Object.keys(items).forEach((key) => {
const itemForKey = items[key];
const itemDescriptor = {
version: itemForKey.version,
item: itemForKey,
};
this._itemCache.set(cacheKey(kind, key), itemDescriptor);
});
});
}
cb();
};
this._core.init((0, sortDataSet_1.default)(allData), afterStoreInit);
}, callback);
}
get(kind, key, callback) {
if (this._itemCache) {
const item = this._itemCache.get(cacheKey(kind, key));
if (item) {
callback(itemIfNotDeleted(item));
return;
}
}
const persistKind = persistentStoreKinds_1.persistentStoreKinds[kind.namespace];
this._core.get(persistKind, key, (descriptor) => {
var _a;
if (descriptor && descriptor.serializedItem) {
const value = deserialize(persistKind, descriptor);
(_a = this._itemCache) === null || _a === void 0 ? void 0 : _a.set(cacheKey(kind, key), value);
callback(itemIfNotDeleted(value));
return;
}
callback(null);
});
}
initialized(callback) {
var _a;
if (this._isInitialized) {
callback(true);
}
else if ((_a = this._itemCache) === null || _a === void 0 ? void 0 : _a.get(initializationCheckedKey)) {
callback(false);
}
else {
this._core.initialized((storeInitialized) => {
var _a;
this._isInitialized = storeInitialized;
if (!this._isInitialized) {
(_a = this._itemCache) === null || _a === void 0 ? void 0 : _a.set(initializationCheckedKey, true);
}
callback(this._isInitialized);
});
}
}
all(kind, callback) {
var _a;
const items = (_a = this._allItemsCache) === null || _a === void 0 ? void 0 : _a.get(allForKindCacheKey(kind));
if (items) {
callback(items);
return;
}
const persistKind = persistentStoreKinds_1.persistentStoreKinds[kind.namespace];
this._core.getAll(persistKind, (storeItems) => {
var _a;
if (!storeItems) {
callback({});
return;
}
const filteredItems = {};
storeItems.forEach(({ key, item }) => {
const deserializedItem = deserialize(persistKind, item);
const filteredItem = itemIfNotDeleted(deserializedItem);
if (filteredItem) {
filteredItems[key] = filteredItem;
}
});
(_a = this._allItemsCache) === null || _a === void 0 ? void 0 : _a.set(allForKindCacheKey(kind), filteredItems);
callback(filteredItems);
});
}
upsert(kind, data, callback) {
this._queue.enqueue((cb) => {
// Clear the caches which contain all the values of a specific kind.
if (this._allItemsCache) {
this._allItemsCache.clear();
}
const persistKind = persistentStoreKinds_1.persistentStoreKinds[kind.namespace];
this._core.upsert(persistKind, data.key, persistKind.serialize(data), (err, updatedDescriptor) => {
var _a, _b;
if (!err && updatedDescriptor) {
if (updatedDescriptor.serializedItem) {
const value = deserialize(persistKind, updatedDescriptor);
(_a = this._itemCache) === null || _a === void 0 ? void 0 : _a.set(cacheKey(kind, data.key), value);
}
else if (updatedDescriptor.deleted) {
// Deleted and there was not a serialized representation.
(_b = this._itemCache) === null || _b === void 0 ? void 0 : _b.set(data.key, {
key: data.key,
version: updatedDescriptor.version,
deleted: true,
});
}
}
cb();
});
}, callback);
}
delete(kind, key, version, callback) {
this.upsert(kind, { key, version, deleted: true }, callback);
}
close() {
var _a, _b;
(_a = this._itemCache) === null || _a === void 0 ? void 0 : _a.close();
(_b = this._allItemsCache) === null || _b === void 0 ? void 0 : _b.close();
this._core.close();
}
getDescription() {
return this._core.getDescription();
}
}
exports.default = PersistentDataStoreWrapper;
//# sourceMappingURL=PersistentDataStoreWrapper.js.map