Skip to content

Commit 7070b14

Browse files
Merge pull request #276 from splitio/manager_async_fix
Update manager methods to return a promise in consumer modes while the SDK is not operational
2 parents 50cd611 + 12a7952 commit 7070b14

File tree

7 files changed

+75
-26
lines changed

7 files changed

+75
-26
lines changed

CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
1.11.1 (December XX, 2023)
2+
- Bugfixing - Fixed manager methods in consumer modes to return results in a promise when the SDK is not operational (not ready or destroyed).
3+
14
1.11.0 (November 3, 2023)
25
- Added support for Flag Sets on the SDK, which enables grouping feature flags and interacting with the group rather than individually (more details in our documentation):
36
- Added new variations of the get treatment methods to support evaluating flags in given flag set/s.

src/sdkFactory/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
8383

8484
// SDK client and manager
8585
const clientMethod = sdkClientMethodFactory(ctx);
86-
const managerInstance = sdkManagerFactory(log, storage.splits, sdkReadinessManager);
86+
const managerInstance = sdkManagerFactory(settings, storage.splits, sdkReadinessManager);
8787

8888
syncManager && syncManager.start();
8989
signalListener && signalListener.start();

src/sdkFactory/types.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { IIntegrationManager, IIntegrationFactoryParams } from '../integrations/types';
22
import { ISignalListener } from '../listeners/types';
3-
import { ILogger } from '../logger/types';
43
import { IReadinessManager, ISdkReadinessManager } from '../readiness/types';
4+
import type { sdkManagerFactory } from '../sdkManager';
55
import { IFetch, ISplitApi, IEventSourceConstructor } from '../services/types';
6-
import { IStorageAsync, IStorageSync, ISplitsCacheSync, ISplitsCacheAsync, IStorageFactoryParams } from '../storages/types';
6+
import { IStorageAsync, IStorageSync, IStorageFactoryParams } from '../storages/types';
77
import { ISyncManager } from '../sync/types';
88
import { IImpressionObserver } from '../trackers/impressionObserver/types';
99
import { IImpressionsTracker, IEventTracker, ITelemetryTracker, IFilterAdapter, IUniqueKeysTracker } from '../trackers/types';
@@ -87,11 +87,7 @@ export interface ISdkFactoryParams {
8787
syncManagerFactory?: (params: ISdkFactoryContextSync) => ISyncManager,
8888

8989
// Sdk manager factory
90-
sdkManagerFactory: (
91-
log: ILogger,
92-
splits: ISplitsCacheSync | ISplitsCacheAsync,
93-
sdkReadinessManager: ISdkReadinessManager
94-
) => SplitIO.IManager | SplitIO.IAsyncManager,
90+
sdkManagerFactory: typeof sdkManagerFactory,
9591

9692
// Sdk client method factory (ISDK::client method).
9793
// It Allows to distinguish SDK clients with the client-side API (`ICsSDK`) or server-side API (`ISDK` or `IAsyncSDK`).

src/sdkManager/__tests__/index.asyncCache.spec.ts

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { KeyBuilderSS } from '../../storages/KeyBuilderSS';
99
import { ISdkReadinessManager } from '../../readiness/types';
1010
import { loggerMock } from '../../logger/__tests__/sdkLogger.mock';
1111
import { metadata } from '../../storages/__tests__/KeyBuilder.spec';
12+
import { SplitIO } from '../../types';
1213

1314
// @ts-expect-error
1415
const sdkReadinessManagerMock = {
@@ -21,16 +22,16 @@ const sdkReadinessManagerMock = {
2122

2223
const keys = new KeyBuilderSS('prefix', metadata);
2324

24-
describe('MANAGER API', () => {
25+
describe('Manager with async cache', () => {
2526

2627
afterEach(() => { loggerMock.mockClear(); });
2728

28-
test('Async cache (In Redis)', async () => {
29+
test('returns the expected data from the cache', async () => {
2930

3031
/** Setup: create manager */
3132
const connection = new Redis({});
3233
const cache = new SplitsCacheInRedis(loggerMock, keys, connection);
33-
const manager = sdkManagerFactory(loggerMock, cache, sdkReadinessManagerMock);
34+
const manager = sdkManagerFactory({ mode: 'consumer', log: loggerMock }, cache, sdkReadinessManagerMock);
3435
await cache.clear();
3536
await cache.addSplit(splitObject.name, splitObject as any);
3637

@@ -43,7 +44,6 @@ describe('MANAGER API', () => {
4344
const split = await manager.split(splitObject.name);
4445
expect(split).toEqual(splitView);
4546

46-
4747
/** List all the split names */
4848

4949
const names = await manager.names();
@@ -66,18 +66,16 @@ describe('MANAGER API', () => {
6666
expect(await manager.splits()).toEqual([]); // If the factory/client is destroyed, `manager.splits()` will return empty array either way since the storage is not valid.
6767
expect(await manager.names()).toEqual([]); // If the factory/client is destroyed, `manager.names()` will return empty array either way since the storage is not valid.
6868

69-
70-
7169
/** Teardown */
7270
await cache.removeSplit(splitObject.name);
7371
await connection.quit();
7472
});
7573

76-
test('Async cache with error', async () => {
74+
test('handles storage errors', async () => {
7775
// passing an empty object as wrapper, to make method calls of splits cache fail returning a rejected promise.
7876
// @ts-expect-error
7977
const cache = new SplitsCachePluggable(loggerMock, keys, wrapperAdapter(loggerMock, {}));
80-
const manager = sdkManagerFactory(loggerMock, cache, sdkReadinessManagerMock);
78+
const manager = sdkManagerFactory({ mode: 'consumer_partial', log: loggerMock }, cache, sdkReadinessManagerMock);
8179

8280
expect(await manager.split('some_spplit')).toEqual(null);
8381
expect(await manager.splits()).toEqual([]);
@@ -86,4 +84,33 @@ describe('MANAGER API', () => {
8684
expect(loggerMock.error).toBeCalledTimes(3); // 3 error logs, one for each attempt to call a wrapper method
8785
});
8886

87+
test('returns empty results when not operational', async () => {
88+
// SDK is flagged as destroyed
89+
const sdkReadinessManagerMock = {
90+
readinessManager: {
91+
isReady: () => true,
92+
isReadyFromCache: () => true,
93+
isDestroyed: () => true
94+
},
95+
sdkStatus: {}
96+
};
97+
// @ts-expect-error
98+
const manager = sdkManagerFactory({ mode: 'consumer_partial', log: loggerMock }, {}, sdkReadinessManagerMock) as SplitIO.IAsyncManager;
99+
100+
function validateManager() {
101+
expect(manager.split('some_spplit')).resolves.toBe(null);
102+
expect(manager.splits()).resolves.toEqual([]);
103+
expect(manager.names()).resolves.toEqual([]);
104+
}
105+
106+
validateManager();
107+
108+
// SDK is not ready
109+
sdkReadinessManagerMock.readinessManager.isReady = () => false;
110+
sdkReadinessManagerMock.readinessManager.isReadyFromCache = () => false;
111+
sdkReadinessManagerMock.readinessManager.isDestroyed = () => false;
112+
113+
validateManager();
114+
});
115+
89116
});

src/sdkManager/__tests__/index.syncCache.spec.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ const sdkReadinessManagerMock = {
1414
sdkStatus: jest.fn()
1515
} as ISdkReadinessManager;
1616

17-
describe('MANAGER API / Sync cache (In Memory)', () => {
17+
describe('Manager with sync cache (In Memory)', () => {
1818

1919
/** Setup: create manager */
2020
const cache = new SplitsCacheInMemory();
21-
const manager = sdkManagerFactory(loggerMock, cache, sdkReadinessManagerMock);
21+
const manager = sdkManagerFactory({ mode: 'standalone', log: loggerMock }, cache, sdkReadinessManagerMock);
2222
cache.addSplit(splitObject.name, splitObject as any);
2323

2424
test('List all splits', () => {
@@ -57,4 +57,24 @@ describe('MANAGER API / Sync cache (In Memory)', () => {
5757
expect(manager.names()).toEqual([]); // If the factory/client is destroyed, `manager.names()` will return empty array either way since the storage is not valid.
5858
});
5959

60+
test('returns empty results when not operational', async () => {
61+
// SDK is flagged as destroyed
62+
sdkReadinessManagerMock.readinessManager.isDestroyed = () => true;
63+
64+
function validateManager() {
65+
expect(manager.split('some_spplit')).toBe(null);
66+
expect(manager.splits()).toEqual([]);
67+
expect(manager.names()).toEqual([]);
68+
}
69+
70+
validateManager();
71+
72+
// SDK is not ready
73+
sdkReadinessManagerMock.readinessManager.isReady = () => false;
74+
sdkReadinessManagerMock.readinessManager.isReadyFromCache = () => false;
75+
sdkReadinessManagerMock.readinessManager.isDestroyed = () => false;
76+
77+
validateManager();
78+
});
79+
6080
});

src/sdkManager/index.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { validateSplit, validateSplitExistance, validateIfNotDestroyed, validate
55
import { ISplitsCacheAsync, ISplitsCacheSync } from '../storages/types';
66
import { ISdkReadinessManager } from '../readiness/types';
77
import { ISplit } from '../dtos/types';
8-
import { SplitIO } from '../types';
9-
import { ILogger } from '../logger/types';
8+
import { ISettings, SplitIO } from '../types';
9+
import { isStorageSync } from '../trackers/impressionObserver/utils';
1010

1111
const SPLIT_FN_LABEL = 'split';
1212
const SPLITS_FN_LABEL = 'splits';
@@ -49,11 +49,14 @@ function objectsToViews(splitObjects: ISplit[]) {
4949
}
5050

5151
export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplitsCacheAsync>(
52-
log: ILogger,
52+
settings: Pick<ISettings, 'log' | 'mode'>,
5353
splits: TSplitCache,
54-
{ readinessManager, sdkStatus }: ISdkReadinessManager
54+
{ readinessManager, sdkStatus }: ISdkReadinessManager,
5555
): TSplitCache extends ISplitsCacheAsync ? SplitIO.IAsyncManager : SplitIO.IManager {
5656

57+
const log = settings.log;
58+
const isSync = isStorageSync(settings);
59+
5760
return objectAssign(
5861
// Proto-linkage of the readiness Event Emitter
5962
Object.create(sdkStatus),
@@ -64,7 +67,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
6467
split(featureFlagName: string) {
6568
const splitName = validateSplit(log, featureFlagName, SPLIT_FN_LABEL);
6669
if (!validateIfNotDestroyed(log, readinessManager, SPLIT_FN_LABEL) || !validateIfOperational(log, readinessManager, SPLIT_FN_LABEL) || !splitName) {
67-
return null;
70+
return isSync ? null : Promise.resolve(null);
6871
}
6972

7073
const split = splits.getSplit(splitName);
@@ -85,7 +88,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
8588
*/
8689
splits() {
8790
if (!validateIfNotDestroyed(log, readinessManager, SPLITS_FN_LABEL) || !validateIfOperational(log, readinessManager, SPLITS_FN_LABEL)) {
88-
return [];
91+
return isSync ? [] : Promise.resolve([]);
8992
}
9093
const currentSplits = splits.getAll();
9194

@@ -98,7 +101,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
98101
*/
99102
names() {
100103
if (!validateIfNotDestroyed(log, readinessManager, NAMES_FN_LABEL) || !validateIfOperational(log, readinessManager, NAMES_FN_LABEL)) {
101-
return [];
104+
return isSync ? [] : Promise.resolve([]);
102105
}
103106
const splitNames = splits.getSplitNames();
104107

src/trackers/impressionObserver/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ import { ISettings } from '../../types';
44
/**
55
* Storage is async if mode is consumer or partial consumer
66
*/
7-
export function isStorageSync(settings: ISettings) {
7+
export function isStorageSync(settings: Pick<ISettings, 'mode'>) {
88
return [CONSUMER_MODE, CONSUMER_PARTIAL_MODE].indexOf(settings.mode) === -1 ? true : false;
99
}

0 commit comments

Comments
 (0)