From a6e8f3b7b4b6279153d6fb2d95c7725f3a2fd3e0 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Sun, 30 May 2021 22:18:46 -0300 Subject: [PATCH 1/6] updated script --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 932c1a189..848edffb6 100644 --- a/package.json +++ b/package.json @@ -112,8 +112,8 @@ "pretest-ts-decls": "npm run build-es && npm run build-cjs && npm link", "test-ts-decls": "./scripts/ts-tests.sh", "posttest-ts-decls": "npm unlink && npm install", - "browser-test-suite": "npm run test-browser-ci && npm run test-browser-e2e-ci && npm run test-browser-offline && npm run test-browser-destroy && npm run test-browser-errors && npm run test-browser-gaintegration", - "node-test-suite": "npm run test-node && npm run test-node-e2e && npm run test-node-destroy && npm run test-node-offline && npm run test-node-errors && npm run test-node-redis && npm run test-node-custom", + "browser-test-suite": "npm run test-browser-ci && npm run test-browser-e2e-ci && npm run test-browser-offline && npm run test-browser-destroy && npm run test-browser-errors && npm run test-browser-gaintegration && npm run test-browser-push", + "node-test-suite": "npm run test-node && npm run test-node-e2e && npm run test-node-destroy && npm run test-node-offline && npm run test-node-errors && npm run test-node-push && npm run test-node-redis && npm run test-node-custom", "test": "npm run browser-test-suite && npm run node-test-suite", "publish:rc": "npm run check && npm run build && npm publish --tag canary", "publish:stable": "npm run check && npm run build && npm publish" From 346a0071675c3981813674c3d87e2e5912c05063 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 11 Aug 2021 18:09:07 -0300 Subject: [PATCH 2/6] removed getByPrefix method from redis wrapper --- package-lock.json | 12 ++++++------ package.json | 2 +- src/__tests__/consumerMode/ioredisWrapper.js | 3 --- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 092b1dd5a..fdb5a7506 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1356,18 +1356,18 @@ "dev": true }, "@splitsoftware/splitio-commons": { - "version": "0.1.1-canary.12", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-0.1.1-canary.12.tgz", - "integrity": "sha512-BvrRilTTMzF2kDzq3u9JrnzgQaSoVDKp6es8NCiIHgR3rBbP/tetfZPzuKRqDNpncI28JgCBJZOGs/GSRuVobQ==", + "version": "0.1.1-canary.13", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-0.1.1-canary.13.tgz", + "integrity": "sha512-KF1n/ZFm9X4oso8PHgfP+9tgmap6mxnOMrH1ZwSKX5vAoWEvmL0bb3jJ6RqIoiahWuNR2O3GTzLKU7dUYPWMMA==", "requires": { "object-assign": "^4.1.1", "tslib": "^2.1.0" }, "dependencies": { "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" } } }, diff --git a/package.json b/package.json index a4546314f..261fa3024 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ }, "dependencies": { "@babel/runtime": "^7.13.10", - "@splitsoftware/splitio-commons": "0.1.1-canary.12", + "@splitsoftware/splitio-commons": "0.1.1-canary.13", "@types/google.analytics": "0.0.40", "events": "3.1.0", "ioredis": "^4.26.0", diff --git a/src/__tests__/consumerMode/ioredisWrapper.js b/src/__tests__/consumerMode/ioredisWrapper.js index 21bd84cbd..9d8a88fe8 100644 --- a/src/__tests__/consumerMode/ioredisWrapper.js +++ b/src/__tests__/consumerMode/ioredisWrapper.js @@ -31,9 +31,6 @@ export function ioredisWrapper(redisOptions) { getKeysByPrefix(prefix) { return redis.keys(`${prefix}*`); }, - getByPrefix(prefix) { - return this.getKeysByPrefix(prefix).then(keys => redis.mget(...keys)); - }, getMany(keys) { return redis.mget(...keys); }, From 896092dd3b6ae1aaca737be8c7a9e56618a06586 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 22 Nov 2021 19:43:02 -0300 Subject: [PATCH 3/6] renamed CUSTOM to PLUGGABLE --- package-lock.json | 6 +- package.json | 8 +- .../browserSuites/ready-promise.spec.js | 10 +-- src/__tests__/consumerMode/ioredisWrapper.js | 28 +++---- ..._custom.spec.js => node_pluggable.spec.js} | 73 +++++++++---------- src/__tests__/consumerMode/node_redis.spec.js | 6 +- .../nodeSuites/ready-promise.spec.js | 10 +-- src/client/inputValidation.js | 4 +- src/factory/online.js | 2 +- src/listeners/browser.js | 4 +- src/producer/updater/MySegments.js | 2 +- src/producer/updater/SplitChanges.js | 2 +- src/storage/node.js | 4 +- src/sync/SplitUpdateWorker/index.js | 1 - src/utils/constants/index.js | 2 +- ts-tests/index.ts | 2 +- types/index.d.ts | 2 +- types/splitio.d.ts | 12 +-- 18 files changed, 88 insertions(+), 90 deletions(-) rename src/__tests__/consumerMode/{node_custom.spec.js => node_pluggable.spec.js} (84%) diff --git a/package-lock.json b/package-lock.json index 27b65af1e..a3f8a7bad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1267,9 +1267,9 @@ "dev": true }, "@splitsoftware/splitio-commons": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-1.0.0.tgz", - "integrity": "sha512-hup5YVuf9eEd9FJix7siGhFgsUJndCfeMwTLNeO+mPL2gehjGZLS0GjOMofVAa+Y1cDrpo41CQ54w/QIbixYQg==", + "version": "1.0.1-rc.2", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-1.0.1-rc.2.tgz", + "integrity": "sha512-Bya3j+m3qMiiu5s7B8cLOi6QyIUUdZDIald9rFvqf7ul/TQWfGI9dpt3EopV3ZYIm6vY0UeDM9SX96AUNyOEtA==", "requires": { "object-assign": "^4.1.1", "tslib": "^2.1.0" diff --git a/package.json b/package.json index 686f47b98..e2977db4d 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ }, "dependencies": { "@babel/runtime": "^7.13.10", - "@splitsoftware/splitio-commons": "1.0.0", + "@splitsoftware/splitio-commons": "1.0.1-rc.2", "@types/google.analytics": "0.0.40", "events": "3.1.0", "ioredis": "^4.28.0", @@ -103,15 +103,15 @@ "test-browser-errors": "cross-env NODE_ENV=test karma start karma/errors.ci.karma.conf.js", "test-browser-gaintegration": "cross-env NODE_ENV=test karma start karma/gaintegration.ci.karma.conf.js", "test-browser-push": "cross-env NODE_ENV=test karma start karma/push.ci.karma.conf.js", - "test-node": "npm run test-node-unit && npm run test-node-online && npm run test-node-offline && npm run test-node-destroy && npm run test-node-errors && npm run test-node-push && npm run test-node-redis $$ npm run test-node-custom", + "test-node": "npm run test-node-unit && npm run test-node-online && npm run test-node-offline && npm run test-node-destroy && npm run test-node-errors && npm run test-node-push && npm run test-node-consumer-redis $$ npm run test-node-consumer-pluggable", "test-node-unit": "cross-env NODE_ENV=test tape -r @babel/register \"src/*/**/__tests__/**/!(browser).spec.js\" | tap-min", "test-node-online": "cross-env NODE_ENV=test tape -r @babel/register src/__tests__/node.spec.js | tap-min", "test-node-destroy": "cross-env NODE_ENV=test tape -r @babel/register src/__tests__/destroy/node.spec.js | tap-min", "test-node-errors": "cross-env NODE_ENV=test tape -r @babel/register src/__tests__/errorCatching/node.spec.js | tap-min", "test-node-offline": "cross-env NODE_ENV=test tape -r @babel/register src/__tests__/offline/node.spec.js | tap-min", "test-node-push": "cross-env NODE_ENV=test tape -r @babel/register src/__tests__/push/node.spec.js | tap-min", - "test-node-redis": "cross-env NODE_ENV=test tape -r @babel/register \"src/__tests__/consumerMode/node_redis.spec.js\" | tap-min", - "test-node-custom": "cross-env NODE_ENV=test tape -r @babel/register \"src/__tests__/consumerMode/node_custom.spec.js\" | tap-min", + "test-node-consumer-redis": "cross-env NODE_ENV=test tape -r @babel/register \"src/__tests__/consumerMode/node_redis.spec.js\" | tap-min", + "test-node-consumer-pluggable": "cross-env NODE_ENV=test tape -r @babel/register \"src/__tests__/consumerMode/node_pluggable.spec.js\" | tap-min", "pretest-ts-decls": "npm run build-es && npm run build-cjs && npm link", "test-ts-decls": "./scripts/ts-tests.sh", "posttest-ts-decls": "npm unlink && npm install", diff --git a/src/__tests__/browserSuites/ready-promise.spec.js b/src/__tests__/browserSuites/ready-promise.spec.js index c8f29c830..6a743b6d3 100644 --- a/src/__tests__/browserSuites/ready-promise.spec.js +++ b/src/__tests__/browserSuites/ready-promise.spec.js @@ -418,7 +418,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { // We also use the manager to get some of the promises const manager = splitio.manager(); - // promise1 is handled inmediately. Thus, the 'reject' callback is expected to be called in 0.15 seconds aprox. + // promise1 is handled immediately. Thus, the 'reject' callback is expected to be called in 0.15 seconds aprox. setTimeout(() => { const promise1 = client.ready(); const tStart = Date.now(); @@ -434,7 +434,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { }); }, 0); - // promise2 is handled in 0.15 seconds, when the promise is just rejected. Thus, the 'reject' callback is expected to be called inmediately (0 seconds aprox). + // promise2 is handled in 0.15 seconds, when the promise is just rejected. Thus, the 'reject' callback is expected to be called immediately (0 seconds aprox). setTimeout(() => { const promise2 = manager.ready(); const tStart = Date.now(); @@ -446,11 +446,11 @@ export default function readyPromiseAssertions(fetchMock, assert) { t.pass('### SDK TIMED OUT - time out is triggered before retry attempt finishes'); assertGetTreatmentControlNotReady(t, client); const tDelta = Date.now() - tStart; - assert.ok(tDelta < 20, 'The "reject" callback is expected to be called inmediately (0 seconds aprox).'); + assert.ok(tDelta < 20, 'The "reject" callback is expected to be called immediately (0 seconds aprox).'); }); }, fromSecondsToMillis(0.15)); - // promise3 is handled in 0.2 seconds, when the promise is just resolved. Thus, the 'resolve' callback is expected to be called inmediately (0 seconds aprox). + // promise3 is handled in 0.2 seconds, when the promise is just resolved. Thus, the 'resolve' callback is expected to be called immediately (0 seconds aprox). setTimeout(() => { const promise3 = manager.ready(); const tStart = Date.now(); @@ -459,7 +459,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { t.pass('### SDK IS READY - retry attempt finishes before the requestTimeoutBeforeReady limit'); assertGetTreatmentWhenReady(t, client); const tDelta = Date.now() - tStart; - assert.ok(tDelta < 20, 'The "resolve" callback is expected to be called inmediately (0 seconds aprox).'); + assert.ok(tDelta < 20, 'The "resolve" callback is expected to be called immediately (0 seconds aprox).'); return Promise.resolve(); }, () => { diff --git a/src/__tests__/consumerMode/ioredisWrapper.js b/src/__tests__/consumerMode/ioredisWrapper.js index 9d8a88fe8..400ce03de 100644 --- a/src/__tests__/consumerMode/ioredisWrapper.js +++ b/src/__tests__/consumerMode/ioredisWrapper.js @@ -7,12 +7,21 @@ import { parseRedisOptions } from '../../utils/settings/storage/node'; * Operations fail until `connect` is resolved when the Redis 'ready' event is emitted. * * @param {Object} redisOptions redis options with the format expected at `settings.storage.options` - * @returns {import("@splitsoftware/splitio-commons/types/storages/types").ICustomStorageWrapper} wrapper for IORedis client + * @returns {import("@splitsoftware/splitio-commons/types/storages/types").IPluggableStorageWrapper} wrapper for IORedis client */ export function ioredisWrapper(redisOptions) { + const options = RedisAdapter._defineOptions(parseRedisOptions(redisOptions)); + /** @type ioredis.Redis */ - let redis; + const redis = new ioredis(...RedisAdapter._defineLibrarySettings(options)); + + let isConnected = false; + redis.on('ready', () => { isConnected = true; }); + // There is no need to listen for redis 'error' event, because in that case ioredis calls will be rejected. + // It is done to avoid getting the message `Unhandled error event: Error: connect ECONNREFUSED`. + // Also, we cannot reject the connect promise on an error, because the SDK will not get ready if the connection is established after the error. + redis.on('error', () => { }); return { get(key) { @@ -62,20 +71,13 @@ export function ioredisWrapper(redisOptions) { return redis.smembers(key); }, connect() { - const options = RedisAdapter._defineOptions(parseRedisOptions(redisOptions)); - redis = new ioredis(...RedisAdapter._defineLibrarySettings(options)); - return new Promise((res) => { - redis.on('ready', res); - - // There is no need to listen for redis 'error' event, because in that case ioredis calls will be rejected. - // But it is done to avoid getting the ioredis message `Unhandled error event: Error: connect ECONNREFUSED`. - // If we reject the promise, the SDK client will not get ready if the redis connection is established after an error. - redis.on('error', () => { }); + if (isConnected) res(); + else redis.on('ready', res); }); }, - close() { - return Promise.resolve(redis && redis.disconnect()); // close the connection + disconnect() { + return Promise.resolve(redis && redis.disconnect()); } }; } diff --git a/src/__tests__/consumerMode/node_custom.spec.js b/src/__tests__/consumerMode/node_pluggable.spec.js similarity index 84% rename from src/__tests__/consumerMode/node_custom.spec.js rename to src/__tests__/consumerMode/node_pluggable.spec.js index 49afe0631..5e943adf9 100644 --- a/src/__tests__/consumerMode/node_custom.spec.js +++ b/src/__tests__/consumerMode/node_pluggable.spec.js @@ -10,7 +10,6 @@ import { ioredisWrapper } from './ioredisWrapper'; import { exec } from 'child_process'; import { SplitFactory } from '../../index'; import { merge } from '../../utils/lang'; -import SettingsFactory from '../../utils/settings'; import { nearlyEqual } from '../testUtils'; const expectedSplitName = 'hierarchical_splits_testing_on'; @@ -30,15 +29,11 @@ const redisOptions = { /** @type SplitIO.INodeAsyncSettings */ const config = { core: { - authorizationKey: 'uoj4sb69bjv7d4d027f7ukkitd53ek6a9ai9' - }, - urls: { - sdk: 'https://sdk-aws-staging.split.io/api', - events: 'https://events-aws-staging.split.io/api' + authorizationKey: 'SOME API KEY' // in consumer mode, api key is only used to identify the sdk instance }, mode: 'consumer', storage: { - type: 'CUSTOM', + type: 'PLUGGABLE', prefix: redisPrefix, wrapper: ioredisWrapper(redisOptions) }, @@ -73,7 +68,7 @@ const initializeRedisServer = () => { return promise; }; -tape('NodeJS Custom Storage using a wrapper for Ioredis', function (t) { +tape('NodeJS Pluggable Storage using a wrapper for Ioredis', function (t) { t.test('Regular usage', assert => { initializeRedisServer() @@ -100,11 +95,16 @@ tape('NodeJS Custom Storage using a wrapper for Ioredis', function (t) { const splitsResult = manager.splits(); assert.equal(typeof getTreatmentResult.then, 'function', 'GetTreatment calls should always return a promise on Consumer mode.'); - // NOTE: unlike others, JS SDK attempts to retrieve splits from storage even if SDK_READY has not been emitted. - // This could change in a coming breaking change, in order to return a control treatment without calling the storage wrapper. - // ATM, we have to set `enableOfflineQueue: false` in our ioredisWrapper, to make wrapper calls fail immediately if Redis 'ready' event was not emitted. - // The label might be 'exception' instead of 'not ready', if the wrapper operation promise is settled after the SDK is ready. - assert.equal(await getTreatmentResult, 'control', 'Evaluations using custom storage should be control if initiated before SDK_READY.'); + // NOTE: unlike spec, JS SDK attempts to retrieve splits from storage even if SDK_READY has not been emitted, because it is flagged ready from cache. + // This could change to return control treatment without calling the storage wrapper (Breaking change). + // ATM, ioredisWrapper sets `enableOfflineQueue` to false (https://github.com/luin/ioredis#offline-queue), to make wrapper calls fail immediately until Redis 'ready' event is emitted. + // Therefore, the impression label is 'exception' instead of 'not ready'. + assert.equal(await getTreatmentResult, 'control', 'Evaluations using pluggable storage should be control if initiated before SDK_READY.'); + // @TODO SDK should not be operational, until wrapper connects + assert.deepEqual({ + isReadyFromCache: client.__context.get(client.__context.constants.READY_FROM_CACHE, true), + isReady: client.__context.get(client.__context.constants.READY, true) + }, { isReadyFromCache: true, isReady: undefined }, 'SDK in consumer mode is operational inmediatelly'); assert.equal(typeof trackResult.then, 'function', 'Track calls should always return a promise on Consumer mode.'); assert.false(await trackResult, 'If the event failed to be queued due to a wrapper operation failure, the promise will resolve to false'); @@ -118,43 +118,43 @@ tape('NodeJS Custom Storage using a wrapper for Ioredis', function (t) { await client.ready(); - assert.equal(await client.getTreatment('UT_Segment_member', 'UT_IN_SEGMENT'), 'on', '`getTreatment` evaluation using custom storage should be correct.'); - assert.equal((await client.getTreatmentWithConfig('other', 'UT_IN_SEGMENT')).treatment, 'off', '`getTreatmentWithConfig` evaluation using custom storage should be correct.'); + assert.equal(await client.getTreatment('UT_Segment_member', 'UT_IN_SEGMENT'), 'on', '`getTreatment` evaluation using pluggable storage should be correct.'); + assert.equal((await client.getTreatmentWithConfig('other', 'UT_IN_SEGMENT')).treatment, 'off', '`getTreatmentWithConfig` evaluation using pluggable storage should be correct.'); - assert.equal((await client.getTreatments('UT_Segment_member', ['UT_NOT_IN_SEGMENT']))['UT_NOT_IN_SEGMENT'], 'off', '`getTreatments` evaluation using custom storage should be correct.'); - assert.equal((await client.getTreatmentsWithConfig('other', ['UT_NOT_IN_SEGMENT']))['UT_NOT_IN_SEGMENT'].treatment, 'on', '`getTreatmentsWithConfig` evaluation using custom storage should be correct.'); + assert.equal((await client.getTreatments('UT_Segment_member', ['UT_NOT_IN_SEGMENT']))['UT_NOT_IN_SEGMENT'], 'off', '`getTreatments` evaluation using pluggable storage should be correct.'); + assert.equal((await client.getTreatmentsWithConfig('other', ['UT_NOT_IN_SEGMENT']))['UT_NOT_IN_SEGMENT'].treatment, 'on', '`getTreatmentsWithConfig` evaluation using pluggable storage should be correct.'); assert.equal(await client.getTreatment('UT_Segment_member', 'UT_SET_MATCHER', { permissions: ['admin'] - }), 'on', 'Evaluations using custom storage should be correct.'); + }), 'on', 'Evaluations using pluggable storage should be correct.'); assert.equal(await client.getTreatment('UT_Segment_member', 'UT_SET_MATCHER', { permissions: ['not_matching'] - }), 'off', 'Evaluations using custom storage should be correct.'); + }), 'off', 'Evaluations using pluggable storage should be correct.'); assert.equal(await client.getTreatment('UT_Segment_member', 'UT_NOT_SET_MATCHER', { permissions: ['create'] - }), 'off', 'Evaluations using custom storage should be correct.'); + }), 'off', 'Evaluations using pluggable storage should be correct.'); assert.equal(await client.getTreatment('UT_Segment_member', 'UT_NOT_SET_MATCHER', { permissions: ['not_matching'] - }), 'on', 'Evaluations using custom storage should be correct.'); + }), 'on', 'Evaluations using pluggable storage should be correct.'); assert.deepEqual(await client.getTreatmentWithConfig('UT_Segment_member', 'UT_NOT_SET_MATCHER', { permissions: ['not_matching'] }), { treatment: 'on', config: null - }, 'Evaluations using custom storage should be correct, including configs.'); + }, 'Evaluations using pluggable storage should be correct, including configs.'); assert.deepEqual(await client.getTreatmentWithConfig('UT_Segment_member', 'always-on-with-config'), { treatment: 'on', config: expectedConfig - }, 'Evaluations using custom storage should be correct, including configs.'); + }, 'Evaluations using pluggable storage should be correct, including configs.'); - assert.equal(await client.getTreatment('UT_Segment_member', 'always-on'), 'on', 'Evaluations using custom storage should be correct.'); + assert.equal(await client.getTreatment('UT_Segment_member', 'always-on'), 'on', 'Evaluations using pluggable storage should be correct.'); // Below splits were added manually to the redis_mock.json file. // They are all_keys (always evaluate to on) which depend from always-on split. the _on/off is what treatment they are expecting there. - assert.equal(await client.getTreatment('UT_Segment_member', 'hierarchical_splits_testing_on'), 'on', 'Evaluations using custom storage should be correct.'); - assert.equal(await client.getTreatment('UT_Segment_member', 'hierarchical_splits_testing_off'), 'off', 'Evaluations using custom storage should be correct.'); - assert.equal(await client.getTreatment('UT_Segment_member', 'hierarchical_splits_testing_on_negated'), 'off', 'Evaluations using custom storage should be correct.'); + assert.equal(await client.getTreatment('UT_Segment_member', 'hierarchical_splits_testing_on'), 'on', 'Evaluations using pluggable storage should be correct.'); + assert.equal(await client.getTreatment('UT_Segment_member', 'hierarchical_splits_testing_off'), 'off', 'Evaluations using pluggable storage should be correct.'); + assert.equal(await client.getTreatment('UT_Segment_member', 'hierarchical_splits_testing_on_negated'), 'off', 'Evaluations using pluggable storage should be correct.'); assert.equal(typeof client.track('nicolas@split.io', 'user', 'test.redis.event', 18).then, 'function', 'Track calls should always return a promise on Consumer mode.'); assert.equal(typeof client.track().then, 'function', 'Track calls should always return a promise on Consumer mode, even when parameters are incorrect.'); @@ -198,7 +198,7 @@ tape('NodeJS Custom Storage using a wrapper for Ioredis', function (t) { // Unlike RedisAdapter, operations to ioredisWrapper fail if it is not ready. client.getTreatment('UT_Segment_member', 'always-on').then(treatment => { - assert.equal(treatment, 'control', 'Evaluations using custom storage should be control if Redis was not ready'); + assert.equal(treatment, 'control', 'Evaluations using pluggable storage should be control if Redis was not ready'); }); client.track('nicolas@split.io', 'user', 'test.redis.event', 18).then(result => { assert.false(result, 'If the event was not queued because Redis was not ready, the promise will resolve to false'); @@ -237,8 +237,8 @@ tape('NodeJS Custom Storage using a wrapper for Ioredis', function (t) { assert.pass('Ready promise is resolved once SDK_READY is emitted'); // some asserts to test regular usage - assert.equal(await client.getTreatment('UT_Segment_member', 'UT_IN_SEGMENT'), 'on', 'Evaluations using custom storage should be correct.'); - assert.equal(await client.getTreatment('other', 'UT_IN_SEGMENT'), 'off', 'Evaluations using custom storage should be correct.'); + assert.equal(await client.getTreatment('UT_Segment_member', 'UT_IN_SEGMENT'), 'on', 'Evaluations using pluggable storage should be correct.'); + assert.equal(await client.getTreatment('other', 'UT_IN_SEGMENT'), 'off', 'Evaluations using pluggable storage should be correct.'); assert.true(await client.track('nicolas@split.io', 'user', 'test.redis.event', 18), 'If the event was succesfully queued the promise will resolve to true'); assert.false(await client.track(), 'If the event was NOT succesfully queued the promise will resolve to false'); @@ -265,8 +265,8 @@ tape('NodeJS Custom Storage using a wrapper for Ioredis', function (t) { assert.pass('SDK_READY event must be emitted'); // some asserts to test regular usage - assert.equal(await client2.getTreatment('UT_Segment_member', 'UT_IN_SEGMENT'), 'on', 'Evaluations using custom storage should be correct.'); - assert.equal(await client2.getTreatment('other', 'UT_IN_SEGMENT'), 'off', 'Evaluations using custom storage should be correct.'); + assert.equal(await client2.getTreatment('UT_Segment_member', 'UT_IN_SEGMENT'), 'on', 'Evaluations using pluggable storage should be correct.'); + assert.equal(await client2.getTreatment('other', 'UT_IN_SEGMENT'), 'off', 'Evaluations using pluggable storage should be correct.'); assert.true(await client2.track('nicolas@split.io', 'user', 'test.redis.event', 18), 'If the event was succesfully queued the promise will resolve to true'); assert.false(await client2.track(), 'If the event was NOT succesfully queued the promise will resolve to false'); @@ -384,7 +384,6 @@ tape('NodeJS Custom Storage using a wrapper for Ioredis', function (t) { for (let config of configs) { // Redis client and keys required to check Redis store. - const setting = SettingsFactory({ ...config, storage: { ...config.storage, wrapper: ioredisWrapper(redisOptions) } }); const connection = new RedisClient(redisOptions.url); const eventKey = `${redisPrefix}.SPLITIO.impressions`; @@ -409,15 +408,15 @@ tape('NodeJS Custom Storage using a wrapper for Ioredis', function (t) { let redisImpressions = await connection.lrange(impressionsKey, 0, -1); assert.equal(redisImpressions.length, 1, 'After getting a treatment, we should have one impression on Redis.'); const parsedImpression = JSON.parse(redisImpressions[0]); - assert.equal(parsedImpression.m.i, setting.core.IPAddressesEnabled ? IP_VALUE : NA, `If IPAddressesEnabled is true, the property .m.i of the impression object must be equal to the machine ip, or "${NA}" otherwise.`); - assert.equal(parsedImpression.m.n, setting.core.IPAddressesEnabled ? HOSTNAME_VALUE : NA, `If IPAddressesEnabled is true, the property .m.n of the impression object must be equal to the machine hostname, or "${NA}" otherwise.`); + assert.equal(parsedImpression.m.i, sdk.settings.core.IPAddressesEnabled ? IP_VALUE : NA, `If IPAddressesEnabled is true, the property .m.i of the impression object must be equal to the machine ip, or "${NA}" otherwise.`); + assert.equal(parsedImpression.m.n, sdk.settings.core.IPAddressesEnabled ? HOSTNAME_VALUE : NA, `If IPAddressesEnabled is true, the property .m.n of the impression object must be equal to the machine hostname, or "${NA}" otherwise.`); // Assert if the event object was stored properly let redisEvents = await connection.lrange(eventKey, 0, -1); assert.equal(redisEvents.length, 1, 'After tracking an event, we should have one event on Redis.'); const parsedEvent = JSON.parse(redisEvents[0]); - assert.equal(parsedEvent.m.i, setting.core.IPAddressesEnabled ? IP_VALUE : NA, `If IPAddressesEnabled is true, the property .m.i of the event object must be equal to the machine ip, or "${NA}" otherwise.`); - assert.equal(parsedEvent.m.n, setting.core.IPAddressesEnabled ? HOSTNAME_VALUE : NA, `If IPAddressesEnabled is true, the property .m.n of the event object must be equal to the machine hostname, or "${NA}" otherwise.`); + assert.equal(parsedEvent.m.i, sdk.settings.core.IPAddressesEnabled ? IP_VALUE : NA, `If IPAddressesEnabled is true, the property .m.i of the event object must be equal to the machine ip, or "${NA}" otherwise.`); + assert.equal(parsedEvent.m.n, sdk.settings.core.IPAddressesEnabled ? HOSTNAME_VALUE : NA, `If IPAddressesEnabled is true, the property .m.n of the event object must be equal to the machine hostname, or "${NA}" otherwise.`); // Deallocate Split and Redis clients await client.destroy(); diff --git a/src/__tests__/consumerMode/node_redis.spec.js b/src/__tests__/consumerMode/node_redis.spec.js index 4f75e51ac..d7f9ade9d 100644 --- a/src/__tests__/consumerMode/node_redis.spec.js +++ b/src/__tests__/consumerMode/node_redis.spec.js @@ -21,11 +21,7 @@ const redisPort = '6385'; const config = { core: { - authorizationKey: 'uoj4sb69bjv7d4d027f7ukkitd53ek6a9ai9' - }, - urls: { - sdk: 'https://sdk-aws-staging.split.io/api', - events: 'https://events-aws-staging.split.io/api' + authorizationKey: 'SOME API KEY' // in consumer mode, api key is only used to identify the sdk instance }, mode: 'consumer', storage: { diff --git a/src/__tests__/nodeSuites/ready-promise.spec.js b/src/__tests__/nodeSuites/ready-promise.spec.js index 5b745803b..26d76a88d 100644 --- a/src/__tests__/nodeSuites/ready-promise.spec.js +++ b/src/__tests__/nodeSuites/ready-promise.spec.js @@ -389,7 +389,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { // We also use the manager to get some of the promises const manager = splitio.manager(); - // promise1 is handled inmediately. Thus, the 'reject' callback is expected to be called in 0.15 seconds aprox. + // promise1 is handled immediately. Thus, the 'reject' callback is expected to be called in 0.15 seconds aprox. setTimeout(() => { const promise1 = client.ready(); const tStart = Date.now(); @@ -405,7 +405,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { }); }, 0); - // promise2 is handled in 0.15 seconds, when the promise is just rejected. Thus, the 'reject' callback is expected to be called inmediately (0 seconds aprox). + // promise2 is handled in 0.15 seconds, when the promise is just rejected. Thus, the 'reject' callback is expected to be called immediately (0 seconds aprox). setTimeout(() => { const promise2 = manager.ready(); const tStart = Date.now(); @@ -417,11 +417,11 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { t.pass('### SDK TIMED OUT - time out is triggered before retry attempt finishes'); assertGetTreatmentControlNotReady(t, client, key); const tDelta = Date.now() - tStart; - assert.ok(tDelta < 20, 'The "reject" callback is expected to be called inmediately (0 seconds aprox).'); + assert.ok(tDelta < 20, 'The "reject" callback is expected to be called immediately (0 seconds aprox).'); }); }, fromSecondsToMillis(0.15)); - // promise3 is handled in 0.2 seconds, when the promise is just resolved. Thus, the 'resolve' callback is expected to be called inmediately (0 seconds aprox). + // promise3 is handled in 0.2 seconds, when the promise is just resolved. Thus, the 'resolve' callback is expected to be called immediately (0 seconds aprox). setTimeout(() => { const promise3 = manager.ready(); const tStart = Date.now(); @@ -430,7 +430,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { t.pass('### SDK IS READY - retry attempt finishes before the requestTimeoutBeforeReady limit'); assertGetTreatmentWhenReady(t, client, key); const tDelta = Date.now() - tStart; - assert.ok(tDelta < 20, 'The "resolve" callback is expected to be called inmediately (0 seconds aprox).'); + assert.ok(tDelta < 20, 'The "resolve" callback is expected to be called immediately (0 seconds aprox).'); return Promise.resolve(); }, () => { diff --git a/src/client/inputValidation.js b/src/client/inputValidation.js index 86d19bd77..021c2e26a 100644 --- a/src/client/inputValidation.js +++ b/src/client/inputValidation.js @@ -13,7 +13,7 @@ import { validateIfReady } from '../utils/inputValidation'; import { startsWith } from '../utils/lang'; -import { STORAGE_REDIS, STORAGE_CUSTOM, CONTROL, CONTROL_WITH_CONFIG } from '../utils/constants'; +import { STORAGE_REDIS, STORAGE_PLUGGABLE, CONTROL, CONTROL_WITH_CONFIG } from '../utils/constants'; /** * We will validate the input before actually executing the client methods. We should "guard" the client here, @@ -21,7 +21,7 @@ import { STORAGE_REDIS, STORAGE_CUSTOM, CONTROL, CONTROL_WITH_CONFIG } from '../ */ function ClientInputValidationLayer(context, isKeyBinded, isTTBinded) { const settings = context.get(context.constants.SETTINGS); - const isStorageSync = settings.storage.type !== STORAGE_REDIS && settings.storage.type !== STORAGE_CUSTOM; + const isStorageSync = settings.storage.type !== STORAGE_REDIS && settings.storage.type !== STORAGE_PLUGGABLE; // instantiate the client const client = ClientFactory(context); // Keep a reference to the original methods diff --git a/src/factory/online.js b/src/factory/online.js index a0863434b..7aee1aea4 100644 --- a/src/factory/online.js +++ b/src/factory/online.js @@ -51,7 +51,7 @@ function SplitFactoryOnline(context, readyTrackers, mainClientMetricCollectors) break; } case CONSUMER_MODE: { - context.put(context.constants.READY_FROM_CACHE, true); // For SDK inner workings it's supposed to be ready from cache. + context.put(context.constants.READY_FROM_CACHE, true); // For SDK inner workings it's supposed to be ready from cache, to be operational for evaluations immediately break; } } diff --git a/src/listeners/browser.js b/src/listeners/browser.js index 1cc0534ce..20ec5e685 100644 --- a/src/listeners/browser.js +++ b/src/listeners/browser.js @@ -60,13 +60,15 @@ export default class BrowserSignalListener { * using beacon API if possible, or falling back to regular post transport. */ flushData() { + if (!this.syncManager) return; + this._flushImpressions(); this._flushEvents(); if (this.impressionsCounter) { this._flushImpressionsCount(); } // Close streaming - if (this.syncManager && this.syncManager.pushManager) this.syncManager.pushManager.stop(); + if (this.syncManager.pushManager) this.syncManager.pushManager.stop(); } _flushImpressions() { diff --git a/src/producer/updater/MySegments.js b/src/producer/updater/MySegments.js index f2a528f98..4a17f3dca 100644 --- a/src/producer/updater/MySegments.js +++ b/src/producer/updater/MySegments.js @@ -32,7 +32,7 @@ export default function MySegmentsUpdaterFactory(context) { let readyOnAlreadyExistentState = true; let startingUp = true; - // @TODO if allowing custom storages, handle async execution and wrap errors as SplitErrors to distinguish from user callback errors + // @TODO if allowing pluggable storages, handle async execution and wrap errors as SplitErrors to distinguish from user callback errors function updateSegments(segmentsData) { const mySegmentsCache = storage.segments; diff --git a/src/producer/updater/SplitChanges.js b/src/producer/updater/SplitChanges.js index 6acc512b4..b1e7ebbde 100644 --- a/src/producer/updater/SplitChanges.js +++ b/src/producer/updater/SplitChanges.js @@ -85,7 +85,7 @@ export default function SplitChangesUpdaterFactory(context, isNode = false) { log.debug(`Segment names collected ${mutation.segments}`); // Write into storage - // @TODO if allowing custom storages, wrap errors as SplitErrors to distinguish from user callback errors + // @TODO wrap errors as SplitErrors or migrate error handling as in JS-commons return Promise.all([ // calling first `setChangenumber` method, to perform cache flush if split filter queryString changed storage.splits.setChangeNumber(splitChanges.till), diff --git a/src/storage/node.js b/src/storage/node.js index aa306c8db..3f3a9287a 100644 --- a/src/storage/node.js +++ b/src/storage/node.js @@ -13,7 +13,7 @@ import EventsCacheInMemory from './EventsCache/InMemory'; import EventsCacheInRedis from './EventsCache/InRedis'; import KeyBuilder from './Keys'; import MetaBuilder from './Meta'; -import { STORAGE_MEMORY, STORAGE_REDIS, STORAGE_CUSTOM } from '../utils/constants'; +import { STORAGE_MEMORY, STORAGE_REDIS, STORAGE_PLUGGABLE } from '../utils/constants'; import { PluggableStorage } from '@splitsoftware/splitio-commons'; import LogFactory from '../utils/logger'; @@ -60,7 +60,7 @@ const NodeStorageFactory = context => { }; } - case STORAGE_CUSTOM: { + case STORAGE_PLUGGABLE: { const storageFactory = PluggableStorage(storage); diff --git a/src/sync/SplitUpdateWorker/index.js b/src/sync/SplitUpdateWorker/index.js index 3e9560435..27722b9ef 100644 --- a/src/sync/SplitUpdateWorker/index.js +++ b/src/sync/SplitUpdateWorker/index.js @@ -67,7 +67,6 @@ export default class SplitUpdateWorker { * @param {string} defaultTreatment default treatment value */ killSplit(changeNumber, splitName, defaultTreatment) { - // @TODO handle retry due to errors in storage, once we allow the definition of custom async storages this.splitStorage.killLocally(splitName, defaultTreatment, changeNumber).then((updated) => { // trigger an SDK_UPDATE if Split was killed locally if (updated) this.splitsEventEmitter.emit(this.splitsEventEmitter.SDK_SPLITS_ARRIVED, true); diff --git a/src/utils/constants/index.js b/src/utils/constants/index.js index 7a8dca494..98e1527b5 100644 --- a/src/utils/constants/index.js +++ b/src/utils/constants/index.js @@ -7,7 +7,7 @@ export const CONSUMER_MODE = 'consumer'; export const STORAGE_MEMORY = 'MEMORY'; export const STORAGE_REDIS = 'REDIS'; export const STORAGE_LOCALSTORAGE = 'LOCALSTORAGE'; -export const STORAGE_CUSTOM = 'CUSTOM'; +export const STORAGE_PLUGGABLE = 'PLUGGABLE'; // Special treatments export const CONTROL = 'control'; export const CONTROL_WITH_CONFIG = { diff --git a/ts-tests/index.ts b/ts-tests/index.ts index 40f81c0f9..ac8b62496 100644 --- a/ts-tests/index.ts +++ b/ts-tests/index.ts @@ -184,7 +184,7 @@ asyncSettings = { authorizationKey: 'key' }, storage: { - type: 'CUSTOM', + type: 'PLUGGABLE', wrapper: new MyWrapper() } }; diff --git a/types/index.d.ts b/types/index.d.ts index c09977c09..6e29f76a0 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,4 +1,4 @@ -// Declaration file for Javascript and Node Split Software SDK v8.1.0 +// Declaration file for Javascript and Node Split Software SDK // Project: http://www.split.io/ // Definitions by: Nico Zelaya diff --git a/types/splitio.d.ts b/types/splitio.d.ts index 9f13f2ecd..6c3e803ec 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -1,4 +1,4 @@ -// Type definitions for Javascript and Node Split Software SDK v8.1.0 +// Type definitions for Javascript and Node Split Software SDK // Project: http://www.split.io/ // Definitions by: Nico Zelaya @@ -51,7 +51,7 @@ type SDKMode = 'standalone' | 'consumer'; * Storage types. * @typedef {string} StorageType */ -type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'CUSTOM'; +type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'PLUGGABLE'; /** * Settings interface. This is a representation of the settings the SDK expose, that's why * most of it's props are readonly. Only features should be rewritten when localhost mode is active. @@ -329,7 +329,7 @@ interface INodeBasicSettings extends ISharedSettings { */ options?: Object, /** - * Custom storage wrapper. Use it with type: 'CUSTOM' + * Storage wrapper. Use it with type: 'PLUGGABLE' * @property {Object} wrapper */ wrapper?: Object, @@ -619,7 +619,7 @@ declare namespace SplitIO { * Asynchronous storages valid types for NodeJS. * @typedef {string} NodeAsyncStorage */ - type NodeAsyncStorage = 'REDIS' | 'CUSTOM'; + type NodeAsyncStorage = 'REDIS' | 'PLUGGABLE'; /** * Storage valid types for the browser. * @typedef {string} BrowserStorage @@ -1032,7 +1032,7 @@ declare namespace SplitIO { interface INodeAsyncSettings extends INodeBasicSettings { storage: { /** - * Async storage type (Redis or Custom) to be instantiated by the SDK. + * Async storage type ('REDIS' or 'PLUGGABLE') to be instantiated by the SDK. * @property {NodeAsyncStorage} type */ type: NodeAsyncStorage, @@ -1042,7 +1042,7 @@ declare namespace SplitIO { */ options?: Object, /** - * Custom storage wrapper. Use it with type: 'CUSTOM' + * Storage wrapper. Use it with type: 'PLUGGABLE' * @property {Object} wrapper */ wrapper?: Object, From 444d7141600e2f161ea7179d860ceb87d1553820 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 22 Nov 2021 19:48:22 -0300 Subject: [PATCH 4/6] updated ci-cd --- .github/workflows/ci-cd.yml | 2 +- ts-tests/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 1957c1335..6ba5a3b3f 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -24,7 +24,7 @@ jobs: - name: Set up nodejs uses: actions/setup-node@v2 with: - node-version: 'lts/*' + node-version: '14' cache: 'npm' - name: npm ci diff --git a/ts-tests/package.json b/ts-tests/package.json index 47cb1bdb4..09c34edc7 100644 --- a/ts-tests/package.json +++ b/ts-tests/package.json @@ -6,7 +6,7 @@ "license": "Apache-2.0", "repository": "splitio/javascript-client", "dependencies": { - "@types/node": "^14.17.9", + "@types/node": "^14.17.34", "typescript": "^3.7.4" } } From 62a727dbaf90a6bd8f4c51deb9b8eb491453e22e Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 25 Jan 2022 18:25:47 -0300 Subject: [PATCH 5/6] update JS-commons dependency --- package-lock.json | 7 +++---- package.json | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 08a61dac3..651876012 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1267,11 +1267,10 @@ "dev": true }, "@splitsoftware/splitio-commons": { - "version": "1.0.1-rc.2", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-1.0.1-rc.2.tgz", - "integrity": "sha512-Bya3j+m3qMiiu5s7B8cLOi6QyIUUdZDIald9rFvqf7ul/TQWfGI9dpt3EopV3ZYIm6vY0UeDM9SX96AUNyOEtA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-1.2.0.tgz", + "integrity": "sha512-UzfB/QNjbLkjSsxsOqb6S7qEp4DXMcVZ5gD1v6NKDuuSTF+m3B/xZPp9cXrzGDgZ9pwCgDAbScWRHvRBwLfA4g==", "requires": { - "object-assign": "^4.1.1", "tslib": "^2.1.0" } }, diff --git a/package.json b/package.json index 5b02a9e41..2bb18cdea 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ }, "dependencies": { "@babel/runtime": "^7.13.10", - "@splitsoftware/splitio-commons": "1.0.1-rc.2", + "@splitsoftware/splitio-commons": "^1.2.0", "@types/google.analytics": "0.0.40", "events": "3.1.0", "ioredis": "^4.28.0", From 459f3c16908add4b753711a739bcad3b98a3083f Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 29 Sep 2022 19:42:38 -0300 Subject: [PATCH 6/6] update incr and decr methods of ioredisWrapper with increment and decrement params --- src/__tests__/consumerMode/ioredisWrapper.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/__tests__/consumerMode/ioredisWrapper.js b/src/__tests__/consumerMode/ioredisWrapper.js index 400ce03de..9ea4a1787 100644 --- a/src/__tests__/consumerMode/ioredisWrapper.js +++ b/src/__tests__/consumerMode/ioredisWrapper.js @@ -43,11 +43,11 @@ export function ioredisWrapper(redisOptions) { getMany(keys) { return redis.mget(...keys); }, - incr(key) { - return redis.incr(key); + incr(key, increment = 1) { + return redis.incrby(key, increment); }, - decr(key) { - return redis.decr(key); + decr(key, decrement = 1) { + return redis.decrby(key, decrement); }, pushItems(key, items) { return redis.rpush(key, items);