Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
3 changes: 2 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
1.2.0 (September 9, 2025)
1.2.0 (September 11, 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
Expand Down
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ npm install @splitsoftware/splitio
npm install @openfeature/server-sdk
```

### Register the Split provider with OpenFeature
### Register the Split provider with OpenFeature using splitClient
```js
const OpenFeature = require('@openfeature/server-sdk').OpenFeature;
const SplitFactory = require('@splitsoftware/splitio').SplitFactory;
Expand All @@ -35,6 +35,18 @@ const provider = new OpenFeatureSplitProvider({splitClient});
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);
```

## Use of OpenFeature with Split
After the initial setup you can use OpenFeature according to their [documentation](https://docs.openfeature.dev/docs/reference/concepts/evaluation-api/).

Expand Down
31 changes: 21 additions & 10 deletions src/__tests__/nodeSuites/client.spec.js
Original file line number Diff line number Diff line change
@@ -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');
Expand All @@ -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 () => {
Expand Down Expand Up @@ -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' });
});
Expand Down
32 changes: 22 additions & 10 deletions src/__tests__/nodeSuites/provider.spec.js
Original file line number Diff line number Diff line change
@@ -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 () => {
Expand Down Expand Up @@ -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' },
Expand Down
7 changes: 5 additions & 2 deletions src/__tests__/testUtils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ const config = {
authorizationKey: 'localhost'
},
features: './split.yaml',
debug: 'DEBUG'
}
/**
* get a Split client in localhost mode for testing purposes
Expand All @@ -106,4 +105,8 @@ export function getLocalHostSplitClient() {

export function getRedisSplitClient(redisPort) {
return SplitFactory(getRedisConfig(redisPort)).client();
}
}

export function getSplitFactory() {
return SplitFactory(config);
}
30 changes: 23 additions & 7 deletions src/lib/js-split-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -191,6 +203,10 @@ export class OpenFeatureSplitProvider implements Provider {
this.client.track(targetingKey, trafficType, trackingEventName, value, properties);
}

async onClose?(): Promise<void> {
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;
Expand Down