diff --git a/src/storage/__tests__/ImpressionsCache/InRedis/node.spec.js b/src/storage/__tests__/ImpressionsCache/InRedis/node.spec.js index 707d71394..2289e1220 100644 --- a/src/storage/__tests__/ImpressionsCache/InRedis/node.spec.js +++ b/src/storage/__tests__/ImpressionsCache/InRedis/node.spec.js @@ -19,9 +19,11 @@ import sinon from 'sinon'; import KeyBuilder from '../../../Keys'; import ImpressionsCacheInRedis from '../../../ImpressionsCache/InRedis'; import SettingsFactory from '../../../../utils/settings'; +import { CONSUMER_MODE } from '../../../../utils/constants'; tape('IMPRESSIONS CACHE IN REDIS / should incrementally store values', async function(assert) { const settings = SettingsFactory({ + mode: CONSUMER_MODE, storage: { type: 'REDIS', prefix: 'ut_impr_cache' @@ -87,6 +89,7 @@ tape('IMPRESSIONS CACHE IN REDIS / should incrementally store values', async fun tape('IMPRESSIONS CACHE IN REDIS / should not resolve track before calling expire', async function(assert) { const impressionsKey = 'ut_impr_cache_2.SPLITIO.impressions'; const settings = SettingsFactory({ + mode: CONSUMER_MODE, storage: { type: 'REDIS', prefix: 'ut_impr_cache_2' diff --git a/src/utils/__tests__/settings/node.spec.js b/src/utils/__tests__/settings/node.spec.js index 1c63ce871..9059abbed 100644 --- a/src/utils/__tests__/settings/node.spec.js +++ b/src/utils/__tests__/settings/node.spec.js @@ -28,6 +28,7 @@ tape('SETTINGS / Redis options should be properly parsed', assert => { core: { authorizationKey: 'dummy token' }, + mode: CONSUMER_MODE, storage: { type: 'REDIS', options: { @@ -44,6 +45,7 @@ tape('SETTINGS / Redis options should be properly parsed', assert => { core: { authorizationKey: 'dummy token' }, + mode: CONSUMER_MODE, storage: { type: 'REDIS', options: { @@ -84,13 +86,15 @@ tape('SETTINGS / IPAddressesEnabled should be overwritable and true by default', authorizationKey: 'dummy token', IPAddressesEnabled: false }, - mode: CONSUMER_MODE + mode: CONSUMER_MODE, + storage: { type: 'REDIS' } }); const settingsWithIPAddressEnabledAndConsumerMode = SettingsFactory({ core: { authorizationKey: 'dummy token' }, - mode: CONSUMER_MODE + mode: CONSUMER_MODE, + storage: { type: 'PLUGGABLE' } }); assert.equal(settingsWithIPAddressDisabled.core.IPAddressesEnabled, false, 'When creating a setting instance, it will have the provided value for IPAddressesEnabled'); @@ -122,4 +126,35 @@ tape('SETTINGS / streamingEnabled should be overwritable and true by default', a assert.equal(settingsWithStreamingEnabled.streamingEnabled, true, 'If streamingEnabled is not provided, it will be true.'); assert.end(); -}); \ No newline at end of file +}); + +tape('SETTINGS / Throws exception if no "REDIS" or "PLUGGABLE" storage is provided in consumer mode', (assert) => { + assert.throws(() => { + SettingsFactory({ + core: { + authorizationKey: 'dummy token' + }, + mode: CONSUMER_MODE, + storage: { type: 'invalid type' } + }); + }, /A Pluggable or Redis storage is required on consumer mode/); + + assert.end(); +}); + +tape('SETTINGS / Fallback to InMemory storage if no valid storage is provided in standlone and localhost modes', (assert) => { + // 'REDIS' and 'PLUGGABLE' are not valid storages for standalone and localhost modes + const settings = [ + SettingsFactory({ + core: { authorizationKey: 'localhost' }, // localhost mode + storage: { type: 'REDIS' } + }), SettingsFactory({ + core: { authorizationKey: 'dummy token' }, // standalone mode + storage: { type: 'PLUGGABLE' } + }) + ]; + + settings.forEach(setting => { assert.equal(setting.storage.type, 'MEMORY'); }); + + assert.end(); +}); diff --git a/src/utils/settings/storage/browser.js b/src/utils/settings/storage/browser.js index 996f3391f..609c69969 100644 --- a/src/utils/settings/storage/browser.js +++ b/src/utils/settings/storage/browser.js @@ -56,7 +56,7 @@ const ParseStorageSettings = settings => { if (type !== STORAGE_MEMORY && type !== STORAGE_LOCALSTORAGE || type === STORAGE_LOCALSTORAGE && !isLocalStorageAvailable()) { fallbackToMemory(); - log.warn('Invalid or unavailable storage. Fallbacking into MEMORY storage'); + log.error('Invalid or unavailable storage. Fallbacking into MEMORY storage'); } return { diff --git a/src/utils/settings/storage/node.js b/src/utils/settings/storage/node.js index 372dd88b4..7c9cd3f41 100644 --- a/src/utils/settings/storage/node.js +++ b/src/utils/settings/storage/node.js @@ -13,7 +13,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. **/ -import { LOCALHOST_MODE, STORAGE_CUSTOM, STORAGE_MEMORY, STORAGE_REDIS } from '../../constants'; +import { LOCALHOST_MODE, STORAGE_PLUGGABLE, STORAGE_MEMORY, STORAGE_REDIS, CONSUMER_MODE, STANDALONE_MODE } from '../../constants'; +import logFactory from '../../../utils/logger'; +const log = logFactory('splitio-settings'); export function parseRedisOptions(options) { let { @@ -75,24 +77,35 @@ const ParseStorageSettings = (settings) => { prefix }; - // In other cases we can have MEMORY or REDIS + const fallbackToMemory = () => { + log.error('The provided storage is invalid. It requires consumer mode. Fallbacking into default MEMORY storage.'); + return { + type: STORAGE_MEMORY, + prefix + }; + }; + + // In other cases we can have MEMORY, REDIS or PLUGGABLE switch (type) { - case STORAGE_REDIS: { - return { + case STORAGE_REDIS: + if (mode === STANDALONE_MODE) return fallbackToMemory(); + else return { type, prefix, options: parseRedisOptions(options) }; - } - // For custom storage, we return the original storage options, without updating + // For pluggable storage, we return the original storage options, without updating // the prefix because it is done internally by the PluggableStorage module. - case STORAGE_CUSTOM: - return settings.storage; + case STORAGE_PLUGGABLE: + if (mode === STANDALONE_MODE) return fallbackToMemory(); + else return settings.storage; // For now, we don't have modifiers or settings for MEMORY in NodeJS case STORAGE_MEMORY: default: { + // Consumer mode requires an async storage + if (mode === CONSUMER_MODE) throw new Error('A Pluggable or Redis storage is required on consumer mode'); return { type: STORAGE_MEMORY, prefix diff --git a/ts-tests/index.ts b/ts-tests/index.ts index ac8b62496..859c49581 100644 --- a/ts-tests/index.ts +++ b/ts-tests/index.ts @@ -146,6 +146,7 @@ asyncSettings = { core: { authorizationKey: 'key' }, + mode: 'consumer', storage: { type: 'REDIS', options: {} @@ -183,6 +184,7 @@ asyncSettings = { core: { authorizationKey: 'key' }, + mode: 'consumer', storage: { type: 'PLUGGABLE', wrapper: new MyWrapper() @@ -310,23 +312,23 @@ tracked = client.track('myEventType', undefined, { prop1: 1, prop2: '2', prop3: /*** Repeating tests for Async Client ***/ // Events constants we get (same as for sync client, just for interface checking) -const eventConstsAsymc: { [key: string]: SplitIO.Event } = client.Event; -splitEvent = client.Event.SDK_READY; -splitEvent = client.Event.SDK_READY_FROM_CACHE; -splitEvent = client.Event.SDK_READY_TIMED_OUT; -splitEvent = client.Event.SDK_UPDATE; +const eventConstsAsync: { [key: string]: SplitIO.Event } = asyncClient.Event; +splitEvent = asyncClient.Event.SDK_READY; +splitEvent = asyncClient.Event.SDK_READY_FROM_CACHE; +splitEvent = asyncClient.Event.SDK_READY_TIMED_OUT; +splitEvent = asyncClient.Event.SDK_UPDATE; // Client implements methods from NodeJS.Events. (same as for sync client, just for interface checking) -client = client.on(splitEvent, () => { }); -const a1: boolean = client.emit(splitEvent); -client = client.removeAllListeners(splitEvent); -client = client.removeAllListeners(); -const b1: number = client.listenerCount(splitEvent); -nodeEventEmitter = client; +asyncClient = asyncClient.on(splitEvent, () => { }); +const a1: boolean = asyncClient.emit(splitEvent); +asyncClient = asyncClient.removeAllListeners(splitEvent); +asyncClient = asyncClient.removeAllListeners(); +const b1: number = asyncClient.listenerCount(splitEvent); +nodeEventEmitter = asyncClient; // Ready and destroy (same as for sync client, just for interface checking) -const readyPromise1: Promise = client.ready(); -client.destroy(); +const readyPromise1: Promise = asyncClient.ready(); +asyncClient.destroy(); // We can call getTreatment but always with a key. asyncTreatment = asyncClient.getTreatment(splitKey, 'mySplit'); @@ -541,7 +543,6 @@ let fullNodeSettings: SplitIO.INodeSettings = { } }; fullNodeSettings.storage.type = 'MEMORY'; -fullNodeSettings.mode = 'consumer'; let fullAsyncSettings: SplitIO.INodeAsyncSettings = { core: { @@ -572,12 +573,13 @@ let fullAsyncSettings: SplitIO.INodeAsyncSettings = { prefix: 'PREFIX' }, impressionListener: impressionListener, - mode: 'standalone', + mode: 'consumer', debug: true, sync: { splitFilters: splitFilters } }; +fullAsyncSettings.storage.type = 'PLUGGABLE'; // debug property can be a log level fullBrowserSettings.debug = 'ERROR'; diff --git a/types/splitio.d.ts b/types/splitio.d.ts index 6c3e803ec..291e584e3 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -341,7 +341,8 @@ interface INodeBasicSettings extends ISharedSettings { prefix?: string }, /** - * The SDK mode. Possible values are "standalone" (which is the default) and "consumer". For "localhost" mode, use "localhost" as authorizationKey. + * The SDK mode. Possible values are "standalone", which is the default when using a synchronous storage, like 'MEMORY' and 'LOCALSTORAGE', + * and "consumer", which must be set when using an asynchronous storage, like 'REDIS' and 'PLUGGABLE'. For "localhost" mode, use "localhost" as authorizationKey. * @property {SDKMode} mode * @default standalone */ @@ -1020,7 +1021,15 @@ declare namespace SplitIO { * @default SPLITIO */ prefix?: string - } + }, + /** + * The SDK mode. When using the default 'MEMORY' storage or `LOCALSTORAGE` as storage type, the only possible value is "standalone", which is the default. + * For "localhost" mode, use "localhost" as authorizationKey. + * + * @property {'standalone'} mode + * @default standalone + */ + mode?: 'standalone' } /** * Settings interface with async storage for SDK instances created on NodeJS. @@ -1047,12 +1056,20 @@ declare namespace SplitIO { */ wrapper?: Object, /** - * Optional prefix to prevent any kind of data collision between SDK versions. + * Optional prefix added to the storage keys to prevent any kind of data collision between SDK versions. * @property {string} prefix * @default SPLITIO */ prefix?: string - } + }, + /** + * The SDK mode. When using 'REDIS' or 'PLUGGABLE' storage type, the only possible value is "consumer", which is required. + * + * @see {@link @TODO} + * + * @property {'consumer'} mode + */ + mode: 'consumer' } /** * This represents the interface for the SDK instance with synchronous storage.