diff --git a/.eslintrc b/.eslintrc index 94782cad..6457f1b6 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,7 +4,8 @@ ], "parser": "@typescript-eslint/parser", "plugins": [ - "@typescript-eslint" + "@typescript-eslint", + "import" ], "env": { @@ -15,10 +16,8 @@ "globals": { // global TS types - "EventSource": "readonly", // @TODO remove. Configure as a type declaration "MessageEvent": "readonly", // @TODO remove. Configure as a type declaration "Event": "readonly", // @TODO remove. Configure as a type declaration - "NodeJS": "readonly", "UniversalAnalytics": "readonly" // @TODO remove when moving GA integrations to Browser-SDK }, @@ -81,7 +80,9 @@ } ], "compat/compat": ["error", "defaults, not ie < 10, not node < 6"], - "no-throw-literal": "error" + "no-throw-literal": "error", + "import/no-default-export": "error", + "import/no-self-import": "error" }, "parserOptions": { "ecmaVersion": 2015, diff --git a/CHANGES.txt b/CHANGES.txt index 429ad003..b6952027 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,11 +1,18 @@ +1.1.0 (January 11, 2022) + - Added support for the SDK to run in "consumer" and "partial consumer" modes, with a pluggable implementation of it's internal storage, enabling + customers to implement this caching with any storage technology of choice and connect it to the SDK instance to be used instead of its default in-memory storage. + - Updated multiple modules due to general polishing and improvements, including the replacement of default exports with named exports, to avoid runtime errors with some particular configurations of Webpack projects. + - Updated ioredis dependency for vulnerability fixes. + - Bugfixing - Fixed issue returning dynamic configs if treatment name contains a dot ("."). + 1.0.0 (October 20, 2021) -- BREAKING CHANGE on multiple modules due to general polishing, improvements and bug fixes. In most cases the change is to use named exports. This affected mostly modules related with synchronization and storages. + - BREAKING CHANGE on multiple modules due to general polishing, improvements and bug fixes. In most cases the change is to use named exports. This affected mostly modules related with synchronization and storages. - Updated streaming logic to use the newest version of our streaming service, including: - Integration with Auth service V2, connecting to the new channels and applying the received connection delay. - Implemented handling of the new MySegmentsV2 notification types (SegmentRemoval, KeyList, Bounded and Unbounded) - New control notification for environment scoped streaming reset. -- Updated localhost mode to emit SDK_READY_FROM_CACHE event in Browser when using localStorage (Related to issue https://github.com/splitio/react-client/issues/34). -- Updated dependencies for vulnerability fixes. + - Updated localhost mode to emit SDK_READY_FROM_CACHE event in Browser when using localStorage (Related to issue https://github.com/splitio/react-client/issues/34). + - Updated dependencies for vulnerability fixes. 0.1.0 (March 30, 2021) - Initial public release. It includes common modules to be consumed by the different Split implementations written in JavaScript. Based on the original JS SDK in the `javascript-client` repository. diff --git a/LICENSE b/LICENSE index 18dec1b3..051b5fd9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright © 2021 Split Software, Inc. +Copyright © 2022 Split Software, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 51caf41d..7d6d7a13 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Split Javascript SDK common components -[![npm version](https://badge.fury.io/js/%40splitsoftware%2Fsplitio-commons.svg)](https://badge.fury.io/js/%40splitsoftware%2Fsplitio) [![Build Status](https://travis-ci.com/splitio/javascript-commons.svg?branch=main)](https://travis-ci.com/splitio/javascript-commons) +[![npm version](https://badge.fury.io/js/%40splitsoftware%2Fsplitio-commons.svg)](https://badge.fury.io/js/%40splitsoftware%2Fsplitio-commons) [![Build Status](https://github.com/splitio/javascript-commons/actions/workflows/ci.yml/badge.svg)](https://github.com/splitio/javascript-commons/actions/workflows/ci.yml) ## Overview This library is designed to work with Split, the platform for controlled rollouts, which serves features to your users via a Split feature flag to manage your complete customer experience. @@ -30,8 +30,8 @@ Split has built and maintains SDKs for: * iOS [Github](https://github.com/splitio/ios-client) [Docs](https://help.split.io/hc/en-us/articles/360020401491-iOS-SDK) * Java [Github](https://github.com/splitio/java-client) [Docs](https://help.split.io/hc/en-us/articles/360020405151-Java-SDK) * Javascript [Github](https://github.com/splitio/javascript-client) [Docs](https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK) -* Node [Github](https://github.com/splitio/javascript-client) [Docs](https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK) * Javascript for Browser [Github](https://github.com/splitio/javascript-browser-client) [Docs](https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK) +* Node [Github](https://github.com/splitio/javascript-client) [Docs](https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK) * PHP [Github](https://github.com/splitio/php-client) [Docs](https://help.split.io/hc/en-us/articles/360020350372-PHP-SDK) * Python [Github](https://github.com/splitio/python-client) [Docs](https://help.split.io/hc/en-us/articles/360020359652-Python-SDK) * React [Github](https://github.com/splitio/react-client) [Docs](https://help.split.io/hc/en-us/articles/360038825091-React-SDK) diff --git a/package-lock.json b/package-lock.json index f32024b1..0b72381e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1053,9 +1053,9 @@ } }, "@types/ioredis": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.17.4.tgz", - "integrity": "sha512-kb5+thmQJ7HHyOAnCOeqRJlF2fyvadHghnLLLKZzCNyShStJeIQtNGGDjA30gWqj6UFSDAWBfGEMKrFDrGfvzQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.28.1.tgz", + "integrity": "sha512-raYHPqRWrfnEoym94BY28mG1+tcZqh3dsp2q7x5IyMAAEvIdu+H0X8diASMpncIm+oHyH9dalOeOnGOL/YnuOA==", "dev": true, "requires": { "@types/node": "*" @@ -1101,6 +1101,12 @@ "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", "dev": true }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, "@types/lodash": { "version": "4.14.162", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.162.tgz", @@ -1113,12 +1119,6 @@ "integrity": "sha512-Zw1vhUSQZYw+7u5dAwNbIA9TuTotpzY/OF7sJM9FqPOF3SPjKnxrjoTktXDZgUjybf4cWVBP7O8wvKdSaGHweg==", "dev": true }, - "@types/object-assign": { - "version": "4.0.30", - "resolved": "https://registry.npmjs.org/@types/object-assign/-/object-assign-4.0.30.tgz", - "integrity": "sha1-iUk3HVqZ9Dge4PHfCpt6GH4H5lI=", - "dev": true - }, "@types/prettier": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.1.tgz", @@ -1487,12 +1487,36 @@ "sprintf-js": "~1.0.2" } }, + "array-includes": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", + "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + } + }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "array.prototype.flat": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", + "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0" + } + }, "ast-metadata-inferer": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/ast-metadata-inferer/-/ast-metadata-inferer-0.2.0.tgz", @@ -1725,6 +1749,16 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1960,6 +1994,15 @@ "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", "dev": true }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1967,9 +2010,9 @@ "dev": true }, "denque": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", - "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", + "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", "dev": true }, "detect-newline": { @@ -2046,6 +2089,45 @@ "ansi-colors": "^4.1.1" } }, + "es-abstract": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", + "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", + "is-string": "^1.0.7", + "is-weakref": "^1.0.1", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -2262,6 +2344,107 @@ } } }, + "eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-module-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz", + "integrity": "sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "find-up": "^2.1.0", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + } + } + }, "eslint-plugin-compat": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-3.7.0.tgz", @@ -2285,6 +2468,71 @@ } } }, + "eslint-plugin-import": { + "version": "2.25.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz", + "integrity": "sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg==", + "dev": true, + "requires": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.1", + "has": "^1.0.3", + "is-core-module": "^2.8.0", + "is-glob": "^4.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.5", + "resolve": "^1.20.0", + "tsconfig-paths": "^3.11.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "is-core-module": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -2696,6 +2944,17 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, "get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -2708,6 +2967,16 @@ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -2780,12 +3049,33 @@ "function-bind": "^1.1.1" } }, + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, "html-encoding-sniffer": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", @@ -2893,10 +3183,21 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, "ioredis": { - "version": "4.27.2", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.27.2.tgz", - "integrity": "sha512-7OpYymIthonkC2Jne5uGWXswdhlua1S1rWGAERaotn0hGJWTSURvxdHA9G6wNbT/qKCloCja/FHsfKXW8lpTmg==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.28.2.tgz", + "integrity": "sha512-kQ+Iv7+c6HsDdPP2XUHaMv8DhnSeAeKEwMbaoqsXYbO+03dItXt7+5jGQDRyjdRUV2rFJbzg7P4Qt1iX2tqkOg==", "dev": true, "requires": { "cluster-key-slot": "^1.1.0", @@ -2904,6 +3205,7 @@ "denque": "^1.1.0", "lodash.defaults": "^4.2.0", "lodash.flatten": "^4.4.0", + "lodash.isarguments": "^3.1.0", "p-map": "^2.1.0", "redis-commands": "1.7.0", "redis-errors": "^1.2.0", @@ -2911,6 +3213,31 @@ "standard-as-callback": "^2.1.0" } }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true + }, "is-ci": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz", @@ -2929,6 +3256,15 @@ "has": "^1.0.3" } }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2956,36 +3292,94 @@ "is-extglob": "^2.1.1" } }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-number-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", + "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "dev": true + }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-subset": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", "dev": true }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -4480,6 +4874,12 @@ "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", "dev": true }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -4675,10 +5075,40 @@ "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", "dev": true }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } }, "once": { "version": "1.4.0", @@ -5046,6 +5476,17 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "signal-exit": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", @@ -5178,6 +5619,26 @@ } } }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -5421,6 +5882,35 @@ } } }, + "tsconfig-paths": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", + "integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, "tslib": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", @@ -5479,6 +5969,18 @@ "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", "dev": true }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + } + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -5593,6 +6095,19 @@ "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", diff --git a/package.json b/package.json index 01e594a7..dc09153e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "1.0.0", + "version": "1.1.0", "description": "Split Javascript SDK common components", "main": "cjs/index.js", "module": "esm/index.js", @@ -44,24 +44,22 @@ "bugs": "https://github.com/splitio/javascript-commons/issues", "homepage": "https://github.com/splitio/javascript-commons#readme", "dependencies": { - "object-assign": "^4.1.1", "tslib": "^2.1.0" }, "devDependencies": { "@types/google.analytics": "0.0.40", - "@types/ioredis": "^4.14.1", + "@types/ioredis": "^4.28.0", "@types/jest": "^27.0.0", "@types/lodash": "^4.14.162", - "@types/node": "^14.14.7", - "@types/object-assign": "^4.0.30", "@typescript-eslint/eslint-plugin": "^4.2.0", "@typescript-eslint/parser": "^4.2.0", "cross-env": "^7.0.2", "csv-streamify": "^4.0.0", "eslint": "^7.32.0", "eslint-plugin-compat": "3.7.0", + "eslint-plugin-import": "^2.25.3", "fetch-mock": "^9.10.7", - "ioredis": "^4.26.0", + "ioredis": "^4.28.0", "jest": "^27.2.3", "jest-localstorage-mock": "^2.4.3", "js-yaml": "^3.14.0", diff --git a/src/__tests__/testUtils/eventSourceMock.ts b/src/__tests__/testUtils/eventSourceMock.ts index 281421b3..f47615ed 100644 --- a/src/__tests__/testUtils/eventSourceMock.ts +++ b/src/__tests__/testUtils/eventSourceMock.ts @@ -12,7 +12,7 @@ * */ -import EventEmitter from '../../utils/MinEvents'; +import { EventEmitter } from '../../utils/MinEvents'; import { IEventEmitter } from '../../types'; type ReadyStateType = 0 | 1 | 2; diff --git a/src/evaluator/Engine.ts b/src/evaluator/Engine.ts index d7466caa..af65a87f 100644 --- a/src/evaluator/Engine.ts +++ b/src/evaluator/Engine.ts @@ -1,7 +1,7 @@ import { get } from '../utils/lang'; -import parser from './parser'; +import { parser } from './parser'; import { keyParser } from '../utils/key'; -import thenable from '../utils/promise/thenable'; +import { thenable } from '../utils/promise/thenable'; import * as LabelsConstants from '../utils/labels'; import { CONTROL } from '../utils/constants'; import { ISplit, MaybeThenable } from '../dtos/types'; @@ -17,7 +17,7 @@ function evaluationResult(result: IEvaluation | undefined, defaultTreatment: str }; } -export default class Engine { +export class Engine { constructor(private baseInfo: ISplit, private evaluator: IEvaluator) { diff --git a/src/evaluator/combiners/__tests__/and.spec.ts b/src/evaluator/combiners/__tests__/and.spec.ts index 1990546e..8d31a9d4 100644 --- a/src/evaluator/combiners/__tests__/and.spec.ts +++ b/src/evaluator/combiners/__tests__/and.spec.ts @@ -1,16 +1,16 @@ -import andCombiner from '../and'; +import { andCombinerContext } from '../and'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; test('COMBINER AND / should always return true', async function () { - let AND = andCombiner(loggerMock, [() => true, () => true, () => true]); + let AND = andCombinerContext(loggerMock, [() => true, () => true, () => true]); expect(await AND('always true')).toBe(true); // should always return true }); test('COMBINER AND / should always return false', async function () { - let AND = andCombiner(loggerMock, [() => true, () => true, () => false]); + let AND = andCombinerContext(loggerMock, [() => true, () => true, () => false]); expect(await AND('always false')).toBe(false); // should always return false }); diff --git a/src/evaluator/combiners/__tests__/ifelseif.spec.ts b/src/evaluator/combiners/__tests__/ifelseif.spec.ts index eb874ebc..983b21a1 100644 --- a/src/evaluator/combiners/__tests__/ifelseif.spec.ts +++ b/src/evaluator/combiners/__tests__/ifelseif.spec.ts @@ -1,5 +1,5 @@ // @ts-nocheck -import ifElseIfCombinerFactory from '../ifelseif'; +import { ifElseIfCombinerContext } from '../ifelseif'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; test('IF ELSE IF COMBINER / should correctly propagate context parameters and predicates returns value', async function () { @@ -17,7 +17,7 @@ test('IF ELSE IF COMBINER / should correctly propagate context parameters and pr } let predicates = [evaluator]; - let ifElseIfEvaluator = ifElseIfCombinerFactory(loggerMock, predicates); + let ifElseIfEvaluator = ifElseIfCombinerContext(loggerMock, predicates); expect(await ifElseIfEvaluator(inputKey, inputSeed, inputAttributes) === evaluationResult).toBe(true); console.log(`evaluator should return ${evaluationResult}`); @@ -36,7 +36,7 @@ test('IF ELSE IF COMBINER / should stop evaluating when one matcher return a tre } ]; - let ifElseIfEvaluator = ifElseIfCombinerFactory(loggerMock, predicates); + let ifElseIfEvaluator = ifElseIfCombinerContext(loggerMock, predicates); expect(await ifElseIfEvaluator()).toBe('exclude'); // exclude treatment found }); @@ -54,7 +54,7 @@ test('IF ELSE IF COMBINER / should return undefined if there is none matching ru } ]; - const ifElseIfEvaluator = ifElseIfCombinerFactory(loggerMock, predicates); + const ifElseIfEvaluator = ifElseIfCombinerContext(loggerMock, predicates); expect(await ifElseIfEvaluator() === undefined).toBe(true); }); diff --git a/src/evaluator/combiners/and.ts b/src/evaluator/combiners/and.ts index 271801dd..b229a22b 100644 --- a/src/evaluator/combiners/and.ts +++ b/src/evaluator/combiners/and.ts @@ -1,11 +1,11 @@ import { findIndex } from '../../utils/lang'; import { ILogger } from '../../logger/types'; -import thenable from '../../utils/promise/thenable'; +import { thenable } from '../../utils/promise/thenable'; import { MaybeThenable } from '../../dtos/types'; import { IMatcher } from '../types'; import { ENGINE_COMBINER_AND } from '../../logger/constants'; -export default function andCombinerContext(log: ILogger, matchers: IMatcher[]) { +export function andCombinerContext(log: ILogger, matchers: IMatcher[]) { function andResults(results: boolean[]): boolean { // Array.prototype.every is supported by target environments diff --git a/src/evaluator/combiners/ifelseif.ts b/src/evaluator/combiners/ifelseif.ts index 9aa162f7..2ecb95ff 100644 --- a/src/evaluator/combiners/ifelseif.ts +++ b/src/evaluator/combiners/ifelseif.ts @@ -1,13 +1,13 @@ import { findIndex } from '../../utils/lang'; import { ILogger } from '../../logger/types'; -import thenable from '../../utils/promise/thenable'; +import { thenable } from '../../utils/promise/thenable'; import * as LabelsConstants from '../../utils/labels'; import { CONTROL } from '../../utils/constants'; import { SplitIO } from '../../types'; import { IEvaluation, IEvaluator, ISplitEvaluator } from '../types'; import { ENGINE_COMBINER_IFELSEIF, ENGINE_COMBINER_IFELSEIF_NO_TREATMENT, ERROR_ENGINE_COMBINER_IFELSEIF } from '../../logger/constants'; -export default function ifElseIfCombinerContext(log: ILogger, predicates: IEvaluator[]): IEvaluator { +export function ifElseIfCombinerContext(log: ILogger, predicates: IEvaluator[]): IEvaluator { function unexpectedInputHandler() { log.error(ERROR_ENGINE_COMBINER_IFELSEIF); diff --git a/src/evaluator/condition/__tests__/engineUtils.spec.ts b/src/evaluator/condition/__tests__/engineUtils.spec.ts index 0f1a6d87..8682c851 100644 --- a/src/evaluator/condition/__tests__/engineUtils.spec.ts +++ b/src/evaluator/condition/__tests__/engineUtils.spec.ts @@ -1,5 +1,5 @@ import * as engineUtils from '../engineUtils'; -import Treatments from '../../treatments'; +import { Treatments } from '../../treatments'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; const treatmentsMock = Treatments.parse([{ diff --git a/src/evaluator/condition/index.ts b/src/evaluator/condition/index.ts index 542617ea..5cf1cac8 100644 --- a/src/evaluator/condition/index.ts +++ b/src/evaluator/condition/index.ts @@ -1,5 +1,5 @@ import { getTreatment, shouldApplyRollout } from './engineUtils'; -import thenable from '../../utils/promise/thenable'; +import { thenable } from '../../utils/promise/thenable'; import * as LabelsConstants from '../../utils/labels'; import { MaybeThenable } from '../../dtos/types'; import { IEvaluation, IEvaluator, ISplitEvaluator } from '../types'; @@ -22,7 +22,7 @@ function match(log: ILogger, matchingResult: boolean, bucketingKey: string | und } // Condition factory -export default function conditionContext(log: ILogger, matcherEvaluator: (...args: any) => MaybeThenable, treatments: { getTreatmentFor: (x: number) => string }, label: string, conditionType: 'ROLLOUT' | 'WHITELIST'): IEvaluator { +export function conditionContext(log: ILogger, matcherEvaluator: (...args: any) => MaybeThenable, treatments: { getTreatmentFor: (x: number) => string }, label: string, conditionType: 'ROLLOUT' | 'WHITELIST'): IEvaluator { return function conditionEvaluator(key: SplitIO.SplitKey, seed: number, trafficAllocation?: number, trafficAllocationSeed?: number, attributes?: SplitIO.Attributes, splitEvaluator?: ISplitEvaluator) { diff --git a/src/evaluator/index.ts b/src/evaluator/index.ts index 3c12c9b3..9f6cccf1 100644 --- a/src/evaluator/index.ts +++ b/src/evaluator/index.ts @@ -1,7 +1,6 @@ -import Engine from './Engine'; -import thenable from '../utils/promise/thenable'; +import { Engine } from './Engine'; +import { thenable } from '../utils/promise/thenable'; import * as LabelsConstants from '../utils/labels'; -import { get } from '../utils/lang'; import { CONTROL } from '../utils/constants'; import { ISplit, MaybeThenable } from '../dtos/types'; import { IStorageAsync, IStorageSync } from '../storages/types'; @@ -106,17 +105,17 @@ function getEvaluation( const split = Engine.parse(log, splitJSON, storage); evaluation = split.getTreatment(key, attributes, evaluateFeature); - // If the storage is async, evaluation and changeNumber will return a thenable + // If the storage is async and the evaluated split uses segment, evaluation is thenable if (thenable(evaluation)) { return evaluation.then(result => { result.changeNumber = split.getChangeNumber(); - result.config = get(splitJSON, `configurations.${result.treatment}`, null); + result.config = splitJSON.configurations && splitJSON.configurations[result.treatment] || null; return result; }); } else { evaluation.changeNumber = split.getChangeNumber(); // Always sync and optional - evaluation.config = get(splitJSON, `configurations.${evaluation.treatment}`, null); + evaluation.config = splitJSON.configurations && splitJSON.configurations[evaluation.treatment] || null; } } diff --git a/src/evaluator/matchers/__tests__/all.spec.ts b/src/evaluator/matchers/__tests__/all.spec.ts index d4a47b09..aea0063d 100644 --- a/src/evaluator/matchers/__tests__/all.spec.ts +++ b/src/evaluator/matchers/__tests__/all.spec.ts @@ -1,5 +1,5 @@ import { matcherTypes } from '../matcherTypes'; -import matcherFactory from '..'; +import { matcherFactory } from '..'; import { IMatcher, IMatcherDto } from '../../types'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/evaluator/matchers/__tests__/between.spec.ts b/src/evaluator/matchers/__tests__/between.spec.ts index ca95ba16..9e7308f1 100644 --- a/src/evaluator/matchers/__tests__/between.spec.ts +++ b/src/evaluator/matchers/__tests__/between.spec.ts @@ -1,5 +1,5 @@ import { matcherTypes } from '../matcherTypes'; -import matcherFactory from '..'; +import { matcherFactory } from '..'; import { IMatcher, IMatcherDto } from '../../types'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/evaluator/matchers/__tests__/boolean.spec.ts b/src/evaluator/matchers/__tests__/boolean.spec.ts index 515b46d5..8f33893c 100644 --- a/src/evaluator/matchers/__tests__/boolean.spec.ts +++ b/src/evaluator/matchers/__tests__/boolean.spec.ts @@ -1,5 +1,5 @@ import { matcherTypes } from '../matcherTypes'; -import matcherFactory from '..'; +import { matcherFactory } from '..'; import { IMatcher, IMatcherDto } from '../../types'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/evaluator/matchers/__tests__/cont_all.spec.ts b/src/evaluator/matchers/__tests__/cont_all.spec.ts index 8ea23697..2b11df6f 100644 --- a/src/evaluator/matchers/__tests__/cont_all.spec.ts +++ b/src/evaluator/matchers/__tests__/cont_all.spec.ts @@ -1,5 +1,5 @@ import { matcherTypes } from '../matcherTypes'; -import matcherFactory from '..'; +import { matcherFactory } from '..'; import { IMatcher, IMatcherDto } from '../../types'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/evaluator/matchers/__tests__/cont_any.spec.ts b/src/evaluator/matchers/__tests__/cont_any.spec.ts index 13896900..cc4481a6 100644 --- a/src/evaluator/matchers/__tests__/cont_any.spec.ts +++ b/src/evaluator/matchers/__tests__/cont_any.spec.ts @@ -1,5 +1,5 @@ import { matcherTypes } from '../matcherTypes'; -import matcherFactory from '..'; +import { matcherFactory } from '..'; import { IMatcher, IMatcherDto } from '../../types'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/evaluator/matchers/__tests__/cont_str.spec.ts b/src/evaluator/matchers/__tests__/cont_str.spec.ts index f8c56d4e..527ab0f7 100644 --- a/src/evaluator/matchers/__tests__/cont_str.spec.ts +++ b/src/evaluator/matchers/__tests__/cont_str.spec.ts @@ -1,5 +1,5 @@ import { matcherTypes } from '../matcherTypes'; -import matcherFactory from '..'; +import { matcherFactory } from '..'; import { IMatcher, IMatcherDto } from '../../types'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/evaluator/matchers/__tests__/dependency.spec.ts b/src/evaluator/matchers/__tests__/dependency.spec.ts index 6f4f62ec..0e41a6cf 100644 --- a/src/evaluator/matchers/__tests__/dependency.spec.ts +++ b/src/evaluator/matchers/__tests__/dependency.spec.ts @@ -1,5 +1,5 @@ import { matcherTypes } from '../matcherTypes'; -import matcherFactory from '..'; +import { matcherFactory } from '..'; import { evaluateFeature } from '../../index'; import { IMatcher, IMatcherDto } from '../../types'; import { IStorageSync } from '../../../storages/types'; diff --git a/src/evaluator/matchers/__tests__/eq.spec.ts b/src/evaluator/matchers/__tests__/eq.spec.ts index 40b40f8d..57ffb394 100644 --- a/src/evaluator/matchers/__tests__/eq.spec.ts +++ b/src/evaluator/matchers/__tests__/eq.spec.ts @@ -1,5 +1,5 @@ import { matcherTypes } from '../matcherTypes'; -import matcherFactory from '..'; +import { matcherFactory } from '..'; import { IMatcher, IMatcherDto } from '../../types'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/evaluator/matchers/__tests__/eq_set.spec.ts b/src/evaluator/matchers/__tests__/eq_set.spec.ts index 17450cf6..9acf6412 100644 --- a/src/evaluator/matchers/__tests__/eq_set.spec.ts +++ b/src/evaluator/matchers/__tests__/eq_set.spec.ts @@ -1,5 +1,5 @@ import { matcherTypes } from '../matcherTypes'; -import matcherFactory from '..'; +import { matcherFactory } from '..'; import { IMatcher, IMatcherDto } from '../../types'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/evaluator/matchers/__tests__/ew.spec.ts b/src/evaluator/matchers/__tests__/ew.spec.ts index c5980d0b..7d071c5b 100644 --- a/src/evaluator/matchers/__tests__/ew.spec.ts +++ b/src/evaluator/matchers/__tests__/ew.spec.ts @@ -1,5 +1,5 @@ import { matcherTypes } from '../matcherTypes'; -import matcherFactory from '..'; +import { matcherFactory } from '..'; import { IMatcher, IMatcherDto } from '../../types'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/evaluator/matchers/__tests__/gte.spec.ts b/src/evaluator/matchers/__tests__/gte.spec.ts index f7a4ec54..0c6046b4 100644 --- a/src/evaluator/matchers/__tests__/gte.spec.ts +++ b/src/evaluator/matchers/__tests__/gte.spec.ts @@ -1,5 +1,5 @@ import { matcherTypes } from '../matcherTypes'; -import matcherFactory from '..'; +import { matcherFactory } from '..'; import { IMatcher, IMatcherDto } from '../../types'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/evaluator/matchers/__tests__/lte.spec.ts b/src/evaluator/matchers/__tests__/lte.spec.ts index ad920020..a461269f 100644 --- a/src/evaluator/matchers/__tests__/lte.spec.ts +++ b/src/evaluator/matchers/__tests__/lte.spec.ts @@ -1,5 +1,5 @@ import { matcherTypes } from '../matcherTypes'; -import matcherFactory from '..'; +import { matcherFactory } from '..'; import { IMatcher, IMatcherDto } from '../../types'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/evaluator/matchers/__tests__/part_of.spec.ts b/src/evaluator/matchers/__tests__/part_of.spec.ts index 9472d389..1b48f093 100644 --- a/src/evaluator/matchers/__tests__/part_of.spec.ts +++ b/src/evaluator/matchers/__tests__/part_of.spec.ts @@ -1,5 +1,5 @@ import { matcherTypes } from '../matcherTypes'; -import matcherFactory from '..'; +import { matcherFactory } from '..'; import { IMatcher, IMatcherDto } from '../../types'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/evaluator/matchers/__tests__/regex.spec.ts b/src/evaluator/matchers/__tests__/regex.spec.ts index 3465d623..e93164c0 100644 --- a/src/evaluator/matchers/__tests__/regex.spec.ts +++ b/src/evaluator/matchers/__tests__/regex.spec.ts @@ -1,5 +1,5 @@ import { matcherTypes } from '../matcherTypes'; -import matcherFactory from '..'; +import { matcherFactory } from '..'; import fs from 'fs'; import rl from 'readline'; import { IMatcher, IMatcherDto } from '../../types'; diff --git a/src/evaluator/matchers/__tests__/segment/client_side.spec.ts b/src/evaluator/matchers/__tests__/segment/client_side.spec.ts index 7514a862..3bace2ca 100644 --- a/src/evaluator/matchers/__tests__/segment/client_side.spec.ts +++ b/src/evaluator/matchers/__tests__/segment/client_side.spec.ts @@ -1,5 +1,5 @@ import { matcherTypes } from '../../matcherTypes'; -import matcherFactory from '../..'; +import { matcherFactory } from '../..'; import { IMatcher, IMatcherDto } from '../../../types'; import { IStorageSync } from '../../../../storages/types'; import { loggerMock } from '../../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/evaluator/matchers/__tests__/segment/server_side.spec.ts b/src/evaluator/matchers/__tests__/segment/server_side.spec.ts index 97d9dcc7..906aec62 100644 --- a/src/evaluator/matchers/__tests__/segment/server_side.spec.ts +++ b/src/evaluator/matchers/__tests__/segment/server_side.spec.ts @@ -1,5 +1,5 @@ import { matcherTypes } from '../../matcherTypes'; -import matcherFactory from '../..'; +import { matcherFactory } from '../..'; import { IMatcher, IMatcherDto } from '../../../types'; import { IStorageSync } from '../../../../storages/types'; import { loggerMock } from '../../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/evaluator/matchers/__tests__/sw.spec.ts b/src/evaluator/matchers/__tests__/sw.spec.ts index ec8cdb5b..3bf6ca3f 100644 --- a/src/evaluator/matchers/__tests__/sw.spec.ts +++ b/src/evaluator/matchers/__tests__/sw.spec.ts @@ -1,5 +1,5 @@ import { matcherTypes } from '../matcherTypes'; -import matcherFactory from '..'; +import { matcherFactory } from '..'; import { IMatcher, IMatcherDto } from '../../types'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/evaluator/matchers/__tests__/whitelist.spec.ts b/src/evaluator/matchers/__tests__/whitelist.spec.ts index 4c7aa723..64e1f2b2 100644 --- a/src/evaluator/matchers/__tests__/whitelist.spec.ts +++ b/src/evaluator/matchers/__tests__/whitelist.spec.ts @@ -1,5 +1,5 @@ import { matcherTypes } from '../matcherTypes'; -import matcherFactory from '..'; +import { matcherFactory } from '..'; import { _Set } from '../../../utils/lang/sets'; import { IMatcher, IMatcherDto } from '../../types'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/evaluator/matchers/all.ts b/src/evaluator/matchers/all.ts index 8b8c64fb..51326a91 100644 --- a/src/evaluator/matchers/all.ts +++ b/src/evaluator/matchers/all.ts @@ -1,7 +1,7 @@ import { ENGINE_MATCHER_ALL } from '../../logger/constants'; import { ILogger } from '../../logger/types'; -export default function allMatcherContext(log: ILogger) { +export function allMatcherContext(log: ILogger) { return function allMatcher(runtimeAttr: string): boolean { log.debug(ENGINE_MATCHER_ALL); diff --git a/src/evaluator/matchers/between.ts b/src/evaluator/matchers/between.ts index f56709b3..063e2a59 100644 --- a/src/evaluator/matchers/between.ts +++ b/src/evaluator/matchers/between.ts @@ -2,7 +2,7 @@ import { IBetweenMatcherData } from '../../dtos/types'; import { ENGINE_MATCHER_BETWEEN } from '../../logger/constants'; import { ILogger } from '../../logger/types'; -export default function betweenMatcherContext(log: ILogger, ruleVO: IBetweenMatcherData) /*: Function */ { +export function betweenMatcherContext(log: ILogger, ruleVO: IBetweenMatcherData) /*: Function */ { return function betweenMatcher(runtimeAttr: number): boolean { let isBetween = runtimeAttr >= ruleVO.start && runtimeAttr <= ruleVO.end; diff --git a/src/evaluator/matchers/boolean.ts b/src/evaluator/matchers/boolean.ts index 6e64a268..821d8f98 100644 --- a/src/evaluator/matchers/boolean.ts +++ b/src/evaluator/matchers/boolean.ts @@ -1,7 +1,7 @@ import { ENGINE_MATCHER_BOOLEAN } from '../../logger/constants'; import { ILogger } from '../../logger/types'; -export default function booleanMatcherContext(log: ILogger, ruleAttr: boolean) /*: Function */ { +export function booleanMatcherContext(log: ILogger, ruleAttr: boolean) /*: Function */ { return function booleanMatcher(runtimeAttr: boolean): boolean { let booleanMatches = ruleAttr === runtimeAttr; diff --git a/src/evaluator/matchers/cont_all.ts b/src/evaluator/matchers/cont_all.ts index 02bc35e1..e52a9e85 100644 --- a/src/evaluator/matchers/cont_all.ts +++ b/src/evaluator/matchers/cont_all.ts @@ -2,7 +2,7 @@ import { ENGINE_MATCHER_CONTAINS_ALL } from '../../logger/constants'; import { ILogger } from '../../logger/types'; import { findIndex } from '../../utils/lang'; -export default function containsAllMatcherContext(log: ILogger, ruleAttr: string[]) /*: Function */ { +export function containsAllSetMatcherContext(log: ILogger, ruleAttr: string[]) /*: Function */ { return function containsAllMatcher(runtimeAttr: string[]): boolean { let containsAll = true; diff --git a/src/evaluator/matchers/cont_any.ts b/src/evaluator/matchers/cont_any.ts index 69fcdb3e..6ab0b08c 100644 --- a/src/evaluator/matchers/cont_any.ts +++ b/src/evaluator/matchers/cont_any.ts @@ -2,7 +2,7 @@ import { ENGINE_MATCHER_CONTAINS_ANY } from '../../logger/constants'; import { ILogger } from '../../logger/types'; import { findIndex } from '../../utils/lang'; -export default function containsAnyMatcherContext(log: ILogger, ruleAttr: string[]) /*: Function */ { +export function containsAnySetMatcherContext(log: ILogger, ruleAttr: string[]) /*: Function */ { return function containsAnyMatcher(runtimeAttr: string[]): boolean { let containsAny = false; diff --git a/src/evaluator/matchers/cont_str.ts b/src/evaluator/matchers/cont_str.ts index 0a15d13e..b3dfe506 100644 --- a/src/evaluator/matchers/cont_str.ts +++ b/src/evaluator/matchers/cont_str.ts @@ -2,7 +2,7 @@ import { isString } from '../../utils/lang'; import { ILogger } from '../../logger/types'; import { ENGINE_MATCHER_CONTAINS_STRING } from '../../logger/constants'; -export default function containsStringMatcherContext(log: ILogger, ruleAttr: string[]) /*: Function */ { +export function containsStringMatcherContext(log: ILogger, ruleAttr: string[]) /*: Function */ { return function containsStringMatcher(runtimeAttr: string): boolean { let contains = ruleAttr.some(e => isString(runtimeAttr) && runtimeAttr.indexOf(e) > -1); diff --git a/src/evaluator/matchers/dependency.ts b/src/evaluator/matchers/dependency.ts index e3b8723b..42040328 100644 --- a/src/evaluator/matchers/dependency.ts +++ b/src/evaluator/matchers/dependency.ts @@ -1,11 +1,11 @@ import { IDependencyMatcherData, MaybeThenable } from '../../dtos/types'; import { IStorageAsync, IStorageSync } from '../../storages/types'; import { ILogger } from '../../logger/types'; -import thenable from '../../utils/promise/thenable'; +import { thenable } from '../../utils/promise/thenable'; import { IDependencyMatcherValue, IEvaluation, ISplitEvaluator } from '../types'; import { ENGINE_MATCHER_DEPENDENCY, ENGINE_MATCHER_DEPENDENCY_PRE } from '../../logger/constants'; -export default function dependencyMatcherContext(log: ILogger, { split, treatments }: IDependencyMatcherData, storage: IStorageSync | IStorageAsync) { +export function dependencyMatcherContext(log: ILogger, { split, treatments }: IDependencyMatcherData, storage: IStorageSync | IStorageAsync) { function checkTreatment(evaluation: IEvaluation, acceptableTreatments: string[], parentName: string) { let matches = false; diff --git a/src/evaluator/matchers/eq.ts b/src/evaluator/matchers/eq.ts index ddccc264..019b1a19 100644 --- a/src/evaluator/matchers/eq.ts +++ b/src/evaluator/matchers/eq.ts @@ -1,7 +1,7 @@ import { ENGINE_MATCHER_EQUAL } from '../../logger/constants'; import { ILogger } from '../../logger/types'; -export default function equalToMatcherContext(log: ILogger, ruleAttr: number) /*: Function */ { +export function equalToMatcherContext(log: ILogger, ruleAttr: number) /*: Function */ { return function equalToMatcher(runtimeAttr: number): boolean { let isEqual = runtimeAttr === ruleAttr; diff --git a/src/evaluator/matchers/eq_set.ts b/src/evaluator/matchers/eq_set.ts index f0fddd27..b36b1103 100644 --- a/src/evaluator/matchers/eq_set.ts +++ b/src/evaluator/matchers/eq_set.ts @@ -2,7 +2,7 @@ import { ENGINE_MATCHER_EQUAL_TO_SET } from '../../logger/constants'; import { ILogger } from '../../logger/types'; import { findIndex } from '../../utils/lang'; -export default function equalToSetMatcherContext(log: ILogger, ruleAttr: string[]) /*: Function */ { +export function equalToSetMatcherContext(log: ILogger, ruleAttr: string[]) /*: Function */ { return function equalToSetMatcher(runtimeAttr: string[]): boolean { // Length being the same is the first condition. let isEqual = runtimeAttr.length === ruleAttr.length; diff --git a/src/evaluator/matchers/ew.ts b/src/evaluator/matchers/ew.ts index 20d46c63..851e0e2c 100644 --- a/src/evaluator/matchers/ew.ts +++ b/src/evaluator/matchers/ew.ts @@ -2,7 +2,7 @@ import { ENGINE_MATCHER_ENDS_WITH } from '../../logger/constants'; import { ILogger } from '../../logger/types'; import { endsWith as strEndsWith } from '../../utils/lang'; -export default function endsWithMatcherContext(log: ILogger, ruleAttr: string[]) /*: Function */ { +export function endsWithMatcherContext(log: ILogger, ruleAttr: string[]) /*: Function */ { return function endsWithMatcher(runtimeAttr: string): boolean { let endsWith = ruleAttr.some(e => strEndsWith(runtimeAttr, e)); diff --git a/src/evaluator/matchers/gte.ts b/src/evaluator/matchers/gte.ts index a08f55f5..83d83e4e 100644 --- a/src/evaluator/matchers/gte.ts +++ b/src/evaluator/matchers/gte.ts @@ -1,7 +1,7 @@ import { ENGINE_MATCHER_GREATER } from '../../logger/constants'; import { ILogger } from '../../logger/types'; -export default function greaterThanEqualMatcherContext(log: ILogger, ruleAttr: number) /*: Function */ { +export function greaterThanEqualMatcherContext(log: ILogger, ruleAttr: number) /*: Function */ { return function greaterThanEqualMatcher(runtimeAttr: number): boolean { let isGreaterEqualThan = runtimeAttr >= ruleAttr; diff --git a/src/evaluator/matchers/index.ts b/src/evaluator/matchers/index.ts index 8cf50779..ecb195b4 100644 --- a/src/evaluator/matchers/index.ts +++ b/src/evaluator/matchers/index.ts @@ -1,49 +1,49 @@ -import allMatcher from './all'; -import segmentMatcher from './segment'; -import whitelistMatcher from './whitelist'; -import eqMatcher from './eq'; -import gteMatcher from './gte'; -import lteMatcher from './lte'; -import betweenMatcher from './between'; -import equalToSetMatcher from './eq_set'; -import containsAllSetMatcher from './cont_all'; -import containsAnySetMatcher from './cont_any'; -import partOfSetMatcher from './part_of'; -import swMatcher from './sw'; -import ewMatcher from './ew'; -import containsStrMatcher from './cont_str'; -import dependencyMatcher from './dependency'; -import booleanMatcher from './boolean'; -import stringMatcher from './string'; +import { allMatcherContext } from './all'; +import { segmentMatcherContext } from './segment'; +import { whitelistMatcherContext } from './whitelist'; +import { equalToMatcherContext } from './eq'; +import { greaterThanEqualMatcherContext } from './gte'; +import { lessThanEqualMatcherContext } from './lte'; +import { betweenMatcherContext } from './between'; +import { equalToSetMatcherContext } from './eq_set'; +import { containsAnySetMatcherContext } from './cont_any'; +import { containsAllSetMatcherContext } from './cont_all'; +import { partOfSetMatcherContext } from './part_of'; +import { endsWithMatcherContext } from './ew'; +import { startsWithMatcherContext } from './sw'; +import { containsStringMatcherContext } from './cont_str'; +import { dependencyMatcherContext } from './dependency'; +import { booleanMatcherContext } from './boolean'; +import { stringMatcherContext } from './string'; import { IStorageAsync, IStorageSync } from '../../storages/types'; import { IMatcher, IMatcherDto } from '../types'; import { ILogger } from '../../logger/types'; const matchers = [ undefined, // UNDEFINED: 0, - allMatcher, // ALL_KEYS: 1, - segmentMatcher, // IN_SEGMENT: 2, - whitelistMatcher, // WHITELIST: 3, - eqMatcher, // EQUAL_TO: 4, - gteMatcher, // GREATER_THAN_OR_EQUAL_TO: 5, - lteMatcher, // LESS_THAN_OR_EQUAL_TO: 6, - betweenMatcher, // BETWEEN: 7, - equalToSetMatcher, // EQUAL_TO_SET: 8, - containsAnySetMatcher, // CONTAINS_ANY_OF_SET: 9, - containsAllSetMatcher, // CONTAINS_ALL_OF_SET: 10, - partOfSetMatcher, // PART_OF_SET: 11, - ewMatcher, // ENDS_WITH: 12, - swMatcher, // STARTS_WITH: 13, - containsStrMatcher, // CONTAINS_STRING: 14, - dependencyMatcher, // IN_SPLIT_TREATMENT: 15, - booleanMatcher, // EQUAL_TO_BOOLEAN: 16, - stringMatcher // MATCHES_STRING: 17 + allMatcherContext, // ALL_KEYS: 1, + segmentMatcherContext, // IN_SEGMENT: 2, + whitelistMatcherContext, // WHITELIST: 3, + equalToMatcherContext, // EQUAL_TO: 4, + greaterThanEqualMatcherContext, // GREATER_THAN_OR_EQUAL_TO: 5, + lessThanEqualMatcherContext, // LESS_THAN_OR_EQUAL_TO: 6, + betweenMatcherContext, // BETWEEN: 7, + equalToSetMatcherContext, // EQUAL_TO_SET: 8, + containsAnySetMatcherContext, // CONTAINS_ANY_OF_SET: 9, + containsAllSetMatcherContext, // CONTAINS_ALL_OF_SET: 10, + partOfSetMatcherContext, // PART_OF_SET: 11, + endsWithMatcherContext, // ENDS_WITH: 12, + startsWithMatcherContext, // STARTS_WITH: 13, + containsStringMatcherContext, // CONTAINS_STRING: 14, + dependencyMatcherContext, // IN_SPLIT_TREATMENT: 15, + booleanMatcherContext, // EQUAL_TO_BOOLEAN: 16, + stringMatcherContext // MATCHES_STRING: 17 ]; /** * Matcher factory. */ -export default function matcherFactory(log: ILogger, matcherDto: IMatcherDto, storage?: IStorageSync | IStorageAsync): IMatcher | undefined { +export function matcherFactory(log: ILogger, matcherDto: IMatcherDto, storage?: IStorageSync | IStorageAsync): IMatcher | undefined { let { type, value diff --git a/src/evaluator/matchers/lte.ts b/src/evaluator/matchers/lte.ts index 06883b94..b2ebeeb6 100644 --- a/src/evaluator/matchers/lte.ts +++ b/src/evaluator/matchers/lte.ts @@ -1,7 +1,7 @@ import { ENGINE_MATCHER_LESS } from '../../logger/constants'; import { ILogger } from '../../logger/types'; -export default function lessThanEqualMatcherContext(log: ILogger, ruleAttr: number) /*: function */ { +export function lessThanEqualMatcherContext(log: ILogger, ruleAttr: number) /*: function */ { return function lessThanEqualMatcher(runtimeAttr: number): boolean { let isLessEqualThan = runtimeAttr <= ruleAttr; diff --git a/src/evaluator/matchers/part_of.ts b/src/evaluator/matchers/part_of.ts index ccabf3e4..75385731 100644 --- a/src/evaluator/matchers/part_of.ts +++ b/src/evaluator/matchers/part_of.ts @@ -2,7 +2,7 @@ import { findIndex } from '../../utils/lang'; import { ILogger } from '../../logger/types'; import { ENGINE_MATCHER_PART_OF } from '../../logger/constants'; -export default function partOfMatcherContext(log: ILogger, ruleAttr: string[]) /*: Function */ { +export function partOfSetMatcherContext(log: ILogger, ruleAttr: string[]) /*: Function */ { return function partOfMatcher(runtimeAttr: string[]): boolean { // To be part of the length should be minor or equal. let isPartOf = runtimeAttr.length <= ruleAttr.length; diff --git a/src/evaluator/matchers/segment.ts b/src/evaluator/matchers/segment.ts index 3e9db7d5..ad5ec30a 100644 --- a/src/evaluator/matchers/segment.ts +++ b/src/evaluator/matchers/segment.ts @@ -1,10 +1,10 @@ import { MaybeThenable } from '../../dtos/types'; import { ISegmentsCacheBase } from '../../storages/types'; import { ILogger } from '../../logger/types'; -import thenable from '../../utils/promise/thenable'; +import { thenable } from '../../utils/promise/thenable'; import { ENGINE_MATCHER_SEGMENT } from '../../logger/constants'; -export default function matcherSegmentContext(log: ILogger, segmentName: string, storage: { segments: ISegmentsCacheBase }) { +export function segmentMatcherContext(log: ILogger, segmentName: string, storage: { segments: ISegmentsCacheBase }) { return function segmentMatcher(key: string): MaybeThenable { const isInSegment = storage.segments.isInSegment(segmentName, key); diff --git a/src/evaluator/matchers/string.ts b/src/evaluator/matchers/string.ts index f0e0512f..81e8bc48 100644 --- a/src/evaluator/matchers/string.ts +++ b/src/evaluator/matchers/string.ts @@ -1,7 +1,7 @@ import { ENGINE_MATCHER_STRING_INVALID, ENGINE_MATCHER_STRING } from '../../logger/constants'; import { ILogger } from '../../logger/types'; -export default function stringMatcherContext(log: ILogger, ruleAttr: string) /*: Function */ { +export function stringMatcherContext(log: ILogger, ruleAttr: string) /*: Function */ { return function stringMatcher(runtimeAttr: string): boolean { let re; diff --git a/src/evaluator/matchers/sw.ts b/src/evaluator/matchers/sw.ts index e2bfc03e..7f014b20 100644 --- a/src/evaluator/matchers/sw.ts +++ b/src/evaluator/matchers/sw.ts @@ -2,7 +2,7 @@ import { ENGINE_MATCHER_STARTS_WITH } from '../../logger/constants'; import { ILogger } from '../../logger/types'; import { startsWith } from '../../utils/lang'; -export default function startsWithMatcherContext(log: ILogger, ruleAttr: string[]) /*: Function */ { +export function startsWithMatcherContext(log: ILogger, ruleAttr: string[]) /*: Function */ { return function startsWithMatcher(runtimeAttr: string): boolean { let matches = ruleAttr.some(e => startsWith(runtimeAttr, e)); diff --git a/src/evaluator/matchers/whitelist.ts b/src/evaluator/matchers/whitelist.ts index b84cb129..54fb35c8 100644 --- a/src/evaluator/matchers/whitelist.ts +++ b/src/evaluator/matchers/whitelist.ts @@ -2,7 +2,7 @@ import { setToArray, ISet } from '../../utils/lang/sets'; import { ILogger } from '../../logger/types'; import { ENGINE_MATCHER_WHITELIST } from '../../logger/constants'; -export default function whitelistMatcherContext(log: ILogger, ruleAttr: ISet) /*: Function */ { +export function whitelistMatcherContext(log: ILogger, ruleAttr: ISet) /*: Function */ { return function whitelistMatcher(runtimeAttr: string): boolean { let isInWhitelist = ruleAttr.has(runtimeAttr); diff --git a/src/evaluator/matchersTransform/__tests__/segment.spec.ts b/src/evaluator/matchersTransform/__tests__/segment.spec.ts index bfc9c2a8..1d25811e 100644 --- a/src/evaluator/matchersTransform/__tests__/segment.spec.ts +++ b/src/evaluator/matchersTransform/__tests__/segment.spec.ts @@ -1,4 +1,4 @@ -import transform from '../segment'; +import { segmentTransform } from '../segment'; test('TRANSFORMS / a segment object should be flatten to a string', function () { const segmentName = 'employees'; @@ -6,14 +6,14 @@ test('TRANSFORMS / a segment object should be flatten to a string', function () segmentName }; - const plainSegmentName = transform(sample); + const plainSegmentName = segmentTransform(sample); expect(segmentName).toBe(plainSegmentName); // extracted segmentName matches }); test('TRANSFORMS / if there is none segmentName entry, returns undefined', function () { const sample = undefined; - const undefinedSegmentName = transform(sample); + const undefinedSegmentName = segmentTransform(sample); expect(undefinedSegmentName).toBe(undefined); // expected to be undefined }); diff --git a/src/evaluator/matchersTransform/__tests__/whitelist.spec.ts b/src/evaluator/matchersTransform/__tests__/whitelist.spec.ts index c171631b..5f846390 100644 --- a/src/evaluator/matchersTransform/__tests__/whitelist.spec.ts +++ b/src/evaluator/matchersTransform/__tests__/whitelist.spec.ts @@ -1,4 +1,4 @@ -import transform from '../whitelist'; +import { whitelistTransform } from '../whitelist'; test('TRANSFORMS / a whitelist Array should be casted into a Set', function () { let sample = { @@ -9,7 +9,7 @@ test('TRANSFORMS / a whitelist Array should be casted into a Set', function () { ] }; - let sampleSet = transform(sample); + let sampleSet = whitelistTransform(sample); sample.whitelist.forEach(item => { if (!sampleSet.has(item)) { @@ -18,7 +18,7 @@ test('TRANSFORMS / a whitelist Array should be casted into a Set', function () { }); // @ts-ignore - sampleSet = transform({}); + sampleSet = whitelistTransform({}); expect(sampleSet.size).toBe(0); // Empty Set if passed an object without a whitelist expect(true).toBe(true); // Everything looks fine diff --git a/src/evaluator/matchersTransform/index.ts b/src/evaluator/matchersTransform/index.ts index abb1c8b0..b63b47f7 100644 --- a/src/evaluator/matchersTransform/index.ts +++ b/src/evaluator/matchersTransform/index.ts @@ -1,9 +1,9 @@ import { findIndex } from '../../utils/lang'; import { matcherTypes, matcherTypesMapper, matcherDataTypes } from '../matchers/matcherTypes'; -import segmentTransform from './segment'; -import whitelistTransform from './whitelist'; -import setTransform from './set'; -import numericTransform from './unaryNumeric'; +import { segmentTransform } from './segment'; +import { whitelistTransform } from './whitelist'; +import { setTransform } from './set'; +import { numericTransform } from './unaryNumeric'; import { zeroSinceHH, zeroSinceSS } from '../convertions'; import { IBetweenMatcherData, IInSegmentMatcherData, ISplitMatcher, IUnaryNumericMatcherData, IWhitelistMatcherData } from '../../dtos/types'; import { IMatcherDto } from '../types'; @@ -11,7 +11,7 @@ import { IMatcherDto } from '../types'; /** * Flat the complex matcherGroup structure into something handy. */ -export default function matchersTransform(matchers: ISplitMatcher[]): IMatcherDto[] { +export function matchersTransform(matchers: ISplitMatcher[]): IMatcherDto[] { let parsedMatchers = matchers.map(matcher => { let { diff --git a/src/evaluator/matchersTransform/segment.ts b/src/evaluator/matchersTransform/segment.ts index 40783b8f..00674cf2 100644 --- a/src/evaluator/matchersTransform/segment.ts +++ b/src/evaluator/matchersTransform/segment.ts @@ -3,6 +3,6 @@ import { IInSegmentMatcherData } from '../../dtos/types'; /** * Extract segment name as a plain string. */ -export default function transform(segment?: IInSegmentMatcherData) { +export function segmentTransform(segment?: IInSegmentMatcherData) { return segment ? segment.segmentName : undefined; } diff --git a/src/evaluator/matchersTransform/set.ts b/src/evaluator/matchersTransform/set.ts index bc130fa1..aefbe612 100644 --- a/src/evaluator/matchersTransform/set.ts +++ b/src/evaluator/matchersTransform/set.ts @@ -3,6 +3,6 @@ import { IWhitelistMatcherData } from '../../dtos/types'; /** * Extract whitelist array. Used by set and string matchers */ -export default function transform(whitelistObject: IWhitelistMatcherData) { +export function setTransform(whitelistObject: IWhitelistMatcherData) { return whitelistObject.whitelist; } diff --git a/src/evaluator/matchersTransform/unaryNumeric.ts b/src/evaluator/matchersTransform/unaryNumeric.ts index b8df02f6..701e9925 100644 --- a/src/evaluator/matchersTransform/unaryNumeric.ts +++ b/src/evaluator/matchersTransform/unaryNumeric.ts @@ -3,6 +3,6 @@ import { IUnaryNumericMatcherData } from '../../dtos/types'; /** * Extract value from unary matcher data. */ -export default function transform(unaryNumericObject: IUnaryNumericMatcherData) { +export function numericTransform(unaryNumericObject: IUnaryNumericMatcherData) { return unaryNumericObject.value; } diff --git a/src/evaluator/matchersTransform/whitelist.ts b/src/evaluator/matchersTransform/whitelist.ts index 635bf935..8b74870d 100644 --- a/src/evaluator/matchersTransform/whitelist.ts +++ b/src/evaluator/matchersTransform/whitelist.ts @@ -4,6 +4,6 @@ import { _Set } from '../../utils/lang/sets'; /** * Extract whitelist as a set. Used by 'WHITELIST' matcher. */ -export default function transform(whitelistObject: IWhitelistMatcherData) { +export function whitelistTransform(whitelistObject: IWhitelistMatcherData) { return new _Set(whitelistObject.whitelist); } diff --git a/src/evaluator/parser/__tests__/boolean.spec.ts b/src/evaluator/parser/__tests__/boolean.spec.ts index 81f8a30b..7d304ec9 100644 --- a/src/evaluator/parser/__tests__/boolean.spec.ts +++ b/src/evaluator/parser/__tests__/boolean.spec.ts @@ -1,4 +1,4 @@ -import parser from '..'; +import { parser } from '..'; import { keyParser } from '../../../utils/key'; import { ISplitCondition } from '../../../dtos/types'; import { IEvaluation } from '../../types'; diff --git a/src/evaluator/parser/__tests__/index.spec.ts b/src/evaluator/parser/__tests__/index.spec.ts index a27fecc7..41176fbd 100644 --- a/src/evaluator/parser/__tests__/index.spec.ts +++ b/src/evaluator/parser/__tests__/index.spec.ts @@ -1,5 +1,5 @@ // @ts-nocheck -import parser from '..'; +import { parser } from '..'; import { keyParser } from '../../../utils/key'; import { ISplitCondition } from '../../../dtos/types'; import { bucket } from '../../../utils/murmur3/murmur3'; diff --git a/src/evaluator/parser/__tests__/invalidMatcher.spec.ts b/src/evaluator/parser/__tests__/invalidMatcher.spec.ts index 3ff988bd..1920bf91 100644 --- a/src/evaluator/parser/__tests__/invalidMatcher.spec.ts +++ b/src/evaluator/parser/__tests__/invalidMatcher.spec.ts @@ -1,5 +1,5 @@ // @ts-nocheck -import parser from '..'; +import { parser } from '..'; import { ISplitCondition } from '../../../dtos/types'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/evaluator/parser/__tests__/regex.spec.ts b/src/evaluator/parser/__tests__/regex.spec.ts index ee2b0904..176c961a 100644 --- a/src/evaluator/parser/__tests__/regex.spec.ts +++ b/src/evaluator/parser/__tests__/regex.spec.ts @@ -1,4 +1,4 @@ -import parser from '..'; +import { parser } from '..'; import { keyParser } from '../../../utils/key'; import { ISplitCondition } from '../../../dtos/types'; import { IEvaluation } from '../../types'; diff --git a/src/evaluator/parser/__tests__/set.spec.ts b/src/evaluator/parser/__tests__/set.spec.ts index de652ed3..5d46abb8 100644 --- a/src/evaluator/parser/__tests__/set.spec.ts +++ b/src/evaluator/parser/__tests__/set.spec.ts @@ -1,5 +1,5 @@ // @ts-nocheck -import parser from '..'; +import { parser } from '..'; import { keyParser } from '../../../utils/key'; import { ISplitCondition } from '../../../dtos/types'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/evaluator/parser/__tests__/string.spec.ts b/src/evaluator/parser/__tests__/string.spec.ts index 326ade21..dc10f3c6 100644 --- a/src/evaluator/parser/__tests__/string.spec.ts +++ b/src/evaluator/parser/__tests__/string.spec.ts @@ -1,5 +1,5 @@ // @ts-nocheck -import parser from '..'; +import { parser } from '..'; import { ISplitCondition } from '../../../dtos/types'; import { keyParser } from '../../../utils/key'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/evaluator/parser/__tests__/trafficAllocation.spec.ts b/src/evaluator/parser/__tests__/trafficAllocation.spec.ts index ddf5afae..d9af5ca9 100644 --- a/src/evaluator/parser/__tests__/trafficAllocation.spec.ts +++ b/src/evaluator/parser/__tests__/trafficAllocation.spec.ts @@ -1,5 +1,5 @@ // @ts-nocheck -import parser from '..'; +import { parser } from '..'; import { keyParser } from '../../../utils/key'; import { ISplitCondition } from '../../../dtos/types'; import { IEvaluation } from '../../types'; diff --git a/src/evaluator/parser/index.ts b/src/evaluator/parser/index.ts index 4fdc6f0f..8e53a321 100644 --- a/src/evaluator/parser/index.ts +++ b/src/evaluator/parser/index.ts @@ -1,18 +1,18 @@ -import matchersTransform from '../matchersTransform'; -import Treatments from '../treatments'; -import matcherFactory from '../matchers'; -import sanitizeValue from '../value'; -import conditionFactory from '../condition'; -import ifElseIfCombiner from '../combiners/ifelseif'; -import andCombiner from '../combiners/and'; -import thenable from '../../utils/promise/thenable'; +import { matchersTransform } from '../matchersTransform'; +import { Treatments } from '../treatments'; +import { matcherFactory } from '../matchers'; +import { sanitizeValue } from '../value'; +import { conditionContext } from '../condition'; +import { ifElseIfCombinerContext } from '../combiners/ifelseif'; +import { andCombinerContext } from '../combiners/and'; +import { thenable } from '../../utils/promise/thenable'; import { IEvaluator, IMatcherDto, ISplitEvaluator } from '../types'; import { ISplitCondition } from '../../dtos/types'; import { IStorageAsync, IStorageSync } from '../../storages/types'; import { SplitIO } from '../../types'; import { ILogger } from '../../logger/types'; -export default function parser(log: ILogger, conditions: ISplitCondition[], storage: IStorageSync | IStorageAsync): IEvaluator { +export function parser(log: ILogger, conditions: ISplitCondition[], storage: IStorageSync | IStorageAsync): IEvaluator { let predicates = []; for (let i = 0; i < conditions.length; i++) { @@ -53,9 +53,9 @@ export default function parser(log: ILogger, conditions: ISplitCondition[], stor break; } - predicates.push(conditionFactory( + predicates.push(conditionContext( log, - andCombiner(log, expressions), + andCombinerContext(log, expressions), Treatments.parse(partitions), label, conditionType @@ -63,5 +63,5 @@ export default function parser(log: ILogger, conditions: ISplitCondition[], stor } // Instanciate evaluator given the set of conditions using if else if logic - return ifElseIfCombiner(log, predicates); + return ifElseIfCombinerContext(log, predicates); } diff --git a/src/evaluator/treatments/__tests__/index.spec.ts b/src/evaluator/treatments/__tests__/index.spec.ts index 453fb224..771127f1 100644 --- a/src/evaluator/treatments/__tests__/index.spec.ts +++ b/src/evaluator/treatments/__tests__/index.spec.ts @@ -1,4 +1,4 @@ -import Treatments from '..'; +import { Treatments } from '..'; test('TREATMENTS / parse 2 treatments', () => { let t = Treatments.parse([{ diff --git a/src/evaluator/treatments/index.ts b/src/evaluator/treatments/index.ts index 665bfd33..35f7da28 100644 --- a/src/evaluator/treatments/index.ts +++ b/src/evaluator/treatments/index.ts @@ -1,7 +1,7 @@ import { ISplitPartition } from '../../dtos/types'; import { findIndex } from '../../utils/lang'; -export default class Treatments { +export class Treatments { private _ranges: number[]; private _treatments: string[]; diff --git a/src/evaluator/value/index.ts b/src/evaluator/value/index.ts index 228d7d0a..319e05e2 100644 --- a/src/evaluator/value/index.ts +++ b/src/evaluator/value/index.ts @@ -1,7 +1,7 @@ import { SplitIO } from '../../types'; import { IMatcherDto } from '../types'; import { ILogger } from '../../logger/types'; -import sanitizeValue from './sanitize'; +import { sanitize } from './sanitize'; import { ENGINE_VALUE, ENGINE_VALUE_NO_ATTRIBUTES, ENGINE_VALUE_INVALID } from '../../logger/constants'; function parseValue(log: ILogger, key: string, attributeName: string | null, attributes: SplitIO.Attributes) { @@ -23,10 +23,10 @@ function parseValue(log: ILogger, key: string, attributeName: string | null, att /** * Defines value to be matched (key / attribute). */ -export default function value(log: ILogger, key: string, matcherDto: IMatcherDto, attributes: SplitIO.Attributes) { +export function sanitizeValue(log: ILogger, key: string, matcherDto: IMatcherDto, attributes: SplitIO.Attributes) { const attributeName = matcherDto.attribute; const valueToMatch = parseValue(log, key, attributeName, attributes); - const sanitizedValue = sanitizeValue(log, matcherDto.type, valueToMatch, matcherDto.dataType, attributes); + const sanitizedValue = sanitize(log, matcherDto.type, valueToMatch, matcherDto.dataType, attributes); if (sanitizedValue !== undefined) { return sanitizedValue; diff --git a/src/evaluator/value/sanitize.ts b/src/evaluator/value/sanitize.ts index 7ca7b5cd..6a36a59d 100644 --- a/src/evaluator/value/sanitize.ts +++ b/src/evaluator/value/sanitize.ts @@ -69,7 +69,7 @@ function getProcessingFunction(matcherTypeID: number, dataType: string) { /** * Sanitize matcher value */ -export default function sanitize(log: ILogger, matcherTypeID: number, value: string | number | boolean | Array | undefined, dataType: string, attributes: SplitIO.Attributes) { +export function sanitize(log: ILogger, matcherTypeID: number, value: string | number | boolean | Array | undefined, dataType: string, attributes: SplitIO.Attributes) { const processor = getProcessingFunction(matcherTypeID, dataType); let sanitizedValue: string | number | boolean | Array | IDependencyMatcherValue | undefined; diff --git a/src/integrations/__tests__/browser.spec.ts b/src/integrations/__tests__/browser.spec.ts index 1b79d8bb..40316654 100644 --- a/src/integrations/__tests__/browser.spec.ts +++ b/src/integrations/__tests__/browser.spec.ts @@ -33,7 +33,7 @@ function clearMocks() { } // Test target -import browserIMF from '../browser'; +import { integrationsManagerFactory as browserIMF } from '../browser'; import { BrowserIntegration } from '../ga/types'; describe('IntegrationsManagerFactory for browser', () => { diff --git a/src/integrations/browser.ts b/src/integrations/browser.ts index f3a17713..d4ad8de8 100644 --- a/src/integrations/browser.ts +++ b/src/integrations/browser.ts @@ -1,7 +1,7 @@ import { GOOGLE_ANALYTICS_TO_SPLIT, SPLIT_TO_GOOGLE_ANALYTICS } from '../utils/constants/browser'; import { IIntegration, IIntegrationManager, IIntegrationFactoryParams } from './types'; import { BrowserIntegration } from './ga/types'; -import pluggableIntegrationsManagerFactory from './pluggable'; +import { pluggableIntegrationsManagerFactory } from './pluggable'; import { GoogleAnalyticsToSplit } from './ga/GoogleAnalyticsToSplit'; import { SplitToGoogleAnalytics } from './ga/SplitToGoogleAnalytics'; @@ -14,7 +14,7 @@ import { SplitToGoogleAnalytics } from './ga/SplitToGoogleAnalytics'; * * @returns integration manager or undefined if `integrations` are not present in settings. */ -export default function integrationsManagerFactory( +export function integrationsManagerFactory( integrations: BrowserIntegration[], params: IIntegrationFactoryParams ): IIntegrationManager | undefined { diff --git a/src/integrations/ga/GaToSplit.ts b/src/integrations/ga/GaToSplit.ts index 360c93a1..334f6baa 100644 --- a/src/integrations/ga/GaToSplit.ts +++ b/src/integrations/ga/GaToSplit.ts @@ -1,5 +1,5 @@ /* eslint-disable no-undef */ -import objectAssign from 'object-assign'; +import { objectAssign } from '../../utils/lang/objectAssign'; import { isString, isFiniteNumber, uniqAsStrings } from '../../utils/lang'; import { validateEvent, diff --git a/src/integrations/pluggable.ts b/src/integrations/pluggable.ts index 14544f73..aed6806c 100644 --- a/src/integrations/pluggable.ts +++ b/src/integrations/pluggable.ts @@ -11,7 +11,7 @@ import { IIntegration, IIntegrationManager, IIntegrationFactoryParams } from './ * * @returns integration manager or undefined if `integrations` are not present in settings. */ -export default function integrationsManagerFactory( +export function pluggableIntegrationsManagerFactory( integrations: Array<(params: IIntegrationFactoryParams) => IIntegration | void>, params: IIntegrationFactoryParams ): IIntegrationManager | undefined { diff --git a/src/listeners/__tests__/browser.spec.ts b/src/listeners/__tests__/browser.spec.ts index 3bd83169..8af13e8b 100644 --- a/src/listeners/__tests__/browser.spec.ts +++ b/src/listeners/__tests__/browser.spec.ts @@ -1,4 +1,4 @@ -import BrowserSignalListener from '../browser'; +import { BrowserSignalListener } from '../browser'; import { IEventsCacheSync, IImpressionCountsCacheSync, IImpressionsCacheSync, IStorageSync } from '../../storages/types'; import { ISplitApi } from '../../services/types'; import { fullSettings } from '../../utils/settingsValidation/__tests__/settings.mocks'; @@ -105,8 +105,8 @@ function triggerUnloadEvent() { /* Mocks end */ -test('Browser JS listener / Impressions optimized mode', () => { - +test('Browser JS listener / consumer mode', () => { + // No SyncManager ==> consumer mode const listener = new BrowserSignalListener(undefined, fullSettings, fakeStorageOptimized as IStorageSync, fakeSplitApi); listener.start(); @@ -116,6 +116,33 @@ test('Browser JS listener / Impressions optimized mode', () => { triggerUnloadEvent(); + // Unload event was triggered, but sendBeacon and post services should not be called + expect(global.window.navigator.sendBeacon).toBeCalledTimes(0); + expect(fakeSplitApi.postTestImpressionsBulk).not.toBeCalled(); + expect(fakeSplitApi.postEventsBulk).not.toBeCalled(); + expect(fakeSplitApi.postTestImpressionsCount).not.toBeCalled(); + + // pre-check and call stop + expect(global.window.removeEventListener).not.toBeCalled(); + listener.stop(); + + // removed correct listener from correct signal on stop. + expect((global.window.removeEventListener as jest.Mock).mock.calls).toEqual([[UNLOAD_DOM_EVENT, listener.flushData]]); +}); + +test('Browser JS listener / standalone mode / Impressions optimized mode', () => { + const syncManagerMock = {}; + + // @ts-expect-error + const listener = new BrowserSignalListener(syncManagerMock, fullSettings, fakeStorageOptimized as IStorageSync, fakeSplitApi); + + listener.start(); + + // Assigned right function to right signal. + expect((global.window.addEventListener as jest.Mock).mock.calls).toEqual([[UNLOAD_DOM_EVENT, listener.flushData]]); + + triggerUnloadEvent(); + // Unload event was triggered. Thus sendBeacon method should have been called three times. expect(global.window.navigator.sendBeacon).toBeCalledTimes(3); @@ -132,7 +159,7 @@ test('Browser JS listener / Impressions optimized mode', () => { expect((global.window.removeEventListener as jest.Mock).mock.calls).toEqual([[UNLOAD_DOM_EVENT, listener.flushData]]); }); -test('Browser JS listener / Impressions debug mode', () => { +test('Browser JS listener / standalone mode / Impressions debug mode', () => { const syncManagerMockWithPushManager = { pushManager: { stop: jest.fn() } }; // @ts-expect-error @@ -166,7 +193,7 @@ test('Browser JS listener / Impressions debug mode', () => { expect((global.window.removeEventListener as jest.Mock).mock.calls).toEqual([[UNLOAD_DOM_EVENT, listener.flushData]]); }); -test('Browser JS listener / Impressions debug mode without sendBeacon API', () => { +test('Browser JS listener / standalone mode / Impressions debug mode without sendBeacon API', () => { // remove sendBeacon API const sendBeacon = global.navigator.sendBeacon; // @ts-expect-error global.navigator.sendBeacon = undefined; diff --git a/src/listeners/__tests__/node.spec.ts b/src/listeners/__tests__/node.spec.ts index 1755fa7d..bc594df9 100644 --- a/src/listeners/__tests__/node.spec.ts +++ b/src/listeners/__tests__/node.spec.ts @@ -1,4 +1,4 @@ -import NodeSignalListener from '../node'; +import { NodeSignalListener } from '../node'; import { fullSettings } from '../../utils/settingsValidation/__tests__/settings.mocks'; const processOnSpy = jest.spyOn(process, 'on'); diff --git a/src/listeners/browser.ts b/src/listeners/browser.ts index c5c4fdfd..9705af2c 100644 --- a/src/listeners/browser.ts +++ b/src/listeners/browser.ts @@ -8,7 +8,7 @@ import { IResponse, ISplitApi } from '../services/types'; import { ImpressionDTO, ISettings } from '../types'; import { ImpressionsPayload } from '../sync/submitters/types'; import { OPTIMIZED, DEBUG } from '../utils/constants'; -import objectAssign from 'object-assign'; +import { objectAssign } from '../utils/lang/objectAssign'; import { CLEANUP_REGISTERING, CLEANUP_DEREGISTERING } from '../logger/constants'; import { ISyncManager } from '../sync/types'; @@ -19,7 +19,7 @@ const EVENT_NAME = 'for unload page event.'; /** * We'll listen for 'unload' event over the window object, since it's the standard way to listen page reload and close. */ -export default class BrowserSignalListener implements ISignalListener { +export class BrowserSignalListener implements ISignalListener { private fromImpressionsCollector: (data: ImpressionDTO[]) => ImpressionsPayload; @@ -63,6 +63,8 @@ export default class BrowserSignalListener implements ISignalListener { * using beacon API if possible, or falling back to regular post transport. */ flushData() { + if (!this.syncManager) return; // In consumer mode there is not sync manager and data to flush + const eventsUrl = this.settings.urls.events; const extraMetadata = { // sim stands for Sync/Split Impressions Mode @@ -74,7 +76,7 @@ export default class BrowserSignalListener implements ISignalListener { if (this.storage.impressionCounts) this._flushData(eventsUrl + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, fromImpressionCountsCollector); // Close streaming connection - if (this.syncManager && this.syncManager.pushManager) this.syncManager.pushManager.stop(); + if (this.syncManager.pushManager) this.syncManager.pushManager.stop(); } private _flushData(url: string, cache: IRecorderCacheProducerSync, postService: (body: string) => Promise, fromCacheToPayload?: (cacheData: TState) => any, extraMetadata?: {}) { diff --git a/src/listeners/node.ts b/src/listeners/node.ts index 9d8a22e9..f57557be 100644 --- a/src/listeners/node.ts +++ b/src/listeners/node.ts @@ -1,6 +1,6 @@ // @TODO eventually migrate to JS-Node-SDK package. import { ISignalListener } from './types'; -import thenable from '../utils/promise/thenable'; +import { thenable } from '../utils/promise/thenable'; import { MaybeThenable } from '../dtos/types'; import { ISettings } from '../types'; import { LOG_PREFIX_CLEANUP, CLEANUP_REGISTERING, CLEANUP_DEREGISTERING } from '../logger/constants'; @@ -16,7 +16,7 @@ const EVENT_NAME = 'for SIGTERM signal.'; * you should call the cleanup logic yourself, since we cannot ensure the data is sent after * the process is already exiting. */ -export default class NodeSignalListener implements ISignalListener { +export class NodeSignalListener implements ISignalListener { private handler: () => MaybeThenable; private settings: ISettings; diff --git a/src/logger/constants.ts b/src/logger/constants.ts index 5ca7317c..ab711cac 100644 --- a/src/logger/constants.ts +++ b/src/logger/constants.ts @@ -91,9 +91,8 @@ export const WARN_INTEGRATION_INVALID = 218; export const WARN_SPLITS_FILTER_IGNORED = 219; export const WARN_SPLITS_FILTER_INVALID = 220; export const WARN_SPLITS_FILTER_EMPTY = 221; -export const WARN_STORAGE_INVALID = 222; -export const WARN_API_KEY = 223; -export const STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2 = 224; +export const WARN_API_KEY = 222; +export const STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2 = 223; export const ERROR_ENGINE_COMBINER_IFELSEIF = 300; export const ERROR_LOGLEVEL_INVALID = 301; @@ -119,6 +118,7 @@ export const ERROR_EMPTY_ARRAY = 320; export const ERROR_INVALID_IMPRESSIONS_MODE = 321; export const ERROR_HTTP = 322; export const ERROR_LOCALHOST_MODULE_REQUIRED = 323; +export const ERROR_STORAGE_INVALID = 324; // Log prefixes (a.k.a. tags or categories) export const LOG_PREFIX_SETTINGS = 'settings'; diff --git a/src/logger/index.ts b/src/logger/index.ts index ed2e5032..4fba156a 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -1,4 +1,4 @@ -import objectAssign from 'object-assign'; +import { objectAssign } from '../utils/lang/objectAssign'; import { ILoggerOptions, ILogger } from './types'; import { find } from '../utils/lang'; import { LogLevel } from '../types'; diff --git a/src/logger/messages/error.ts b/src/logger/messages/error.ts index 75269bc9..fee6c423 100644 --- a/src/logger/messages/error.ts +++ b/src/logger/messages/error.ts @@ -30,5 +30,6 @@ export const codesError: [number, string][] = [ [c.ERROR_EMPTY_ARRAY, '%s: %s must be a non-empty array.'], // initialization / settings validation [c.ERROR_INVALID_IMPRESSIONS_MODE, c.LOG_PREFIX_SETTINGS + ': you passed an invalid "impressionsMode". It should be one of the following values: %s. Defaulting to "%s" mode.'], - [c.ERROR_LOCALHOST_MODULE_REQUIRED, c.LOG_PREFIX_SETTINGS + ': an invalid value was received for "sync.localhostMode" config. A valid entity should be provided for localhost mode.'] + [c.ERROR_LOCALHOST_MODULE_REQUIRED, c.LOG_PREFIX_SETTINGS + ': an invalid value was received for "sync.localhostMode" config. A valid entity should be provided for localhost mode.'], + [c.ERROR_STORAGE_INVALID, c.LOG_PREFIX_SETTINGS+': The provided storage is invalid.%s Fallbacking into default MEMORY storage'], ]; diff --git a/src/logger/messages/info.ts b/src/logger/messages/info.ts index 22083bf6..42838345 100644 --- a/src/logger/messages/info.ts +++ b/src/logger/messages/info.ts @@ -24,7 +24,7 @@ export const codesInfo: [number, string][] = codesWarn.concat([ [c.SUBMITTERS_PUSH, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Pushing %s %s.'], [c.STREAMING_REFRESH_TOKEN, c.LOG_PREFIX_SYNC_STREAMING + 'Refreshing streaming token in %s seconds, and connecting streaming in %s seconds.'], [c.STREAMING_RECONNECT, c.LOG_PREFIX_SYNC_STREAMING + 'Attempting to reconnect in %s seconds.'], - [c.STREAMING_CONNECTING, c.LOG_PREFIX_SYNC_STREAMING + '%sConnecting to streaming.'], + [c.STREAMING_CONNECTING, c.LOG_PREFIX_SYNC_STREAMING + 'Connecting to streaming.'], [c.STREAMING_DISABLED, c.LOG_PREFIX_SYNC_STREAMING + 'Streaming is disabled for given Api key. Switching to polling mode.'], [c.STREAMING_DISCONNECTING, c.LOG_PREFIX_SYNC_STREAMING + 'Disconnecting from streaming.'], [c.SYNC_START_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming not available. Starting polling.'], diff --git a/src/logger/messages/warn.ts b/src/logger/messages/warn.ts index 350eeb43..f6a66eec 100644 --- a/src/logger/messages/warn.ts +++ b/src/logger/messages/warn.ts @@ -21,15 +21,14 @@ export const codesWarn: [number, string][] = codesError.concat([ [c.WARN_TRIMMING_PROPERTIES, '%s: Event has more than 300 properties. Some of them will be trimmed when processed.'], [c.WARN_CONVERTING, '%s: %s "%s" is not of type string, converting.'], [c.WARN_TRIMMING, '%s: %s "%s" has extra whitespace, trimming.'], - [c.WARN_NOT_EXISTENT_SPLIT, '%s: split "%s" does not exist in this environment, please double check what splits exist in the web console.'], + [c.WARN_NOT_EXISTENT_SPLIT, '%s: split "%s" does not exist in this environment, please double check what splits exist in the Split web console.'], [c.WARN_LOWERCASE_TRAFFIC_TYPE, '%s: traffic_type_name should be all lowercase - converting string to lowercase.'], - [c.WARN_NOT_EXISTENT_TT, '%s: traffic type "%s" does not have any corresponding split in this environment, make sure you\'re tracking your events to a valid traffic type defined in the web console.'], + [c.WARN_NOT_EXISTENT_TT, '%s: traffic type "%s" does not have any corresponding split in this environment, make sure you\'re tracking your events to a valid traffic type defined in the Split web console.'], // initialization / settings validation [c.WARN_INTEGRATION_INVALID, c.LOG_PREFIX_SETTINGS+': %s integration item(s) at settings is invalid. %s'], [c.WARN_SPLITS_FILTER_IGNORED, c.LOG_PREFIX_SETTINGS+': split filters have been configured but will have no effect if mode is not "%s", since synchronization is being deferred to an external tool.'], [c.WARN_SPLITS_FILTER_INVALID, c.LOG_PREFIX_SETTINGS+': split filter at position %s is invalid. It must be an object with a valid filter type ("byName" or "byPrefix") and a list of "values".'], [c.WARN_SPLITS_FILTER_EMPTY, c.LOG_PREFIX_SETTINGS+': splitFilters configuration must be a non-empty array of filter objects.'], - [c.WARN_STORAGE_INVALID, c.LOG_PREFIX_SETTINGS+': The provided storage is invalid. Fallbacking into default MEMORY storage'], [c.WARN_API_KEY, c.LOG_PREFIX_SETTINGS+': You already have %s. We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application'], [c.STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching MySegments due to an error processing %s notification: %s'], diff --git a/src/readiness/__tests__/readinessManager.spec.ts b/src/readiness/__tests__/readinessManager.spec.ts index ae30b7cb..0a6726c5 100644 --- a/src/readiness/__tests__/readinessManager.spec.ts +++ b/src/readiness/__tests__/readinessManager.spec.ts @@ -1,5 +1,5 @@ import { readinessManagerFactory } from '../readinessManager'; -import EventEmitter from '../../utils/MinEvents'; +import { EventEmitter } from '../../utils/MinEvents'; import { IReadinessManager } from '../types'; import { SDK_READY, SDK_UPDATE, SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_READY_FROM_CACHE, SDK_SPLITS_CACHE_LOADED, SDK_READY_TIMED_OUT } from '../constants'; diff --git a/src/readiness/__tests__/sdkReadinessManager.spec.ts b/src/readiness/__tests__/sdkReadinessManager.spec.ts index 5bfda13e..c1929bdc 100644 --- a/src/readiness/__tests__/sdkReadinessManager.spec.ts +++ b/src/readiness/__tests__/sdkReadinessManager.spec.ts @@ -2,7 +2,7 @@ import { loggerMock } from '../../logger/__tests__/sdkLogger.mock'; import { IEventEmitter } from '../../types'; import { SDK_READY, SDK_READY_FROM_CACHE, SDK_READY_TIMED_OUT, SDK_UPDATE } from '../constants'; -import sdkReadinessManagerFactory from '../sdkReadinessManager'; +import { sdkReadinessManagerFactory } from '../sdkReadinessManager'; import { IReadinessManager } from '../types'; import { ERROR_CLIENT_LISTENER, CLIENT_READY_FROM_CACHE, CLIENT_READY, CLIENT_NO_LISTENER } from '../../logger/constants'; diff --git a/src/readiness/readinessManager.ts b/src/readiness/readinessManager.ts index 394ba056..7e0c0431 100644 --- a/src/readiness/readinessManager.ts +++ b/src/readiness/readinessManager.ts @@ -1,4 +1,4 @@ -import objectAssign from 'object-assign'; +import { objectAssign } from '../utils/lang/objectAssign'; import { IEventEmitter } from '../types'; import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED, SDK_SEGMENTS_ARRIVED, SDK_READY_TIMED_OUT, SDK_READY_FROM_CACHE, SDK_UPDATE, SDK_READY } from './constants'; import { IReadinessEventEmitter, IReadinessManager, ISegmentsEventEmitter, ISplitsEventEmitter } from './types'; @@ -12,7 +12,7 @@ function splitsEventEmitterFactory(EventEmitter: new () => IEventEmitter): ISpli // `isSplitKill` condition avoids an edge-case of wrongly emitting SDK_READY if: // - `/mySegments` fetch and SPLIT_KILL occurs before `/splitChanges` fetch, and // - storage has cached splits (for which case `splitsStorage.killLocally` can return true) - splitsEventEmitter.on(SDK_SPLITS_ARRIVED, (isSplitKill) => { if (!isSplitKill) splitsEventEmitter.splitsArrived = true; }); + splitsEventEmitter.on(SDK_SPLITS_ARRIVED, (isSplitKill: boolean) => { if (!isSplitKill) splitsEventEmitter.splitsArrived = true; }); splitsEventEmitter.once(SDK_SPLITS_CACHE_LOADED, () => { splitsEventEmitter.splitsCacheLoaded = true; }); return splitsEventEmitter; diff --git a/src/readiness/sdkReadinessManager.ts b/src/readiness/sdkReadinessManager.ts index 360527c2..52f519ad 100644 --- a/src/readiness/sdkReadinessManager.ts +++ b/src/readiness/sdkReadinessManager.ts @@ -1,5 +1,5 @@ -import objectAssign from 'object-assign'; -import promiseWrapper from '../utils/promise/wrapper'; +import { objectAssign } from '../utils/lang/objectAssign'; +import { promiseWrapper } from '../utils/promise/wrapper'; import { readinessManagerFactory } from './readinessManager'; import { ISdkReadinessManager } from './types'; import { IEventEmitter } from '../types'; @@ -19,7 +19,7 @@ const REMOVE_LISTENER_EVENT = 'removeListener'; * by the SDK. It is required to properly log the warning 'No listeners for SDK Readiness detected' * @param readinessManager optional readinessManager to use. only used internally for `shared` method */ -export default function sdkReadinessManagerFactory( +export function sdkReadinessManagerFactory( log: ILogger, EventEmitter: new () => IEventEmitter, readyTimeout = 0, diff --git a/src/sdkClient/__tests__/sdkClientMethod.spec.ts b/src/sdkClient/__tests__/sdkClientMethod.spec.ts index 5adc1d88..604f20f6 100644 --- a/src/sdkClient/__tests__/sdkClientMethod.spec.ts +++ b/src/sdkClient/__tests__/sdkClientMethod.spec.ts @@ -8,11 +8,11 @@ const errorMessage = 'Shared Client not supported by the storage mechanism. Crea const paramMocks = [ // No SyncManager (i.e., Async SDK) and No signal listener { - storage: { destroy: jest.fn() }, + storage: { destroy: jest.fn(() => Promise.resolve()) }, syncManager: undefined, sdkReadinessManager: { sdkStatus: jest.fn(), readinessManager: { destroy: jest.fn() } }, signalListener: undefined, - settings: { mode: CONSUMER_MODE, log: loggerMock } + settings: { mode: CONSUMER_MODE, log: loggerMock, core: { authorizationKey: 'api key '} } }, // SyncManager (i.e., Sync SDK) and Signal listener { @@ -20,11 +20,11 @@ const paramMocks = [ syncManager: { stop: jest.fn(), flush: jest.fn(() => Promise.resolve()) }, sdkReadinessManager: { sdkStatus: jest.fn(), readinessManager: { destroy: jest.fn() } }, signalListener: { stop: jest.fn() }, - settings: { mode: STANDALONE_MODE, log: loggerMock } + settings: { mode: STANDALONE_MODE, log: loggerMock, core: { authorizationKey: 'api key '} } } ]; -test.each(paramMocks)('sdkClientMethodFactory', (params) => { +test.each(paramMocks)('sdkClientMethodFactory', (params, done: any) => { // @ts-expect-error const sdkClientMethod = sdkClientMethodFactory(params); @@ -38,7 +38,7 @@ test.each(paramMocks)('sdkClientMethodFactory', (params) => { // multiple calls should return the same instance expect(sdkClientMethod()).toBe(client); - // `client.destroy` method should stop internal components (other client methods where validated in `client.spec.ts`) + // `client.destroy` method should stop internal components (other client methods are validated in `client.spec.ts`) client.destroy().then(() => { expect(params.sdkReadinessManager.readinessManager.destroy).toBeCalledTimes(1); expect(params.storage.destroy).toBeCalledTimes(1); @@ -48,6 +48,8 @@ test.each(paramMocks)('sdkClientMethodFactory', (params) => { expect(params.syncManager.flush).toBeCalledTimes(1); } if (params.signalListener) expect(params.signalListener.stop).toBeCalledTimes(1); + + done(); }); // calling the function with parameters should throw an error diff --git a/src/sdkClient/__tests__/sdkClientMethodCS.spec.ts b/src/sdkClient/__tests__/sdkClientMethodCS.spec.ts index 345909f9..0a5c8e59 100644 --- a/src/sdkClient/__tests__/sdkClientMethodCS.spec.ts +++ b/src/sdkClient/__tests__/sdkClientMethodCS.spec.ts @@ -4,7 +4,7 @@ import { assertClientApi } from './testUtils'; /** Mocks */ import * as clientCS from '../clientCS'; -const clientCSDecoratorSpy = jest.spyOn(clientCS, 'default'); +const clientCSDecoratorSpy = jest.spyOn(clientCS, 'clientCSDecorator'); import { settingsWithKey, settingsWithKeyAndTT, settingsWithKeyObject } from '../../utils/settingsValidation/__tests__/settings.mocks'; diff --git a/src/sdkClient/client.ts b/src/sdkClient/client.ts index c6889678..a572047e 100644 --- a/src/sdkClient/client.ts +++ b/src/sdkClient/client.ts @@ -1,5 +1,5 @@ import { evaluateFeature, evaluateFeatures } from '../evaluator'; -import thenable from '../utils/promise/thenable'; +import { thenable } from '../utils/promise/thenable'; import { getMatching, getBucketing } from '../utils/key'; import { validateSplitExistance } from '../utils/inputValidation/splitExistance'; import { validateTrafficTypeExistance } from '../utils/inputValidation/trafficTypeExistance'; @@ -15,7 +15,7 @@ import { IMPRESSION, IMPRESSION_QUEUEING } from '../logger/constants'; * Creator of base client with getTreatments and track methods. */ // @TODO missing time tracking to collect telemetry -export default function clientFactory(params: IClientFactoryParams): SplitIO.IClient | SplitIO.IAsyncClient { +export function clientFactory(params: IClientFactoryParams): SplitIO.IClient | SplitIO.IAsyncClient { const { sdkReadinessManager: { readinessManager }, storage, settings: { log, mode }, impressionsTracker, eventTracker } = params; function getTreatment(key: SplitIO.SplitKey, splitName: string, attributes: SplitIO.Attributes | undefined, withConfig = false) { diff --git a/src/sdkClient/clientCS.ts b/src/sdkClient/clientCS.ts index 50a4ba2e..dee6a6af 100644 --- a/src/sdkClient/clientCS.ts +++ b/src/sdkClient/clientCS.ts @@ -1,4 +1,4 @@ -import objectAssign from 'object-assign'; +import { objectAssign } from '../utils/lang/objectAssign'; import { SplitIO } from '../types'; @@ -9,7 +9,7 @@ import { SplitIO } from '../types'; * @param key validated split key * @param trafficType validated traffic type */ -export default function clientCSDecorator(client: SplitIO.IClient, key: SplitIO.SplitKey, trafficType?: string): SplitIO.ICsClient { +export function clientCSDecorator(client: SplitIO.IClient, key: SplitIO.SplitKey, trafficType?: string): SplitIO.ICsClient { return objectAssign(client, { // In the client-side API, we bind a key to the client `getTreatment*` methods getTreatment: client.getTreatment.bind(client, key), diff --git a/src/sdkClient/clientInputValidation.ts b/src/sdkClient/clientInputValidation.ts index c0fc26bf..df0eaebc 100644 --- a/src/sdkClient/clientInputValidation.ts +++ b/src/sdkClient/clientInputValidation.ts @@ -1,4 +1,4 @@ -import objectAssign from 'object-assign'; +import { objectAssign } from '../utils/lang/objectAssign'; import { validateAttributes, validateEvent, @@ -22,7 +22,7 @@ import { ILogger } from '../logger/types'; * Decorator that validates the input before actually executing the client methods. * We should "guard" the client here, while not polluting the "real" implementation of those methods. */ -export default function clientInputValidationDecorator(log: ILogger, client: TClient, readinessManager: IReadinessManager, isStorageSync = false): TClient { +export function clientInputValidationDecorator(log: ILogger, client: TClient, readinessManager: IReadinessManager, isStorageSync = false): TClient { /** * Avoid repeating this validations code diff --git a/src/sdkClient/sdkClient.ts b/src/sdkClient/sdkClient.ts index dd325dcb..6630cd94 100644 --- a/src/sdkClient/sdkClient.ts +++ b/src/sdkClient/sdkClient.ts @@ -1,9 +1,9 @@ -import objectAssign from 'object-assign'; +import { objectAssign } from '../utils/lang/objectAssign'; import { IStatusInterface, SplitIO } from '../types'; -import { CONSUMER_MODE } from '../utils/constants'; +import { CONSUMER_MODE, CONSUMER_PARTIAL_MODE } from '../utils/constants'; import { releaseApiKey } from '../utils/inputValidation/apiKey'; -import clientFactory from './client'; -import clientInputValidationDecorator from './clientInputValidation'; +import { clientFactory } from './client'; +import { clientInputValidationDecorator } from './clientInputValidation'; import { ISdkClientFactoryParams } from './types'; /** @@ -21,8 +21,8 @@ export function sdkClientFactory(params: ISdkClientFactoryParams): SplitIO.IClie settings.log, clientFactory(params), sdkReadinessManager.readinessManager, - // @TODO isStorageSync could be extracted from the storage itself (e.g. `storage.isSync`) to simplify interfaces - settings.mode === CONSUMER_MODE ? false : true, + // storage is async if and only if mode is consumer or partial consumer + [CONSUMER_MODE, CONSUMER_PARTIAL_MODE].indexOf(settings.mode) === -1 ? true : false, ), // Sdk destroy @@ -37,11 +37,11 @@ export function sdkClientFactory(params: ISdkClientFactoryParams): SplitIO.IClie sdkReadinessManager.readinessManager.destroy(); signalListener && signalListener.stop(); - // Cleanup storage - storage.destroy(); - // Release the API Key if it is the main client if (!sharedClient) releaseApiKey(settings.core.authorizationKey); + + // Cleanup storage + return storage.destroy(); }); } } diff --git a/src/sdkClient/sdkClientMethodCS.ts b/src/sdkClient/sdkClientMethodCS.ts index aeb29720..c0d28f60 100644 --- a/src/sdkClient/sdkClientMethodCS.ts +++ b/src/sdkClient/sdkClientMethodCS.ts @@ -1,13 +1,13 @@ -import clientCSDecorator from './clientCS'; +import { clientCSDecorator } from './clientCS'; import { ISdkClientFactoryParams } from './types'; import { SplitIO } from '../types'; import { validateKey } from '../utils/inputValidation/key'; import { getMatching, keyParser } from '../utils/key'; import { sdkClientFactory } from './sdkClient'; -import { IStorageSyncCS } from '../storages/types'; import { ISyncManagerCS } from '../sync/types'; -import objectAssign from 'object-assign'; +import { objectAssign } from '../utils/lang/objectAssign'; import { RETRIEVE_CLIENT_DEFAULT, NEW_SHARED_CLIENT, RETRIEVE_CLIENT_EXISTING } from '../logger/constants'; +import { SDK_SEGMENTS_ARRIVED } from '../readiness/constants'; function buildInstanceId(key: SplitIO.SplitKey) { // @ts-ignore @@ -58,15 +58,25 @@ export function sdkClientMethodCSFactory(params: ISdkClientFactoryParams): (key? const matchingKey = getMatching(validKey); const sharedSdkReadiness = sdkReadinessManager.shared(readyTimeout); - const sharedStorage = (storage as IStorageSyncCS).shared(matchingKey); - const sharedSyncManager = syncManager && (syncManager as ISyncManagerCS).shared(matchingKey, sharedSdkReadiness.readinessManager, sharedStorage); + const sharedStorage = storage.shared && storage.shared(matchingKey, (err) => { + if (err) return; + // Emit SDK_READY in consumer mode for shared clients + sharedSdkReadiness.readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED); + }); + + // 3 possibilities: + // - Standalone mode: both syncManager and sharedSyncManager are defined + // - Consumer mode: both syncManager and sharedSyncManager are undefined + // - Consumer partial mode: syncManager is defined (only for submitters) but sharedSyncManager is undefined + // @ts-ignore + const sharedSyncManager = syncManager && sharedStorage && (syncManager as ISyncManagerCS).shared(matchingKey, sharedSdkReadiness.readinessManager, sharedStorage); // As shared clients reuse all the storage information, we don't need to check here if we // will use offline or online mode. We should stick with the original decision. clientInstances[instanceId] = clientCSDecorator( sdkClientFactory(objectAssign({}, params, { sdkReadinessManager: sharedSdkReadiness, - storage: sharedStorage, + storage: sharedStorage || storage, syncManager: sharedSyncManager, signalListener: undefined, // only the main client "destroy" method stops the signal listener sharedClient: true diff --git a/src/sdkClient/sdkClientMethodCSWithTT.ts b/src/sdkClient/sdkClientMethodCSWithTT.ts index 684f3a5b..1c844296 100644 --- a/src/sdkClient/sdkClientMethodCSWithTT.ts +++ b/src/sdkClient/sdkClientMethodCSWithTT.ts @@ -1,14 +1,14 @@ -import clientCSDecorator from './clientCS'; +import { clientCSDecorator } from './clientCS'; import { ISdkClientFactoryParams } from './types'; import { SplitIO } from '../types'; import { validateKey } from '../utils/inputValidation/key'; import { validateTrafficType } from '../utils/inputValidation/trafficType'; import { getMatching, keyParser } from '../utils/key'; import { sdkClientFactory } from './sdkClient'; -import { IStorageSyncCS } from '../storages/types'; import { ISyncManagerCS } from '../sync/types'; -import objectAssign from 'object-assign'; +import { objectAssign } from '../utils/lang/objectAssign'; import { RETRIEVE_CLIENT_DEFAULT, NEW_SHARED_CLIENT, RETRIEVE_CLIENT_EXISTING } from '../logger/constants'; +import { SDK_SEGMENTS_ARRIVED } from '../readiness/constants'; function buildInstanceId(key: SplitIO.SplitKey, trafficType?: string) { // @ts-ignore @@ -72,15 +72,25 @@ export function sdkClientMethodCSFactory(params: ISdkClientFactoryParams): (key? const matchingKey = getMatching(validKey); const sharedSdkReadiness = sdkReadinessManager.shared(readyTimeout); - const sharedStorage = (storage as IStorageSyncCS).shared(matchingKey); - const sharedSyncManager = (syncManager as ISyncManagerCS).shared(matchingKey, sharedSdkReadiness.readinessManager, sharedStorage); + const sharedStorage = storage.shared && storage.shared(matchingKey, (err) => { + if (err) return; + // Emit SDK_READY in consumer mode for shared clients + sharedSdkReadiness.readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED); + }); + + // 3 possibilities: + // - Standalone mode: both syncManager and sharedSyncManager are defined + // - Consumer mode: both syncManager and sharedSyncManager are undefined + // - Consumer partial mode: syncManager is defined (only for submitters) but sharedSyncManager is undefined + // @ts-ignore + const sharedSyncManager = syncManager && sharedStorage && (syncManager as ISyncManagerCS).shared(matchingKey, sharedSdkReadiness.readinessManager, sharedStorage); // As shared clients reuse all the storage information, we don't need to check here if we // will use offline or online mode. We should stick with the original decision. clientInstances[instanceId] = clientCSDecorator( sdkClientFactory(objectAssign({}, params, { sdkReadinessManager: sharedSdkReadiness, - storage: sharedStorage, + storage: sharedStorage || storage, syncManager: sharedSyncManager, signalListener: undefined, // only the main client "destroy" method stops the signal listener sharedClient: true @@ -89,7 +99,7 @@ export function sdkClientMethodCSFactory(params: ISdkClientFactoryParams): (key? validTrafficType ); - sharedSyncManager.start(); + sharedSyncManager && sharedSyncManager.start(); log.info(NEW_SHARED_CLIENT); } else { diff --git a/src/sdkFactory/__tests__/index.spec.ts b/src/sdkFactory/__tests__/index.spec.ts index 3e758f68..58a1144f 100644 --- a/src/sdkFactory/__tests__/index.spec.ts +++ b/src/sdkFactory/__tests__/index.spec.ts @@ -2,7 +2,7 @@ import { ISdkFactoryParams } from '../types'; import { sdkFactory } from '../index'; import { fullSettings } from '../../utils/settingsValidation/__tests__/settings.mocks'; import { SplitIO } from '../../types'; -import EventEmitter from '../../utils/MinEvents'; +import { EventEmitter } from '../../utils/MinEvents'; /** Mocks */ diff --git a/src/sdkFactory/index.ts b/src/sdkFactory/index.ts index 0f89f516..61433a5e 100644 --- a/src/sdkFactory/index.ts +++ b/src/sdkFactory/index.ts @@ -1,7 +1,7 @@ import { ISdkFactoryParams } from './types'; -import sdkReadinessManagerFactory from '../readiness/sdkReadinessManager'; -import impressionsTrackerFactory from '../trackers/impressionsTracker'; -import eventTrackerFactory from '../trackers/eventTracker'; +import { sdkReadinessManagerFactory } from '../readiness/sdkReadinessManager'; +import { impressionsTrackerFactory } from '../trackers/impressionsTracker'; +import { eventTrackerFactory } from '../trackers/eventTracker'; import { IStorageFactoryParams, IStorageSync } from '../storages/types'; import { SplitIO } from '../types'; import { ISplitApi } from '../services/types'; @@ -40,12 +40,16 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO. matchingKey: getMatching(settings.core.key), splitFiltersValidation: settings.sync.__splitFiltersValidation, - // Callback used in consumer mode (`syncManagerFactory` is undefined) to emit SDK_READY - onReadyCb: !syncManagerFactory ? (error) => { - if (error) return; // don't emit SDK_READY if storage failed to connect. + // ATM, only used by PluggableStorage + mode: settings.mode, + + // Callback used to emit SDK_READY in consumer mode, where `syncManagerFactory` is undefined, + // or partial consumer mode, where it only has submitters, and therefore it doesn't emit readiness events. + onReadyCb: (error) => { + if (error) return; // Don't emit SDK_READY if storage failed to connect. Error message is logged by wrapperAdapter readinessManager.splits.emit(SDK_SPLITS_ARRIVED); readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED); - } : undefined, + }, metadata: metadataBuilder(settings), log }; diff --git a/src/sdkFactory/types.ts b/src/sdkFactory/types.ts index b0d87343..d907d2d0 100644 --- a/src/sdkFactory/types.ts +++ b/src/sdkFactory/types.ts @@ -3,7 +3,7 @@ import { ISignalListener } from '../listeners/types'; import { ILogger } from '../logger/types'; import { ISdkReadinessManager } from '../readiness/types'; import { ISdkClientFactoryParams } from '../sdkClient/types'; -import { IFetch, ISplitApi } from '../services/types'; +import { IFetch, ISplitApi, IEventSourceConstructor } from '../services/types'; import { IStorageAsync, IStorageSync, ISplitsCacheSync, ISplitsCacheAsync, IStorageFactoryParams } from '../storages/types'; import { ISyncManager, ISyncManagerFactoryParams } from '../sync/types'; import { IImpressionObserver } from '../trackers/impressionObserver/types'; @@ -16,7 +16,7 @@ import { SplitIO, ISettings, IEventEmitter } from '../types'; export interface IPlatform { getOptions?: () => object getFetch?: () => (IFetch | undefined) - getEventSource?: () => (typeof EventSource | undefined) + getEventSource?: () => (IEventSourceConstructor | undefined) EventEmitter: new () => IEventEmitter } diff --git a/src/sdkManager/__tests__/index.asyncCache.spec.ts b/src/sdkManager/__tests__/index.asyncCache.spec.ts index 9829f9e2..202c0016 100644 --- a/src/sdkManager/__tests__/index.asyncCache.spec.ts +++ b/src/sdkManager/__tests__/index.asyncCache.spec.ts @@ -2,10 +2,10 @@ import Redis from 'ioredis'; import splitObject from './mocks/input.json'; import splitView from './mocks/output.json'; import { sdkManagerFactory } from '../index'; -import SplitsCacheInRedis from '../../storages/inRedis/SplitsCacheInRedis'; +import { SplitsCacheInRedis } from '../../storages/inRedis/SplitsCacheInRedis'; import { SplitsCachePluggable } from '../../storages/pluggable/SplitsCachePluggable'; import { wrapperAdapter } from '../../storages/pluggable/wrapperAdapter'; -import KeyBuilderSS from '../../storages/KeyBuilderSS'; +import { KeyBuilderSS } from '../../storages/KeyBuilderSS'; import { ISdkReadinessManager } from '../../readiness/types'; import { loggerMock } from '../../logger/__tests__/sdkLogger.mock'; diff --git a/src/sdkManager/__tests__/index.syncCache.spec.ts b/src/sdkManager/__tests__/index.syncCache.spec.ts index 4da65cd4..96238c1b 100644 --- a/src/sdkManager/__tests__/index.syncCache.spec.ts +++ b/src/sdkManager/__tests__/index.syncCache.spec.ts @@ -1,7 +1,7 @@ import splitObject from './mocks/input.json'; import splitView from './mocks/output.json'; import { sdkManagerFactory } from '../index'; -import SplitsCacheInMemory from '../../storages/inMemory/SplitsCacheInMemory'; +import { SplitsCacheInMemory } from '../../storages/inMemory/SplitsCacheInMemory'; import { ISdkReadinessManager } from '../../readiness/types'; import { loggerMock } from '../../logger/__tests__/sdkLogger.mock'; diff --git a/src/sdkManager/index.ts b/src/sdkManager/index.ts index 2536f121..e0ec5024 100644 --- a/src/sdkManager/index.ts +++ b/src/sdkManager/index.ts @@ -1,5 +1,5 @@ -import objectAssign from 'object-assign'; -import thenable from '../utils/promise/thenable'; +import { objectAssign } from '../utils/lang/objectAssign'; +import { thenable } from '../utils/promise/thenable'; import { find } from '../utils/lang'; import { validateSplit, validateSplitExistance, validateIfNotDestroyed, validateIfOperational } from '../utils/inputValidation'; import { ISplitsCacheAsync, ISplitsCacheSync } from '../storages/types'; diff --git a/src/services/splitApi.ts b/src/services/splitApi.ts index a080be33..dbbcb953 100644 --- a/src/services/splitApi.ts +++ b/src/services/splitApi.ts @@ -2,7 +2,7 @@ import { IPlatform } from '../sdkFactory/types'; import { ISettings } from '../types'; import { splitHttpClientFactory } from './splitHttpClient'; import { ISplitApi } from './types'; -import objectAssign from 'object-assign'; +import { objectAssign } from '../utils/lang/objectAssign'; const noCacheHeaderOptions = { headers: { 'Cache-Control': 'no-cache' } }; @@ -16,7 +16,10 @@ function userKeyToQueryParam(userKey: string) { * @param settings validated settings object * @param platform object containing environment-specific `getFetch` and `getOptions` dependencies */ -export function splitApiFactory(settings: ISettings, platform: Pick): ISplitApi { +export function splitApiFactory( + settings: Pick, + platform: Pick +): ISplitApi { const urls = settings.urls; const filterQueryString = settings.sync.__splitFiltersValidation && settings.sync.__splitFiltersValidation.queryString; diff --git a/src/services/splitHttpClient.ts b/src/services/splitHttpClient.ts index 8f413f5e..ee031149 100644 --- a/src/services/splitHttpClient.ts +++ b/src/services/splitHttpClient.ts @@ -1,5 +1,5 @@ import { IFetch, IRequestOptions, IResponse, ISplitHttpClient } from './types'; -import objectAssign from 'object-assign'; +import { objectAssign } from '../utils/lang/objectAssign'; import { ERROR_HTTP, ERROR_CLIENT_CANNOT_GET_READY } from '../logger/constants'; import { ISettings } from '../types'; diff --git a/src/services/types.ts b/src/services/types.ts index b1bdf954..22650c5b 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -50,8 +50,8 @@ export type IPostMetricsCounters = (body: string) => Promise export type IPostMetricsTimes = (body: string) => Promise export interface ISplitApi { - getSdkAPIHealthCheck: IHealthCheckAPI - getEventsAPIHealthCheck: IHealthCheckAPI + getSdkAPIHealthCheck: IHealthCheckAPI + getEventsAPIHealthCheck: IHealthCheckAPI fetchAuth: IFetchAuth fetchSplitChanges: IFetchSplitChanges fetchSegmentChanges: IFetchSegmentChanges @@ -62,3 +62,17 @@ export interface ISplitApi { postMetricsCounters: IPostMetricsCounters postMetricsTimes: IPostMetricsTimes } + +// Minimal version of EventSource API used by the SDK +interface EventSourceEventMap { + 'error': Event + 'message': MessageEvent + 'open': Event +} + +interface IEventSource { + addEventListener(type: K, listener: (this: IEventSource, ev: EventSourceEventMap[K]) => any): void + close(): void +} + +export type IEventSourceConstructor = new (url: string, eventSourceInitDict?: any) => IEventSource diff --git a/src/storages/AbstractSegmentsCacheSync.ts b/src/storages/AbstractSegmentsCacheSync.ts index 6b2799dc..ad371912 100644 --- a/src/storages/AbstractSegmentsCacheSync.ts +++ b/src/storages/AbstractSegmentsCacheSync.ts @@ -6,7 +6,7 @@ import { ISegmentsCacheSync } from './types'; * This class provides a skeletal implementation of the ISegmentsCacheSync interface * to minimize the effort required to implement this interface. */ -export default abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync { +export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync { /** * For server-side synchronizer: add `segmentKeys` list of keys to `name` segment. * For client-side synchronizer: add `name` segment to the cache. `segmentKeys` is undefined. diff --git a/src/storages/AbstractSplitsCacheAsync.ts b/src/storages/AbstractSplitsCacheAsync.ts index e213b9a3..6c44ffcd 100644 --- a/src/storages/AbstractSplitsCacheAsync.ts +++ b/src/storages/AbstractSplitsCacheAsync.ts @@ -5,7 +5,7 @@ import { ISplit } from '../dtos/types'; * This class provides a skeletal implementation of the ISplitsCacheAsync interface * to minimize the effort required to implement this interface. */ -export default abstract class AbstractSplitsCacheAsync implements ISplitsCacheAsync { +export abstract class AbstractSplitsCacheAsync implements ISplitsCacheAsync { abstract addSplit(name: string, split: string): Promise abstract addSplits(entries: [string, string][]): Promise diff --git a/src/storages/AbstractSplitsCacheSync.ts b/src/storages/AbstractSplitsCacheSync.ts index 9bc31467..e07ee914 100644 --- a/src/storages/AbstractSplitsCacheSync.ts +++ b/src/storages/AbstractSplitsCacheSync.ts @@ -5,7 +5,7 @@ import { ISplit } from '../dtos/types'; * This class provides a skeletal implementation of the ISplitsCacheSync interface * to minimize the effort required to implement this interface. */ -export default abstract class AbstractSplitsCacheSync implements ISplitsCacheSync { +export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync { abstract addSplit(name: string, split: string): boolean; diff --git a/src/storages/KeyBuilder.ts b/src/storages/KeyBuilder.ts index b761b7dd..080ed3d9 100644 --- a/src/storages/KeyBuilder.ts +++ b/src/storages/KeyBuilder.ts @@ -12,7 +12,7 @@ export function validatePrefix(prefix: unknown) { DEFAULT_PREFIX; // use default prefix if none is provided } -export default class KeyBuilder { +export class KeyBuilder { protected readonly prefix: string; diff --git a/src/storages/KeyBuilderCS.ts b/src/storages/KeyBuilderCS.ts index 6bfe7adf..d4f9e902 100644 --- a/src/storages/KeyBuilderCS.ts +++ b/src/storages/KeyBuilderCS.ts @@ -1,7 +1,7 @@ import { startsWith } from '../utils/lang'; -import KeyBuilder from './KeyBuilder'; +import { KeyBuilder } from './KeyBuilder'; -export default class KeyBuilderCS extends KeyBuilder { +export class KeyBuilderCS extends KeyBuilder { protected readonly regexSplitsCacheKey: RegExp; protected readonly matchingKey: string; diff --git a/src/storages/KeyBuilderSS.ts b/src/storages/KeyBuilderSS.ts index 8944eecb..2f8898c1 100644 --- a/src/storages/KeyBuilderSS.ts +++ b/src/storages/KeyBuilderSS.ts @@ -1,11 +1,11 @@ -import KeyBuilder from './KeyBuilder'; +import { KeyBuilder } from './KeyBuilder'; import { IMetadata } from '../dtos/types'; // NOT USED // const everythingAfterCount = /count\.([^/]+)$/; // const latencyMetricNameAndBucket = /latency\.([^/]+)\.bucket\.([0-9]+)$/; -export default class KeyBuilderSS extends KeyBuilder { +export class KeyBuilderSS extends KeyBuilder { protected readonly metadata: IMetadata; diff --git a/src/storages/__tests__/KeyBuilder.spec.ts b/src/storages/__tests__/KeyBuilder.spec.ts index 6016d169..e0b82ebe 100644 --- a/src/storages/__tests__/KeyBuilder.spec.ts +++ b/src/storages/__tests__/KeyBuilder.spec.ts @@ -1,5 +1,5 @@ -import KeyBuilder from '../KeyBuilder'; -import KeyBuilderSS from '../KeyBuilderSS'; +import { KeyBuilder } from '../KeyBuilder'; +import { KeyBuilderSS } from '../KeyBuilderSS'; test('KEYS / splits keys', () => { const builder = new KeyBuilder(); diff --git a/src/storages/__tests__/testUtils.ts b/src/storages/__tests__/testUtils.ts index b09e9c1b..4905adb2 100644 --- a/src/storages/__tests__/testUtils.ts +++ b/src/storages/__tests__/testUtils.ts @@ -1,4 +1,4 @@ -import { IStorageSync, IStorageAsync } from '../types'; +import { IStorageSync, IStorageAsync, IImpressionsCacheSync, IEventsCacheSync } from '../types'; // Assert that instances created by storage factories have the expected interface export function assertStorageInterface(storage: IStorageSync | IStorageAsync) { @@ -12,6 +12,13 @@ export function assertStorageInterface(storage: IStorageSync | IStorageAsync) { expect(!storage.impressionCounts || typeof storage.impressionCounts === 'object').toBeTruthy; } +export function assertSyncRecorderCacheInterface(cache: IEventsCacheSync | IImpressionsCacheSync) { + expect(typeof cache.isEmpty).toBe('function'); + expect(typeof cache.clear).toBe('function'); + expect(typeof cache.state).toBe('function'); + expect(typeof cache.track).toBe('function'); +} + // Split mocks export const splitWithUserTT = '{ "trafficTypeName": "user_tt", "conditions": [] }'; diff --git a/src/storages/findLatencyIndex.ts b/src/storages/findLatencyIndex.ts index b060abd6..908752d9 100644 --- a/src/storages/findLatencyIndex.ts +++ b/src/storages/findLatencyIndex.ts @@ -1,7 +1,7 @@ import { isNaNNumber } from '../utils/lang'; // @TODO add unit tests -export default function findLatencyIndex(latency: number, min = 0, max = 23, base = 1.5): number { +export function findLatencyIndex(latency: number, min = 0, max = 23, base = 1.5): number { const index = Math.min(max, Math.max(min, Math.floor(Math.log(latency) / Math.log(base)))); return isNaNNumber(index) ? 0 : index; // index is NaN if latency is not a positive number } diff --git a/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts b/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts index 12289470..7bc6690a 100644 --- a/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +++ b/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts @@ -1,9 +1,9 @@ import { ILogger } from '../../logger/types'; -import AbstractSegmentsCacheSync from '../AbstractSegmentsCacheSync'; -import KeyBuilderCS from '../KeyBuilderCS'; +import { AbstractSegmentsCacheSync } from '../AbstractSegmentsCacheSync'; +import { KeyBuilderCS } from '../KeyBuilderCS'; import { LOG_PREFIX, DEFINED } from './constants'; -export default class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync { +export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync { private readonly keys: KeyBuilderCS; private readonly log: ILogger; diff --git a/src/storages/inLocalStorage/SplitsCacheInLocal.ts b/src/storages/inLocalStorage/SplitsCacheInLocal.ts index 68565b55..c35cc15b 100644 --- a/src/storages/inLocalStorage/SplitsCacheInLocal.ts +++ b/src/storages/inLocalStorage/SplitsCacheInLocal.ts @@ -1,14 +1,14 @@ import { ISplit, ISplitFiltersValidation } from '../../dtos/types'; -import AbstractSplitsCacheSync, { usesSegments } from '../AbstractSplitsCacheSync'; +import { AbstractSplitsCacheSync, usesSegments } from '../AbstractSplitsCacheSync'; import { isFiniteNumber, toNumber, isNaNNumber } from '../../utils/lang'; -import KeyBuilderCS from '../KeyBuilderCS'; +import { KeyBuilderCS } from '../KeyBuilderCS'; import { ILogger } from '../../logger/types'; import { LOG_PREFIX } from './constants'; /** * ISplitsCacheSync implementation that stores split definitions in browser LocalStorage. */ -export default class SplitsCacheInLocal extends AbstractSplitsCacheSync { +export class SplitsCacheInLocal extends AbstractSplitsCacheSync { private readonly keys: KeyBuilderCS; private readonly splitFiltersValidation: ISplitFiltersValidation; diff --git a/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts b/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts index 158f8c2c..36072550 100644 --- a/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts +++ b/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts @@ -1,5 +1,5 @@ -import MySegmentsCacheInLocal from '../MySegmentsCacheInLocal'; -import KeyBuilderCS from '../../KeyBuilderCS'; +import { MySegmentsCacheInLocal } from '../MySegmentsCacheInLocal'; +import { KeyBuilderCS } from '../../KeyBuilderCS'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; test('SEGMENT CACHE / in LocalStorage', () => { diff --git a/src/storages/inLocalStorage/__tests__/SplitsCacheInLocal.spec.ts b/src/storages/inLocalStorage/__tests__/SplitsCacheInLocal.spec.ts index 0f3172f7..d43bb222 100644 --- a/src/storages/inLocalStorage/__tests__/SplitsCacheInLocal.spec.ts +++ b/src/storages/inLocalStorage/__tests__/SplitsCacheInLocal.spec.ts @@ -1,5 +1,5 @@ -import SplitsCacheInLocal from '../SplitsCacheInLocal'; -import KeyBuilderCS from '../../KeyBuilderCS'; +import { SplitsCacheInLocal } from '../SplitsCacheInLocal'; +import { KeyBuilderCS } from '../../KeyBuilderCS'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { splitWithUserTT, splitWithAccountTT, splitWithAccountTTAndUsesSegments } from '../../__tests__/testUtils'; diff --git a/src/storages/inLocalStorage/index.ts b/src/storages/inLocalStorage/index.ts index 6587208b..0d35341b 100644 --- a/src/storages/inLocalStorage/index.ts +++ b/src/storages/inLocalStorage/index.ts @@ -1,14 +1,14 @@ -import ImpressionsCacheInMemory from '../inMemory/ImpressionsCacheInMemory'; -import ImpressionCountsCacheInMemory from '../inMemory/ImpressionCountsCacheInMemory'; -import EventsCacheInMemory from '../inMemory/EventsCacheInMemory'; -import { IStorageFactoryParams, IStorageSyncCS, IStorageSyncFactory } from '../types'; +import { ImpressionsCacheInMemory } from '../inMemory/ImpressionsCacheInMemory'; +import { ImpressionCountsCacheInMemory } from '../inMemory/ImpressionCountsCacheInMemory'; +import { EventsCacheInMemory } from '../inMemory/EventsCacheInMemory'; +import { IStorageFactoryParams, IStorageSync, IStorageSyncFactory } from '../types'; import { validatePrefix } from '../KeyBuilder'; -import KeyBuilderCS from '../KeyBuilderCS'; +import { KeyBuilderCS } from '../KeyBuilderCS'; import { isLocalStorageAvailable } from '../../utils/env/isLocalStorageAvailable'; -import SplitsCacheInLocal from './SplitsCacheInLocal'; -import MySegmentsCacheInLocal from './MySegmentsCacheInLocal'; -import MySegmentsCacheInMemory from '../inMemory/MySegmentsCacheInMemory'; -import SplitsCacheInMemory from '../inMemory/SplitsCacheInMemory'; +import { SplitsCacheInLocal } from './SplitsCacheInLocal'; +import { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal'; +import { MySegmentsCacheInMemory } from '../inMemory/MySegmentsCacheInMemory'; +import { SplitsCacheInMemory } from '../inMemory/SplitsCacheInMemory'; import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../../utils/constants/browser'; import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS'; import { LOG_PREFIX } from './constants'; @@ -25,7 +25,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn const prefix = validatePrefix(options.prefix); - function InLocalStorageCSFactory(params: IStorageFactoryParams): IStorageSyncCS { + function InLocalStorageCSFactory(params: IStorageFactoryParams): IStorageSync { // Fallback to InMemoryStorage if LocalStorage API is not available if (!isLocalStorageAvailable()) { diff --git a/src/storages/inMemory/CountsCacheInMemory.ts b/src/storages/inMemory/CountsCacheInMemory.ts index 8f5dd1fd..ff7cd591 100644 --- a/src/storages/inMemory/CountsCacheInMemory.ts +++ b/src/storages/inMemory/CountsCacheInMemory.ts @@ -1,6 +1,6 @@ import { ICountsCacheSync } from '../types'; -export default class CountsCacheInMemory implements ICountsCacheSync { +export class CountsCacheInMemory implements ICountsCacheSync { private counters: Record = {}; diff --git a/src/storages/inMemory/EventsCacheInMemory.ts b/src/storages/inMemory/EventsCacheInMemory.ts index e60d00d6..d209cba6 100644 --- a/src/storages/inMemory/EventsCacheInMemory.ts +++ b/src/storages/inMemory/EventsCacheInMemory.ts @@ -3,7 +3,7 @@ import { IEventsCacheSync } from '../types'; const MAX_QUEUE_BYTE_SIZE = 5 * 1024 * 1024; // 5M -export default class EventsCacheInMemory implements IEventsCacheSync { +export class EventsCacheInMemory implements IEventsCacheSync { private onFullQueue?: () => void; private readonly maxQueue: number; diff --git a/src/storages/inMemory/ImpressionCountsCacheInMemory.ts b/src/storages/inMemory/ImpressionCountsCacheInMemory.ts index db6b31c6..f7598d2a 100644 --- a/src/storages/inMemory/ImpressionCountsCacheInMemory.ts +++ b/src/storages/inMemory/ImpressionCountsCacheInMemory.ts @@ -1,7 +1,7 @@ import { truncateTimeFrame } from '../../utils/time'; import { IImpressionCountsCacheSync } from '../types'; -export default class ImpressionCountsCacheInMemory implements IImpressionCountsCacheSync { +export class ImpressionCountsCacheInMemory implements IImpressionCountsCacheSync { private cache: Record = {}; /** diff --git a/src/storages/inMemory/ImpressionsCacheInMemory.ts b/src/storages/inMemory/ImpressionsCacheInMemory.ts index 9b9e3faa..dcbf6b7a 100644 --- a/src/storages/inMemory/ImpressionsCacheInMemory.ts +++ b/src/storages/inMemory/ImpressionsCacheInMemory.ts @@ -1,7 +1,7 @@ import { IImpressionsCacheSync } from '../types'; import { ImpressionDTO } from '../../types'; -export default class ImpressionsCacheInMemory implements IImpressionsCacheSync { +export class ImpressionsCacheInMemory implements IImpressionsCacheSync { private queue: ImpressionDTO[] = []; diff --git a/src/storages/inMemory/InMemoryStorage.ts b/src/storages/inMemory/InMemoryStorage.ts index 349ac6e1..00d666fc 100644 --- a/src/storages/inMemory/InMemoryStorage.ts +++ b/src/storages/inMemory/InMemoryStorage.ts @@ -1,9 +1,9 @@ -import SplitsCacheInMemory from './SplitsCacheInMemory'; -import SegmentsCacheInMemory from './SegmentsCacheInMemory'; -import ImpressionsCacheInMemory from './ImpressionsCacheInMemory'; -import EventsCacheInMemory from './EventsCacheInMemory'; +import { SplitsCacheInMemory } from './SplitsCacheInMemory'; +import { SegmentsCacheInMemory } from './SegmentsCacheInMemory'; +import { ImpressionsCacheInMemory } from './ImpressionsCacheInMemory'; +import { EventsCacheInMemory } from './EventsCacheInMemory'; import { IStorageFactoryParams, IStorageSync } from '../types'; -import ImpressionCountsCacheInMemory from './ImpressionCountsCacheInMemory'; +import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory'; import { STORAGE_MEMORY } from '../../utils/constants'; /** @@ -13,9 +13,6 @@ import { STORAGE_MEMORY } from '../../utils/constants'; */ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageSync { - // InMemory storage is always ready - if (params.onReadyCb) setTimeout(params.onReadyCb); - return { splits: new SplitsCacheInMemory(), segments: new SegmentsCacheInMemory(), diff --git a/src/storages/inMemory/InMemoryStorageCS.ts b/src/storages/inMemory/InMemoryStorageCS.ts index d59e4c04..7ac34ea8 100644 --- a/src/storages/inMemory/InMemoryStorageCS.ts +++ b/src/storages/inMemory/InMemoryStorageCS.ts @@ -1,9 +1,9 @@ -import SplitsCacheInMemory from './SplitsCacheInMemory'; -import MySegmentsCacheInMemory from './MySegmentsCacheInMemory'; -import ImpressionsCacheInMemory from './ImpressionsCacheInMemory'; -import EventsCacheInMemory from './EventsCacheInMemory'; -import { IStorageSyncCS, IStorageFactoryParams } from '../types'; -import ImpressionCountsCacheInMemory from './ImpressionCountsCacheInMemory'; +import { SplitsCacheInMemory } from './SplitsCacheInMemory'; +import { MySegmentsCacheInMemory } from './MySegmentsCacheInMemory'; +import { ImpressionsCacheInMemory } from './ImpressionsCacheInMemory'; +import { EventsCacheInMemory } from './EventsCacheInMemory'; +import { IStorageSync, IStorageFactoryParams } from '../types'; +import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory'; import { STORAGE_MEMORY } from '../../utils/constants'; /** @@ -11,7 +11,7 @@ import { STORAGE_MEMORY } from '../../utils/constants'; * * @param params parameters required by EventsCacheSync */ -export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorageSyncCS { +export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorageSync { return { splits: new SplitsCacheInMemory(), diff --git a/src/storages/inMemory/LatenciesCacheInMemory.ts b/src/storages/inMemory/LatenciesCacheInMemory.ts index 2ec6f054..240b5a8b 100644 --- a/src/storages/inMemory/LatenciesCacheInMemory.ts +++ b/src/storages/inMemory/LatenciesCacheInMemory.ts @@ -1,7 +1,7 @@ import { ILatenciesCacheSync } from '../types'; -import findLatencyIndex from '../findLatencyIndex'; +import { findLatencyIndex } from '../findLatencyIndex'; -export default class LatenciesCacheInMemory implements ILatenciesCacheSync { +export class LatenciesCacheInMemory implements ILatenciesCacheSync { private counters: Record = {}; diff --git a/src/storages/inMemory/MySegmentsCacheInMemory.ts b/src/storages/inMemory/MySegmentsCacheInMemory.ts index 041e31cb..02519d18 100644 --- a/src/storages/inMemory/MySegmentsCacheInMemory.ts +++ b/src/storages/inMemory/MySegmentsCacheInMemory.ts @@ -1,10 +1,10 @@ -import AbstractSegmentsCacheSync from '../AbstractSegmentsCacheSync'; +import { AbstractSegmentsCacheSync } from '../AbstractSegmentsCacheSync'; /** * Default MySegmentsCacheInMemory implementation that stores MySegments in memory. * Supported by all JS runtimes. */ -export default class MySegmentsCacheInMemory extends AbstractSegmentsCacheSync { +export class MySegmentsCacheInMemory extends AbstractSegmentsCacheSync { private segmentCache: Record = {}; diff --git a/src/storages/inMemory/SegmentsCacheInMemory.ts b/src/storages/inMemory/SegmentsCacheInMemory.ts index 70f4d691..ca86c86d 100644 --- a/src/storages/inMemory/SegmentsCacheInMemory.ts +++ b/src/storages/inMemory/SegmentsCacheInMemory.ts @@ -1,4 +1,4 @@ -import AbstractSegmentsCacheSync from '../AbstractSegmentsCacheSync'; +import { AbstractSegmentsCacheSync } from '../AbstractSegmentsCacheSync'; import { ISet, _Set } from '../../utils/lang/sets'; import { isIntegerNumber } from '../../utils/lang'; @@ -6,7 +6,7 @@ import { isIntegerNumber } from '../../utils/lang'; * Default ISplitsCacheSync implementation that stores split definitions in memory. * Supported by all JS runtimes. */ -export default class SegmentsCacheInMemory extends AbstractSegmentsCacheSync { +export class SegmentsCacheInMemory extends AbstractSegmentsCacheSync { private segmentCache: Record> = {}; private segmentChangeNumber: Record = {}; diff --git a/src/storages/inMemory/SplitsCacheInMemory.ts b/src/storages/inMemory/SplitsCacheInMemory.ts index 39eefdcf..32b0ed32 100644 --- a/src/storages/inMemory/SplitsCacheInMemory.ts +++ b/src/storages/inMemory/SplitsCacheInMemory.ts @@ -1,12 +1,12 @@ import { ISplit } from '../../dtos/types'; -import AbstractSplitsCacheSync, { usesSegments } from '../AbstractSplitsCacheSync'; +import { AbstractSplitsCacheSync, usesSegments } from '../AbstractSplitsCacheSync'; import { isFiniteNumber } from '../../utils/lang'; /** * Default ISplitsCacheSync implementation that stores split definitions in memory. * Supported by all JS runtimes. */ -export default class SplitsCacheInMemory extends AbstractSplitsCacheSync { +export class SplitsCacheInMemory extends AbstractSplitsCacheSync { private splitsCache: Record = {}; private ttCache: Record = {}; diff --git a/src/storages/inMemory/__tests__/CountsCacheInMemory.spec.ts b/src/storages/inMemory/__tests__/CountsCacheInMemory.spec.ts index e4f411d8..bb8ebcc0 100644 --- a/src/storages/inMemory/__tests__/CountsCacheInMemory.spec.ts +++ b/src/storages/inMemory/__tests__/CountsCacheInMemory.spec.ts @@ -1,7 +1,7 @@ -import CountsCache from '../CountsCacheInMemory'; +import { CountsCacheInMemory } from '../CountsCacheInMemory'; test('COUNT CACHE IN MEMORY / should count metric names incrementatly', () => { - const cache = new CountsCache(); + const cache = new CountsCacheInMemory(); cache.track('counted-metric-one'); cache.track('counted-metric-one'); diff --git a/src/storages/inMemory/__tests__/EventsCacheInMemory.spec.ts b/src/storages/inMemory/__tests__/EventsCacheInMemory.spec.ts index a4d73884..37a6a4ad 100644 --- a/src/storages/inMemory/__tests__/EventsCacheInMemory.spec.ts +++ b/src/storages/inMemory/__tests__/EventsCacheInMemory.spec.ts @@ -1,16 +1,16 @@ // @ts-nocheck -import EventsCache from '../EventsCacheInMemory'; +import { EventsCacheInMemory } from '../EventsCacheInMemory'; test('EVENTS CACHE / Should be able to instantiate and start with an empty queue', () => { let cache; - const createInstance = () => cache = new EventsCache(500); // 500 as eventsQueueSize + const createInstance = () => cache = new EventsCacheInMemory(500); // 500 as eventsQueueSize expect(createInstance).not.toThrow(); // Creation should not throw. expect(cache.state()).toEqual([]); // The queue starts empty. }); test('EVENTS CACHE / Should be able to add items sequentially and retrieve the queue', () => { - const cache = new EventsCache(500); + const cache = new EventsCacheInMemory(500); const queueValues = [1, '2', { p: 3 }, ['4']]; expect(cache.track.bind(cache, queueValues[0])).not.toThrow(); // Calling track should not throw @@ -26,7 +26,7 @@ test('EVENTS CACHE / Should be able to add items sequentially and retrieve the q }); test('EVENTS CACHE / Should be able to clear the queue and accumulated byte size', () => { - const cache = new EventsCache(500); + const cache = new EventsCacheInMemory(500); cache.track('test1', 2019); cache.clear(); @@ -36,7 +36,7 @@ test('EVENTS CACHE / Should be able to clear the queue and accumulated byte size }); test('EVENTS CACHE / Should be able to tell if the queue is empty', () => { - const cache = new EventsCache(500); + const cache = new EventsCacheInMemory(500); expect(cache.state().length === 0).toBe(true); // The queue is empty, expect(cache.isEmpty()).toBe(true); // so if it is empty, it returns true. @@ -48,7 +48,7 @@ test('EVENTS CACHE / Should be able to tell if the queue is empty', () => { }); test('EVENTS CACHE / Should be able to return the DTO we will send to BE', () => { - const cache = new EventsCache(500); + const cache = new EventsCacheInMemory(500); const queueValues = [1, '2', { p: 3 }, ['4']]; cache.track(queueValues[0]); @@ -63,7 +63,7 @@ test('EVENTS CACHE / Should be able to return the DTO we will send to BE', () => test('EVENTS CACHE / Should call "onFullQueueCb" when the queue is full (count wise).', () => { let cbCalled = 0; - const cache = new EventsCache(3); // small eventsQueueSize to be reached + const cache = new EventsCacheInMemory(3); // small eventsQueueSize to be reached cache.setOnFullQueueCb(() => { cbCalled++; cache.clear(); }); cache.track(0); @@ -89,7 +89,7 @@ test('EVENTS CACHE / Should call "onFullQueueCb" when the queue is full (count w test('EVENTS CACHE / Should call "onFullQueueCb" when the queue is full (size wise).', () => { let cbCalled = 0; - const cache = new EventsCache(99999999); // big eventsQueueSize to never be reached + const cache = new EventsCacheInMemory(99999999); // big eventsQueueSize to never be reached cache.setOnFullQueueCb(() => { cbCalled++; cache.clear(); }); // The track method receives the size, which calculation is validated elsewhere. @@ -108,7 +108,7 @@ test('EVENTS CACHE / Should call "onFullQueueCb" when the queue is full (size wi test('EVENTS CACHE / Should not throw if the "onFullQueueCb" callback was not provided.', () => { - const cache = new EventsCache(3); // small eventsQueueSize to be reached + const cache = new EventsCacheInMemory(3); // small eventsQueueSize to be reached cache.track(0, 2048); cache.track(1, 1024); // Cache still not full, diff --git a/src/storages/inMemory/__tests__/ImpressionCountsCacheInMemory.spec.ts b/src/storages/inMemory/__tests__/ImpressionCountsCacheInMemory.spec.ts index 68288808..00d878c9 100644 --- a/src/storages/inMemory/__tests__/ImpressionCountsCacheInMemory.spec.ts +++ b/src/storages/inMemory/__tests__/ImpressionCountsCacheInMemory.spec.ts @@ -1,9 +1,9 @@ // @ts-nocheck -import ImpressionCountsCache from '../ImpressionCountsCacheInMemory'; +import { ImpressionCountsCacheInMemory } from '../ImpressionCountsCacheInMemory'; test('IMPRESSION COUNTS CACHE / Impression Counter Test makeKey', () => { const timestamp = new Date(2020, 9, 2, 10, 0, 0).getTime(); - const counter = new ImpressionCountsCache(); + const counter = new ImpressionCountsCacheInMemory(); expect(counter._makeKey('someFeature', new Date(2020, 9, 2, 10, 53, 12).getTime())).toBe(`someFeature::${timestamp}`); expect(counter._makeKey('', new Date(2020, 9, 2, 10, 53, 12).getTime())).toBe(`::${timestamp}`); @@ -13,7 +13,7 @@ test('IMPRESSION COUNTS CACHE / Impression Counter Test makeKey', () => { test('IMPRESSION COUNTS CACHE / Impression Counter Test BasicUsage', () => { const timestamp = new Date(2020, 9, 2, 10, 10, 12).getTime(); - const counter = new ImpressionCountsCache(); + const counter = new ImpressionCountsCacheInMemory(); counter.track('feature1', timestamp, 1); counter.track('feature1', timestamp + 1, 1); counter.track('feature1', timestamp + 2, 1); diff --git a/src/storages/inMemory/__tests__/ImpressionsCacheInMemory.spec.ts b/src/storages/inMemory/__tests__/ImpressionsCacheInMemory.spec.ts index f98644c6..e5f7ed1b 100644 --- a/src/storages/inMemory/__tests__/ImpressionsCacheInMemory.spec.ts +++ b/src/storages/inMemory/__tests__/ImpressionsCacheInMemory.spec.ts @@ -1,8 +1,8 @@ -import ImpressionsCache from '../ImpressionsCacheInMemory'; +import { ImpressionsCacheInMemory } from '../ImpressionsCacheInMemory'; test('IMPRESSIONS CACHE IN MEMORY / should incrementally store values', () => { - const c = new ImpressionsCache(); + const c = new ImpressionsCacheInMemory(); // @ts-expect-error c.track([0]); // @ts-expect-error diff --git a/src/storages/inMemory/__tests__/LatenciesCacheInMemory.spec.ts b/src/storages/inMemory/__tests__/LatenciesCacheInMemory.spec.ts index 9e43668e..18ad0a3a 100644 --- a/src/storages/inMemory/__tests__/LatenciesCacheInMemory.spec.ts +++ b/src/storages/inMemory/__tests__/LatenciesCacheInMemory.spec.ts @@ -1,8 +1,8 @@ -import LatenciesCache from '../LatenciesCacheInMemory'; +import { LatenciesCacheInMemory } from '../LatenciesCacheInMemory'; test('METRICS (LATENCIES) CACHE IN MEMORY / should count based on ranges', () => { const metricName = 'testing'; - const c1 = new LatenciesCache(); + const c1 = new LatenciesCacheInMemory(); c1.track(metricName, 1); c1.track(metricName, 1.2); @@ -25,7 +25,7 @@ test('METRICS (LATENCIES) CACHE IN MEMORY / should count based on ranges', () => test('METRICS (LATENCIES) CACHE IN MEMORY / clear', () => { const metricName = 'testing'; - const c1 = new LatenciesCache(); + const c1 = new LatenciesCacheInMemory(); c1.track(metricName, 1); c1.track(metricName, 1000); diff --git a/src/storages/inMemory/__tests__/MySegmentsCacheInMemory.spec.ts b/src/storages/inMemory/__tests__/MySegmentsCacheInMemory.spec.ts index d3ad6c13..3ac0717c 100644 --- a/src/storages/inMemory/__tests__/MySegmentsCacheInMemory.spec.ts +++ b/src/storages/inMemory/__tests__/MySegmentsCacheInMemory.spec.ts @@ -1,7 +1,7 @@ -import MySegmentsCache from '../MySegmentsCacheInMemory'; +import { MySegmentsCacheInMemory } from '../MySegmentsCacheInMemory'; test('MY SEGMENTS CACHE / in memory', () => { - const cache = new MySegmentsCache(); + const cache = new MySegmentsCacheInMemory(); cache.addToSegment('mocked-segment'); diff --git a/src/storages/inMemory/__tests__/SegmentsCacheInMemory.spec.ts b/src/storages/inMemory/__tests__/SegmentsCacheInMemory.spec.ts index 70ca6b12..4d868849 100644 --- a/src/storages/inMemory/__tests__/SegmentsCacheInMemory.spec.ts +++ b/src/storages/inMemory/__tests__/SegmentsCacheInMemory.spec.ts @@ -1,9 +1,9 @@ -import SegmentsCache from '../SegmentsCacheInMemory'; +import { SegmentsCacheInMemory } from '../SegmentsCacheInMemory'; describe('SEGMENTS CACHE IN MEMORY', () => { test('isInSegment, set/getChangeNumber, add/removeFromSegment', () => { - const cache = new SegmentsCache(); + const cache = new SegmentsCacheInMemory(); cache.addToSegment('mocked-segment', [ 'a', 'b', 'c' @@ -29,7 +29,7 @@ describe('SEGMENTS CACHE IN MEMORY', () => { }); test('registerSegment / getRegisteredSegments', async () => { - const cache = new SegmentsCache(); + const cache = new SegmentsCacheInMemory(); await cache.registerSegments(['s1']); await cache.registerSegments(['s2']); diff --git a/src/storages/inMemory/__tests__/SplitsCacheInMemory.spec.ts b/src/storages/inMemory/__tests__/SplitsCacheInMemory.spec.ts index 0aa93ede..7d20655d 100644 --- a/src/storages/inMemory/__tests__/SplitsCacheInMemory.spec.ts +++ b/src/storages/inMemory/__tests__/SplitsCacheInMemory.spec.ts @@ -1,7 +1,7 @@ -import SplitsCache from '../SplitsCacheInMemory'; +import { SplitsCacheInMemory } from '../SplitsCacheInMemory'; test('SPLITS CACHE / In Memory', () => { - const cache = new SplitsCache(); + const cache = new SplitsCacheInMemory(); cache.addSplit('lol1', '{ "name": "something"}'); cache.addSplit('lol2', '{ "name": "something else"}'); @@ -31,7 +31,7 @@ test('SPLITS CACHE / In Memory', () => { }); test('SPLITS CACHE / In Memory / Get Keys', () => { - const cache = new SplitsCache(); + const cache = new SplitsCacheInMemory(); cache.addSplit('lol1', '{ "name": "something"}'); cache.addSplit('lol2', '{ "name": "something else"}'); @@ -43,7 +43,7 @@ test('SPLITS CACHE / In Memory / Get Keys', () => { }); test('SPLITS CACHE / In Memory / trafficTypeExists and ttcache tests', () => { - const cache = new SplitsCache(); + const cache = new SplitsCacheInMemory(); cache.addSplits([ // loop of addSplit ['split1', '{ "trafficTypeName": "user_tt" }'], @@ -82,7 +82,7 @@ test('SPLITS CACHE / In Memory / trafficTypeExists and ttcache tests', () => { }); test('SPLITS CACHE / In Memory / killLocally', () => { - const cache = new SplitsCache(); + const cache = new SplitsCacheInMemory(); cache.addSplit('lol1', '{ "name": "something"}'); cache.addSplit('lol2', '{ "name": "something else"}'); const initialChangeNumber = cache.getChangeNumber(); diff --git a/src/storages/inRedis/CountsCacheInRedis.ts b/src/storages/inRedis/CountsCacheInRedis.ts index b48b9280..cb551fa2 100644 --- a/src/storages/inRedis/CountsCacheInRedis.ts +++ b/src/storages/inRedis/CountsCacheInRedis.ts @@ -1,8 +1,8 @@ import { ICountsCacheAsync } from '../types'; -import KeyBuilderSS from '../KeyBuilderSS'; +import { KeyBuilderSS } from '../KeyBuilderSS'; import { Redis } from 'ioredis'; -export default class CountsCacheInRedis implements ICountsCacheAsync { +export class CountsCacheInRedis implements ICountsCacheAsync { private readonly redis: Redis; private readonly keys: KeyBuilderSS; diff --git a/src/storages/inRedis/EventsCacheInRedis.ts b/src/storages/inRedis/EventsCacheInRedis.ts index f4bc133b..e2702ab8 100644 --- a/src/storages/inRedis/EventsCacheInRedis.ts +++ b/src/storages/inRedis/EventsCacheInRedis.ts @@ -6,7 +6,7 @@ import { ILogger } from '../../logger/types'; import { LOG_PREFIX } from './constants'; import { StoredEventWithMetadata } from '../../sync/submitters/types'; -export default class EventsCacheInRedis implements IEventsCacheAsync { +export class EventsCacheInRedis implements IEventsCacheAsync { private readonly log: ILogger; private readonly key: string; diff --git a/src/storages/inRedis/ImpressionsCacheInRedis.ts b/src/storages/inRedis/ImpressionsCacheInRedis.ts index 7f399e64..d4bd2d96 100644 --- a/src/storages/inRedis/ImpressionsCacheInRedis.ts +++ b/src/storages/inRedis/ImpressionsCacheInRedis.ts @@ -7,7 +7,7 @@ import { ILogger } from '../../logger/types'; const IMPRESSIONS_TTL_REFRESH = 3600; // 1 hr -export default class ImpressionsCacheInRedis implements IImpressionsCacheAsync { +export class ImpressionsCacheInRedis implements IImpressionsCacheAsync { private readonly log: ILogger; private readonly key: string; diff --git a/src/storages/inRedis/LatenciesCacheInRedis.ts b/src/storages/inRedis/LatenciesCacheInRedis.ts index 143a759b..f69b7244 100644 --- a/src/storages/inRedis/LatenciesCacheInRedis.ts +++ b/src/storages/inRedis/LatenciesCacheInRedis.ts @@ -1,9 +1,9 @@ import { ILatenciesCacheAsync } from '../types'; -import KeyBuilderSS from '../KeyBuilderSS'; -import findLatencyIndex from '../findLatencyIndex'; +import { KeyBuilderSS } from '../KeyBuilderSS'; +import { findLatencyIndex } from '../findLatencyIndex'; import { Redis } from 'ioredis'; -export default class LatenciesCacheInRedis implements ILatenciesCacheAsync { +export class LatenciesCacheInRedis implements ILatenciesCacheAsync { private readonly redis: Redis; private readonly keys: KeyBuilderSS; diff --git a/src/storages/inRedis/RedisAdapter.ts b/src/storages/inRedis/RedisAdapter.ts index 99dccd45..05ef94f7 100644 --- a/src/storages/inRedis/RedisAdapter.ts +++ b/src/storages/inRedis/RedisAdapter.ts @@ -2,8 +2,8 @@ import ioredis from 'ioredis'; import { ILogger } from '../../logger/types'; import { merge, isString } from '../../utils/lang'; import { _Set, setToArray, ISet } from '../../utils/lang/sets'; -import thenable from '../../utils/promise/thenable'; -import timeout from '../../utils/promise/timeout'; +import { thenable } from '../../utils/promise/thenable'; +import { timeout } from '../../utils/promise/timeout'; const LOG_PREFIX = 'storage:redis-adapter: '; @@ -32,7 +32,7 @@ interface IRedisCommand { /** * Redis adapter on top of the library of choice (written with ioredis) for some extra control. */ -export default class RedisAdapter extends ioredis { +export class RedisAdapter extends ioredis { private readonly log: ILogger private _options: object; private _notReadyCommandsQueue?: IRedisCommand[]; diff --git a/src/storages/inRedis/SegmentsCacheInRedis.ts b/src/storages/inRedis/SegmentsCacheInRedis.ts index daee13a5..a144e2b7 100644 --- a/src/storages/inRedis/SegmentsCacheInRedis.ts +++ b/src/storages/inRedis/SegmentsCacheInRedis.ts @@ -2,10 +2,10 @@ import { Redis } from 'ioredis'; import { ILogger } from '../../logger/types'; import { isNaNNumber } from '../../utils/lang'; import { LOG_PREFIX } from '../inLocalStorage/constants'; -import KeyBuilderSS from '../KeyBuilderSS'; +import { KeyBuilderSS } from '../KeyBuilderSS'; import { ISegmentsCacheAsync } from '../types'; -export default class SegmentsCacheInRedis implements ISegmentsCacheAsync { +export class SegmentsCacheInRedis implements ISegmentsCacheAsync { private readonly log: ILogger; private readonly redis: Redis; diff --git a/src/storages/inRedis/SplitsCacheInRedis.ts b/src/storages/inRedis/SplitsCacheInRedis.ts index 92e5c09c..28996d06 100644 --- a/src/storages/inRedis/SplitsCacheInRedis.ts +++ b/src/storages/inRedis/SplitsCacheInRedis.ts @@ -1,10 +1,10 @@ import { isFiniteNumber, isNaNNumber } from '../../utils/lang'; -import KeyBuilderSS from '../KeyBuilderSS'; +import { KeyBuilderSS } from '../KeyBuilderSS'; import { Redis } from 'ioredis'; import { ILogger } from '../../logger/types'; import { LOG_PREFIX } from './constants'; import { ISplit } from '../../dtos/types'; -import AbstractSplitsCacheAsync from '../AbstractSplitsCacheAsync'; +import { AbstractSplitsCacheAsync } from '../AbstractSplitsCacheAsync'; /** * Discard errors for an answer of multiple operations. @@ -20,7 +20,7 @@ function processPipelineAnswer(results: Array<[Error | null, string]>): string[] * ISplitsCacheAsync implementation that stores split definitions in Redis. * Supported by Node. */ -export default class SplitsCacheInRedis extends AbstractSplitsCacheAsync { +export class SplitsCacheInRedis extends AbstractSplitsCacheAsync { private readonly log: ILogger; private readonly redis: Redis; diff --git a/src/storages/inRedis/__tests__/CountsCacheInRedis.spec.ts b/src/storages/inRedis/__tests__/CountsCacheInRedis.spec.ts index 8220a3ce..b8032ac2 100644 --- a/src/storages/inRedis/__tests__/CountsCacheInRedis.spec.ts +++ b/src/storages/inRedis/__tests__/CountsCacheInRedis.spec.ts @@ -1,6 +1,6 @@ import Redis from 'ioredis'; -import KeyBuilderSS from '../../KeyBuilderSS'; -import CountsCacheInRedis from '../CountsCacheInRedis'; +import { KeyBuilderSS } from '../../KeyBuilderSS'; +import { CountsCacheInRedis } from '../CountsCacheInRedis'; const prefix = 'counts_cache_ut'; const metadata = { s: 'version', i: 'ip', n: 'hostname' }; diff --git a/src/storages/inRedis/__tests__/EventsCacheInRedis.spec.ts b/src/storages/inRedis/__tests__/EventsCacheInRedis.spec.ts index 2ed55687..9d648feb 100644 --- a/src/storages/inRedis/__tests__/EventsCacheInRedis.spec.ts +++ b/src/storages/inRedis/__tests__/EventsCacheInRedis.spec.ts @@ -1,6 +1,6 @@ import Redis from 'ioredis'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; -import EventsCacheInRedis from '../EventsCacheInRedis'; +import { EventsCacheInRedis } from '../EventsCacheInRedis'; import { fakeMetadata, fakeEvent1, fakeEvent1stored, fakeEvent2, fakeEvent2stored, fakeEvent3, fakeEvent3stored } from '../../pluggable/__tests__/EventsCachePluggable.spec'; const prefix = 'events_cache_ut'; diff --git a/src/storages/inRedis/__tests__/ImpressionsCacheInRedis.spec.ts b/src/storages/inRedis/__tests__/ImpressionsCacheInRedis.spec.ts index 0701bd7b..8dcd1398 100644 --- a/src/storages/inRedis/__tests__/ImpressionsCacheInRedis.spec.ts +++ b/src/storages/inRedis/__tests__/ImpressionsCacheInRedis.spec.ts @@ -1,5 +1,5 @@ -import Redis from '../RedisAdapter'; -import ImpressionsCacheInRedis from '../ImpressionsCacheInRedis'; +import { RedisAdapter } from '../RedisAdapter'; +import { ImpressionsCacheInRedis } from '../ImpressionsCacheInRedis'; import IORedis, { BooleanResponse } from 'ioredis'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { fakeMetadata, o1, o2, o3, o1stored, o2stored, o3stored } from '../../pluggable/__tests__/ImpressionsCachePluggable.spec'; @@ -7,7 +7,7 @@ import { fakeMetadata, o1, o2, o3, o1stored, o2stored, o3stored } from '../../pl describe('IMPRESSIONS CACHE IN REDIS', () => { test('`track`, `count`, `popNWithMetadata` and `drop` methods', async () => { - const connection = new Redis(loggerMock, {}); + const connection = new RedisAdapter(loggerMock, {}); const impressionsKey = 'impr_cache_ut.impressions'; const c = new ImpressionsCacheInRedis(loggerMock, impressionsKey, connection, fakeMetadata); @@ -49,7 +49,7 @@ describe('IMPRESSIONS CACHE IN REDIS', () => { test('`track` should not resolve before calling expire', async () => { const impressionsKey = 'impr_cache_ut_2.impressions'; - const connection = new Redis(loggerMock, {}); + const connection = new RedisAdapter(loggerMock, {}); const c = new ImpressionsCacheInRedis(loggerMock, impressionsKey, connection, fakeMetadata); diff --git a/src/storages/inRedis/__tests__/LatenciesCacheInRedis.spec.ts b/src/storages/inRedis/__tests__/LatenciesCacheInRedis.spec.ts index e592bdd9..7567b969 100644 --- a/src/storages/inRedis/__tests__/LatenciesCacheInRedis.spec.ts +++ b/src/storages/inRedis/__tests__/LatenciesCacheInRedis.spec.ts @@ -1,6 +1,6 @@ import Redis from 'ioredis'; -import KeyBuilderSS from '../../KeyBuilderSS'; -import LatenciesCacheInRedis from '../LatenciesCacheInRedis'; +import { KeyBuilderSS } from '../../KeyBuilderSS'; +import { LatenciesCacheInRedis } from '../LatenciesCacheInRedis'; const prefix = 'latencies_cache_UT'; const metadata = { s: 'js_someversion', i: 'some_ip', n: 'some_hostname' }; diff --git a/src/storages/inRedis/__tests__/RedisAdapter.spec.ts b/src/storages/inRedis/__tests__/RedisAdapter.spec.ts index 9557162d..c9e789f6 100644 --- a/src/storages/inRedis/__tests__/RedisAdapter.spec.ts +++ b/src/storages/inRedis/__tests__/RedisAdapter.spec.ts @@ -49,11 +49,11 @@ const timeoutMock = jest.fn(function timeout(ms, originalPromise) { }); jest.mock('../../../utils/promise/timeout'); -import timeout from '../../../utils/promise/timeout'; +import { timeout } from '../../../utils/promise/timeout'; (timeout as jest.Mock).mockImplementation(timeoutMock); // Test target -import RedisAdapter from '../RedisAdapter'; +import { RedisAdapter } from '../RedisAdapter'; function clearAllMocks() { loggerMock.mockClear(); diff --git a/src/storages/inRedis/__tests__/SegmentsCacheInRedis.spec.ts b/src/storages/inRedis/__tests__/SegmentsCacheInRedis.spec.ts index dc3ea4b8..e230fbc0 100644 --- a/src/storages/inRedis/__tests__/SegmentsCacheInRedis.spec.ts +++ b/src/storages/inRedis/__tests__/SegmentsCacheInRedis.spec.ts @@ -1,6 +1,6 @@ import Redis from 'ioredis'; -import SegmentsCacheInRedis from '../SegmentsCacheInRedis'; -import KeyBuilderSS from '../../KeyBuilderSS'; +import { SegmentsCacheInRedis } from '../SegmentsCacheInRedis'; +import { KeyBuilderSS } from '../../KeyBuilderSS'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; // @ts-expect-error. Doesn't require metadata diff --git a/src/storages/inRedis/__tests__/SplitsCacheInRedis.spec.ts b/src/storages/inRedis/__tests__/SplitsCacheInRedis.spec.ts index 7ab8384b..fdb4777a 100644 --- a/src/storages/inRedis/__tests__/SplitsCacheInRedis.spec.ts +++ b/src/storages/inRedis/__tests__/SplitsCacheInRedis.spec.ts @@ -1,6 +1,6 @@ import Redis from 'ioredis'; -import SplitsCacheInRedis from '../SplitsCacheInRedis'; -import KeyBuilderSS from '../../KeyBuilderSS'; +import { SplitsCacheInRedis } from '../SplitsCacheInRedis'; +import { KeyBuilderSS } from '../../KeyBuilderSS'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { splitWithUserTT, splitWithAccountTT } from '../../__tests__/testUtils'; diff --git a/src/storages/inRedis/index.ts b/src/storages/inRedis/index.ts index b13413ee..de72a220 100644 --- a/src/storages/inRedis/index.ts +++ b/src/storages/inRedis/index.ts @@ -1,13 +1,13 @@ -import RedisAdapter from './RedisAdapter'; +import { RedisAdapter } from './RedisAdapter'; import { IStorageAsync, IStorageAsyncFactory, IStorageFactoryParams } from '../types'; import { validatePrefix } from '../KeyBuilder'; -import KeyBuilderSS from '../KeyBuilderSS'; -import SplitsCacheInRedis from './SplitsCacheInRedis'; -import SegmentsCacheInRedis from './SegmentsCacheInRedis'; -import ImpressionsCacheInRedis from './ImpressionsCacheInRedis'; -import EventsCacheInRedis from './EventsCacheInRedis'; -import LatenciesCacheInRedis from './LatenciesCacheInRedis'; -import CountsCacheInRedis from './CountsCacheInRedis'; +import { KeyBuilderSS } from '../KeyBuilderSS'; +import { SplitsCacheInRedis } from './SplitsCacheInRedis'; +import { SegmentsCacheInRedis } from './SegmentsCacheInRedis'; +import { ImpressionsCacheInRedis } from './ImpressionsCacheInRedis'; +import { EventsCacheInRedis } from './EventsCacheInRedis'; +import { LatenciesCacheInRedis } from './LatenciesCacheInRedis'; +import { CountsCacheInRedis } from './CountsCacheInRedis'; import { STORAGE_REDIS } from '../../utils/constants'; export interface InRedisStorageOptions { @@ -30,7 +30,7 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy // subscription to Redis connect event in order to emit SDK_READY event on consumer mode redisClient.on('connect', () => { - if (onReadyCb) onReadyCb(); + onReadyCb(); }); return { diff --git a/src/storages/pluggable/EventsCachePluggable.ts b/src/storages/pluggable/EventsCachePluggable.ts index ff0b19dc..d30d43b7 100644 --- a/src/storages/pluggable/EventsCachePluggable.ts +++ b/src/storages/pluggable/EventsCachePluggable.ts @@ -1,4 +1,4 @@ -import { ICustomStorageWrapper, IEventsCacheAsync } from '../types'; +import { IPluggableStorageWrapper, IEventsCacheAsync } from '../types'; import { IMetadata } from '../../dtos/types'; import { SplitIO } from '../../types'; import { ILogger } from '../../logger/types'; @@ -8,11 +8,11 @@ import { StoredEventWithMetadata } from '../../sync/submitters/types'; export class EventsCachePluggable implements IEventsCacheAsync { private readonly log: ILogger; - private readonly wrapper: ICustomStorageWrapper; + private readonly wrapper: IPluggableStorageWrapper; private readonly key: string; private readonly metadata: IMetadata; - constructor(log: ILogger, key: string, wrapper: ICustomStorageWrapper, metadata: IMetadata) { + constructor(log: ILogger, key: string, wrapper: IPluggableStorageWrapper, metadata: IMetadata) { this.log = log; this.key = key; this.wrapper = wrapper; diff --git a/src/storages/pluggable/ImpressionsCachePluggable.ts b/src/storages/pluggable/ImpressionsCachePluggable.ts index debe30ed..4de3e33e 100644 --- a/src/storages/pluggable/ImpressionsCachePluggable.ts +++ b/src/storages/pluggable/ImpressionsCachePluggable.ts @@ -1,4 +1,4 @@ -import { ICustomStorageWrapper, IImpressionsCacheAsync } from '../types'; +import { IPluggableStorageWrapper, IImpressionsCacheAsync } from '../types'; import { IMetadata } from '../../dtos/types'; import { ImpressionDTO } from '../../types'; import { ILogger } from '../../logger/types'; @@ -8,10 +8,10 @@ export class ImpressionsCachePluggable implements IImpressionsCacheAsync { private readonly log: ILogger; private readonly key: string; - private readonly wrapper: ICustomStorageWrapper; + private readonly wrapper: IPluggableStorageWrapper; private readonly metadata: IMetadata; - constructor(log: ILogger, key: string, wrapper: ICustomStorageWrapper, metadata: IMetadata) { + constructor(log: ILogger, key: string, wrapper: IPluggableStorageWrapper, metadata: IMetadata) { this.log = log; this.key = key; this.wrapper = wrapper; diff --git a/src/storages/pluggable/SegmentsCachePluggable.ts b/src/storages/pluggable/SegmentsCachePluggable.ts index ec4c3fc3..995c66df 100644 --- a/src/storages/pluggable/SegmentsCachePluggable.ts +++ b/src/storages/pluggable/SegmentsCachePluggable.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable no-unused-vars */ import { isNaNNumber } from '../../utils/lang'; -import KeyBuilderSS from '../KeyBuilderSS'; -import { ICustomStorageWrapper, ISegmentsCacheAsync } from '../types'; +import { KeyBuilderSS } from '../KeyBuilderSS'; +import { IPluggableStorageWrapper, ISegmentsCacheAsync } from '../types'; import { ILogger } from '../../logger/types'; import { LOG_PREFIX } from './constants'; import { _Set } from '../../utils/lang/sets'; @@ -14,9 +14,9 @@ export class SegmentsCachePluggable implements ISegmentsCacheAsync { private readonly log: ILogger; private readonly keys: KeyBuilderSS; - private readonly wrapper: ICustomStorageWrapper; + private readonly wrapper: IPluggableStorageWrapper; - constructor(log: ILogger, keys: KeyBuilderSS, wrapper: ICustomStorageWrapper) { + constructor(log: ILogger, keys: KeyBuilderSS, wrapper: IPluggableStorageWrapper) { this.log = log; this.keys = keys; this.wrapper = wrapper; diff --git a/src/storages/pluggable/SplitsCachePluggable.ts b/src/storages/pluggable/SplitsCachePluggable.ts index e4e29ce4..6fec1420 100644 --- a/src/storages/pluggable/SplitsCachePluggable.ts +++ b/src/storages/pluggable/SplitsCachePluggable.ts @@ -1,10 +1,10 @@ import { isFiniteNumber, isNaNNumber } from '../../utils/lang'; -import KeyBuilder from '../KeyBuilder'; -import { ICustomStorageWrapper } from '../types'; +import { KeyBuilder } from '../KeyBuilder'; +import { IPluggableStorageWrapper } from '../types'; import { ILogger } from '../../logger/types'; import { ISplit } from '../../dtos/types'; import { LOG_PREFIX } from './constants'; -import AbstractSplitsCacheAsync from '../AbstractSplitsCacheAsync'; +import { AbstractSplitsCacheAsync } from '../AbstractSplitsCacheAsync'; /** * ISplitsCacheAsync implementation for pluggable storages. @@ -13,15 +13,15 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync { private readonly log: ILogger; private readonly keys: KeyBuilder; - private readonly wrapper: ICustomStorageWrapper; + private readonly wrapper: IPluggableStorageWrapper; /** - * Create a SplitsCache that uses a custom storage wrapper. + * Create a SplitsCache that uses a storage wrapper. * @param log Logger instance. * @param keys Key builder. * @param wrapper Adapted wrapper storage. */ - constructor(log: ILogger, keys: KeyBuilder, wrapper: ICustomStorageWrapper) { + constructor(log: ILogger, keys: KeyBuilder, wrapper: IPluggableStorageWrapper) { super(); this.log = log; this.keys = keys; diff --git a/src/storages/pluggable/__tests__/SegmentsCachePluggable.spec.ts b/src/storages/pluggable/__tests__/SegmentsCachePluggable.spec.ts index d0ec3a56..0ab833c8 100644 --- a/src/storages/pluggable/__tests__/SegmentsCachePluggable.spec.ts +++ b/src/storages/pluggable/__tests__/SegmentsCachePluggable.spec.ts @@ -1,5 +1,5 @@ import { SegmentsCachePluggable } from '../SegmentsCachePluggable'; -import KeyBuilderSS from '../../KeyBuilderSS'; +import { KeyBuilderSS } from '../../KeyBuilderSS'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { wrapperMock } from './wrapper.mock'; diff --git a/src/storages/pluggable/__tests__/SplitsCachePluggable.spec.ts b/src/storages/pluggable/__tests__/SplitsCachePluggable.spec.ts index 99069585..652fb07a 100644 --- a/src/storages/pluggable/__tests__/SplitsCachePluggable.spec.ts +++ b/src/storages/pluggable/__tests__/SplitsCachePluggable.spec.ts @@ -1,5 +1,5 @@ import { SplitsCachePluggable } from '../SplitsCachePluggable'; -import KeyBuilder from '../../KeyBuilder'; +import { KeyBuilder } from '../../KeyBuilder'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { wrapperMockFactory } from './wrapper.mock'; import { splitWithUserTT, splitWithAccountTT } from '../../__tests__/testUtils'; diff --git a/src/storages/pluggable/__tests__/index.spec.ts b/src/storages/pluggable/__tests__/index.spec.ts index 8356a3ad..6f37f4f1 100644 --- a/src/storages/pluggable/__tests__/index.spec.ts +++ b/src/storages/pluggable/__tests__/index.spec.ts @@ -10,7 +10,8 @@ const prefix = 'some_prefix'; // Test target import { PluggableStorage } from '../index'; -import { assertStorageInterface } from '../../__tests__/testUtils'; +import { assertStorageInterface, assertSyncRecorderCacheInterface } from '../../__tests__/testUtils'; +import { CONSUMER_PARTIAL_MODE } from '../../../utils/constants'; describe('PLUGGABLE STORAGE', () => { @@ -31,13 +32,24 @@ describe('PLUGGABLE STORAGE', () => { assertStorageInterface(storage); // the instance must implement the storage interface expect(wrapperMock.connect).toBeCalledTimes(1); // wrapper connect method should be called once when storage is created + // shared storage has the same cache items than storage instance, but a no-op destroy + const sharedOnReadyCb = jest.fn(); + const sharedStorage = storage.shared('some_key', sharedOnReadyCb); + assertStorageInterface(sharedStorage); + expect(sharedStorage.splits).toBe(storage.splits); + expect(wrapperMock.connect).toBeCalledTimes(2); + expect(await storage.splits.getSplit('some_split')).toBe(null); + expect(await sharedStorage.splits.getSplit('some_split')).toBe(null); + expect(wrapperMock.get).toBeCalledTimes(2); expect(wrapperMock.get).toBeCalledWith(`${prefix}.SPLITIO.split.some_split`); // keys prefix should be the provided one await storage.destroy(); - expect(wrapperMock.close).toBeCalledTimes(1); // wrapper close method should be called once when storage is destroyed + await sharedStorage.destroy(); + expect(wrapperMock.disconnect).toBeCalledTimes(1); // wrapper disconnect method should be called once when storage is destroyed expect(internalSdkParams.onReadyCb).toBeCalledTimes(1); // onReady callback should be called when the wrapper connect resolved with true + expect(sharedOnReadyCb).toBeCalledTimes(1); }); test('throws an exception if wrapper doesn\'t implement the expected interface', async () => { @@ -45,7 +57,7 @@ describe('PLUGGABLE STORAGE', () => { expect(() => PluggableStorage({ wrapper: wrapperMock })).not.toThrow(); // not prefix but valid wrapper is OK // Throws exception if no object is passed as wrapper - const errorNoValidWrapper = 'Expecting custom storage `wrapper` in options, but no valid wrapper instance was provided.'; + const errorNoValidWrapper = 'Expecting pluggable storage `wrapper` in options, but no valid wrapper instance was provided.'; expect(() => PluggableStorage()).toThrow(errorNoValidWrapper); expect(() => PluggableStorage({ wrapper: undefined })).toThrow(errorNoValidWrapper); expect(() => PluggableStorage({ wrapper: 'invalid wrapper' })).toThrow(errorNoValidWrapper); @@ -53,10 +65,30 @@ describe('PLUGGABLE STORAGE', () => { // Throws exception if the given object is not a valid wrapper, informing which methods are missing const invalidWrapper = wrapperMockFactory(); invalidWrapper.connect = undefined; - invalidWrapper.close = 'invalid function'; + invalidWrapper.disconnect = 'invalid function'; const errorNoValidWrapperInterface = 'The provided wrapper instance doesn’t follow the expected interface. Check our docs.'; expect(() => PluggableStorage({ wrapper: invalidWrapper })).toThrow(errorNoValidWrapperInterface); expect(() => PluggableStorage({ wrapper: {} })).toThrow(errorNoValidWrapperInterface); }); + test('creates a storage instance for partial consumer mode (events and impressions cache in memory)', async () => { + const storageFactory = PluggableStorage({ prefix, wrapper: wrapperMock }); + const storage = storageFactory({ ...internalSdkParams, mode: CONSUMER_PARTIAL_MODE, optimize: true }); + + assertStorageInterface(storage); + expect(wrapperMock.connect).toBeCalledTimes(1); + + // Sync cache + assertSyncRecorderCacheInterface(storage.events); + assertSyncRecorderCacheInterface(storage.impressions); + assertSyncRecorderCacheInterface(storage.impressionCounts); + + // But event track is async + const eventResult = storage.events.track('some data'); + expect(typeof eventResult.then === 'function').toBeTruthy(); + expect(await eventResult).toBe(true); + + const impResult = storage.impressions.track(['some data']); + expect(impResult).toBe(undefined); + }); }); diff --git a/src/storages/pluggable/__tests__/wrapper.mock.ts b/src/storages/pluggable/__tests__/wrapper.mock.ts index da4b2122..594ecbcf 100644 --- a/src/storages/pluggable/__tests__/wrapper.mock.ts +++ b/src/storages/pluggable/__tests__/wrapper.mock.ts @@ -1,6 +1,6 @@ import { startsWith, toNumber } from '../../../utils/lang'; -// Creates an in memory ICustomStorageWrapper implementation with Jest mocks +// Creates an in memory IPluggableStorageWrapper implementation with Jest mocks export function wrapperMockFactory() { /** Holds items and list of items */ @@ -103,9 +103,9 @@ export function wrapperMockFactory() { return Promise.reject('key is not a set'); }), - // always connects and close + // always connects and disconnects connect: jest.fn(() => Promise.resolve()), - close: jest.fn(() => Promise.resolve()), + disconnect: jest.fn(() => Promise.resolve()), mockClear() { this._cache = {}; @@ -117,7 +117,7 @@ export function wrapperMockFactory() { this.decr.mockClear(); this.getMany.mockClear(); this.connect.mockClear(); - this.close.mockClear(); + this.disconnect.mockClear(); this.getAndSet.mockClear(); this.pushItems.mockClear(); this.popItems.mockClear(); diff --git a/src/storages/pluggable/__tests__/wrapperAdapter.spec.ts b/src/storages/pluggable/__tests__/wrapperAdapter.spec.ts index dd081b6f..90805b90 100644 --- a/src/storages/pluggable/__tests__/wrapperAdapter.spec.ts +++ b/src/storages/pluggable/__tests__/wrapperAdapter.spec.ts @@ -1,6 +1,6 @@ // @ts-nocheck import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; -import thenable from '../../../utils/promise/thenable'; +import { thenable } from '../../../utils/promise/thenable'; import { LOG_PREFIX } from '../constants'; /** Mocks */ @@ -28,7 +28,7 @@ export const wrapperWithIssues = { decr: 'invalid value', getMany: throwsException, connect: rejectedPromise, - close: invalidThenable, + disconnect: invalidThenable, getAndSet: 'invalid value', pushItems: rejectedPromise, popItems: invalidThenable, @@ -48,7 +48,7 @@ const VALID_METHOD_CALLS = { 'decr': ['some_key'], 'getMany': [['some_key_1', 'some_key_2']], 'connect': [], - 'close': [], + 'disconnect': [], 'getAndSet': ['some_key', 'some_value'], 'pushItems': ['some_key_list', ['item1', 'item2']], 'popItems': ['some_key_list'], diff --git a/src/storages/pluggable/constants.ts b/src/storages/pluggable/constants.ts index 1f845f45..f28d60b8 100644 --- a/src/storages/pluggable/constants.ts +++ b/src/storages/pluggable/constants.ts @@ -1 +1 @@ -export const LOG_PREFIX = 'storage:pluggable:'; +export const LOG_PREFIX = 'storage:pluggable: '; diff --git a/src/storages/pluggable/inMemoryWrapper.ts b/src/storages/pluggable/inMemoryWrapper.ts index efb2c6db..cb3a77e8 100644 --- a/src/storages/pluggable/inMemoryWrapper.ts +++ b/src/storages/pluggable/inMemoryWrapper.ts @@ -1,15 +1,18 @@ -import { ICustomStorageWrapper } from '../types'; +import { IPluggableStorageWrapper } from '../types'; import { startsWith, toNumber } from '../../utils/lang'; import { ISet, setToArray, _Set } from '../../utils/lang/sets'; /** - * Creates a ICustomStorageWrapper implementation that stores items in memory. + * Creates a IPluggableStorageWrapper implementation that stores items in memory. * The `_cache` property is the object were items are stored. * Intended for testing purposes. + * + * @param connDelay delay in millis for `connect` resolve. If not provided, `connect` resolves inmediatelly. */ -export function inMemoryWrapperFactory(): ICustomStorageWrapper & { _cache: Record> } { +export function inMemoryWrapperFactory(connDelay?: number): IPluggableStorageWrapper & { _cache: Record>, _setConnDelay(connDelay: number): void } { let _cache: Record> = {}; + let _connDelay = connDelay; return { /** Holds items (for key-value operations), list of items (for list operations) and set of items (for set operations) */ @@ -109,8 +112,19 @@ export function inMemoryWrapperFactory(): ICustomStorageWrapper & { _cache: Reco return Promise.reject('key is not a set'); }, - // always connects and close - connect() { return Promise.resolve(); }, - close() { return Promise.resolve(); }, + // always connects and disconnects + connect() { + if (typeof _connDelay === 'number') { + return new Promise(res => setTimeout(res, _connDelay)); + } else { + return Promise.resolve(); + } + }, + disconnect() { return Promise.resolve(); }, + + // for testing + _setConnDelay(connDelay: number) { + _connDelay = connDelay; + } }; } diff --git a/src/storages/pluggable/index.ts b/src/storages/pluggable/index.ts index 2afb1bf4..fef04aa6 100644 --- a/src/storages/pluggable/index.ts +++ b/src/storages/pluggable/index.ts @@ -1,6 +1,6 @@ -import { ICustomStorageWrapper, IStorageAsync, IStorageAsyncFactory, IStorageFactoryParams } from '../types'; +import { IPluggableStorageWrapper, IStorageAsync, IStorageAsyncFactory, IStorageFactoryParams } from '../types'; -import KeyBuilderSS from '../KeyBuilderSS'; +import { KeyBuilderSS } from '../KeyBuilderSS'; import { SplitsCachePluggable } from './SplitsCachePluggable'; import { SegmentsCachePluggable } from './SegmentsCachePluggable'; import { ImpressionsCachePluggable } from './ImpressionsCachePluggable'; @@ -8,14 +8,17 @@ import { EventsCachePluggable } from './EventsCachePluggable'; import { wrapperAdapter, METHODS_TO_PROMISE_WRAP } from './wrapperAdapter'; import { isObject } from '../../utils/lang'; import { validatePrefix } from '../KeyBuilder'; -import { STORAGE_CUSTOM } from '../../utils/constants'; +import { CONSUMER_PARTIAL_MODE, STORAGE_PLUGGABLE } from '../../utils/constants'; +import { ImpressionsCacheInMemory } from '../inMemory/ImpressionsCacheInMemory'; +import { EventsCacheInMemory } from '../inMemory/EventsCacheInMemory'; +import { ImpressionCountsCacheInMemory } from '../inMemory/ImpressionCountsCacheInMemory'; -const NO_VALID_WRAPPER = 'Expecting custom storage `wrapper` in options, but no valid wrapper instance was provided.'; +const NO_VALID_WRAPPER = 'Expecting pluggable storage `wrapper` in options, but no valid wrapper instance was provided.'; const NO_VALID_WRAPPER_INTERFACE = 'The provided wrapper instance doesn’t follow the expected interface. Check our docs.'; export interface PluggableStorageOptions { prefix?: string - wrapper: ICustomStorageWrapper + wrapper: IPluggableStorageWrapper } /** @@ -32,6 +35,25 @@ function validatePluggableStorageOptions(options: any) { if (missingMethods.length) throw new Error(`${NO_VALID_WRAPPER_INTERFACE} The following methods are missing or invalid: ${missingMethods}`); } +// subscription to wrapper connect event in order to emit SDK_READY event +function wrapperConnect(wrapper: IPluggableStorageWrapper, onReadyCb: (error?: any) => void) { + wrapper.connect().then(() => { + onReadyCb(); + }).catch((e) => { + onReadyCb(e || new Error('Error connecting wrapper')); + }); +} + +// Async return type in `client.track` method on consumer partial mode +// No need to promisify impressions cache +function promisifyEventsTrack(events: any) { + const origTrack = events.track; + events.track = function () { + return Promise.resolve(origTrack.apply(this, arguments)); + }; + return events; +} + /** * Pluggable storage factory for consumer server-side & client-side SplitFactory. */ @@ -41,31 +63,39 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn const prefix = validatePrefix(options.prefix); - function PluggableStorageFactory({ log, metadata, onReadyCb }: IStorageFactoryParams): IStorageAsync { + function PluggableStorageFactory({ log, metadata, onReadyCb, mode, eventsQueueSize, optimize }: IStorageFactoryParams): IStorageAsync { const keys = new KeyBuilderSS(prefix, metadata); const wrapper = wrapperAdapter(log, options.wrapper); + const isPartialConsumer = mode === CONSUMER_PARTIAL_MODE; - // subscription to Wrapper connect event in order to emit SDK_READY event - wrapper.connect().then(() => { - if (onReadyCb) onReadyCb(); - }).catch((e) => { - if (onReadyCb) onReadyCb(e || new Error('Error connecting wrapper')); - }); + // Connects to wrapper and emits SDK_READY event on main client + wrapperConnect(wrapper, onReadyCb); return { splits: new SplitsCachePluggable(log, keys, wrapper), segments: new SegmentsCachePluggable(log, keys, wrapper), - impressions: new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata), - events: new EventsCachePluggable(log, keys.buildEventsKey(), wrapper, metadata), + impressions: isPartialConsumer ? new ImpressionsCacheInMemory() : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata), + impressionCounts: optimize ? new ImpressionCountsCacheInMemory() : undefined, + events: isPartialConsumer ? promisifyEventsTrack(new EventsCacheInMemory(eventsQueueSize)) : new EventsCachePluggable(log, keys.buildEventsKey(), wrapper, metadata), // @TODO add telemetry cache when required - // Disconnect the underlying storage, to release its resources (such as open files, database connections, etc). + // Disconnect the underlying storage destroy() { - return wrapper.close(); + return wrapper.disconnect(); + }, + + // emits SDK_READY event on shared clients and returns a reference to the storage + shared(_, onReadyCb) { + wrapperConnect(wrapper, onReadyCb); + return { + ...this, + // no-op destroy, to disconnect the wrapper only when the main client is destroyed + destroy() { } + }; } }; } - PluggableStorageFactory.type = STORAGE_CUSTOM; + PluggableStorageFactory.type = STORAGE_PLUGGABLE; return PluggableStorageFactory; } diff --git a/src/storages/pluggable/wrapperAdapter.ts b/src/storages/pluggable/wrapperAdapter.ts index 0b93f494..c47a5d8b 100644 --- a/src/storages/pluggable/wrapperAdapter.ts +++ b/src/storages/pluggable/wrapperAdapter.ts @@ -1,5 +1,5 @@ import { ILogger } from '../../logger/types'; -import { ICustomStorageWrapper } from '../types'; +import { IPluggableStorageWrapper } from '../types'; import { LOG_PREFIX } from './constants'; export const METHODS_TO_PROMISE_WRAP: string[] = [ @@ -19,18 +19,18 @@ export const METHODS_TO_PROMISE_WRAP: string[] = [ 'removeItems', 'getItems', 'connect', - 'close' + 'disconnect' ]; /** - * Adapter of the Custom Storage Wrapper. + * Adapter of the Pluggable Storage Wrapper. * Used to handle exceptions as rejected promises, in order to simplify the error handling on storages. * * @param log logger instance - * @param wrapper custom storage wrapper to adapt + * @param wrapper storage wrapper to adapt * @returns an adapted version of the given storage wrapper */ -export function wrapperAdapter(log: ILogger, wrapper: ICustomStorageWrapper): ICustomStorageWrapper { +export function wrapperAdapter(log: ILogger, wrapper: IPluggableStorageWrapper): IPluggableStorageWrapper { const wrapperAdapter: Record = {}; diff --git a/src/storages/types.ts b/src/storages/types.ts index 288e1fea..0e3a6001 100644 --- a/src/storages/types.ts +++ b/src/storages/types.ts @@ -1,12 +1,12 @@ import { MaybeThenable, IMetadata, ISplitFiltersValidation } from '../dtos/types'; import { ILogger } from '../logger/types'; import { StoredEventWithMetadata, StoredImpressionWithMetadata } from '../sync/submitters/types'; -import { SplitIO, ImpressionDTO } from '../types'; +import { SplitIO, ImpressionDTO, SDKMode } from '../types'; /** - * Interface of a custom wrapper storage. + * Interface of a pluggable storage wrapper. */ -export interface ICustomStorageWrapper { +export interface IPluggableStorageWrapper { /** Key-Value operations */ @@ -28,7 +28,7 @@ export interface ICustomStorageWrapper { * @returns {Promise} A promise that resolves if the operation success, whether the key was added or updated. * The promise rejects if the operation fails. */ - set: (key: string, value: string) => Promise + set: (key: string, value: string) => Promise /** * Add or update an item with a specified `key` and `value`. * @@ -47,7 +47,7 @@ export interface ICustomStorageWrapper { * @returns {Promise} A promise that resolves if the operation success, whether the key existed and was removed or it didn't exist. * The promise rejects if the operation fails, for example, if there is a connection error. */ - del: (key: string) => Promise + del: (key: string) => Promise /** * Returns all keys matching the given prefix. * @@ -70,7 +70,7 @@ export interface ICustomStorageWrapper { /** Integer operations */ /** - * Increments in 1 the given `key` value or set it in 1 if the value doesn't exist. + * Increments in 1 the given `key` value or set it to 1 if the value doesn't exist. * * @function incr * @param {string} key Key to increment @@ -79,7 +79,7 @@ export interface ICustomStorageWrapper { */ incr: (key: string) => Promise /** - * Decrements in 1 the given `key` value or set it in -1 if the value doesn't exist. + * Decrements in 1 the given `key` value or set it to -1 if the value doesn't exist. * * @function decr * @param {string} key Key to decrement @@ -140,20 +140,20 @@ export interface ICustomStorageWrapper { * @function addItems * @param {string} key Set key * @param {string} items Items to add - * @returns {Promise} A promise that resolves if the operation success. + * @returns {Promise} A promise that resolves if the operation success. * The promise rejects if the operation fails, for example, if there is a connection error or the key holds a value that is not a set. */ - addItems: (key: string, items: string[]) => Promise + addItems: (key: string, items: string[]) => Promise /** * Remove the specified `items` from the set stored at `key`. Those items that are not part of the set are ignored. * * @function removeItems * @param {string} key Set key * @param {string} items Items to remove - * @returns {Promise} A promise that resolves if the operation success. If key does not exist, the promise also resolves. + * @returns {Promise} A promise that resolves if the operation success. If key does not exist, the promise also resolves. * The promise rejects if the operation fails, for example, if there is a connection error or the key holds a value that is not a set. */ - removeItems: (key: string, items: string[]) => Promise + removeItems: (key: string, items: string[]) => Promise /** * Returns all the items of the `key` set. * @@ -169,7 +169,7 @@ export interface ICustomStorageWrapper { /** * Connects to the underlying storage. * It is meant for storages that requires to be connected to some database or server. Otherwise it can just return a resolved promise. - * Note: will be called once on SplitFactory instantiation. + * Note: will be called once on SplitFactory instantiation and once per each shared client instantiation. * * @function connect * @returns {Promise} A promise that resolves when the wrapper successfully connect to the underlying storage. @@ -177,15 +177,15 @@ export interface ICustomStorageWrapper { */ connect: () => Promise /** - * Disconnects the underlying storage. + * Disconnects from the underlying storage. * It is meant for storages that requires to be closed, in order to release resources. Otherwise it can just return a resolved promise. - * Note: will be called once on SplitFactory client destroy. + * Note: will be called once on SplitFactory main client destroy. * - * @function close + * @function disconnect * @returns {Promise} A promise that resolves when the operation ends. * The promise never rejects. */ - close: () => Promise + disconnect: () => Promise } /** Splits cache */ @@ -271,7 +271,7 @@ export interface ISegmentsCacheSync extends ISegmentsCacheBase { export interface ISegmentsCacheAsync extends ISegmentsCacheBase { addToSegment(name: string, segmentKeys: string[]): Promise removeFromSegment(name: string, segmentKeys: string[]): Promise - isInSegment(name: string, key?: string): Promise + isInSegment(name: string, key: string): Promise registerSegments(names: string[]): Promise getRegisteredSegments(): Promise setChangeNumber(name: string, changeNumber: number): Promise @@ -395,7 +395,8 @@ export interface IStorageBase< events: TEventsCache, latencies?: TLatenciesCache, counts?: TCountsCache, - destroy(): void, + destroy(): void | Promise, + shared?: (matchingKey: string, onReadyCb: (error?: any) => void) => this } export type IStorageSync = IStorageBase< @@ -407,15 +408,11 @@ export type IStorageSync = IStorageBase< ICountsCacheSync > -export interface IStorageSyncCS extends IStorageSync { - shared(matchingKey: string): IStorageSync -} - export type IStorageAsync = IStorageBase< ISplitsCacheAsync, ISegmentsCacheAsync, - IImpressionsCacheAsync, - IEventsCacheAsync, + IImpressionsCacheAsync | IImpressionsCacheSync, + IEventsCacheAsync | IEventsCacheSync, ILatenciesCacheAsync, ICountsCacheAsync > @@ -433,14 +430,17 @@ export interface IStorageFactoryParams { matchingKey?: string, /* undefined on server-side SDKs */ splitFiltersValidation?: ISplitFiltersValidation, + // ATM, only used by PluggableStorage + mode?: SDKMode, + // This callback is invoked when the storage is ready to be used. Error-first callback style: if an error is passed, // it means that the storge fail to connect and shouldn't be used. // It is meant for emitting SDK_READY event in consumer mode, and for synchronizer to wait before using the storage. - onReadyCb?: (error?: any) => void, + onReadyCb: (error?: any) => void, metadata: IMetadata, } -export type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'CUSTOM'; +export type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'PLUGGABLE'; export type IStorageSyncFactory = { type: StorageType, diff --git a/src/sync/__tests__/syncTask.spec.ts b/src/sync/__tests__/syncTask.spec.ts index b947e0a3..d4a7502b 100644 --- a/src/sync/__tests__/syncTask.spec.ts +++ b/src/sync/__tests__/syncTask.spec.ts @@ -1,4 +1,4 @@ -import syncTaskFactory from '../syncTask'; +import { syncTaskFactory } from '../syncTask'; import { loggerMock } from '../../logger/__tests__/sdkLogger.mock'; const period = 30; diff --git a/src/sync/offline/splitsParser/parseCondition.ts b/src/sync/offline/splitsParser/parseCondition.ts index 671fbed1..a2223bec 100644 --- a/src/sync/offline/splitsParser/parseCondition.ts +++ b/src/sync/offline/splitsParser/parseCondition.ts @@ -7,7 +7,7 @@ export interface IMockSplitEntry { config?: string } -export default function parseCondition(data: IMockSplitEntry): ISplitCondition { +export function parseCondition(data: IMockSplitEntry): ISplitCondition { const treatment = data.treatment; if (data.keys) { diff --git a/src/sync/offline/splitsParser/splitsParserFromFile.ts b/src/sync/offline/splitsParser/splitsParserFromFile.ts index 95ea8d14..d65d6a55 100644 --- a/src/sync/offline/splitsParser/splitsParserFromFile.ts +++ b/src/sync/offline/splitsParser/splitsParserFromFile.ts @@ -5,7 +5,7 @@ import path from 'path'; // @ts-ignore import yaml from 'js-yaml'; import { isString, endsWith, find, forOwn, uniq, } from '../../../utils/lang'; -import parseCondition, { IMockSplitEntry } from './parseCondition'; +import { parseCondition, IMockSplitEntry } from './parseCondition'; import { ISplitPartial } from '../../../dtos/types'; import { ISettings, SplitIO } from '../../../types'; import { ILogger } from '../../../logger/types'; diff --git a/src/sync/offline/splitsParser/splitsParserFromSettings.ts b/src/sync/offline/splitsParser/splitsParserFromSettings.ts index f9cf9175..8e892969 100644 --- a/src/sync/offline/splitsParser/splitsParserFromSettings.ts +++ b/src/sync/offline/splitsParser/splitsParserFromSettings.ts @@ -1,7 +1,7 @@ import { ISplitPartial } from '../../../dtos/types'; import { ISettings, SplitIO } from '../../../types'; import { isObject, forOwn } from '../../../utils/lang'; -import parseCondition from './parseCondition'; +import { parseCondition } from './parseCondition'; function hasTreatmentChanged(prev: string | SplitIO.TreatmentWithConfig, curr: string | SplitIO.TreatmentWithConfig) { if (typeof prev !== typeof curr) return true; diff --git a/src/sync/offline/syncManagerOffline.ts b/src/sync/offline/syncManagerOffline.ts index 58a75b6f..253493f2 100644 --- a/src/sync/offline/syncManagerOffline.ts +++ b/src/sync/offline/syncManagerOffline.ts @@ -1,6 +1,6 @@ import { ISyncManager, ISyncManagerCS, ISyncManagerFactoryParams } from '../types'; -import fromObjectSyncTaskFactory from './syncTasks/fromObjectSyncTask'; -import objectAssign from 'object-assign'; +import { fromObjectSyncTaskFactory } from './syncTasks/fromObjectSyncTask'; +import { objectAssign } from '../../utils/lang/objectAssign'; import { ISplitsParser } from './splitsParser/types'; import { IReadinessManager } from '../../readiness/types'; import { SDK_SEGMENTS_ARRIVED } from '../../readiness/constants'; diff --git a/src/sync/offline/syncTasks/fromObjectSyncTask.ts b/src/sync/offline/syncTasks/fromObjectSyncTask.ts index 2eccc27c..8cef1c7c 100644 --- a/src/sync/offline/syncTasks/fromObjectSyncTask.ts +++ b/src/sync/offline/syncTasks/fromObjectSyncTask.ts @@ -3,7 +3,7 @@ import { IReadinessManager } from '../../../readiness/types'; import { ISplitsCacheSync } from '../../../storages/types'; import { ISplitsParser } from '../splitsParser/types'; import { ISplitPartial } from '../../../dtos/types'; -import syncTaskFactory from '../../syncTask'; +import { syncTaskFactory } from '../../syncTask'; import { ISyncTask } from '../../types'; import { ISettings } from '../../../types'; import { CONTROL } from '../../../utils/constants'; @@ -79,7 +79,7 @@ export function fromObjectUpdaterFactory( /** * PollingManager in Offline mode */ -export default function fromObjectSyncTaskFactory( +export function fromObjectSyncTaskFactory( splitsParser: ISplitsParser, storage: { splits: ISplitsCacheSync }, readiness: IReadinessManager, diff --git a/src/sync/polling/fetchers/mySegmentsFetcher.ts b/src/sync/polling/fetchers/mySegmentsFetcher.ts index 298ef558..8ea7a5f4 100644 --- a/src/sync/polling/fetchers/mySegmentsFetcher.ts +++ b/src/sync/polling/fetchers/mySegmentsFetcher.ts @@ -6,7 +6,7 @@ import { IMySegmentsFetcher } from './types'; * Factory of MySegments fetcher. * MySegments fetcher is a wrapper around `mySegments` API service that parses the response and handle errors. */ -export default function mySegmentsFetcherFactory(fetchMySegments: IFetchMySegments, userMatchingKey: string): IMySegmentsFetcher { +export function mySegmentsFetcherFactory(fetchMySegments: IFetchMySegments, userMatchingKey: string): IMySegmentsFetcher { return function mySegmentsFetcher( noCache?: boolean, diff --git a/src/sync/polling/fetchers/segmentChangesFetcher.ts b/src/sync/polling/fetchers/segmentChangesFetcher.ts index 0bf14400..e28e0aa8 100644 --- a/src/sync/polling/fetchers/segmentChangesFetcher.ts +++ b/src/sync/polling/fetchers/segmentChangesFetcher.ts @@ -21,7 +21,7 @@ function greedyFetch(fetchSegmentChanges: IFetchSegmentChanges, since: number, s * Factory of SegmentChanges fetcher. * SegmentChanges fetcher is a wrapper around `segmentChanges` API service that parses the response and handle errors and retries. */ -export default function segmentChangesFetcherFactory(fetchSegmentChanges: IFetchSegmentChanges): ISegmentChangesFetcher { +export function segmentChangesFetcherFactory(fetchSegmentChanges: IFetchSegmentChanges): ISegmentChangesFetcher { return function segmentChangesFetcher( since: number, diff --git a/src/sync/polling/fetchers/splitChangesFetcher.ts b/src/sync/polling/fetchers/splitChangesFetcher.ts index 47de07ea..42e45463 100644 --- a/src/sync/polling/fetchers/splitChangesFetcher.ts +++ b/src/sync/polling/fetchers/splitChangesFetcher.ts @@ -5,7 +5,7 @@ import { ISplitChangesFetcher } from './types'; * Factory of SplitChanges fetcher. * SplitChanges fetcher is a wrapper around `splitChanges` API service that parses the response and handle errors. */ -export default function splitChangesFetcherFactory(fetchSplitChanges: IFetchSplitChanges): ISplitChangesFetcher { +export function splitChangesFetcherFactory(fetchSplitChanges: IFetchSplitChanges): ISplitChangesFetcher { return function splitChangesFetcher( since: number, diff --git a/src/sync/polling/pollingManagerCS.ts b/src/sync/polling/pollingManagerCS.ts index 7fbbc747..138ed003 100644 --- a/src/sync/polling/pollingManagerCS.ts +++ b/src/sync/polling/pollingManagerCS.ts @@ -3,8 +3,8 @@ import { forOwn } from '../../utils/lang'; import { IReadinessManager } from '../../readiness/types'; import { ISplitApi } from '../../services/types'; import { IStorageSync } from '../../storages/types'; -import mySegmentsSyncTaskFactory from './syncTasks/mySegmentsSyncTask'; -import splitsSyncTaskFactory from './syncTasks/splitsSyncTask'; +import { mySegmentsSyncTaskFactory } from './syncTasks/mySegmentsSyncTask'; +import { splitsSyncTaskFactory } from './syncTasks/splitsSyncTask'; import { ISettings } from '../../types'; import { getMatching } from '../../utils/key'; import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../../readiness/constants'; @@ -14,7 +14,7 @@ import { POLLING_SMART_PAUSING, POLLING_START, POLLING_STOP } from '../../logger * Expose start / stop mechanism for polling data from services. * For client-side API with multiple clients. */ -export default function pollingManagerCSFactory( +export function pollingManagerCSFactory( splitApi: ISplitApi, storage: IStorageSync, readiness: IReadinessManager, diff --git a/src/sync/polling/pollingManagerSS.ts b/src/sync/polling/pollingManagerSS.ts index 773da265..c1e7edad 100644 --- a/src/sync/polling/pollingManagerSS.ts +++ b/src/sync/polling/pollingManagerSS.ts @@ -1,17 +1,17 @@ -import splitsSyncTaskFactory from './syncTasks/splitsSyncTask'; -import segmentsSyncTaskFactory from './syncTasks/segmentsSyncTask'; +import { splitsSyncTaskFactory } from './syncTasks/splitsSyncTask'; +import { segmentsSyncTaskFactory } from './syncTasks/segmentsSyncTask'; import { IStorageSync } from '../../storages/types'; import { IReadinessManager } from '../../readiness/types'; import { ISplitApi } from '../../services/types'; import { ISettings } from '../../types'; import { IPollingManager, ISegmentsSyncTask, ISplitsSyncTask } from './types'; -import thenable from '../../utils/promise/thenable'; +import { thenable } from '../../utils/promise/thenable'; import { POLLING_START, POLLING_STOP, LOG_PREFIX_SYNC_POLLING } from '../../logger/constants'; /** * Expose start / stop mechanism for pulling data from services. */ -export default function pollingManagerSSFactory( +export function pollingManagerSSFactory( splitApi: ISplitApi, storage: IStorageSync, readiness: IReadinessManager, diff --git a/src/sync/polling/syncTasks/mySegmentsSyncTask.ts b/src/sync/polling/syncTasks/mySegmentsSyncTask.ts index 6547c3c6..7b830d21 100644 --- a/src/sync/polling/syncTasks/mySegmentsSyncTask.ts +++ b/src/sync/polling/syncTasks/mySegmentsSyncTask.ts @@ -1,16 +1,16 @@ import { IStorageSync } from '../../../storages/types'; import { IReadinessManager } from '../../../readiness/types'; -import syncTaskFactory from '../../syncTask'; +import { syncTaskFactory } from '../../syncTask'; import { ISegmentsSyncTask } from '../types'; import { IFetchMySegments } from '../../../services/types'; -import mySegmentsFetcherFactory from '../fetchers/mySegmentsFetcher'; +import { mySegmentsFetcherFactory } from '../fetchers/mySegmentsFetcher'; import { ISettings } from '../../../types'; import { mySegmentsUpdaterFactory } from '../updaters/mySegmentsUpdater'; /** * Creates a sync task that periodically executes a `mySegmentsUpdater` task */ -export default function mySegmentsSyncTaskFactory( +export function mySegmentsSyncTaskFactory( fetchMySegments: IFetchMySegments, storage: IStorageSync, readiness: IReadinessManager, diff --git a/src/sync/polling/syncTasks/segmentsSyncTask.ts b/src/sync/polling/syncTasks/segmentsSyncTask.ts index 69cda5a3..f5a93711 100644 --- a/src/sync/polling/syncTasks/segmentsSyncTask.ts +++ b/src/sync/polling/syncTasks/segmentsSyncTask.ts @@ -1,8 +1,8 @@ import { IStorageSync } from '../../../storages/types'; import { IReadinessManager } from '../../../readiness/types'; -import syncTaskFactory from '../../syncTask'; +import { syncTaskFactory } from '../../syncTask'; import { ISegmentsSyncTask } from '../types'; -import segmentChangesFetcherFactory from '../fetchers/segmentChangesFetcher'; +import { segmentChangesFetcherFactory } from '../fetchers/segmentChangesFetcher'; import { IFetchSegmentChanges } from '../../../services/types'; import { ISettings } from '../../../types'; import { segmentChangesUpdaterFactory } from '../updaters/segmentChangesUpdater'; @@ -10,7 +10,7 @@ import { segmentChangesUpdaterFactory } from '../updaters/segmentChangesUpdater' /** * Creates a sync task that periodically executes a `segmentChangesUpdater` task */ -export default function segmentsSyncTaskFactory( +export function segmentsSyncTaskFactory( fetchSegmentChanges: IFetchSegmentChanges, storage: IStorageSync, readiness: IReadinessManager, diff --git a/src/sync/polling/syncTasks/splitsSyncTask.ts b/src/sync/polling/syncTasks/splitsSyncTask.ts index 04544c02..6807a0b4 100644 --- a/src/sync/polling/syncTasks/splitsSyncTask.ts +++ b/src/sync/polling/syncTasks/splitsSyncTask.ts @@ -1,8 +1,8 @@ import { IStorageSync } from '../../../storages/types'; import { IReadinessManager } from '../../../readiness/types'; -import syncTaskFactory from '../../syncTask'; +import { syncTaskFactory } from '../../syncTask'; import { ISplitsSyncTask } from '../types'; -import splitChangesFetcherFactory from '../fetchers/splitChangesFetcher'; +import { splitChangesFetcherFactory } from '../fetchers/splitChangesFetcher'; import { IFetchSplitChanges } from '../../../services/types'; import { ISettings } from '../../../types'; import { splitChangesUpdaterFactory } from '../updaters/splitChangesUpdater'; @@ -10,7 +10,7 @@ import { splitChangesUpdaterFactory } from '../updaters/splitChangesUpdater'; /** * Creates a sync task that periodically executes a `splitChangesUpdater` task */ -export default function splitsSyncTaskFactory( +export function splitsSyncTaskFactory( fetchSplitChanges: IFetchSplitChanges, storage: IStorageSync, readiness: IReadinessManager, diff --git a/src/sync/polling/updaters/__tests__/splitChangesUpdater.spec.ts b/src/sync/polling/updaters/__tests__/splitChangesUpdater.spec.ts index cfe8dac6..60cc9b52 100644 --- a/src/sync/polling/updaters/__tests__/splitChangesUpdater.spec.ts +++ b/src/sync/polling/updaters/__tests__/splitChangesUpdater.spec.ts @@ -1,14 +1,14 @@ import { ISplit } from '../../../../dtos/types'; import { readinessManagerFactory } from '../../../../readiness/readinessManager'; import { splitApiFactory } from '../../../../services/splitApi'; -import SegmentsCacheInMemory from '../../../../storages/inMemory/SegmentsCacheInMemory'; -import SplitsCacheInMemory from '../../../../storages/inMemory/SplitsCacheInMemory'; -import splitChangesFetcherFactory from '../../fetchers/splitChangesFetcher'; +import { SegmentsCacheInMemory } from '../../../../storages/inMemory/SegmentsCacheInMemory'; +import { SplitsCacheInMemory } from '../../../../storages/inMemory/SplitsCacheInMemory'; +import { splitChangesFetcherFactory } from '../../fetchers/splitChangesFetcher'; import { splitChangesUpdaterFactory, parseSegments, computeSplitsMutation } from '../splitChangesUpdater'; import splitChangesMock1 from '../../../../__tests__/mocks/splitchanges.since.-1.json'; import fetchMock from '../../../../__tests__/testUtils/fetchMock'; import { settingsSplitApi } from '../../../../utils/settingsValidation/__tests__/settings.mocks'; -import EventEmitter from '../../../../utils/MinEvents'; +import { EventEmitter } from '../../../../utils/MinEvents'; import { loggerMock } from '../../../../logger/__tests__/sdkLogger.mock'; const activeSplitWithSegments = { diff --git a/src/sync/polling/updaters/mySegmentsUpdater.ts b/src/sync/polling/updaters/mySegmentsUpdater.ts index 4ceb4ab3..a72ee54b 100644 --- a/src/sync/polling/updaters/mySegmentsUpdater.ts +++ b/src/sync/polling/updaters/mySegmentsUpdater.ts @@ -1,7 +1,7 @@ import { IMySegmentsFetcher } from '../fetchers/types'; import { ISegmentsCacheSync, ISplitsCacheSync } from '../../../storages/types'; import { ISegmentsEventEmitter } from '../../../readiness/types'; -import timeout from '../../../utils/promise/timeout'; +import { timeout } from '../../../utils/promise/timeout'; import { SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants'; import { ILogger } from '../../../logger/types'; import { SYNC_MYSEGMENTS_FETCH_RETRY } from '../../../logger/constants'; @@ -38,7 +38,7 @@ export function mySegmentsUpdaterFactory( // mySegmentsPromise = tracker.start(tracker.TaskNames.MY_SEGMENTS_FETCH, startingUp ? metricCollectors : false, mySegmentsPromise); } - // @TODO if allowing custom storages, handle async execution + // @TODO if allowing pluggable storages, handle async execution function updateSegments(segmentsData: SegmentsData) { let shouldNotifyUpdate; diff --git a/src/sync/polling/updaters/segmentChangesUpdater.ts b/src/sync/polling/updaters/segmentChangesUpdater.ts index eefedf88..90a1bc06 100644 --- a/src/sync/polling/updaters/segmentChangesUpdater.ts +++ b/src/sync/polling/updaters/segmentChangesUpdater.ts @@ -6,7 +6,7 @@ import { findIndex } from '../../../utils/lang'; import { SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants'; import { ILogger } from '../../../logger/types'; import { LOG_PREFIX_INSTANTIATION, LOG_PREFIX_SYNC_SEGMENTS } from '../../../logger/constants'; -import thenable from '../../../utils/promise/thenable'; +import { thenable } from '../../../utils/promise/thenable'; type ISegmentChangesUpdater = (segmentNames?: string[], noCache?: boolean, fetchOnlyNew?: boolean) => Promise diff --git a/src/sync/polling/updaters/splitChangesUpdater.ts b/src/sync/polling/updaters/splitChangesUpdater.ts index ce490771..822dbf13 100644 --- a/src/sync/polling/updaters/splitChangesUpdater.ts +++ b/src/sync/polling/updaters/splitChangesUpdater.ts @@ -3,7 +3,7 @@ import { ISegmentsCacheBase, ISplitsCacheBase } from '../../../storages/types'; import { ISplitChangesFetcher } from '../fetchers/types'; import { ISplit, ISplitChangesResponse } from '../../../dtos/types'; import { ISplitsEventEmitter } from '../../../readiness/types'; -import timeout from '../../../utils/promise/timeout'; +import { timeout } from '../../../utils/promise/timeout'; import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/constants'; import { ILogger } from '../../../logger/types'; import { SYNC_SPLITS_FETCH, SYNC_SPLITS_NEW, SYNC_SPLITS_REMOVED, SYNC_SPLITS_SEGMENTS, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants'; @@ -168,9 +168,12 @@ export function splitChangesUpdaterFactory( return false; }); - // After triggering the requests, if we have cached splits information let's notify that. + // After triggering the requests, if we have cached splits information let's notify that to emit SDK_READY_FROM_CACHE. + // Wrapping in a promise since checkCache can be async. if (splitsEventEmitter && startingUp) { - Promise.resolve(splits.checkCache()).then(cacheReady => { if (cacheReady) splitsEventEmitter.emit(SDK_SPLITS_CACHE_LOADED); }); + Promise.resolve(splits.checkCache()).then(isCacheReady => { + if (isCacheReady) splitsEventEmitter.emit(SDK_SPLITS_CACHE_LOADED); + }); } return fetcherPromise; } diff --git a/src/sync/streaming/AuthClient/__tests__/index.spec.ts b/src/sync/streaming/AuthClient/__tests__/index.spec.ts index afaf5f73..70604400 100644 --- a/src/sync/streaming/AuthClient/__tests__/index.spec.ts +++ b/src/sync/streaming/AuthClient/__tests__/index.spec.ts @@ -3,7 +3,7 @@ import { splitApiFactory } from '../../../../services/splitApi'; import { authDataResponseSample, authDataSample, jwtSampleInvalid, jwtSampleNoChannels, jwtSampleNoIat, userKeySample, userKeyBase64HashSample } from '../../__tests__/dataMocks'; import fetchMock from '../../../../__tests__/testUtils/fetchMock'; import { settingsSplitApi } from '../../../../utils/settingsValidation/__tests__/settings.mocks'; -import EventEmitter from '../../../../utils/MinEvents'; +import { EventEmitter } from '../../../../utils/MinEvents'; // module to test import { authenticateFactory, hashUserKey } from '../index'; diff --git a/src/sync/streaming/AuthClient/index.ts b/src/sync/streaming/AuthClient/index.ts index 8f19c34e..96ae9ca8 100644 --- a/src/sync/streaming/AuthClient/index.ts +++ b/src/sync/streaming/AuthClient/index.ts @@ -1,6 +1,6 @@ import { IFetchAuth } from '../../../services/types'; import { IAuthenticate, IAuthToken } from './types'; -import objectAssign from 'object-assign'; +import { objectAssign } from '../../../utils/lang/objectAssign'; import { encodeToBase64 } from '../../../utils/base64'; import { decodeJWTtoken } from '../../../utils/jwt'; import { hash } from '../../../utils/murmur3/murmur3'; diff --git a/src/sync/streaming/SSEClient/__tests__/index.spec.ts b/src/sync/streaming/SSEClient/__tests__/index.spec.ts index 6155224b..9564f1b5 100644 --- a/src/sync/streaming/SSEClient/__tests__/index.spec.ts +++ b/src/sync/streaming/SSEClient/__tests__/index.spec.ts @@ -4,7 +4,7 @@ import { authDataSample, channelsQueryParamSample } from '../../__tests__/dataMo import { fullSettings as settings } from '../../../../utils/settingsValidation/__tests__/settings.mocks'; import { url } from '../../../../utils/settingsValidation/url'; -import SSClient from '../index'; +import { SSEClient } from '../index'; const EXPECTED_URL = url(settings, '/sse') + '?channels=' + channelsQueryParamSample + @@ -17,12 +17,12 @@ const EXPECTED_HEADERS = { }; test('SSClient / instance creation throws error if EventSource is not provided', () => { - expect(() => { new SSClient(settings); }).toThrow(Error); - expect(() => { new SSClient(settings, false, () => undefined); }).toThrow(Error); + expect(() => { new SSEClient(settings); }).toThrow(Error); + expect(() => { new SSEClient(settings, false, () => undefined); }).toThrow(Error); }); test('SSClient / instance creation success if EventSource is provided', () => { - const instance = new SSClient(settings, false, () => EventSourceMock); + const instance = new SSEClient(settings, false, () => EventSourceMock); expect(instance.eventSource).toBe(EventSourceMock); }); @@ -35,7 +35,7 @@ test('SSClient / setEventHandler, open and close methods', () => { }; // instance SSEClient - const instance = new SSClient(settings, false, () => EventSourceMock); + const instance = new SSEClient(settings, false, () => EventSourceMock); instance.setEventHandler(handler); // open connection @@ -81,7 +81,7 @@ test('SSClient / setEventHandler, open and close methods', () => { test('SSClient / open method: URL with metadata query params', () => { - const instance = new SSClient(settings, false, () => EventSourceMock); + const instance = new SSEClient(settings, false, () => EventSourceMock); instance.open(authDataSample); const EXPECTED_BROWSER_URL = EXPECTED_URL + `&SplitSDKVersion=${settings.version}&SplitSDKClientKey=${EXPECTED_HEADERS.SplitSDKClientKey}`; @@ -99,7 +99,7 @@ test('SSClient / open method: URL and metadata headers with IP and Hostname', () hostname: 'some hostname' } }; - const instance = new SSClient(settingsWithRuntime, true, () => EventSourceMock); + const instance = new SSEClient(settingsWithRuntime, true, () => EventSourceMock); instance.open(authDataSample); expect(instance.connection.url).toBe(EXPECTED_URL); // URL is properly set for streaming connection @@ -114,7 +114,7 @@ test('SSClient / open method: URL and metadata headers with IP and Hostname', () test('SSClient / open method: URL and metadata headers without IP and Hostname', () => { - const instance = new SSClient(settings, true, () => EventSourceMock); + const instance = new SSEClient(settings, true, () => EventSourceMock); instance.open(authDataSample); expect(instance.connection.url).toBe(EXPECTED_URL); // URL is properly set for streaming connection diff --git a/src/sync/streaming/SSEClient/index.ts b/src/sync/streaming/SSEClient/index.ts index 7e3cc4bb..378ac9c0 100644 --- a/src/sync/streaming/SSEClient/index.ts +++ b/src/sync/streaming/SSEClient/index.ts @@ -1,3 +1,4 @@ +import { IEventSourceConstructor } from '../../../services/types'; import { ISettings } from '../../../types'; import { IAuthTokenPushEnabled } from '../AuthClient/types'; import { ISSEClient, ISseEventHandler } from './types'; @@ -29,11 +30,11 @@ function buildSSEHeaders(settings: ISettings) { /** * Handles streaming connections with EventSource API */ -export default class SSEClient implements ISSEClient { +export class SSEClient implements ISSEClient { // Instance properties: - eventSource: typeof EventSource; + eventSource?: IEventSourceConstructor; streamingUrl: string; - connection?: InstanceType; + connection?: InstanceType; handler?: ISseEventHandler; useHeaders?: boolean; headers: Record; @@ -46,8 +47,7 @@ export default class SSEClient implements ISSEClient { * @param getEventSource Function to get the EventSource constructor. * @throws 'EventSource API is not available. ' if EventSource is not available. */ - constructor(settings: ISettings, useHeaders?: boolean, getEventSource?: () => (typeof EventSource | undefined)) { - // @ts-expect-error + constructor(settings: ISettings, useHeaders?: boolean, getEventSource?: () => (IEventSourceConstructor | undefined)) { this.eventSource = getEventSource && getEventSource(); // if eventSource is not available, throw an exception if (!this.eventSource) throw new Error('EventSource API is not available. '); @@ -79,7 +79,7 @@ export default class SSEClient implements ISSEClient { ).join(','); const url = `${this.streamingUrl}?channels=${channelsQueryParam}&accessToken=${authToken.token}&v=${VERSION}&heartbeats=true`; // same results using `&heartbeats=false` - this.connection = new this.eventSource( + this.connection = new this.eventSource!( // For client-side SDKs, SplitSDKClientKey and SplitSDKClientKey metadata is passed as query params, // because native EventSource implementations for browser doesn't support headers. this.useHeaders ? url : url + `&SplitSDKVersion=${this.headers.SplitSDKVersion}&SplitSDKClientKey=${this.headers.SplitSDKClientKey}`, diff --git a/src/sync/streaming/SSEHandler/NotificationKeeper.ts b/src/sync/streaming/SSEHandler/NotificationKeeper.ts index 1df88250..03dae152 100644 --- a/src/sync/streaming/SSEHandler/NotificationKeeper.ts +++ b/src/sync/streaming/SSEHandler/NotificationKeeper.ts @@ -9,7 +9,7 @@ const CONTROL_CHANNEL_REGEXS = [/control_pri$/, /control_sec$/]; * @param pushEmitter emitter for events related to streaming support */ // @TODO update logic to handle OCCUPANCY for any region and rename according to new spec (e.g.: PUSH_SUBSYSTEM_UP --> PUSH_SUBSYSTEM_UP) -export default function notificationKeeperFactory(pushEmitter: IPushEventEmitter) { +export function notificationKeeperFactory(pushEmitter: IPushEventEmitter) { let channels = CONTROL_CHANNEL_REGEXS.map(regex => ({ regex, diff --git a/src/sync/streaming/SSEHandler/__tests__/index.spec.ts b/src/sync/streaming/SSEHandler/__tests__/index.spec.ts index 79f8c573..fea55027 100644 --- a/src/sync/streaming/SSEHandler/__tests__/index.spec.ts +++ b/src/sync/streaming/SSEHandler/__tests__/index.spec.ts @@ -1,5 +1,5 @@ // @ts-nocheck -import SSEHandlerFactory from '..'; +import { SSEHandlerFactory } from '..'; import { PUSH_SUBSYSTEM_UP, PUSH_NONRETRYABLE_ERROR, PUSH_SUBSYSTEM_DOWN, PUSH_RETRYABLE_ERROR, MY_SEGMENTS_UPDATE, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, MY_SEGMENTS_UPDATE_V2, ControlType } from '../../constants'; import { loggerMock } from '../../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/sync/streaming/SSEHandler/index.ts b/src/sync/streaming/SSEHandler/index.ts index b0cd7069..b0b46767 100644 --- a/src/sync/streaming/SSEHandler/index.ts +++ b/src/sync/streaming/SSEHandler/index.ts @@ -1,5 +1,5 @@ import { errorParser, messageParser } from './NotificationParser'; -import notificationKeeperFactory from './NotificationKeeper'; +import { notificationKeeperFactory } from './NotificationKeeper'; import { PUSH_RETRYABLE_ERROR, PUSH_NONRETRYABLE_ERROR, OCCUPANCY, CONTROL, MY_SEGMENTS_UPDATE, MY_SEGMENTS_UPDATE_V2, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE } from '../constants'; import { IPushEventEmitter } from '../types'; import { ISseEventHandler } from '../SSEClient/types'; @@ -25,7 +25,7 @@ function isRetryableError(error: INotificationError) { * @param log factory logger * @param pushEmitter emitter for events related to streaming support */ -export default function SSEHandlerFactory(log: ILogger, pushEmitter: IPushEventEmitter): ISseEventHandler { +export function SSEHandlerFactory(log: ILogger, pushEmitter: IPushEventEmitter): ISseEventHandler { const notificationKeeper = notificationKeeperFactory(pushEmitter); diff --git a/src/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.ts b/src/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.ts index 192e81fa..26bc5514 100644 --- a/src/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.ts +++ b/src/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.ts @@ -1,12 +1,12 @@ import { ISegmentsSyncTask } from '../../polling/types'; -import Backoff from '../../../utils/Backoff'; +import { Backoff } from '../../../utils/Backoff'; import { IUpdateWorker } from './types'; import { SegmentsData } from '../SSEHandler/types'; /** * MySegmentsUpdateWorker class */ -export default class MySegmentsUpdateWorker implements IUpdateWorker { +export class MySegmentsUpdateWorker implements IUpdateWorker { private readonly mySegmentsSyncTask: ISegmentsSyncTask; private maxChangeNumber: number; diff --git a/src/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.ts b/src/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.ts index 3a299024..5c52872d 100644 --- a/src/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.ts +++ b/src/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.ts @@ -1,5 +1,5 @@ import { ISegmentsCacheSync } from '../../../storages/types'; -import Backoff from '../../../utils/Backoff'; +import { Backoff } from '../../../utils/Backoff'; import { ISegmentsSyncTask } from '../../polling/types'; import { ISegmentUpdateData } from '../SSEHandler/types'; import { IUpdateWorker } from './types'; @@ -7,7 +7,7 @@ import { IUpdateWorker } from './types'; /** * SegmentUpdateWorker class */ -export default class SegmentsUpdateWorker implements IUpdateWorker { +export class SegmentsUpdateWorker implements IUpdateWorker { private readonly segmentsCache: ISegmentsCacheSync; private readonly segmentsSyncTask: ISegmentsSyncTask; diff --git a/src/sync/streaming/UpdateWorkers/SplitsUpdateWorker.ts b/src/sync/streaming/UpdateWorkers/SplitsUpdateWorker.ts index 944d5776..c9d12f65 100644 --- a/src/sync/streaming/UpdateWorkers/SplitsUpdateWorker.ts +++ b/src/sync/streaming/UpdateWorkers/SplitsUpdateWorker.ts @@ -1,7 +1,7 @@ import { SDK_SPLITS_ARRIVED } from '../../../readiness/constants'; import { ISplitsEventEmitter } from '../../../readiness/types'; import { ISplitsCacheSync } from '../../../storages/types'; -import Backoff from '../../../utils/Backoff'; +import { Backoff } from '../../../utils/Backoff'; import { ISegmentsSyncTask, ISplitsSyncTask } from '../../polling/types'; import { ISplitKillData, ISplitUpdateData } from '../SSEHandler/types'; import { IUpdateWorker } from './types'; @@ -9,7 +9,7 @@ import { IUpdateWorker } from './types'; /** * SplitsUpdateWorker class */ -export default class SplitsUpdateWorker implements IUpdateWorker { +export class SplitsUpdateWorker implements IUpdateWorker { private readonly splitsCache: ISplitsCacheSync; private readonly splitsSyncTask: ISplitsSyncTask; @@ -83,7 +83,6 @@ export default class SplitsUpdateWorker implements IUpdateWorker { * @param {string} defaultTreatment default treatment value */ killSplit({ changeNumber, splitName, defaultTreatment }: ISplitKillData) { - // @TODO handle retry due to errors in storage, once we allow the definition of custom async storages if (this.splitsCache.killLocally(splitName, defaultTreatment, changeNumber)) { // trigger an SDK_UPDATE if Split was killed locally this.splitsEventEmitter.emit(SDK_SPLITS_ARRIVED, true); diff --git a/src/sync/streaming/UpdateWorkers/__tests__/MySegmentsUpdateWorker.spec.ts b/src/sync/streaming/UpdateWorkers/__tests__/MySegmentsUpdateWorker.spec.ts index 182bc3ad..1e4129e3 100644 --- a/src/sync/streaming/UpdateWorkers/__tests__/MySegmentsUpdateWorker.spec.ts +++ b/src/sync/streaming/UpdateWorkers/__tests__/MySegmentsUpdateWorker.spec.ts @@ -1,5 +1,5 @@ // @ts-nocheck -import MySegmentsUpdateWorker from '../MySegmentsUpdateWorker'; +import { MySegmentsUpdateWorker } from '../MySegmentsUpdateWorker'; function mySegmentsSyncTaskMock() { diff --git a/src/sync/streaming/UpdateWorkers/__tests__/SegmentsUpdateWorker.spec.ts b/src/sync/streaming/UpdateWorkers/__tests__/SegmentsUpdateWorker.spec.ts index 5e6acb6a..4a494657 100644 --- a/src/sync/streaming/UpdateWorkers/__tests__/SegmentsUpdateWorker.spec.ts +++ b/src/sync/streaming/UpdateWorkers/__tests__/SegmentsUpdateWorker.spec.ts @@ -1,6 +1,6 @@ // @ts-nocheck -import SegmentsCacheInMemory from '../../../../storages/inMemory/SegmentsCacheInMemory'; -import SegmentsUpdateWorker from '../SegmentsUpdateWorker'; +import { SegmentsCacheInMemory } from '../../../../storages/inMemory/SegmentsCacheInMemory'; +import { SegmentsUpdateWorker } from '../SegmentsUpdateWorker'; function segmentsSyncTaskMock(segmentsStorage) { diff --git a/src/sync/streaming/UpdateWorkers/__tests__/SplitsUpdateWorker.spec.ts b/src/sync/streaming/UpdateWorkers/__tests__/SplitsUpdateWorker.spec.ts index bf28fa2e..74fd9c1f 100644 --- a/src/sync/streaming/UpdateWorkers/__tests__/SplitsUpdateWorker.spec.ts +++ b/src/sync/streaming/UpdateWorkers/__tests__/SplitsUpdateWorker.spec.ts @@ -1,7 +1,7 @@ // @ts-nocheck import { SDK_SPLITS_ARRIVED } from '../../../../readiness/constants'; -import SplitsCacheInMemory from '../../../../storages/inMemory/SplitsCacheInMemory'; -import SplitsUpdateWorker from '../SplitsUpdateWorker'; +import { SplitsCacheInMemory } from '../../../../storages/inMemory/SplitsCacheInMemory'; +import { SplitsUpdateWorker } from '../SplitsUpdateWorker'; function splitsSyncTaskMock(splitStorage) { diff --git a/src/sync/streaming/UpdateWorkers/types.ts b/src/sync/streaming/UpdateWorkers/types.ts index ea91a154..de70407e 100644 --- a/src/sync/streaming/UpdateWorkers/types.ts +++ b/src/sync/streaming/UpdateWorkers/types.ts @@ -1,4 +1,4 @@ -import Backoff from '../../../utils/Backoff'; +import { Backoff } from '../../../utils/Backoff'; export interface IUpdateWorker { readonly backoff: Backoff, diff --git a/src/sync/streaming/__tests__/pushManager.spec.ts b/src/sync/streaming/__tests__/pushManager.spec.ts index 9b6fb784..26b63f7d 100644 --- a/src/sync/streaming/__tests__/pushManager.spec.ts +++ b/src/sync/streaming/__tests__/pushManager.spec.ts @@ -1,5 +1,5 @@ -import pushManagerFactory from '../pushManager'; -import EventEmitter from '../../../utils/MinEvents'; +import { pushManagerFactory } from '../pushManager'; +import { EventEmitter } from '../../../utils/MinEvents'; import { fullSettings } from '../../../utils/settingsValidation/__tests__/settings.mocks'; import { IPushManagerCS } from '../types'; diff --git a/src/sync/streaming/pushManager.ts b/src/sync/streaming/pushManager.ts index e7b7caac..f5130447 100644 --- a/src/sync/streaming/pushManager.ts +++ b/src/sync/streaming/pushManager.ts @@ -3,15 +3,15 @@ import { ISSEClient } from './SSEClient/types'; import { IStorageSync } from '../../storages/types'; import { IReadinessManager } from '../../readiness/types'; import { ISegmentsSyncTask, IPollingManager } from '../polling/types'; -import objectAssign from 'object-assign'; -import Backoff from '../../utils/Backoff'; -import SSEHandlerFactory from './SSEHandler'; -import MySegmentsUpdateWorker from './UpdateWorkers/MySegmentsUpdateWorker'; -import SegmentsUpdateWorker from './UpdateWorkers/SegmentsUpdateWorker'; -import SplitsUpdateWorker from './UpdateWorkers/SplitsUpdateWorker'; +import { objectAssign } from '../../utils/lang/objectAssign'; +import { Backoff } from '../../utils/Backoff'; +import { SSEHandlerFactory } from './SSEHandler'; +import { MySegmentsUpdateWorker } from './UpdateWorkers/MySegmentsUpdateWorker'; +import { SegmentsUpdateWorker } from './UpdateWorkers/SegmentsUpdateWorker'; +import { SplitsUpdateWorker } from './UpdateWorkers/SplitsUpdateWorker'; import { authenticateFactory, hashUserKey } from './AuthClient'; import { forOwn } from '../../utils/lang'; -import SSEClient from './SSEClient'; +import { SSEClient } from './SSEClient'; import { IFetchAuth } from '../../services/types'; import { ISettings } from '../../types'; import { getMatching } from '../../utils/key'; @@ -29,7 +29,7 @@ import { IAuthTokenPushEnabled } from './AuthClient/types'; * - for server-side if key is not provided in settings. * - for client-side, with support for multiple clients, if key is provided in settings */ -export default function pushManagerFactory( +export function pushManagerFactory( pollingManager: IPollingManager, storage: IStorageSync, readiness: IReadinessManager, @@ -115,7 +115,8 @@ export default function pushManagerFactory( function connectPush() { // Guard condition in case `stop/disconnectPush` has been called (e.g., calling SDK destroy, or app signal close/background) if (disconnected) return; - log.info(STREAMING_CONNECTING, [disconnected === undefined ? '' : 'Re-']); + // @TODO distinguish log for 'Connecting' (1st time) and 'Re-connecting' + log.info(STREAMING_CONNECTING); disconnected = false; const userKeys = userKey ? Object.keys(clients) : undefined; diff --git a/src/sync/submitters/submitterManager.ts b/src/sync/submitters/submitterManager.ts new file mode 100644 index 00000000..7828d947 --- /dev/null +++ b/src/sync/submitters/submitterManager.ts @@ -0,0 +1,22 @@ +import { syncTaskComposite } from '../syncTaskComposite'; +import { eventsSyncTaskFactory } from './eventsSyncTask'; +import { impressionsSyncTaskFactory } from './impressionsSyncTask'; +import { impressionCountsSyncTaskFactory } from './impressionCountsSyncTask'; +import { ISplitApi } from '../../services/types'; +import { IStorageSync } from '../../storages/types'; +import { ISettings } from '../../types'; + +export function submitterManagerFactory( + settings: ISettings, + storage: IStorageSync, + splitApi: ISplitApi, +) { + const log = settings.log; + const submitters = [ + impressionsSyncTaskFactory(log, splitApi.postTestImpressionsBulk, storage.impressions, settings.scheduler.impressionsRefreshRate, settings.core.labelsEnabled), + eventsSyncTaskFactory(log, splitApi.postEventsBulk, storage.events, settings.scheduler.eventsPushRate, settings.startup.eventsFirstPushWindow) + // @TODO add telemetry submitter + ]; + if (storage.impressionCounts) submitters.push(impressionCountsSyncTaskFactory(log, splitApi.postTestImpressionsCount, storage.impressionCounts)); + return syncTaskComposite(submitters); +} diff --git a/src/sync/submitters/submitterSyncTask.ts b/src/sync/submitters/submitterSyncTask.ts index bf92b2e9..a17df77d 100644 --- a/src/sync/submitters/submitterSyncTask.ts +++ b/src/sync/submitters/submitterSyncTask.ts @@ -1,4 +1,4 @@ -import syncTaskFactory from '../syncTask'; +import { syncTaskFactory } from '../syncTask'; import { ISyncTask, ITimeTracker } from '../types'; import { IRecorderCacheProducerSync } from '../../storages/types'; import { ILogger } from '../../logger/types'; diff --git a/src/sync/syncManagerOnline.ts b/src/sync/syncManagerOnline.ts index b0199a9c..c8264106 100644 --- a/src/sync/syncManagerOnline.ts +++ b/src/sync/syncManagerOnline.ts @@ -1,8 +1,5 @@ -import { ISyncManager, ISyncManagerCS, ISyncManagerFactoryParams } from './types'; -import { syncTaskComposite } from './syncTaskComposite'; -import { eventsSyncTaskFactory } from './submitters/eventsSyncTask'; -import { impressionsSyncTaskFactory } from './submitters/impressionsSyncTask'; -import { impressionCountsSyncTaskFactory } from './submitters/impressionCountsSyncTask'; +import { ISyncManagerCS, ISyncManagerFactoryParams } from './types'; +import { submitterManagerFactory } from './submitters/submitterManager'; import { IReadinessManager } from '../readiness/types'; import { IStorageSync } from '../storages/types'; import { IPushManagerFactoryParams, IPushManager, IPushManagerCS } from './streaming/types'; @@ -19,7 +16,7 @@ import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '.. * @param pushManagerFactory optional to build a SyncManager with or without streaming support */ export function syncManagerOnlineFactory( - pollingManagerFactory: (...args: IPollingManagerFactoryParams) => IPollingManager, + pollingManagerFactory?: (...args: IPollingManagerFactoryParams) => IPollingManager, pushManagerFactory?: (...args: IPushManagerFactoryParams) => IPushManager | undefined ): (params: ISyncManagerFactoryParams) => ISyncManagerCS { @@ -37,30 +34,24 @@ export function syncManagerOnlineFactory( const log = settings.log; /** Polling Manager */ - const pollingManager = pollingManagerFactory(splitApi, storage, readiness, settings); + const pollingManager = pollingManagerFactory && pollingManagerFactory(splitApi, storage, readiness, settings); /** Push Manager */ - const pushManager = settings.streamingEnabled && pushManagerFactory ? + const pushManager = settings.streamingEnabled && pollingManager && pushManagerFactory ? pushManagerFactory(pollingManager, storage, readiness, splitApi.fetchAuth, platform, settings) : undefined; /** Submitter Manager */ - // It is not inyected via a factory as push and polling managers, because at the moment it is mandatory and the same for server-side and client-side variants - const submitters = [ - impressionsSyncTaskFactory(log, splitApi.postTestImpressionsBulk, storage.impressions, settings.scheduler.impressionsRefreshRate, settings.core.labelsEnabled), - eventsSyncTaskFactory(log, splitApi.postEventsBulk, storage.events, settings.scheduler.eventsPushRate, settings.startup.eventsFirstPushWindow) - // @TODO add telemetry submitter - ]; - if (storage.impressionCounts) submitters.push(impressionCountsSyncTaskFactory(log, splitApi.postTestImpressionsCount, storage.impressionCounts)); - const submitter = syncTaskComposite(submitters); + // It is not inyected as push and polling managers, because at the moment it is required + const submitter = submitterManagerFactory(settings, storage, splitApi); /** Sync Manager logic */ function startPolling() { - if (!pollingManager.isRunning()) { + if (!pollingManager!.isRunning()) { log.info(SYNC_START_POLLING); - pollingManager.start(); + pollingManager!.start(); } else { log.info(SYNC_CONTINUE_POLLING); } @@ -69,10 +60,10 @@ export function syncManagerOnlineFactory( function stopPollingAndSyncAll() { log.info(SYNC_STOP_POLLING); // if polling, stop - if (pollingManager.isRunning()) pollingManager.stop(); + if (pollingManager!.isRunning()) pollingManager!.stop(); // fetch splits and segments. There is no need to catch this promise (it is always resolved) - pollingManager.syncAll(); + pollingManager!.syncAll(); } if (pushManager) { @@ -91,15 +82,17 @@ export function syncManagerOnlineFactory( */ start() { // start syncing splits and segments - if (pushManager) { - // Doesn't call `syncAll` when the syncManager is resuming - if (startFirstTime) { - pollingManager.syncAll(); - startFirstTime = false; + if (pollingManager) { + if (pushManager) { + // Doesn't call `syncAll` when the syncManager is resuming + if (startFirstTime) { + pollingManager.syncAll(); + startFirstTime = false; + } + pushManager.start(); + } else { + pollingManager.start(); } - pushManager.start(); - } else { - pollingManager.start(); } // start periodic data recording (events, impressions, telemetry). @@ -113,7 +106,7 @@ export function syncManagerOnlineFactory( stop() { // stop syncing if (pushManager) pushManager.stop(); - if (pollingManager.isRunning()) pollingManager.stop(); + if (pollingManager && pollingManager.isRunning()) pollingManager.stop(); // stop periodic data recording (events, impressions, telemetry). if (submitter) submitter.stop(); @@ -130,8 +123,9 @@ export function syncManagerOnlineFactory( }, // [Only used for client-side] - // It assumes that polling and push managers implement the interfaces for client-side - shared(matchingKey: string, readinessManager: IReadinessManager, storage: IStorageSync): ISyncManager { + // If polling and push managers are defined (standalone mode), they implement the interfaces for client-side + shared(matchingKey: string, readinessManager: IReadinessManager, storage: IStorageSync) { + if (!pollingManager) return; const mySegmentsSyncTask = (pollingManager as IPollingManagerCS).add(matchingKey, readinessManager, storage); @@ -139,7 +133,7 @@ export function syncManagerOnlineFactory( isRunning: mySegmentsSyncTask.isRunning, start() { if (pushManager) { - if (pollingManager.isRunning()) { + if (pollingManager!.isRunning()) { // if doing polling, we must start the periodic fetch of data if (storage.splits.usesSegments()) mySegmentsSyncTask.start(); } else { diff --git a/src/sync/syncTask.ts b/src/sync/syncTask.ts index d86cb9f4..eb3a2c30 100644 --- a/src/sync/syncTask.ts +++ b/src/sync/syncTask.ts @@ -13,7 +13,7 @@ import { ISyncTask } from './types'; * @param taskName Optional task name for logging. * @returns A sync task that wraps the given task. */ -export default function syncTaskFactory(log: ILogger, task: (...args: Input) => Promise, period: number, taskName = 'task'): ISyncTask { +export function syncTaskFactory(log: ILogger, task: (...args: Input) => Promise, period: number, taskName = 'task'): ISyncTask { // Flag that indicates if the task is being executed let executing = false; diff --git a/src/sync/types.ts b/src/sync/types.ts index 55566ef1..0f0588d9 100644 --- a/src/sync/types.ts +++ b/src/sync/types.ts @@ -47,7 +47,7 @@ export interface ISyncManager extends ITask { } export interface ISyncManagerCS extends ISyncManager { - shared(matchingKey: string, readinessManager: IReadinessManager, storage: IStorageSync): ISyncManager + shared(matchingKey: string, readinessManager: IReadinessManager, storage: IStorageSync): ISyncManager | undefined } export interface ISyncManagerFactoryParams { diff --git a/src/trackers/__tests__/eventTracker.spec.ts b/src/trackers/__tests__/eventTracker.spec.ts index 0edb462e..aa2cd234 100644 --- a/src/trackers/__tests__/eventTracker.spec.ts +++ b/src/trackers/__tests__/eventTracker.spec.ts @@ -1,6 +1,6 @@ import { loggerMock } from '../../logger/__tests__/sdkLogger.mock'; import { SplitIO } from '../../types'; -import eventTrackerFactory from '../eventTracker'; +import { eventTrackerFactory } from '../eventTracker'; /* Mocks */ diff --git a/src/trackers/__tests__/impressionsTracker.spec.ts b/src/trackers/__tests__/impressionsTracker.spec.ts index b85135f1..fe763d67 100644 --- a/src/trackers/__tests__/impressionsTracker.spec.ts +++ b/src/trackers/__tests__/impressionsTracker.spec.ts @@ -1,5 +1,5 @@ -import impressionsTrackerFactory from '../impressionsTracker'; -import ImpressionCountsCacheInMemory from '../../storages/inMemory/ImpressionCountsCacheInMemory'; +import { impressionsTrackerFactory } from '../impressionsTracker'; +import { ImpressionCountsCacheInMemory } from '../../storages/inMemory/ImpressionCountsCacheInMemory'; import { impressionObserverSSFactory } from '../impressionObserver/impressionObserverSS'; import { impressionObserverCSFactory } from '../impressionObserver/impressionObserverCS'; import { ImpressionDTO } from '../../types'; diff --git a/src/trackers/eventTracker.ts b/src/trackers/eventTracker.ts index b18c0546..b3d63f6e 100644 --- a/src/trackers/eventTracker.ts +++ b/src/trackers/eventTracker.ts @@ -1,5 +1,5 @@ -import objectAssign from 'object-assign'; -import thenable from '../utils/promise/thenable'; +import { objectAssign } from '../utils/lang/objectAssign'; +import { thenable } from '../utils/promise/thenable'; import { IEventsCacheBase } from '../storages/types'; import { IEventsHandler, IEventTracker } from './types'; import { SplitIO } from '../types'; @@ -12,7 +12,7 @@ import { EVENTS_TRACKER_SUCCESS, ERROR_EVENTS_TRACKER } from '../logger/constant * @param eventsCache cache to save events * @param integrationsManager optional event handler used for integrations */ -export default function eventTrackerFactory( +export function eventTrackerFactory( log: ILogger, eventsCache: IEventsCacheBase, integrationsManager?: IEventsHandler diff --git a/src/trackers/impressionObserver/ImpressionObserver.ts b/src/trackers/impressionObserver/ImpressionObserver.ts index 666fa463..377a4f08 100644 --- a/src/trackers/impressionObserver/ImpressionObserver.ts +++ b/src/trackers/impressionObserver/ImpressionObserver.ts @@ -1,8 +1,8 @@ import { ImpressionDTO } from '../../types'; -import LRUCache from '../../utils/LRUCache'; +import { LRUCache } from '../../utils/LRUCache'; import { IImpressionObserver } from './types'; -export default class ImpressionObserver implements IImpressionObserver { +export class ImpressionObserver implements IImpressionObserver { private cache: LRUCache; private hasher: (impression: ImpressionDTO) => K; diff --git a/src/trackers/impressionObserver/impressionObserverCS.ts b/src/trackers/impressionObserver/impressionObserverCS.ts index bf86409e..712d8738 100644 --- a/src/trackers/impressionObserver/impressionObserverCS.ts +++ b/src/trackers/impressionObserver/impressionObserverCS.ts @@ -1,4 +1,4 @@ -import ImpressionObserver from './ImpressionObserver'; +import { ImpressionObserver } from './ImpressionObserver'; import { hash } from '../../utils/murmur3/murmur3'; import { buildKey } from './buildKey'; import { ImpressionDTO } from '../../types'; diff --git a/src/trackers/impressionObserver/impressionObserverSS.ts b/src/trackers/impressionObserver/impressionObserverSS.ts index 828cfc21..7a81279f 100644 --- a/src/trackers/impressionObserver/impressionObserverSS.ts +++ b/src/trackers/impressionObserver/impressionObserverSS.ts @@ -1,4 +1,4 @@ -import ImpressionObserver from './ImpressionObserver'; +import { ImpressionObserver } from './ImpressionObserver'; import { hash128 } from '../../utils/murmur3/murmur3_128_x86'; import { buildKey } from './buildKey'; import { ImpressionDTO } from '../../types'; diff --git a/src/trackers/impressionObserver/utils.ts b/src/trackers/impressionObserver/utils.ts index 3b921a8f..7fb73b85 100644 --- a/src/trackers/impressionObserver/utils.ts +++ b/src/trackers/impressionObserver/utils.ts @@ -1,11 +1,11 @@ -import { OPTIMIZED, PRODUCER_MODE, STANDALONE_MODE } from '../../utils/constants'; +import { CONSUMER_PARTIAL_MODE, OPTIMIZED, PRODUCER_MODE, STANDALONE_MODE } from '../../utils/constants'; import { ISettings } from '../../types'; /** * Checks if impressions previous time should be added or not. */ export function shouldAddPt(settings: ISettings) { - return [PRODUCER_MODE, STANDALONE_MODE].indexOf(settings.mode) > -1 ? true : false; + return [PRODUCER_MODE, STANDALONE_MODE, CONSUMER_PARTIAL_MODE].indexOf(settings.mode) > -1 ? true : false; } /** diff --git a/src/trackers/impressionsTracker.ts b/src/trackers/impressionsTracker.ts index 1c43f7fb..c7c18e5b 100644 --- a/src/trackers/impressionsTracker.ts +++ b/src/trackers/impressionsTracker.ts @@ -1,5 +1,5 @@ -import objectAssign from 'object-assign'; -import thenable from '../utils/promise/thenable'; +import { objectAssign } from '../utils/lang/objectAssign'; +import { thenable } from '../utils/promise/thenable'; import { truncateTimeFrame } from '../utils/time'; import { IImpressionCountsCacheSync, IImpressionsCacheBase } from '../storages/types'; import { IImpressionsHandler, IImpressionsTracker } from './types'; @@ -18,7 +18,7 @@ import { IMPRESSIONS_TRACKER_SUCCESS, ERROR_IMPRESSIONS_TRACKER, ERROR_IMPRESSIO * @param observer optional impression observer. If provided, previous time (pt property) is included in impression instances * @param countsCache optional cache to save impressions count. If provided, impressions will be deduped (OPTIMIZED mode) */ -export default function impressionsTrackerFactory( +export function impressionsTrackerFactory( log: ILogger, impressionsCache: IImpressionsCacheBase, diff --git a/src/types.ts b/src/types.ts index 691c5beb..d4351696 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,19 +3,20 @@ import { IIntegration, IIntegrationFactoryParams } from './integrations/types'; import { ILogger } from './logger/types'; /* eslint-disable no-use-before-define */ -import { IStorageFactoryParams, IStorageSyncCS, IStorageSync, IStorageAsync, IStorageSyncFactory } from './storages/types'; +import { IStorageFactoryParams, IStorageSync, IStorageAsync, IStorageSyncFactory, IStorageAsyncFactory } from './storages/types'; import { ISyncManagerFactoryParams, ISyncManagerCS } from './sync/types'; /** - * EventEmitter interface with the minimal methods used by the SDK + * Reduced version of NodeJS.EventEmitter interface with the minimal methods used by the SDK + * @see {@link https://nodejs.org/api/events.html} */ -export interface IEventEmitter extends Pick { - addListener(event: string, listener: (...args: any[]) => void): any; - on(event: string, listener: (...args: any[]) => void): any - once(event: string, listener: (...args: any[]) => void): any - removeListener(event: string, listener: (...args: any[]) => void): any; - off(event: string, listener: (...args: any[]) => void): any; - removeAllListeners(event?: string): any +export interface IEventEmitter { + addListener(event: string, listener: (...args: any[]) => void): this; + on(event: string, listener: (...args: any[]) => void): this + once(event: string, listener: (...args: any[]) => void): this + removeListener(event: string, listener: (...args: any[]) => void): this; + off(event: string, listener: (...args: any[]) => void): this; + removeAllListeners(event?: string): this emit(event: string, ...args: any[]): boolean } @@ -52,7 +53,7 @@ type EventConsts = { * SDK Modes. * @typedef {string} SDKMode */ -export type SDKMode = 'standalone' | 'consumer' | 'localhost'; +export type SDKMode = 'standalone' | 'consumer' | 'localhost' | 'consumer_partial'; /** * 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. @@ -85,7 +86,7 @@ export interface ISettings { retriesOnFailureBeforeReady: number, eventsFirstPushWindow: number }, - readonly storage: IStorageSyncFactory, + readonly storage: IStorageSyncFactory | IStorageAsyncFactory, readonly integrations?: Array<(params: IIntegrationFactoryParams) => IIntegration | void>, readonly urls: { events: string, @@ -93,7 +94,7 @@ export interface ISettings { auth: string, streaming: string }, - readonly debug: boolean | LogLevel, + readonly debug: boolean | LogLevel | ILogger, readonly version: string, features: SplitIO.MockedFeaturesFilePath | SplitIO.MockedFeaturesMap, readonly streamingEnabled: boolean, @@ -841,7 +842,7 @@ export namespace SplitIO { * Defines which kind of storage we should instanciate. * @property {Object} storage */ - storage?: (params: IStorageFactoryParams) => IStorageSyncCS, + storage?: (params: IStorageFactoryParams) => IStorageSync | IStorageAsync, /** * List of URLs that the SDK will use as base for it's synchronization functionalities, applicable only when running as standalone. * Do not change these settings unless you're working an advanced use case, like connecting to the Split proxy. diff --git a/src/utils/Backoff.ts b/src/utils/Backoff.ts index 3e39c83a..0e710b4d 100644 --- a/src/utils/Backoff.ts +++ b/src/utils/Backoff.ts @@ -1,4 +1,4 @@ -export default class Backoff { +export class Backoff { static DEFAULT_BASE_MILLIS = 1000; // 1 second static DEFAULT_MAX_MILLIS = 1800000; // 30 minutes diff --git a/src/utils/LRUCache/__tests__/index.spec.ts b/src/utils/LRUCache/__tests__/index.spec.ts index 3e59b0e2..7d35c027 100644 --- a/src/utils/LRUCache/__tests__/index.spec.ts +++ b/src/utils/LRUCache/__tests__/index.spec.ts @@ -1,4 +1,4 @@ -import LRUCache from '..'; +import { LRUCache } from '..'; test('Check Cache', () => { const cache = new LRUCache(5); diff --git a/src/utils/LRUCache/index.ts b/src/utils/LRUCache/index.ts index bad7267f..edf1b59b 100644 --- a/src/utils/LRUCache/index.ts +++ b/src/utils/LRUCache/index.ts @@ -1,7 +1,7 @@ import { IMap, _Map } from '../lang/maps'; import { LinkedList, Node } from './LinkedList'; -export default class LRUCache { +export class LRUCache { maxLen: number; items: IMap>; lru: LinkedList<{ key: K, value: V }>; diff --git a/src/utils/MinEventEmitter.ts b/src/utils/MinEventEmitter.ts index 2bddb0d6..a081f1e7 100644 --- a/src/utils/MinEventEmitter.ts +++ b/src/utils/MinEventEmitter.ts @@ -11,14 +11,14 @@ function checkListener(listener: unknown) { } // @TODO implement missing methods, check spec and add UTs -export default class EventEmitter implements IEventEmitter { +export class EventEmitter implements IEventEmitter { private listeners: Record void, // the event listener boolean // whether it is a one-time listener or not ]>> = {}; - private registerListener(type: string, listener: (...args: any[]) => void, oneTime: boolean): EventEmitter { + private registerListener(type: string, listener: (...args: any[]) => void, oneTime: boolean) { checkListener(listener); // To avoid recursion in the case that type === "newListener" before @@ -33,27 +33,27 @@ export default class EventEmitter implements IEventEmitter { return this; } - addListener(type: string, listener: (...args: any[]) => void): EventEmitter { + addListener(type: string, listener: (...args: any[]) => void) { return this.registerListener(type, listener, false); } // alias of addListener - on(type: string, listener: (...args: any[]) => void): EventEmitter { + on(type: string, listener: (...args: any[]) => void) { return this.addListener(type, listener); } - once(type: string, listener: (...args: any[]) => void): EventEmitter { + once(type: string, listener: (...args: any[]) => void) { return this.registerListener(type, listener, true); } - // eslint-disable-next-line - removeListener(type: string, listener: (...args: any[]) => void): EventEmitter { + // @ts-ignore + removeListener(/* type: string, listener: (...args: any[]) => void */) { throw new Error('Method not implemented.'); } - // alias of removeListener - off(type: string, listener: (...args: any[]) => void): EventEmitter { - return this.removeListener(type, listener); + // @ts-ignore alias of removeListener + off(/* type: string, listener: (...args: any[]) => void */) { + return this.removeListener(/* type, listener */); } emit(type: string, ...args: any[]): boolean { @@ -68,7 +68,7 @@ export default class EventEmitter implements IEventEmitter { return true; } - removeAllListeners(type?: string): EventEmitter { + removeAllListeners(type?: string) { if (!this.listeners[REMOVE_LISTENER_EVENT]) { // if not listening for `removeListener`, no need to emit if (type) { diff --git a/src/utils/MinEvents.ts b/src/utils/MinEvents.ts index e32e255b..71aa3626 100644 --- a/src/utils/MinEvents.ts +++ b/src/utils/MinEvents.ts @@ -39,14 +39,9 @@ var ReflectApply = R && typeof R.apply === 'function' return Function.prototype.apply.call(target, receiver, args); }; -function EventEmitter() { +export const EventEmitter: { new(): IEventEmitter } = function EventEmitter() { EventEmitter.init.call(this); -} - -export default EventEmitter as new () => IEventEmitter; - -// Backwards-compat with node 0.10.x -EventEmitter.EventEmitter = EventEmitter; +}; EventEmitter.prototype._events = undefined; EventEmitter.prototype._eventsCount = 0; diff --git a/src/utils/constants/index.ts b/src/utils/constants/index.ts index 51bcafca..4a4d8303 100644 --- a/src/utils/constants/index.ts +++ b/src/utils/constants/index.ts @@ -1,4 +1,5 @@ import { StorageType } from '../../storages/types'; +import { SDKMode } from '../../types'; // Special treatments export const CONTROL = 'control'; @@ -20,13 +21,14 @@ export const DEBUG = 'DEBUG'; export const OPTIMIZED = 'OPTIMIZED'; // SDK Modes -export const LOCALHOST_MODE = 'localhost'; -export const STANDALONE_MODE = 'standalone'; +export const LOCALHOST_MODE: SDKMode = 'localhost'; +export const STANDALONE_MODE: SDKMode = 'standalone'; export const PRODUCER_MODE = 'producer'; -export const CONSUMER_MODE = 'consumer'; +export const CONSUMER_MODE: SDKMode = 'consumer'; +export const CONSUMER_PARTIAL_MODE: SDKMode = 'consumer_partial'; // Storage types export const STORAGE_MEMORY: StorageType = 'MEMORY'; export const STORAGE_LOCALSTORAGE: StorageType = 'LOCALSTORAGE'; export const STORAGE_REDIS: StorageType = 'REDIS'; -export const STORAGE_CUSTOM: StorageType = 'CUSTOM'; +export const STORAGE_PLUGGABLE: StorageType = 'PLUGGABLE'; diff --git a/src/utils/env/isNode.ts b/src/utils/env/isNode.ts index f189c5b6..8c28623c 100644 --- a/src/utils/env/isNode.ts +++ b/src/utils/env/isNode.ts @@ -1,3 +1,6 @@ -// We check for version truthiness since most shims will have that as empty string. +/** + * 'true' if running in Node.js, or 'false' otherwise. + * We check for version truthiness since most shims will have that as empty string. + */ // eslint-disable-next-line no-undef export const isNode: boolean = typeof process !== 'undefined' && typeof process.version !== 'undefined' && !!process.version ? true : false; diff --git a/src/utils/inputValidation/__tests__/trafficTypeExistance.spec.ts b/src/utils/inputValidation/__tests__/trafficTypeExistance.spec.ts index 293ccb84..ad19302f 100644 --- a/src/utils/inputValidation/__tests__/trafficTypeExistance.spec.ts +++ b/src/utils/inputValidation/__tests__/trafficTypeExistance.spec.ts @@ -1,7 +1,7 @@ import { IReadinessManager } from '../../../readiness/types'; import { ISplitsCacheBase } from '../../../storages/types'; import { LOCALHOST_MODE, STANDALONE_MODE } from '../../constants'; -import thenable from '../../promise/thenable'; +import { thenable } from '../../promise/thenable'; import { WARN_NOT_EXISTENT_TT } from '../../../logger/constants'; /** Mocks */ diff --git a/src/utils/inputValidation/trafficTypeExistance.ts b/src/utils/inputValidation/trafficTypeExistance.ts index 99d9f705..743ceb8f 100644 --- a/src/utils/inputValidation/trafficTypeExistance.ts +++ b/src/utils/inputValidation/trafficTypeExistance.ts @@ -1,4 +1,4 @@ -import thenable from '../promise/thenable'; +import { thenable } from '../promise/thenable'; import { LOCALHOST_MODE } from '../constants'; import { ISplitsCacheBase } from '../../storages/types'; import { IReadinessManager } from '../../readiness/types'; diff --git a/src/utils/lang/binarySearch.ts b/src/utils/lang/binarySearch.ts index 4cf34852..7ff83fa0 100644 --- a/src/utils/lang/binarySearch.ts +++ b/src/utils/lang/binarySearch.ts @@ -6,7 +6,7 @@ * @returns integer number between 0 and `items.length`. This value is the index of the search value, * if it is contained in the array, or the index at which the value should be inserted to keep the array ordered. */ -export default function binarySearch(items: number[], value: number): number { +export function binarySearch(items: number[], value: number): number { let startIndex = 0; let stopIndex = items.length - 1; let middle = Math.floor((stopIndex + startIndex) / 2); diff --git a/src/utils/lang/objectAssign.ts b/src/utils/lang/objectAssign.ts new file mode 100644 index 00000000..23fcbd9a --- /dev/null +++ b/src/utils/lang/objectAssign.ts @@ -0,0 +1,104 @@ +/* +Adaptation of "object-assign" library (https://www.npmjs.com/package/object-assign) +exported as an ES module instead of CommonJS, to avoid extra configuration steps when using +the ESM build of the SDK with tools that doesn't support CommonJS by default (e.g. Rollup). + +object-assign +(c) Sindre Sorhus +@license MIT +*/ + +/* eslint-disable */ +// @ts-nocheck + +var getOwnPropertySymbols = Object.getOwnPropertySymbols; +var hasOwnProperty = Object.prototype.hasOwnProperty; +var propIsEnumerable = Object.prototype.propertyIsEnumerable; + +function toObject(val) { + if (val === null || val === undefined) { + throw new TypeError('Object.assign cannot be called with null or undefined'); + } + + return Object(val); +} + +function shouldUseNative() { + try { + if (!Object.assign) { + return false; + } + + // Detect buggy property enumeration order in older V8 versions. + + // https://bugs.chromium.org/p/v8/issues/detail?id=4118 + var test1 = new String('abc'); + test1[5] = 'de'; + if (Object.getOwnPropertyNames(test1)[0] === '5') { + return false; + } + + // https://bugs.chromium.org/p/v8/issues/detail?id=3056 + var test2 = {}; + for (var i = 0; i < 10; i++) { + test2['_' + String.fromCharCode(i)] = i; + } + var order2 = Object.getOwnPropertyNames(test2).map(function (n) { + return test2[n]; + }); + if (order2.join('') !== '0123456789') { + return false; + } + + // https://bugs.chromium.org/p/v8/issues/detail?id=3056 + var test3 = {}; + 'abcdefghijklmnopqrst'.split('').forEach(function (letter) { + test3[letter] = letter; + }); + if (Object.keys(Object.assign({}, test3)).join('') !== + 'abcdefghijklmnopqrst') { + return false; + } + + return true; + } catch (err) { + // We don't expect any of the above to throw, but better to be safe. + return false; + } +} + +// https://www.npmjs.com/package/@types/object-assign +type ObjectAssign = ((target: T, source: U) => T & U) & + ((target: T, source1: U, source2: V) => T & U & V) & + ((target: T, source1: U, source2: V, source3: W) => T & U & V & W) & + ((target: T, source1: U, source2: V, source3: W, source4: Q) => T & U & V & W & Q) & + ((target: T, source1: U, source2: V, source3: W, source4: Q, source5: R) => T & U & V & W & Q & R) & + ((target: any, ...sources: any[]) => any); + +export const objectAssign: ObjectAssign = shouldUseNative() ? Object.assign : function (target, source) { + var from; + var to = toObject(target); + var symbols; + + for (var s = 1; s < arguments.length; s++) { + from = Object(arguments[s]); + + // eslint-disable-next-line no-restricted-syntax + for (var key in from) { + if (hasOwnProperty.call(from, key)) { + to[key] = from[key]; + } + } + + if (getOwnPropertySymbols) { + symbols = getOwnPropertySymbols(from); + for (var i = 0; i < symbols.length; i++) { + if (propIsEnumerable.call(from, symbols[i])) { + to[symbols[i]] = from[symbols[i]]; + } + } + } + } + + return to; +}; diff --git a/src/utils/promise/thenable.ts b/src/utils/promise/thenable.ts index d63d489b..6770eba1 100644 --- a/src/utils/promise/thenable.ts +++ b/src/utils/promise/thenable.ts @@ -1,2 +1,4 @@ // returns true if the given value is a thenable object -export default (o: any): o is Promise => o !== undefined && o !== null && typeof o.then === 'function'; +export function thenable(o: any): o is Promise { + return o !== undefined && o !== null && typeof o.then === 'function'; +} diff --git a/src/utils/promise/timeout.ts b/src/utils/promise/timeout.ts index 3ca70b8c..9d3c12ee 100644 --- a/src/utils/promise/timeout.ts +++ b/src/utils/promise/timeout.ts @@ -1,4 +1,4 @@ -export default function timeout(ms: number, promise: Promise): Promise { +export function timeout(ms: number, promise: Promise): Promise { if (ms < 1) return promise; return new Promise((resolve, reject) => { diff --git a/src/utils/promise/wrapper.ts b/src/utils/promise/wrapper.ts index 7c7779ec..d0100fd6 100644 --- a/src/utils/promise/wrapper.ts +++ b/src/utils/promise/wrapper.ts @@ -16,7 +16,7 @@ * @returns a promise that doesn't need to be handled for rejection (except when using async/await syntax) and * includes a method named `hasOnFulfilled` that returns true if the promise has attached an onFulfilled handler. */ -export default function promiseWrapper(customPromise: Promise, defaultOnRejected: (_: any) => any): Promise & { hasOnFulfilled: () => boolean } { +export function promiseWrapper(customPromise: Promise, defaultOnRejected: (_: any) => any): Promise & { hasOnFulfilled: () => boolean } { let hasOnFulfilled = false; let hasOnRejected = false; diff --git a/src/utils/settingsValidation/impressionsMode.ts b/src/utils/settingsValidation/impressionsMode.ts index 84ae2f14..ddcda23d 100644 --- a/src/utils/settingsValidation/impressionsMode.ts +++ b/src/utils/settingsValidation/impressionsMode.ts @@ -3,7 +3,7 @@ import { ILogger } from '../../logger/types'; import { SplitIO } from '../../types'; import { DEBUG, OPTIMIZED } from '../constants'; -export default function validImpressionsMode(log: ILogger, impressionsMode: string): SplitIO.ImpressionsMode { +export function validImpressionsMode(log: ILogger, impressionsMode: string): SplitIO.ImpressionsMode { impressionsMode = impressionsMode.toUpperCase(); if ([DEBUG, OPTIMIZED].indexOf(impressionsMode) === -1) { log.error(ERROR_INVALID_IMPRESSIONS_MODE, [[DEBUG, OPTIMIZED], OPTIMIZED]); diff --git a/src/utils/settingsValidation/index.ts b/src/utils/settingsValidation/index.ts index 1840fdb5..e10965a0 100644 --- a/src/utils/settingsValidation/index.ts +++ b/src/utils/settingsValidation/index.ts @@ -1,8 +1,8 @@ import { merge } from '../lang'; -import mode from './mode'; +import { mode } from './mode'; import { validateSplitFilters } from './splitFilters'; import { STANDALONE_MODE, OPTIMIZED, LOCALHOST_MODE } from '../constants'; -import validImpressionsMode from './impressionsMode'; +import { validImpressionsMode } from './impressionsMode'; import { ISettingsValidationParams } from './types'; import { ISettings } from '../../types'; @@ -122,7 +122,7 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV // Startup periods startup.requestTimeoutBeforeReady = fromSecondsToMillis(startup.requestTimeoutBeforeReady); startup.readyTimeout = fromSecondsToMillis(startup.readyTimeout); - startup.eventsFirstPushWindow = fromSecondsToMillis(withDefaults.startup.eventsFirstPushWindow); + startup.eventsFirstPushWindow = fromSecondsToMillis(startup.eventsFirstPushWindow); // ensure a valid SDK mode // @ts-ignore, modify readonly prop diff --git a/src/utils/settingsValidation/mode.ts b/src/utils/settingsValidation/mode.ts index 0b2464b2..8480a24b 100644 --- a/src/utils/settingsValidation/mode.ts +++ b/src/utils/settingsValidation/mode.ts @@ -1,10 +1,10 @@ -import { LOCALHOST_MODE, STANDALONE_MODE, PRODUCER_MODE, CONSUMER_MODE } from '../constants'; +import { LOCALHOST_MODE, STANDALONE_MODE, PRODUCER_MODE, CONSUMER_MODE, CONSUMER_PARTIAL_MODE } from '../constants'; -export default function mode(key: string, mode: string) { +export function mode(key: string, mode: string) { // Leaving the comparison as is, in case we change the mode name but not the setting. if (key === 'localhost') return LOCALHOST_MODE; - if ([STANDALONE_MODE, PRODUCER_MODE, CONSUMER_MODE].indexOf(mode) === -1) throw Error('Invalid mode provided'); + if ([STANDALONE_MODE, PRODUCER_MODE, CONSUMER_MODE, CONSUMER_PARTIAL_MODE].indexOf(mode) === -1) throw Error('Invalid mode provided'); return mode; } diff --git a/src/utils/settingsValidation/storage/__tests__/storageCS.spec.ts b/src/utils/settingsValidation/storage/__tests__/storageCS.spec.ts index 72456b1f..5bd7c389 100644 --- a/src/utils/settingsValidation/storage/__tests__/storageCS.spec.ts +++ b/src/utils/settingsValidation/storage/__tests__/storageCS.spec.ts @@ -5,33 +5,48 @@ import { loggerMock as log } from '../../../../logger/__tests__/sdkLogger.mock'; const mockInLocalStorageFactory = () => { }; mockInLocalStorageFactory.type = 'LOCALSTORAGE'; +const mockPluggableStorageFactory = () => { }; +mockPluggableStorageFactory.type = 'PLUGGABLE'; + describe('storage validator for pluggable storage (client-side)', () => { afterEach(() => { - log.warn.mockClear(); + log.error.mockClear(); }); // Check different types, since `storage` param is defined by the user test('fallbacks to default InMemory storage if the storage is invalid or not provided', () => { - expect(validateStorageCS({ log })).toBe(InMemoryStorageCSFactory); - expect(validateStorageCS({ log, storage: undefined })).toBe(InMemoryStorageCSFactory); - expect(log.warn).not.toBeCalled(); - - expect(validateStorageCS({ log, storage: {} })).toBe(InMemoryStorageCSFactory); - expect(validateStorageCS({ log, storage: 'invalid storage' })).toBe(InMemoryStorageCSFactory); - expect(validateStorageCS({ log, storage: () => { } })).toBe(InMemoryStorageCSFactory); - expect(validateStorageCS({ log, storage: () => { }, mode: 'localhost' })).toBe(InMemoryStorageCSFactory); - expect(log.warn).toBeCalledTimes(4); + expect(validateStorageCS({ log, mode: 'standalone' })).toBe(InMemoryStorageCSFactory); + expect(validateStorageCS({ log, mode: 'standalone', storage: undefined })).toBe(InMemoryStorageCSFactory); + expect(log.error).not.toBeCalled(); + + expect(validateStorageCS({ log, mode: 'standalone', storage: {} })).toBe(InMemoryStorageCSFactory); + expect(validateStorageCS({ log, mode: 'standalone', storage: 'invalid storage' })).toBe(InMemoryStorageCSFactory); + expect(validateStorageCS({ log, mode: 'standalone', storage: () => { } })).toBe(InMemoryStorageCSFactory); + expect(validateStorageCS({ log, mode: 'localhost', storage: () => { } })).toBe(InMemoryStorageCSFactory); + expect(log.error).toBeCalledTimes(4); }); test('returns the provided storage factory if it is valid', () => { - expect(validateStorageCS({ log, storage: mockInLocalStorageFactory })).toBe(mockInLocalStorageFactory); - expect(log.warn).not.toBeCalled(); + expect(validateStorageCS({ log, mode: 'standalone', storage: mockInLocalStorageFactory })).toBe(mockInLocalStorageFactory); + expect(log.error).not.toBeCalled(); }); test('fallbacks to mock InLocalStorage storage if the storage is InLocalStorage and the mode localhost', () => { - expect(validateStorageCS({ log, storage: mockInLocalStorageFactory, mode: 'localhost' })).toBe(__InLocalStorageMockFactory); - expect(log.warn).not.toBeCalled(); + expect(validateStorageCS({ log, mode: 'localhost', storage: mockInLocalStorageFactory })).toBe(__InLocalStorageMockFactory); + expect(log.error).not.toBeCalled(); + }); + + test('throws error if the provided storage factory is not compatible with the mode', () => { + expect(() => { validateStorageCS({ log, mode: 'consumer', storage: mockInLocalStorageFactory }); }).toThrow('A PluggableStorage instance is required on consumer mode'); + expect(() => { validateStorageCS({ log, mode: 'consumer_partial', storage: mockInLocalStorageFactory }); }).toThrow('A PluggableStorage instance is required on consumer mode'); + + expect(log.error).not.toBeCalled(); + + expect(validateStorageCS({ log, mode: 'standalone', storage: mockPluggableStorageFactory })).toBe(InMemoryStorageCSFactory); + expect(validateStorageCS({ log, mode: 'localhost', storage: mockPluggableStorageFactory })).toBe(InMemoryStorageCSFactory); + + expect(log.error).toBeCalledTimes(2); }); }); diff --git a/src/utils/settingsValidation/storage/storageCS.ts b/src/utils/settingsValidation/storage/storageCS.ts index 9cb762e2..097ce95d 100644 --- a/src/utils/settingsValidation/storage/storageCS.ts +++ b/src/utils/settingsValidation/storage/storageCS.ts @@ -1,11 +1,11 @@ import { InMemoryStorageCSFactory } from '../../../storages/inMemory/InMemoryStorageCS'; import { ISettings, SDKMode } from '../../../types'; import { ILogger } from '../../../logger/types'; -import { WARN_STORAGE_INVALID } from '../../../logger/constants'; -import { LOCALHOST_MODE, STORAGE_LOCALSTORAGE, STORAGE_MEMORY } from '../../../utils/constants'; -import { IStorageFactoryParams, IStorageSyncCS } from '../../../storages/types'; +import { ERROR_STORAGE_INVALID } from '../../../logger/constants'; +import { LOCALHOST_MODE, STANDALONE_MODE, STORAGE_PLUGGABLE, STORAGE_LOCALSTORAGE, STORAGE_MEMORY } from '../../../utils/constants'; +import { IStorageFactoryParams, IStorageSync } from '../../../storages/types'; -export function __InLocalStorageMockFactory(params: IStorageFactoryParams): IStorageSyncCS { +export function __InLocalStorageMockFactory(params: IStorageFactoryParams): IStorageSync { const result = InMemoryStorageCSFactory(params); result.splits.checkCache = () => true; // to emit SDK_READY_FROM_CACHE return result; @@ -17,15 +17,17 @@ __InLocalStorageMockFactory.type = STORAGE_MEMORY; * * @param {any} settings config object provided by the user to initialize the sdk * - * @returns {Object} valid storage factory. It might be the default `InMemoryStorageCSFactory` if the provided storage is invalid. + * @returns {Object} valid storage factory. Default to `InMemoryStorageCSFactory` if the provided storage is invalid or not compatible with the sdk mode if mode is standalone or localhost + * + * @throws error if mode is consumer and the provided storage is not compatible */ -export function validateStorageCS(settings: { log: ILogger, storage?: any, mode?: SDKMode }): ISettings['storage'] { +export function validateStorageCS(settings: { log: ILogger, storage?: any, mode: SDKMode }): ISettings['storage'] { let { storage = InMemoryStorageCSFactory, log, mode } = settings; // If an invalid storage is provided, fallback into MEMORY - if (typeof storage !== 'function' || storage.type !== STORAGE_MEMORY && storage.type !== STORAGE_LOCALSTORAGE) { + if (typeof storage !== 'function' || [STORAGE_MEMORY, STORAGE_LOCALSTORAGE, STORAGE_PLUGGABLE].indexOf(storage.type) === -1) { storage = InMemoryStorageCSFactory; - log.warn(WARN_STORAGE_INVALID); + log.error(ERROR_STORAGE_INVALID); } // In localhost mode with InLocalStorage, fallback to a mock InLocalStorage to emit SDK_READY_FROM_CACHE @@ -33,6 +35,17 @@ export function validateStorageCS(settings: { log: ILogger, storage?: any, mode? return __InLocalStorageMockFactory; } + if ([LOCALHOST_MODE, STANDALONE_MODE].indexOf(mode) === -1) { + // Consumer modes require an async storage + if (storage.type !== STORAGE_PLUGGABLE) throw new Error('A PluggableStorage instance is required on consumer mode'); + } else { + // Standalone and localhost modes require a sync storage + if (storage.type === STORAGE_PLUGGABLE) { + storage = InMemoryStorageCSFactory; + log.error(ERROR_STORAGE_INVALID, [' It requires consumer mode.']); + } + } + // return default InMemory storage if provided one is not valid return storage; } diff --git a/src/utils/timeTracker/__tests__/index.spec.ts b/src/utils/timeTracker/__tests__/index.spec.ts index e95def88..e4036fd8 100644 --- a/src/utils/timeTracker/__tests__/index.spec.ts +++ b/src/utils/timeTracker/__tests__/index.spec.ts @@ -1,5 +1,5 @@ -import timer from '../timer'; -import tracker from '../index'; +import { timer } from '../timer'; +import { TrackerAPI } from '../index'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { IResponse } from '../../../services/types'; @@ -19,18 +19,18 @@ test('TIMER / should count the time between two tasks', (done) => { describe('TIME TRACKER', () => { test('should have the correct API', () => { - expect(typeof tracker.start).toBe('function'); // It should have the correct API. - expect(typeof tracker.stop).toBe('function'); // It should have the correct API. - expect(typeof tracker.TaskNames).toBe('object'); // It should have the correct API. + expect(typeof TrackerAPI.start).toBe('function'); // It should have the correct API. + expect(typeof TrackerAPI.stop).toBe('function'); // It should have the correct API. + expect(typeof TrackerAPI.TaskNames).toBe('object'); // It should have the correct API. }); test('start() / should return the correct type', () => { const promise = new Promise(res => { setTimeout(res, 1000); }); - const startNormal = tracker.start(loggerMock, tracker.TaskNames.SDK_READY); - const startNormalFake = tracker.start(loggerMock, 'fakeTask3'); - const startWithPromise = tracker.start(loggerMock, 'fakeTask4', undefined, promise); + const startNormal = TrackerAPI.start(loggerMock, TrackerAPI.TaskNames.SDK_READY); + const startNormalFake = TrackerAPI.start(loggerMock, 'fakeTask3'); + const startWithPromise = TrackerAPI.start(loggerMock, 'fakeTask4', undefined, promise); expect(typeof startNormal).toBe('function'); // If we call start without a promise, it will return the stop function, // @ts-expect-error @@ -42,14 +42,14 @@ describe('TIME TRACKER', () => { }); test('stop() / should stop the timer and return the time, if any', () => { - tracker.start(loggerMock, 'test_task'); + TrackerAPI.start(loggerMock, 'test_task'); // creating two tasks with the same task name - const stopFromStart = tracker.start(loggerMock, 'fakeTask5') as () => number; - const stopFromStart2 = tracker.start(loggerMock, 'fakeTask5') as () => number; + const stopFromStart = TrackerAPI.start(loggerMock, 'fakeTask5') as () => number; + const stopFromStart2 = TrackerAPI.start(loggerMock, 'fakeTask5') as () => number; - const stopNotExistentTask = tracker.stop(loggerMock, 'not_existent'); - const stopNotExistentTaskAndModifier = tracker.stop(loggerMock, 'test_task', 'mod'); + const stopNotExistentTask = TrackerAPI.stop(loggerMock, 'not_existent'); + const stopNotExistentTaskAndModifier = TrackerAPI.stop(loggerMock, 'test_task', 'mod'); expect(typeof stopNotExistentTask).toBe('undefined'); // If we try to stop a timer that does not exist, we get undefined. expect(typeof stopNotExistentTaskAndModifier).toBe('undefined'); // If we try to stop a timer that does not exist, we get undefined. diff --git a/src/utils/timeTracker/index.ts b/src/utils/timeTracker/index.ts index c6899659..a09289b8 100644 --- a/src/utils/timeTracker/index.ts +++ b/src/utils/timeTracker/index.ts @@ -1,6 +1,6 @@ import { uniqueId } from '../lang'; -import timer from './timer'; -import thenable from '../promise/thenable'; +import { timer } from './timer'; +import { thenable } from '../promise/thenable'; import { ILogger } from '../../logger/types'; import { IResponse } from '../../services/types'; @@ -112,7 +112,8 @@ function getCallbackForTask(task: string, collector: MetricsCollector | false): return false; } -const TrackerAPI = { +// Our "time tracker" API +export const TrackerAPI = { /** * "Private" method, used to attach count/countException and stop callbacks to a promise. * @@ -223,6 +224,3 @@ const TrackerAPI = { */ TaskNames: CONSTANTS }; - -// Our "time tracker" API -export default TrackerAPI; diff --git a/src/utils/timeTracker/now/browser.ts b/src/utils/timeTracker/now/browser.ts index 314ab2e4..5c939606 100644 --- a/src/utils/timeTracker/now/browser.ts +++ b/src/utils/timeTracker/now/browser.ts @@ -9,6 +9,4 @@ function nowFactory() { } } -const now = nowFactory(); - -export default now; +export const now = nowFactory(); diff --git a/src/utils/timeTracker/now/node.ts b/src/utils/timeTracker/now/node.ts index dbe3366d..fe5c5f27 100644 --- a/src/utils/timeTracker/now/node.ts +++ b/src/utils/timeTracker/now/node.ts @@ -1,5 +1,5 @@ // @TODO migrate to Node SDK package eventually -export default function now() { +export function now() { // eslint-disable-next-line no-undef let time = process.hrtime(); diff --git a/src/utils/timeTracker/timer.ts b/src/utils/timeTracker/timer.ts index 29b99405..425f993a 100644 --- a/src/utils/timeTracker/timer.ts +++ b/src/utils/timeTracker/timer.ts @@ -1,4 +1,4 @@ -export default function start(now?: () => number) { +export function timer(now?: () => number) { const st = now ? now() : Date.now(); return function stop() {