@flopflip/launchdarkly-adapter
Version:
A adapter around the LaunchDarkly client for flipflop
431 lines (413 loc) • 19 kB
JavaScript
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __typeError = (msg) => {
throw TypeError(msg);
};
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// src/adapter.ts
var _adapterutilities = require('@flopflip/adapter-utilities');
var _cache = require('@flopflip/cache');
var _types = require('@flopflip/types');
var _debouncefn = require('debounce-fn'); var _debouncefn2 = _interopRequireDefault(_debouncefn);
var _launchdarklyjsclientsdk = require('launchdarkly-js-client-sdk');
var _isEqualjs = require('lodash/isEqual.js'); var _isEqualjs2 = _interopRequireDefault(_isEqualjs);
var _mitt = require('mitt'); var _mitt2 = _interopRequireDefault(_mitt);
var _tinywarning = require('tiny-warning'); var _tinywarning2 = _interopRequireDefault(_tinywarning);
var _tsdeepmerge = require('ts-deepmerge');
var _adapterState, _updateFlagsInAdapterState, _getIsAdapterUnsubscribed, _getIsFlagUnsubcribed, _getIsFlagLocked, _withoutUnsubscribedOrLockedFlags, _getIsAnonymousContext, _ensureContext, _initializeClient, _changeClientContext, _maybeUpdateFlagsInCache, _getInitialFlags, _didFlagChange, _setupFlagSubcription;
var LaunchDarklyAdapter = class {
constructor() {
__privateAdd(this, _adapterState);
__privateAdd(this, _updateFlagsInAdapterState, (flags, options) => {
const updatedFlags = Object.entries(flags).reduce(
(updatedFlags2, [flagName, flagValue]) => {
if (__privateGet(this, _getIsFlagLocked).call(this, flagName)) {
return updatedFlags2;
}
if (options == null ? void 0 : options.lockFlags) {
__privateGet(this, _adapterState).lockedFlags.add(flagName);
}
if (options == null ? void 0 : options.unsubscribeFlags) {
__privateGet(this, _adapterState).unsubscribedFlags.add(flagName);
}
const updated = __spreadProps(__spreadValues({}, updatedFlags2), {
[flagName]: flagValue
});
return updated;
},
{}
);
__privateGet(this, _adapterState).flags = __spreadValues(__spreadValues({}, __privateGet(this, _adapterState).flags), updatedFlags);
});
__privateAdd(this, _getIsAdapterUnsubscribed, () => __privateGet(this, _adapterState).subscriptionStatus === _types.AdapterSubscriptionStatus.Unsubscribed);
__privateAdd(this, _getIsFlagUnsubcribed, (flagName) => __privateGet(this, _adapterState).unsubscribedFlags.has(flagName));
__privateAdd(this, _getIsFlagLocked, (flagName) => __privateGet(this, _adapterState).lockedFlags.has(flagName));
__privateAdd(this, _withoutUnsubscribedOrLockedFlags, (flags) => Object.fromEntries(
Object.entries(flags).filter(
([flagName]) => !__privateGet(this, _getIsFlagUnsubcribed).call(this, flagName) && !__privateGet(this, _getIsFlagLocked).call(this, flagName)
)
));
__privateAdd(this, _getIsAnonymousContext, (context) => !(context == null ? void 0 : context.key));
__privateAdd(this, _ensureContext, (context) => {
const isAnonymousContext = __privateGet(this, _getIsAnonymousContext).call(this, context);
return _tsdeepmerge.merge.call(void 0, context, {
key: isAnonymousContext ? void 0 : context.key,
anonymous: isAnonymousContext
});
});
__privateAdd(this, _initializeClient, (clientSideId, context, options) => _launchdarklyjsclientsdk.initialize.call(void 0, clientSideId, context, options));
__privateAdd(this, _changeClientContext, (nextContext) => __async(this, null, function* () {
var _a;
return ((_a = __privateGet(this, _adapterState).client) == null ? void 0 : _a.identify) ? __privateGet(this, _adapterState).client.identify(nextContext) : Promise.reject(
new Error("Can not change user context: client not yet initialized.")
);
}));
__privateAdd(this, _maybeUpdateFlagsInCache, (flagsToCache, cacheIdentifier) => __async(this, null, function* () {
if (cacheIdentifier) {
const cache = yield _cache.getCache.call(void 0,
cacheIdentifier,
_types.adapterIdentifiers.launchdarkly,
// NOTE: LDContextCommon is part of the type which we never use.
__privateGet(this, _adapterState).context
);
const cachedFlags = cache.get();
cache.set(__spreadValues(__spreadValues({}, cachedFlags), flagsToCache));
}
}));
__privateAdd(this, _getInitialFlags, (_0) => __async(this, [_0], function* ({
flags,
throwOnInitializationFailure,
cacheIdentifier,
cacheMode,
initializationTimeout
}) {
if (__privateGet(this, _adapterState).client) {
return __privateGet(this, _adapterState).client.waitForInitialization(initializationTimeout).then(() => __async(this, null, function* () {
let flagsFromSdk;
if (__privateGet(this, _adapterState).client && !flags) {
flagsFromSdk = __privateGet(this, _adapterState).client.allFlags();
} else if (__privateGet(this, _adapterState).client && flags) {
flagsFromSdk = {};
for (const [requestedFlagName, defaultFlagValue] of Object.entries(
flags
)) {
const denormalizedRequestedFlagName = _adapterutilities.denormalizeFlagName.call(void 0, requestedFlagName);
flagsFromSdk[denormalizedRequestedFlagName] = __privateGet(this, _adapterState).client.variation(
denormalizedRequestedFlagName,
defaultFlagValue
);
}
}
if (flagsFromSdk) {
const normalizedFlags = _adapterutilities.normalizeFlags.call(void 0, flagsFromSdk);
yield __privateGet(this, _maybeUpdateFlagsInCache).call(this, normalizedFlags, cacheIdentifier);
const flags2 = __privateGet(this, _withoutUnsubscribedOrLockedFlags).call(this, normalizedFlags);
__privateGet(this, _updateFlagsInAdapterState).call(this, flags2);
if (cacheMode !== _types.cacheModes.lazy) {
__privateGet(this, _adapterState).emitter.emit(
"flagsStateChange",
__privateGet(this, _adapterState).flags
);
}
}
this.setConfigurationStatus(_types.AdapterConfigurationStatus.Configured);
return Promise.resolve({
flagsFromSdk,
initializationStatus: _types.AdapterInitializationStatus.Succeeded
});
})).catch(() => __async(this, null, function* () {
if (throwOnInitializationFailure) {
return Promise.reject(
new Error(
"@flopflip/launchdarkly-adapter: adapter failed to initialize."
)
);
}
console.warn(
"@flopflip/launchdarkly-adapter: adapter failed to initialize."
);
return Promise.resolve({
flagsFromSdk: void 0,
initializationStatus: _types.AdapterInitializationStatus.Failed
});
}));
}
return Promise.reject(
new Error(
"@flopflip/launchdarkly-adapter: can not subscribe with non initialized client."
)
);
}));
__privateAdd(this, _didFlagChange, (flagName, nextFlagValue) => {
const previousFlagValue = this.getFlag(flagName);
if (previousFlagValue === void 0) {
return true;
}
return previousFlagValue !== nextFlagValue;
});
__privateAdd(this, _setupFlagSubcription, ({
flagsFromSdk,
flagsUpdateDelayMs,
cacheIdentifier,
cacheMode
}) => {
for (const flagName in flagsFromSdk) {
if (Object.hasOwn(flagsFromSdk, flagName) && __privateGet(this, _adapterState).client) {
__privateGet(this, _adapterState).client.on(
`change:${flagName}`,
(flagValue) => __async(this, null, function* () {
const [normalizedFlagName, normalizedFlagValue] = _adapterutilities.normalizeFlag.call(void 0,
flagName,
flagValue
);
yield __privateGet(this, _maybeUpdateFlagsInCache).call(this, {
[normalizedFlagName]: normalizedFlagValue
}, cacheIdentifier);
if (__privateGet(this, _getIsFlagUnsubcribed).call(this, normalizedFlagName)) {
return;
}
if (!__privateGet(this, _didFlagChange).call(this, normalizedFlagName, normalizedFlagValue)) {
return;
}
const updatedFlags = {
[normalizedFlagName]: normalizedFlagValue
};
__privateGet(this, _updateFlagsInAdapterState).call(this, updatedFlags);
const flushFlagsUpdate = () => {
if (cacheMode === _types.cacheModes.lazy) {
return;
}
__privateGet(this, _adapterState).emitter.emit(
"flagsStateChange",
__privateGet(this, _adapterState).flags
);
};
const scheduleImmediately = { before: true, after: false };
const scheduleTrailingEdge = { before: false, after: true };
_debouncefn2.default.call(void 0, flushFlagsUpdate, __spreadValues({
wait: flagsUpdateDelayMs
}, flagsUpdateDelayMs ? scheduleTrailingEdge : scheduleImmediately))();
})
);
}
}
});
// External. Flags are autolocked when updated.
this.updateFlags = (flags, options) => {
__privateGet(this, _updateFlagsInAdapterState).call(this, flags, options);
__privateGet(this, _adapterState).emitter.emit(
"flagsStateChange",
__privateGet(this, _adapterState).flags
);
};
this.unsubscribe = () => {
__privateGet(this, _adapterState).subscriptionStatus = _types.AdapterSubscriptionStatus.Unsubscribed;
};
this.subscribe = () => {
__privateGet(this, _adapterState).subscriptionStatus = _types.AdapterSubscriptionStatus.Subscribed;
};
__privateSet(this, _adapterState, {
subscriptionStatus: _types.AdapterSubscriptionStatus.Subscribed,
configurationStatus: _types.AdapterConfigurationStatus.Unconfigured,
context: void 0,
client: void 0,
flags: {},
// Typings are incorrect and state that mitt is not callable.
// Value of type 'MittStatic' is not callable. Did you mean to include 'new'
emitter: _mitt2.default.call(void 0, ),
lockedFlags: /* @__PURE__ */ new Set(),
unsubscribedFlags: /* @__PURE__ */ new Set()
});
this.id = _types.adapterIdentifiers.launchdarkly;
}
configure(adapterArgs, adapterEventHandlers) {
return __async(this, null, function* () {
var _a;
const handleFlagsChange = (nextFlags) => {
if (__privateGet(this, _getIsAdapterUnsubscribed).call(this)) {
return;
}
adapterEventHandlers.onFlagsStateChange({
flags: nextFlags,
id: this.id
});
};
const handleStatusChange = (nextStatus) => {
if (__privateGet(this, _getIsAdapterUnsubscribed).call(this)) {
return;
}
adapterEventHandlers.onStatusStateChange({
status: nextStatus,
id: this.id
});
};
__privateGet(this, _adapterState).configurationStatus = _types.AdapterConfigurationStatus.Configuring;
__privateGet(this, _adapterState).emitter.on("flagsStateChange", handleFlagsChange);
__privateGet(this, _adapterState).emitter.on("statusStateChange", handleStatusChange);
__privateGet(this, _adapterState).emitter.emit("statusStateChange", {
configurationStatus: __privateGet(this, _adapterState).configurationStatus
});
const {
sdk,
context,
flags,
throwOnInitializationFailure = false,
initializationTimeout = 2,
// 2 seconds
flagsUpdateDelayMs
} = adapterArgs;
let cachedFlags;
__privateGet(this, _adapterState).context = __privateGet(this, _ensureContext).call(this, context);
if (adapterArgs.cacheIdentifier) {
const cache = yield _cache.getCache.call(void 0,
adapterArgs.cacheIdentifier,
_types.adapterIdentifiers.launchdarkly,
__privateGet(this, _adapterState).context
);
cachedFlags = cache.get();
if (cachedFlags) {
__privateGet(this, _updateFlagsInAdapterState).call(this, cachedFlags);
__privateGet(this, _adapterState).flags = cachedFlags;
__privateGet(this, _adapterState).emitter.emit("flagsStateChange", cachedFlags);
}
}
__privateGet(this, _adapterState).client = __privateGet(this, _initializeClient).call(this, sdk.clientSideId, __privateGet(this, _adapterState).context, (_a = sdk.clientOptions) != null ? _a : {});
return __privateGet(this, _getInitialFlags).call(this, {
flags,
throwOnInitializationFailure,
cacheIdentifier: adapterArgs.cacheIdentifier,
cacheMode: adapterArgs.cacheMode,
initializationTimeout
}).then(({ flagsFromSdk, initializationStatus }) => {
if (flagsFromSdk) {
__privateGet(this, _setupFlagSubcription).call(this, {
flagsFromSdk,
flagsUpdateDelayMs,
cacheIdentifier: adapterArgs.cacheIdentifier,
cacheMode: adapterArgs.cacheMode
});
}
return { initializationStatus };
});
});
}
reconfigure(adapterArgs, _adapterEventHandlers) {
return __async(this, null, function* () {
if (!this.getIsConfigurationStatus(_types.AdapterConfigurationStatus.Configured)) {
return Promise.reject(
new Error(
"@flopflip/launchdarkly-adapter: please configure adapter before reconfiguring."
)
);
}
const nextContext = __privateGet(this, _ensureContext).call(this, adapterArgs.context);
if (!_isEqualjs2.default.call(void 0, __privateGet(this, _adapterState).context, nextContext)) {
if (adapterArgs.cacheIdentifier) {
const cache = yield _cache.getCache.call(void 0,
adapterArgs.cacheIdentifier,
_types.adapterIdentifiers.launchdarkly,
__privateGet(this, _adapterState).context
);
cache.unset();
}
__privateGet(this, _adapterState).context = nextContext;
yield __privateGet(this, _changeClientContext).call(this, __privateGet(this, _adapterState).context);
}
return Promise.resolve({
initializationStatus: _types.AdapterInitializationStatus.Succeeded
});
});
}
getIsConfigurationStatus(configurationStatus) {
return __privateGet(this, _adapterState).configurationStatus === configurationStatus;
}
setConfigurationStatus(nextConfigurationStatus) {
__privateGet(this, _adapterState).configurationStatus = nextConfigurationStatus;
__privateGet(this, _adapterState).emitter.emit("statusStateChange", {
configurationStatus: __privateGet(this, _adapterState).configurationStatus
});
}
getClient() {
return __privateGet(this, _adapterState).client;
}
getFlag(flagName) {
return __privateGet(this, _adapterState).flags[flagName];
}
updateClientContext(updatedContextProps) {
return __async(this, null, function* () {
const isAdapterConfigured = this.getIsConfigurationStatus(
_types.AdapterConfigurationStatus.Configured
);
_tinywarning2.default.call(void 0,
isAdapterConfigured,
"@flopflip/launchdarkly-adapter: adapter not configured. Client context can not be updated before."
);
if (!isAdapterConfigured) {
return Promise.reject(
new Error("Can not update client context: adapter not yet configured.")
);
}
return __privateGet(this, _changeClientContext).call(this, __spreadValues(__spreadValues({}, __privateGet(this, _adapterState).context), updatedContextProps));
});
}
};
_adapterState = new WeakMap();
_updateFlagsInAdapterState = new WeakMap();
_getIsAdapterUnsubscribed = new WeakMap();
_getIsFlagUnsubcribed = new WeakMap();
_getIsFlagLocked = new WeakMap();
_withoutUnsubscribedOrLockedFlags = new WeakMap();
_getIsAnonymousContext = new WeakMap();
_ensureContext = new WeakMap();
_initializeClient = new WeakMap();
_changeClientContext = new WeakMap();
_maybeUpdateFlagsInCache = new WeakMap();
_getInitialFlags = new WeakMap();
_didFlagChange = new WeakMap();
_setupFlagSubcription = new WeakMap();
var adapter = new LaunchDarklyAdapter();
_adapterutilities.exposeGlobally.call(void 0, adapter);
// src/index.ts
var version = "__@FLOPFLIP/VERSION_OF_RELEASE__";
exports.adapter = adapter; exports.version = version;
//# sourceMappingURL=index.cjs.map