Skip to content

Commit e2c26af

Browse files
tanderson-ldTodd Anderson
andauthored
chore: Adds LDDataSystemOptions for configuring the Data System. (#794)
**Requirements** - [x] I have added test coverage for new or changed functionality - [x] I have followed the repository's [pull request submission guidelines](../blob/main/CONTRIBUTING.md#submitting-pull-requests) - [ ] I have validated my changes against all supported platform versions Will be done on target temp branch eventually. **Related issues** SDK-857 and SDK-1073 --------- Co-authored-by: Todd Anderson <[email protected]>
1 parent d1d9e0f commit e2c26af

File tree

9 files changed

+611
-96
lines changed

9 files changed

+611
-96
lines changed

packages/shared/sdk-server/__tests__/options/Configuration.test.ts

Lines changed: 142 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
1-
import { LDOptions } from '../../src';
1+
import {
2+
ClientContext,
3+
DataSourceOptions,
4+
isStandardOptions,
5+
LDFeatureStore,
6+
LDOptions,
7+
PollingDataSourceOptions,
8+
StandardDataSourceOptions,
9+
} from '../../src';
210
import Configuration from '../../src/options/Configuration';
11+
import InMemoryFeatureStore from '../../src/store/InMemoryFeatureStore';
312
import TestLogger, { LogLevel } from '../Logger';
413

514
function withLogger(options: LDOptions): LDOptions {
@@ -13,7 +22,7 @@ function logger(options: LDOptions): TestLogger {
1322
describe.each([undefined, null, 'potat0', 17, [], {}])('constructed without options', (input) => {
1423
it('should have default options', () => {
1524
// JavaScript is not going to stop you from calling this with whatever
16-
// you want. So we need to tell TS to ingore our bad behavior.
25+
// you want. So we need to tell TS to ignore our bad behavior.
1726
// @ts-ignore
1827
const config = new Configuration(input);
1928

@@ -25,19 +34,20 @@ describe.each([undefined, null, 'potat0', 17, [], {}])('constructed without opti
2534
expect(config.flushInterval).toBe(5);
2635
expect(config.logger).toBeUndefined();
2736
expect(config.offline).toBe(false);
28-
expect(config.pollInterval).toBe(30);
37+
expect((config.dataSystem.dataSource as StandardDataSourceOptions).pollInterval).toEqual(30);
2938
expect(config.privateAttributes).toStrictEqual([]);
3039
expect(config.proxyOptions).toBeUndefined();
3140
expect(config.sendEvents).toBe(true);
3241
expect(config.serviceEndpoints.streaming).toEqual('https://stream.launchdarkly.com');
3342
expect(config.serviceEndpoints.polling).toEqual('https://sdk.launchdarkly.com');
3443
expect(config.serviceEndpoints.events).toEqual('https://events.launchdarkly.com');
35-
expect(config.stream).toBe(true);
36-
expect(config.streamInitialReconnectDelay).toEqual(1);
44+
expect(
45+
(config.dataSystem.dataSource as StandardDataSourceOptions).streamInitialReconnectDelay,
46+
).toEqual(1);
3747
expect(config.tags.value).toBeUndefined();
3848
expect(config.timeout).toEqual(5);
3949
expect(config.tlsParams).toBeUndefined();
40-
expect(config.useLdd).toBe(false);
50+
expect(config.dataSystem.useLdd).toBe(false);
4151
expect(config.wrapperName).toBeUndefined();
4252
expect(config.wrapperVersion).toBeUndefined();
4353
expect(config.hooks).toBeUndefined();
@@ -179,7 +189,9 @@ describe('when setting different options', () => {
179189
])('allow setting and validates pollInterval', (value, expected, warnings) => {
180190
// @ts-ignore
181191
const config = new Configuration(withLogger({ pollInterval: value }));
182-
expect(config.pollInterval).toEqual(expected);
192+
expect((config.dataSystem.dataSource as StandardDataSourceOptions).pollInterval).toEqual(
193+
expected,
194+
);
183195
expect(logger(config).getCount()).toEqual(warnings);
184196
});
185197

@@ -207,7 +219,7 @@ describe('when setting different options', () => {
207219
])('allows setting stream and validates stream', (value, expected, warnings) => {
208220
// @ts-ignore
209221
const config = new Configuration(withLogger({ stream: value }));
210-
expect(config.stream).toEqual(expected);
222+
expect(isStandardOptions(config.dataSystem.dataSource)).toEqual(expected);
211223
expect(logger(config).getCount()).toEqual(warnings);
212224
});
213225

@@ -221,7 +233,7 @@ describe('when setting different options', () => {
221233
])('allows setting stream and validates useLdd', (value, expected, warnings) => {
222234
// @ts-ignore
223235
const config = new Configuration(withLogger({ useLdd: value }));
224-
expect(config.useLdd).toEqual(expected);
236+
expect(config.dataSystem.useLdd).toEqual(expected);
225237
expect(logger(config).getCount()).toEqual(warnings);
226238
});
227239

@@ -408,4 +420,125 @@ describe('when setting different options', () => {
408420
},
409421
]);
410422
});
423+
424+
it('drops invalid datasystem data source options and replaces with defaults', () => {
425+
const config = new Configuration(
426+
withLogger({
427+
dataSystem: { dataSource: { bogus: 'myBogusOptions' } as unknown as DataSourceOptions },
428+
}),
429+
);
430+
expect(isStandardOptions(config.dataSystem.dataSource)).toEqual(true);
431+
logger(config).expectMessages([
432+
{
433+
level: LogLevel.Warn,
434+
matches: /Config option "dataSource" should be of type DataSourceOptions/,
435+
},
436+
]);
437+
});
438+
439+
it('validates the datasystem persistent store is a factory or object', () => {
440+
const config1 = new Configuration(
441+
withLogger({
442+
dataSystem: {
443+
persistentStore: () => new InMemoryFeatureStore(),
444+
},
445+
}),
446+
);
447+
expect(isStandardOptions(config1.dataSystem.dataSource)).toEqual(true);
448+
expect(logger(config1).getCount()).toEqual(0);
449+
450+
const config2 = new Configuration(
451+
withLogger({
452+
dataSystem: {
453+
persistentStore: 'bogus type' as unknown as LDFeatureStore,
454+
},
455+
}),
456+
);
457+
expect(isStandardOptions(config2.dataSystem.dataSource)).toEqual(true);
458+
logger(config2).expectMessages([
459+
{
460+
level: LogLevel.Warn,
461+
matches: /Config option "persistentStore" should be of type LDFeatureStore/,
462+
},
463+
]);
464+
});
465+
466+
it('provides reasonable defaults when datasystem is provided, but some options are missing', () => {
467+
const config = new Configuration(
468+
withLogger({
469+
dataSystem: {},
470+
}),
471+
);
472+
expect(isStandardOptions(config.dataSystem.dataSource)).toEqual(true);
473+
expect(logger(config).getCount()).toEqual(0);
474+
});
475+
476+
it('provides reasonable defaults within the dataSystem.dataSource options when they are missing', () => {
477+
const config = new Configuration(
478+
withLogger({
479+
dataSystem: { dataSource: { type: 'standard' } },
480+
}),
481+
);
482+
expect(isStandardOptions(config.dataSystem.dataSource)).toEqual(true);
483+
expect(logger(config).getCount()).toEqual(0);
484+
});
485+
486+
it('ignores deprecated top level options when dataSystem.dataSource options are provided', () => {
487+
const config = new Configuration(
488+
withLogger({
489+
pollInterval: 501, // should be ignored
490+
streamInitialReconnectDelay: 502, // should be ignored
491+
dataSystem: {
492+
dataSource: { type: 'standard', pollInterval: 100, streamInitialReconnectDelay: 200 }, // should be used
493+
},
494+
}),
495+
);
496+
expect(isStandardOptions(config.dataSystem.dataSource)).toEqual(true);
497+
expect((config.dataSystem.dataSource as StandardDataSourceOptions).pollInterval).toEqual(100);
498+
expect(
499+
(config.dataSystem.dataSource as StandardDataSourceOptions).streamInitialReconnectDelay,
500+
).toEqual(200);
501+
expect(logger(config).getCount()).toEqual(0);
502+
});
503+
504+
it('ignores top level featureStore in favor of the datasystem persistent store', () => {
505+
const shouldNotBeUsed = new InMemoryFeatureStore();
506+
const shouldBeUsed = new InMemoryFeatureStore();
507+
const config = new Configuration(
508+
withLogger({
509+
featureStore: shouldNotBeUsed,
510+
dataSystem: {
511+
persistentStore: shouldBeUsed,
512+
},
513+
}),
514+
);
515+
// @ts-ignore
516+
const result = config.dataSystem.featureStoreFactory(null);
517+
expect(result).toEqual(shouldBeUsed);
518+
});
519+
520+
it('ignores top level useLdd option if datasystem is specified', () => {
521+
const config = new Configuration(
522+
withLogger({
523+
dataSystem: {
524+
persistentStore: new InMemoryFeatureStore(),
525+
},
526+
useLdd: true,
527+
}),
528+
);
529+
const result = config.dataSystem.useLdd;
530+
expect(result).toEqual(undefined);
531+
532+
const config2 = new Configuration(
533+
withLogger({
534+
dataSystem: {
535+
persistentStore: new InMemoryFeatureStore(),
536+
useLdd: true,
537+
},
538+
useLdd: false,
539+
}),
540+
);
541+
const result2 = config2.dataSystem.useLdd;
542+
expect(result2).toEqual(true);
543+
});
411544
});

packages/shared/sdk-server/src/LDClientImpl.ts

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ import {
3030
import { Hook } from './api/integrations/Hook';
3131
import { BigSegmentStoreMembership } from './api/interfaces';
3232
import { LDWaitForInitializationOptions } from './api/LDWaitForInitializationOptions';
33+
import {
34+
isPollingOnlyOptions,
35+
isStandardOptions,
36+
isStreamingOnlyOptions,
37+
} from './api/options/LDDataSystemOptions';
3338
import BigSegmentsManager from './BigSegmentsManager';
3439
import BigSegmentStoreStatusProvider from './BigSegmentStatusProviderImpl';
3540
import { createStreamListeners } from './data_sources/createStreamListeners';
@@ -166,7 +171,7 @@ export default class LDClientImpl implements LDClient {
166171
const baseHeaders = defaultHeaders(_sdkKey, _platform.info, config.tags);
167172

168173
const clientContext = new ClientContext(_sdkKey, config, _platform);
169-
const featureStore = config.featureStoreFactory(clientContext);
174+
const featureStore = config.dataSystem.featureStoreFactory(clientContext);
170175

171176
const dataSourceUpdates = new DataSourceUpdates(featureStore, hasEventListeners, onUpdate);
172177

@@ -219,29 +224,38 @@ export default class LDClientImpl implements LDClient {
219224
const listeners = createStreamListeners(dataSourceUpdates, this._logger, {
220225
put: () => this._initSuccess(),
221226
});
222-
const makeDefaultProcessor = () =>
223-
config.stream
224-
? new StreamingProcessor(
225-
clientContext,
226-
'/all',
227-
[],
228-
listeners,
229-
baseHeaders,
230-
this._diagnosticsManager,
231-
(e) => this._dataSourceErrorHandler(e),
232-
this._config.streamInitialReconnectDelay,
233-
)
234-
: new PollingProcessor(
235-
config,
236-
new Requestor(config, this._platform.requests, baseHeaders),
237-
dataSourceUpdates,
238-
() => this._initSuccess(),
239-
(e) => this._dataSourceErrorHandler(e),
240-
);
227+
const makeDefaultProcessor = () => {
228+
if (isPollingOnlyOptions(config.dataSystem.dataSource)) {
229+
return new PollingProcessor(
230+
new Requestor(config, this._platform.requests, baseHeaders),
231+
config.dataSystem.dataSource.pollInterval ?? 30,
232+
dataSourceUpdates,
233+
config.logger,
234+
() => this._initSuccess(),
235+
(e) => this._dataSourceErrorHandler(e),
236+
);
237+
}
238+
// TODO: SDK-858 Hook up composite data source and config
239+
const reconnectDelay =
240+
isStandardOptions(config.dataSystem.dataSource) ||
241+
isStreamingOnlyOptions(config.dataSystem.dataSource)
242+
? config.dataSystem.dataSource.streamInitialReconnectDelay
243+
: 1;
244+
return new StreamingProcessor(
245+
clientContext,
246+
'/all',
247+
[],
248+
listeners,
249+
baseHeaders,
250+
this._diagnosticsManager,
251+
(e) => this._dataSourceErrorHandler(e),
252+
reconnectDelay,
253+
);
254+
};
241255

242-
if (!(config.offline || config.useLdd)) {
256+
if (!(config.offline || config.dataSystem.useLdd)) {
243257
this._updateProcessor =
244-
config.updateProcessorFactory?.(
258+
config.dataSystem.updateProcessorFactory?.(
245259
clientContext,
246260
dataSourceUpdates,
247261
() => this._initSuccess(),

0 commit comments

Comments
 (0)