Skip to content

Commit e571b45

Browse files
Merge pull request #18 from splitio/fme-12878
Add events metadata
2 parents 1be7560 + 08d85f6 commit e571b45

18 files changed

+993
-211
lines changed

CHANGES.txt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
1.1.0 (February 27, 2026)
2+
- Added ProviderEvents.Ready payload with Split SdkReadyMetadata
3+
- Updated ConfigurationChanged to forward SdkUpdateMetadata in metadata
4+
- Requires @splitsoftware/splitio-browserjs ^1.7.0 for SDK_UPDATE metadata support
5+
16
1.0.0 (October 1, 2025)
2-
- First release.
3-
- Up to date with @openfeature/web-sdk v1.6.1, and @splitsoftware/splitio-browserjs 1.4.0
7+
- First release.
8+
- Up to date with @openfeature/web-sdk v1.6.1, and @splitsoftware/splitio-browserjs 1.4.0

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,22 @@ const context: EvaluationContext = {
5252
await OpenFeature.setContext(context)
5353
```
5454

55+
## Configuration changed event (SDK_UPDATE)
56+
57+
When the Split SDK emits the `SDK_UPDATE` **event** (flags or segments changed), the provider emits OpenFeature’s `ConfigurationChanged` and forwards the event metadata. The metadata shape matches [javascript-commons SdkUpdateMetadata](https://github.com/splitio/javascript-commons): `type` is `'FLAGS_UPDATE' | 'SEGMENTS_UPDATE'` and `names` is the list of flag or segment names that were updated. Handlers receive [Provider Event Details](https://openfeature.dev/specification/types#provider-event-details): `flagsChanged` (when `type === 'FLAGS_UPDATE'`, the `names` array) and `metadata` (`type` as string).
58+
59+
Requires `@splitsoftware/splitio-browserjs` **1.7.0 or later** (metadata was added in 1.7.0).
60+
61+
```js
62+
const { OpenFeature, ProviderEvents } = require('@openfeature/web-sdk');
63+
64+
const client = OpenFeature.getClient();
65+
client.addHandler(ProviderEvents.ConfigurationChanged, (eventDetails) => {
66+
console.log('Flags changed:', eventDetails.flagsChanged);
67+
console.log('Event metadata:', eventDetails.metadata);
68+
});
69+
70+
```
5571
## Evaluate with details
5672
Use the get*Details(...) APIs to get the value and rich context (variant, reason, error code, metadata). This provider includes the Split treatment config as a raw JSON string under flagMetadata["config"]
5773

package-lock.json

Lines changed: 19 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@splitsoftware/openfeature-web-split-provider",
3-
"version": "1.0.0",
3+
"version": "1.1.0",
44
"description": "Split OpenFeature Web Provider",
55
"files": [
66
"README.md",
@@ -37,7 +37,7 @@
3737
"devDependencies": {
3838
"@eslint/js": "^9.35.0",
3939
"@openfeature/web-sdk": "^1.6.1",
40-
"@splitsoftware/splitio-browserjs": "^1.4.0",
40+
"@splitsoftware/splitio-browserjs": "^1.7.0",
4141
"copyfiles": "^2.4.1",
4242
"cross-env": "^7.0.3",
4343
"eslint": "^9.35.0",

src/__tests__/context.spec.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { transformContext } from '../lib/context';
2+
3+
describe('context', () => {
4+
describe('transformContext', () => {
5+
const defaultTrafficType = 'user';
6+
7+
test('uses defaultTrafficType when context has no trafficType', () => {
8+
const result = transformContext({ targetingKey: 'key-1' }, defaultTrafficType);
9+
expect(result.trafficType).toBe('user');
10+
expect(result.targetingKey).toBe('key-1');
11+
expect(result.attributes).toEqual({});
12+
});
13+
14+
test('uses context trafficType when present and non-empty', () => {
15+
const result = transformContext(
16+
{ targetingKey: 'key-1', trafficType: 'account' },
17+
defaultTrafficType
18+
);
19+
expect(result.trafficType).toBe('account');
20+
expect(result.targetingKey).toBe('key-1');
21+
expect(result.attributes).toEqual({});
22+
});
23+
24+
test('falls back to default when trafficType is empty string', () => {
25+
const result = transformContext(
26+
{ targetingKey: 'key-1', trafficType: '' },
27+
defaultTrafficType
28+
);
29+
expect(result.trafficType).toBe('user');
30+
});
31+
32+
test('falls back to default when trafficType is whitespace', () => {
33+
const result = transformContext(
34+
{ targetingKey: 'key-1', trafficType: ' ' },
35+
defaultTrafficType
36+
);
37+
expect(result.trafficType).toBe('user');
38+
});
39+
40+
test('passes remaining context as attributes', () => {
41+
const result = transformContext(
42+
{
43+
targetingKey: 'key-1',
44+
trafficType: 'user',
45+
region: 'eu',
46+
plan: 'pro',
47+
},
48+
defaultTrafficType
49+
);
50+
expect(result.attributes).toEqual({ region: 'eu', plan: 'pro' });
51+
});
52+
53+
test('deep-clones attributes (no reference)', () => {
54+
const attrs = { nested: { value: 1 } };
55+
const result = transformContext(
56+
{ targetingKey: 'k', ...attrs },
57+
defaultTrafficType
58+
);
59+
expect(result.attributes).toEqual({ nested: { value: 1 } });
60+
expect(result.attributes).not.toBe(attrs);
61+
expect(result.attributes.nested).not.toBe(attrs.nested);
62+
});
63+
});
64+
});

src/__tests__/evaluation.spec.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/* eslint-disable @typescript-eslint/no-unused-vars */
2+
import { FlagNotFoundError, StandardResolutionReasons } from '@openfeature/web-sdk';
3+
import { evaluateTreatment } from '../lib/evaluation';
4+
import { CONTROL_TREATMENT } from '../lib/types';
5+
6+
describe('evaluation', () => {
7+
describe('evaluateTreatment', () => {
8+
let mockClient;
9+
10+
beforeEach(() => {
11+
mockClient = {
12+
getTreatmentWithConfig: jest.fn((flagKey, attributes) => ({
13+
treatment: 'v1',
14+
config: '{"x":1}',
15+
})),
16+
};
17+
});
18+
19+
test('returns resolution details with value, variant, flagMetadata, reason', () => {
20+
const consumer = { targetingKey: 'u1', trafficType: 'user', attributes: {} };
21+
const result = evaluateTreatment(mockClient, 'my-flag', consumer);
22+
23+
expect(result.value).toBe('v1');
24+
expect(result.variant).toBe('v1');
25+
expect(result.flagMetadata).toEqual({ config: '{"x":1}' });
26+
expect(result.reason).toBe(StandardResolutionReasons.TARGETING_MATCH);
27+
expect(mockClient.getTreatmentWithConfig).toHaveBeenCalledWith('my-flag', {});
28+
});
29+
30+
test('calls getTreatmentWithConfig with consumer attributes', () => {
31+
const consumer = {
32+
targetingKey: 'u1',
33+
trafficType: 'account',
34+
attributes: { region: 'eu', plan: 'pro' },
35+
};
36+
evaluateTreatment(mockClient, 'flag', consumer);
37+
expect(mockClient.getTreatmentWithConfig).toHaveBeenCalledWith('flag', {
38+
region: 'eu',
39+
plan: 'pro',
40+
});
41+
});
42+
43+
test('uses empty string for config when config is falsy', () => {
44+
mockClient.getTreatmentWithConfig.mockReturnValue({ treatment: 'on', config: null });
45+
const result = evaluateTreatment(mockClient, 'f', {
46+
targetingKey: undefined,
47+
trafficType: 'user',
48+
attributes: {},
49+
});
50+
expect(result.flagMetadata.config).toBe('');
51+
});
52+
53+
test('throws FlagNotFoundError when flagKey is null', () => {
54+
const consumer = { targetingKey: 'u1', trafficType: 'user', attributes: {} };
55+
expect(() => evaluateTreatment(mockClient, null, consumer)).toThrow(FlagNotFoundError);
56+
expect(() => evaluateTreatment(mockClient, null, consumer)).toThrow(
57+
/flagKey must be a non-empty string/
58+
);
59+
});
60+
61+
test('throws FlagNotFoundError when flagKey is empty string', () => {
62+
const consumer = { targetingKey: 'u1', trafficType: 'user', attributes: {} };
63+
expect(() => evaluateTreatment(mockClient, '', consumer)).toThrow(FlagNotFoundError);
64+
});
65+
66+
test('throws FlagNotFoundError when treatment is control', () => {
67+
mockClient.getTreatmentWithConfig.mockReturnValue({
68+
treatment: CONTROL_TREATMENT,
69+
config: '',
70+
});
71+
const consumer = { targetingKey: 'u1', trafficType: 'user', attributes: {} };
72+
expect(() => evaluateTreatment(mockClient, 'flag', consumer)).toThrow(FlagNotFoundError);
73+
expect(() => evaluateTreatment(mockClient, 'flag', consumer)).toThrow(/control/);
74+
});
75+
});
76+
});

0 commit comments

Comments
 (0)