diff --git a/CHANGES.txt b/CHANGES.txt index 5f82f62..4a2ac8e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,8 @@ -1.2.0 (September 9, 2025) +1.2.0 (September 12, 2025) - Up to date with @openfeature/server-sdk 1.19.0 - Added tracking support - Added “evaluate with details” support + - Support provider initialization using splitFactory 1.1.0 (June 16, 2025) - Uses renamed @openfeature/js-sdk to @openfeature/server-sdk diff --git a/README.md b/README.md index 9bcf14f..17b2ad4 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,29 @@ npm install @splitsoftware/splitio npm install @openfeature/server-sdk ``` -### Register the Split provider with OpenFeature +### Register the Split provider with OpenFeature using sdk apiKey +```js +const OpenFeature = require('@openfeature/server-sdk').OpenFeature; +const OpenFeatureSplitProvider = require('@splitsoftware/openfeature-js-split-provider').OpenFeatureSplitProvider; + +const authorizationKey = 'your auth key' +const provider = new OpenFeatureSplitProvider(authorizationKey); +OpenFeature.setProvider(provider); +``` + +### Register the Split provider with OpenFeature using splitFactory +```js +const OpenFeature = require('@openfeature/server-sdk').OpenFeature; +const SplitFactory = require('@splitsoftware/splitio').SplitFactory; +const OpenFeatureSplitProvider = require('@splitsoftware/openfeature-js-split-provider').OpenFeatureSplitProvider; + +const authorizationKey = 'your auth key' +const splitFactory = SplitFactory({core: {authorizationKey}}); +const provider = new OpenFeatureSplitProvider(splitFactory); +OpenFeature.setProvider(provider); +``` + +### Register the Split provider with OpenFeature using splitClient ```js const OpenFeature = require('@openfeature/server-sdk').OpenFeature; const SplitFactory = require('@splitsoftware/splitio').SplitFactory; diff --git a/src/__tests__/nodeSuites/client.spec.js b/src/__tests__/nodeSuites/client.spec.js index e2faccc..83b0b0d 100644 --- a/src/__tests__/nodeSuites/client.spec.js +++ b/src/__tests__/nodeSuites/client.spec.js @@ -1,19 +1,31 @@ /* eslint-disable jest/no-conditional-expect */ import { OpenFeatureSplitProvider } from '../../lib/js-split-provider'; -import { getLocalHostSplitClient } from '../testUtils'; +import { getLocalHostSplitClient, getSplitFactory } from '../testUtils'; import { OpenFeature } from '@openfeature/server-sdk'; -describe('client tests', () => { +const cases = [ + [ + 'openfeature client tests mode: splitClient', + () => ({ splitClient: getLocalHostSplitClient()}), + + ], + [ + 'openfeature client tests mode: splitFactory', + getSplitFactory + ], +]; + +describe.each(cases)('%s', (label, getOptions) => { let client; - let splitClient; let provider; + let options; beforeEach(() => { - splitClient = getLocalHostSplitClient(); - provider = new OpenFeatureSplitProvider({ splitClient }); + options = getOptions(); + provider = new OpenFeatureSplitProvider(options); OpenFeature.setProvider(provider); client = OpenFeature.getClient('test'); @@ -22,9 +34,8 @@ describe('client tests', () => { }; client.setContext(evaluationContext); }); - afterEach(() => { - splitClient.destroy(); - provider = undefined; + afterEach(async () => { + await OpenFeature.close(); }); test('use default test', async () => { @@ -206,13 +217,13 @@ describe('client tests', () => { }); test('track: without value', async () => { - const trackSpy = jest.spyOn(splitClient, 'track'); + const trackSpy = jest.spyOn(options.splitClient ? options.splitClient : options.client(), 'track'); await client.track('my-event', { targetingKey: 'u1', trafficType: 'user' }, { properties: { prop1: 'value1' } }); expect(trackSpy).toHaveBeenCalledWith('u1', 'user', 'my-event', undefined, { prop1: 'value1' }); }); test('track: with value', async () => { - const trackSpy = jest.spyOn(splitClient, 'track'); + const trackSpy = jest.spyOn(options.splitClient ? options.splitClient : options.client(), 'track'); await client.track('my-event', { targetingKey: 'u1', trafficType: 'user' }, { value: 9.99, properties: { prop1: 'value1' } }); expect(trackSpy).toHaveBeenCalledWith('u1', 'user', 'my-event', 9.99, { prop1: 'value1' }); }); diff --git a/src/__tests__/nodeSuites/provider.spec.js b/src/__tests__/nodeSuites/provider.spec.js index abc1aa8..c9ea3d1 100644 --- a/src/__tests__/nodeSuites/provider.spec.js +++ b/src/__tests__/nodeSuites/provider.spec.js @@ -1,20 +1,32 @@ /* eslint-disable jest/no-conditional-expect */ -import { getLocalHostSplitClient } from '../testUtils'; +import { getLocalHostSplitClient, getSplitFactory } from '../testUtils'; import { OpenFeatureSplitProvider } from '../../lib/js-split-provider'; -describe('provider tests', () => { +const cases = [ + [ + 'provider tests mode: splitClient', + () => ({ splitClient: getLocalHostSplitClient()}), + + ], + [ + 'provider tests mode: splitFactory', + getSplitFactory + ], +]; + +describe.each(cases)('%s', (label, getOptions) => { - let splitClient; let provider; + let options; beforeEach(() => { - splitClient = getLocalHostSplitClient(); - provider = new OpenFeatureSplitProvider({ splitClient }); + options = getOptions(); + provider = new OpenFeatureSplitProvider(options); }); - afterEach(() => { - splitClient.destroy(); - provider = undefined; + afterEach(async () => { + jest.clearAllMocks() + await provider.onClose(); }); test('evaluate Boolean null/empty test', async () => { @@ -210,14 +222,14 @@ describe('provider tests', () => { }); test('track: ok without details', async () => { - const trackSpy = jest.spyOn(splitClient, 'track'); + const trackSpy = jest.spyOn(options.splitClient ? options.splitClient : options.client(), 'track'); await provider.track('view', { targetingKey: 'u1', trafficType: 'user' }, null); expect(trackSpy).toHaveBeenCalledTimes(1); expect(trackSpy).toHaveBeenCalledWith('u1', 'user', 'view', undefined, {}); }); test('track: ok with details', async () => { - const trackSpy = jest.spyOn(splitClient, 'track'); + const trackSpy = jest.spyOn(options.splitClient ? options.splitClient : options.client(), 'track'); await provider.track( 'purchase', { targetingKey: 'u1', trafficType: 'user' }, diff --git a/src/__tests__/testUtils/index.js b/src/__tests__/testUtils/index.js index 2f71380..4e7db2b 100644 --- a/src/__tests__/testUtils/index.js +++ b/src/__tests__/testUtils/index.js @@ -95,7 +95,6 @@ const config = { authorizationKey: 'localhost' }, features: './split.yaml', - debug: 'DEBUG' } /** * get a Split client in localhost mode for testing purposes @@ -106,4 +105,8 @@ export function getLocalHostSplitClient() { export function getRedisSplitClient(redisPort) { return SplitFactory(getRedisConfig(redisPort)).client(); -} \ No newline at end of file +} + +export function getSplitFactory() { + return SplitFactory(config); +} diff --git a/src/lib/js-split-provider.ts b/src/lib/js-split-provider.ts index 9f9455c..9c58d66 100644 --- a/src/lib/js-split-provider.ts +++ b/src/lib/js-split-provider.ts @@ -36,16 +36,28 @@ export class OpenFeatureSplitProvider implements Provider { public readonly events = new OpenFeatureEventEmitter(); - constructor(options: SplitProviderOptions | string) { - + private getSplitClient(options: SplitProviderOptions | string | SplitIO.ISDK | SplitIO.IAsyncSDK) { if (typeof(options) === 'string') { const splitFactory = SplitFactory({core: { authorizationKey: options } }); - this.client = splitFactory.client(); - } else { - this.client = options.splitClient; + return splitFactory.client(); + } + + let splitClient; + try { + splitClient = (options as SplitIO.ISDK | SplitIO.IAsyncSDK).client(); + } catch { + splitClient = (options as SplitProviderOptions).splitClient } - this.client.on(this.client.Event.SDK_UPDATE, (payload) => { - this.events.emit(ProviderEvents.ConfigurationChanged, payload) + + return splitClient; + } + + constructor(options: SplitProviderOptions | string | SplitIO.ISDK | SplitIO.IAsyncSDK) { + + this.client = this.getSplitClient(options); + + this.client.on(this.client.Event.SDK_UPDATE, () => { + this.events.emit(ProviderEvents.ConfigurationChanged) }); this.initialized = new Promise((resolve) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -191,6 +203,10 @@ export class OpenFeatureSplitProvider implements Provider { this.client.track(targetingKey, trafficType, trackingEventName, value, properties); } + async onClose?(): Promise { + return this.client.destroy(); + } + //Transform the context into an object useful for the Split API, an key string with arbitrary Split 'Attributes'. private transformContext(context: EvaluationContext): Consumer { const { targetingKey, ...attributes } = context;