UNPKG

@flopflip/launchdarkly-adapter

Version:

A adapter around the LaunchDarkly client for flipflop

431 lines (413 loc) 19 kB
"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