Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8,263 changes: 2,602 additions & 5,661 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@splitsoftware/splitio-commons",
"version": "0.1.1-canary.17",
"version": "0.1.1-rc.18",
"description": "Split Javascript SDK common components",
"main": "cjs/index.js",
"module": "esm/index.js",
Expand Down Expand Up @@ -50,26 +50,26 @@
"devDependencies": {
"@types/google.analytics": "0.0.40",
"@types/ioredis": "^4.14.1",
"@types/jest": "^26.0.10",
"@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.9.0",
"eslint": "^7.32.0",
"eslint-plugin-compat": "3.7.0",
"fetch-mock": "^9.10.7",
"ioredis": "^4.26.0",
"jest": "^26.6.3",
"jest": "^27.2.3",
"jest-localstorage-mock": "^2.4.3",
"js-yaml": "^3.14.0",
"lodash": "^4.17.21",
"node-fetch": "^2.6.1",
"redis-server": "1.2.2",
"rimraf": "^3.0.2",
"ts-jest": "^26.3.0",
"ts-jest": "^27.0.5",
"typescript": "^4.0.2"
},
"sideEffects": false
Expand Down
16 changes: 4 additions & 12 deletions src/listeners/__tests__/node.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ test('Node JS listener / Signal Listener SIGTERM callback with sync handler that
processKillSpy.mockClear();
});

