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
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 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
Expand Down
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
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