test('Node JS listener / Signal Listener SIGTERM callback with async handler', async (done) => {
test('Node JS listener / Signal Listener SIGTERM callback with async handler', async () => {

const fakePromise = new Promise<void>(res => {
setTimeout(() => {
Expand Down Expand Up @@ -113,7 +113,7 @@ test('Node JS listener / Signal Listener SIGTERM callback with async handler', a
expect(listener.stop).not.toBeCalled();
expect(processKillSpy).not.toBeCalled();

fakePromise.then(() => {
await fakePromise.then(() => {
// Clean up is called even if there is an error.
expect(listener.stop).toBeCalledTimes(1);
// It called for kill again, so the shutdown keeps going.
Expand All @@ -122,14 +122,10 @@ test('Node JS listener / Signal Listener SIGTERM callback with async handler', a

// Reset the kill spy since it's used on other tests.
processKillSpy.mockClear();

done();
});

return fakePromise;
});

test('Node JS listener / Signal Listener SIGTERM callback with async handler that throws an error', async (done) => {
test('Node JS listener / Signal Listener SIGTERM callback with async handler that throws an error', async () => {

const fakePromise = new Promise<void>((res, rej) => {
setTimeout(() => {
Expand Down Expand Up @@ -158,7 +154,7 @@ test('Node JS listener / Signal Listener SIGTERM callback with async handler tha
expect(processKillSpy).not.toBeCalled();

// Calling .then since the wrapUp handler does not throw.
(handlerPromise as Promise<void>).then(() => {
await (handlerPromise as Promise<void>).then(() => {
// Clean up is called.
expect(listener.stop).toBeCalledTimes(1);
// It called for kill again, so the shutdown keeps going.
Expand All @@ -169,9 +165,5 @@ test('Node JS listener / Signal Listener SIGTERM callback with async handler tha
processOnSpy.mockRestore();
processRemoveListenerSpy.mockRestore();
processKillSpy.mockRestore();

done();
});

return handlerPromise;
});
3 changes: 1 addition & 2 deletions src/readiness/__tests__/sdkReadinessManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ describe('SDK Readiness Manager - Event emitter', () => {

describe('SDK Readiness Manager - Ready promise', () => {

test('.ready() promise behaviour for clients', async (done) => {
test('.ready() promise behaviour for clients', async () => {
const sdkReadinessManager = sdkReadinessManagerFactory(loggerMock, EventEmitterMock);

const ready = sdkReadinessManager.sdkStatus.ready();
Expand Down Expand Up @@ -258,7 +258,6 @@ describe('SDK Readiness Manager - Ready promise', () => {
loggerMock.mockClear();
testPassedCount++;
expect(testPassedCount).toBe(5);
done();
},
() => { throw new Error('It should be resolved on ready event, not rejected.'); }
);
Expand Down
3 changes: 1 addition & 2 deletions src/sdkFactory/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { IStorageAsync, IStorageSync, ISplitsCacheSync, ISplitsCacheAsync, IStor
import { ISyncManager, ISyncManagerFactoryParams } from '../sync/types';
import { IImpressionObserver } from '../trackers/impressionObserver/types';
import { SplitIO, ISettings, IEventEmitter } from '../types';
import { ISettingsInternal } from '../utils/settingsValidation/types';

/**
* Environment related dependencies.
Expand All @@ -27,7 +26,7 @@ export interface IPlatform {
export interface ISdkFactoryParams {

// The settings must be already validated
settings: ISettingsInternal,
settings: ISettings,

// Platform dependencies
platform: IPlatform,
Expand Down
3 changes: 1 addition & 2 deletions src/services/splitApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { IPlatform } from '../sdkFactory/types';
import { ISettings } from '../types';
import { splitHttpClientFactory } from './splitHttpClient';
import { ISplitApi } from './types';
import { ISettingsInternal } from '../utils/settingsValidation/types';
import objectAssign from 'object-assign';

const noCacheHeaderOptions = { headers: { 'Cache-Control': 'no-cache' } };
Expand All @@ -20,7 +19,7 @@ function userKeyToQueryParam(userKey: string) {
export function splitApiFactory(settings: ISettings, platform: Pick<IPlatform, 'getFetch' | 'getOptions'>): ISplitApi {

const urls = settings.urls;
const filterQueryString = (settings as ISettingsInternal).sync.__splitFiltersValidation && (settings as ISettingsInternal).sync.__splitFiltersValidation.queryString;
const filterQueryString = settings.sync.__splitFiltersValidation && settings.sync.__splitFiltersValidation.queryString;
const SplitSDKImpressionsMode = settings.sync.impressionsMode;
const splitHttpClient = splitHttpClientFactory(settings, platform.getFetch, platform.getOptions);

Expand Down
12 changes: 8 additions & 4 deletions src/storages/inLocalStorage/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ImpressionsCacheInMemory from '../inMemory/ImpressionsCacheInMemory';
import ImpressionCountsCacheInMemory from '../inMemory/ImpressionCountsCacheInMemory';
import EventsCacheInMemory from '../inMemory/EventsCacheInMemory';
import { IStorageFactoryParams, IStorageSyncCS } from '../types';
import { IStorageFactoryParams, IStorageSyncCS, IStorageSyncFactory } from '../types';
import { validatePrefix } from '../KeyBuilder';
import KeyBuilderCS from '../KeyBuilderCS';
import { isLocalStorageAvailable } from '../../utils/env/isLocalStorageAvailable';
Expand All @@ -12,6 +12,7 @@ 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';
import { STORAGE_LOCALSTORAGE } from '../../utils/constants';

export interface InLocalStorageOptions {
prefix?: string
Expand All @@ -20,11 +21,11 @@ export interface InLocalStorageOptions {
/**
* InLocal storage factory for standalone client-side SplitFactory
*/
export function InLocalStorage(options: InLocalStorageOptions = {}) {
export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyncFactory {

const prefix = validatePrefix(options.prefix);

return function InLocalStorageCSFactory(params: IStorageFactoryParams): IStorageSyncCS {
function InLocalStorageCSFactory(params: IStorageFactoryParams): IStorageSyncCS {

// Fallback to InMemoryStorage if LocalStorage API is not available
if (!isLocalStorageAvailable()) {
Expand Down Expand Up @@ -69,5 +70,8 @@ export function InLocalStorage(options: InLocalStorageOptions = {}) {
};
},
};
};
}

InLocalStorageCSFactory.type = STORAGE_LOCALSTORAGE;
return InLocalStorageCSFactory;
}
3 changes: 3 additions & 0 deletions src/storages/inMemory/InMemoryStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ImpressionsCacheInMemory from './ImpressionsCacheInMemory';
import EventsCacheInMemory from './EventsCacheInMemory';
import { IStorageFactoryParams, IStorageSync } from '../types';
import ImpressionCountsCacheInMemory from './ImpressionCountsCacheInMemory';
import { STORAGE_MEMORY } from '../../utils/constants';

/**
* InMemory storage factory for standalone server-side SplitFactory
Expand Down Expand Up @@ -32,3 +33,5 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
}
};
}

InMemoryStorageFactory.type = STORAGE_MEMORY;
3 changes: 3 additions & 0 deletions src/storages/inMemory/InMemoryStorageCS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ImpressionsCacheInMemory from './ImpressionsCacheInMemory';
import EventsCacheInMemory from './EventsCacheInMemory';
import { IStorageSyncCS, IStorageFactoryParams } from '../types';
import ImpressionCountsCacheInMemory from './ImpressionCountsCacheInMemory';
import { STORAGE_MEMORY } from '../../utils/constants';

/**
* InMemory storage factory for standalone client-side SplitFactory
Expand Down Expand Up @@ -46,3 +47,5 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
},
};
}

InMemoryStorageCSFactory.type = STORAGE_MEMORY;
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('IMPRESSIONS CACHE IN REDIS', () => {
await connection.quit();
});

test('`track` should not resolve before calling expire', async (done) => {
test('`track` should not resolve before calling expire', async () => {
const impressionsKey = 'impr_cache_ut_2.impressions';
const connection = new Redis(loggerMock, {});

Expand All @@ -74,7 +74,7 @@ describe('IMPRESSIONS CACHE IN REDIS', () => {
await connection.del(impressionsKey);

// @ts-expect-error
c.track([i1, i2]).then(() => {
await c.track([i1, i2]).then(() => {
connection.del(impressionsKey);
connection.quit(); // Try to disconnect right away.
expect(spy1).toBeCalled(); // Redis rpush was called once before executing external callback.
Expand All @@ -86,7 +86,6 @@ describe('IMPRESSIONS CACHE IN REDIS', () => {
// Finally clean up and wrap up.
spy1.mockRestore();
spy2.mockRestore();
done();
});
});

Expand Down
12 changes: 8 additions & 4 deletions src/storages/inRedis/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import RedisAdapter from './RedisAdapter';
import { IStorageAsync, IStorageFactoryParams } from '../types';
import { IStorageAsync, IStorageAsyncFactory, IStorageFactoryParams } from '../types';
import { validatePrefix } from '../KeyBuilder';
import KeyBuilderSS from '../KeyBuilderSS';
import SplitsCacheInRedis from './SplitsCacheInRedis';
Expand All @@ -8,6 +8,7 @@ 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 {
prefix?: string
Expand All @@ -18,11 +19,11 @@ export interface InRedisStorageOptions {
* InRedis storage factory for consumer server-side SplitFactory, that uses `Ioredis` Redis client for Node.
* @see {@link https://www.npmjs.com/package/ioredis}
*/
export function InRedisStorage(options: InRedisStorageOptions = {}) {
export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsyncFactory {

const prefix = validatePrefix(options.prefix);

return function InRedisStorageFactory({ log, metadata, onReadyCb }: IStorageFactoryParams): IStorageAsync {
function InRedisStorageFactory({ log, metadata, onReadyCb }: IStorageFactoryParams): IStorageAsync {

const keys = new KeyBuilderSS(prefix, metadata);
const redisClient = new RedisAdapter(log, options.options || {});
Expand All @@ -47,5 +48,8 @@ export function InRedisStorage(options: InRedisStorageOptions = {}) {
// @TODO check that caches works as expected when redisClient is disconnected
}
};
};
}

InRedisStorageFactory.type = STORAGE_REDIS;
return InRedisStorageFactory;
}
12 changes: 8 additions & 4 deletions src/storages/pluggable/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ICustomStorageWrapper, IStorageAsync, IStorageFactoryParams } from '../types';
import { ICustomStorageWrapper, IStorageAsync, IStorageAsyncFactory, IStorageFactoryParams } from '../types';

import KeyBuilderSS from '../KeyBuilderSS';
import { SplitsCachePluggable } from './SplitsCachePluggable';
Expand All @@ -8,6 +8,7 @@ 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';

const NO_VALID_WRAPPER = 'Expecting custom 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.';
Expand All @@ -34,13 +35,13 @@ function validatePluggableStorageOptions(options: any) {
/**
* Pluggable storage factory for consumer server-side & client-side SplitFactory.
*/
export function PluggableStorage(options: PluggableStorageOptions) {
export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyncFactory {

validatePluggableStorageOptions(options);

const prefix = validatePrefix(options.prefix);

return function PluggableStorageFactory({ log, metadata, onReadyCb }: IStorageFactoryParams): IStorageAsync {
function PluggableStorageFactory({ log, metadata, onReadyCb }: IStorageFactoryParams): IStorageAsync {
const keys = new KeyBuilderSS(prefix, metadata);
const wrapper = wrapperAdapter(log, options.wrapper);

Expand All @@ -63,5 +64,8 @@ export function PluggableStorage(options: PluggableStorageOptions) {
return wrapper.close();
}
};
};
}

PluggableStorageFactory.type = STORAGE_CUSTOM;
return PluggableStorageFactory;
}
12 changes: 12 additions & 0 deletions src/storages/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,3 +439,15 @@ export interface IStorageFactoryParams {
onReadyCb?: (error?: any) => void,
metadata: IMetadata,
}

export type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'CUSTOM';

export type IStorageSyncFactory = {
type: StorageType,
(params: IStorageFactoryParams): IStorageSync
}

export type IStorageAsyncFactory = {
type: StorageType,
(params: IStorageFactoryParams): IStorageAsync
}
1 change: 0 additions & 1 deletion src/sync/offline/syncTasks/fromObjectSyncTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ export function fromObjectUpdaterFactory(

return Promise.all([
splitsCache.clear(), // required to sync removed splits from mock
splitsCache.setChangeNumber(Date.now()),
splitsCache.addSplits(splits)
]).then(() => {
readiness.splits.emit(SDK_SPLITS_ARRIVED);
Expand Down
9 changes: 7 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ISplitFiltersValidation } from './dtos/types';
import { IIntegration, IIntegrationFactoryParams } from './integrations/types';
import { ILogger } from './logger/types';
/* eslint-disable no-use-before-define */

import { IStorageFactoryParams, IStorageSyncCS, IStorageSync, IStorageAsync } from './storages/types';
import { IStorageFactoryParams, IStorageSyncCS, IStorageSync, IStorageAsync, IStorageSyncFactory } from './storages/types';

/**
* EventEmitter interface with the minimal methods used by the SDK
Expand Down Expand Up @@ -55,6 +56,8 @@ export type SDKMode = 'standalone' | 'consumer' | 'localhost';
* 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.
* @interface ISettings
*
* NOTE: same ISettings interface from public type declarations extended with private properties.
*/
export interface ISettings {
readonly core: {
Expand All @@ -81,7 +84,7 @@ export interface ISettings {
retriesOnFailureBeforeReady: number,
eventsFirstPushWindow: number
},
readonly storage: (params: IStorageFactoryParams) => IStorageSyncCS,
readonly storage: IStorageSyncFactory,
readonly integrations?: Array<(params: IIntegrationFactoryParams) => IIntegration | void>,
readonly urls: {
events: string,
Expand All @@ -96,12 +99,14 @@ export interface ISettings {
readonly sync: {
splitFilters: SplitIO.SplitFilter[],
impressionsMode: SplitIO.ImpressionsMode,
__splitFiltersValidation: ISplitFiltersValidation
},
readonly runtime: {
ip: string | false
hostname: string | false
},
readonly log: ILogger
readonly impressionListener?: unknown
}
/**
* Log levels.
Expand Down
7 changes: 6 additions & 1 deletion src/utils/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { StorageType } from '../../storages/types';

// Special treatments
export const CONTROL = 'control';
export const CONTROL_WITH_CONFIG = {
Expand All @@ -24,4 +26,7 @@ export const PRODUCER_MODE = 'producer';
export const CONSUMER_MODE = 'consumer';

// Storage types
export const STORAGE_MEMORY = 'MEMORY';
export const STORAGE_MEMORY: StorageType = 'MEMORY';
export const STORAGE_LOCALSTORAGE: StorageType = 'LOCALSTORAGE';
export const STORAGE_REDIS: StorageType = 'REDIS';
export const STORAGE_CUSTOM: StorageType = 'CUSTOM';
Loading