From 70691431ded3cf5a6cb13bdc75a072b9c1e85784 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 15 May 2025 19:11:21 -0300 Subject: [PATCH 1/4] Upgrade JS-commons to use flag spec 1.3 --- CHANGES.txt | 3 +- e2e/synchronizer.test.ts | 18 +- e2e/utils/responseMocks.json | 1313 ++++++++++++----------- package-lock.json | 14 +- package.json | 2 +- src/settings/__tests__/index.spec.ts | 2 +- src/settings/defaults.ts | 5 - src/settings/index.ts | 5 +- src/synchronizers/SplitsSynchronizer.ts | 2 +- types/index.d.ts | 6 +- 10 files changed, 692 insertions(+), 678 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d386fe2..30ccbb2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,8 @@ 1.0.0 (May XXX, 2025) + - Added support for synchronizing feature flags with prerequisites and targeting rules based on rule-based segments, by updating the default flag spec version to 1.3. If using the Split Proxy, flag spec 1.3 requires Split Proxy v5.11.0 or higher. If an older version is used, the Synchronizer will fallback to the previous flag spec version (1.2). - Added support for synchronizing SDK impressions with properties. - Added `sync.requestOptions.getHeaderOverrides` configuration option to enhance Synchronizer HTTP request Headers for Authorization Frameworks. - - Updated @splitsoftware/splitio-commons package to version 2.2.0 and some transitive dependencies for vulnerability fixes and other improvements. + - Updated @splitsoftware/splitio-commons package to version 2.3.0 and some transitive dependencies for vulnerability fixes and other improvements. - BREAKING CHANGES: - Dropped support for Node.js v8. The SDK now requires Node.js v14 or above. - Removed internal ponyfills for the `Map` and `Set` global objects. The SDK now requires the runtime environment to support these features natively or provide a polyfill. diff --git a/e2e/synchronizer.test.ts b/e2e/synchronizer.test.ts index 4bab15a..14329ab 100644 --- a/e2e/synchronizer.test.ts +++ b/e2e/synchronizer.test.ts @@ -66,7 +66,7 @@ describe('Synchronizer e2e tests', () => { describe('Runs Synchronizer for the [FIRST] time, and', () => { beforeAll(async () => { - fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.1&since=-1', { status: 200, body: responseMocks.splitChanges[0] }); + fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: responseMocks.splitChanges[0] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/test_maldo?since=-1', { status: 200, body: responseMocks.segmentChanges[0] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/ENDIOS_PEREZ?since=-1', { status: 200, body: responseMocks.segmentChanges[1] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/Lucas_Segments_Tests?since=-1', { status: 200, body: responseMocks.segmentChanges[2] }); @@ -144,7 +144,7 @@ describe('Synchronizer e2e tests', () => { describe('Runs Synchronizer a [SECOND] time and', () => { beforeAll(async () => { - fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.1&since=1619720346271', { status: 200, body: responseMocks.splitChanges[2] }); + fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.3&since=1619720346271&rbSince=-1', { status: 200, body: responseMocks.splitChanges[2] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/test_maldo?since=1589906133231', { status: 200, body: responseMocks.segmentChanges[3] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/Lucas_Segments_Tests?since=1617053238061', { status: 200, body: responseMocks.segmentChanges[6] }); @@ -224,7 +224,7 @@ describe('Synchronizer e2e tests', () => { }); test('Run Synchronizer and check that data was popped from Redis and sent to Split BE', async () => { - fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.1&since=1619720346272', { status: 200, body: responseMocks.splitChanges[3] }); + fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.3&since=1619720346272&rbSince=-1', { status: 200, body: responseMocks.splitChanges[3] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/test_maldo?since=1589906133231', { status: 200, body: responseMocks.segmentChanges[3] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/Lucas_Segments_Tests?since=1617053238061', { status: 200, body: responseMocks.segmentChanges[6] }); @@ -272,7 +272,7 @@ describe('Synchronizer e2e tests', () => { }); test('Run Synchronizer and check that data was popped from Redis and sent to Split BE', async () => { - fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.1&since=1619720346272', { status: 200, body: responseMocks.splitChanges[3] }); + fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.3&since=1619720346272&rbSince=-1', { status: 200, body: responseMocks.splitChanges[3] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/test_maldo?since=1589906133231', { status: 200, body: responseMocks.segmentChanges[3] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/Lucas_Segments_Tests?since=1617053238061', { status: 200, body: responseMocks.segmentChanges[6] }); @@ -347,7 +347,7 @@ describe('Synchronizer e2e tests - OPTIMIZED impressions mode & Flag Sets filter describe('Synchronizer runs the first time', () => { beforeAll(async () => { - fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.1&since=-1&sets=set_b', { status: 200, body: responseMocks.splitChanges[0] }); + fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.3&since=-1&rbSince=-1&sets=set_b', { status: 200, body: responseMocks.splitChanges[0] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/test_maldo?since=-1', { status: 200, body: responseMocks.segmentChanges[0] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/test_maldo?since=1589906133231', { status: 200, body: responseMocks.segmentChanges[3] }); @@ -402,7 +402,7 @@ describe('Synchronizer e2e tests - OPTIMIZED impressions mode & Flag Sets filter describe('Synchronizer runs a second time, and', () => { beforeAll(async () => { - fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.1&since=1619720346271&sets=set_b', { status: 200, body: responseMocks.splitChanges[2] }); + fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.3&since=1619720346271&rbSince=-1&sets=set_b', { status: 200, body: responseMocks.splitChanges[2] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/test_maldo?since=1589906133231', { status: 200, body: responseMocks.segmentChanges[3] }); await _synchronizer.execute(); @@ -460,7 +460,7 @@ describe('Synchronizer e2e tests - OPTIMIZED impressions mode & Flag Sets filter }, }); - fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.1&since=1619720346272&sets=set_b', { status: 500 }); + fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.3&since=1619720346272&rbSince=-1&sets=set_b', { status: 500 }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/test_maldo?since=1589906133231', { status: 200, body: responseMocks.segmentChanges[3] }); expect(await synchronizer.execute()).toBe(false); @@ -477,7 +477,7 @@ describe('Synchronizer e2e tests - OPTIMIZED impressions mode & Flag Sets filter }, }); - fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.1&since=-1&sets=set_b', { status: 500 }); + fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.3&since=-1&rbSince=-1&sets=set_b', { status: 500 }); expect(await synchronizer.execute()).toBe(false); expect(keys.length).toBeGreaterThan(0); @@ -491,7 +491,7 @@ describe('Synchronizer - only Splits & Segments mode', () => { let executeImpressionsAndEventsCallSpy: jest.SpyInstance; beforeAll(async () => { - fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.1&since=-1', { status: 200, body: responseMocks.splitChanges[0] }); + fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: responseMocks.splitChanges[0] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/test_maldo?since=-1', { status: 200, body: responseMocks.segmentChanges[0] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/Lucas_Segments_Tests?since=-1', { status: 200, body: responseMocks.segmentChanges[2] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/test_maldo?since=1589906133231', { status: 200, body: responseMocks.segmentChanges[3] }); diff --git a/e2e/utils/responseMocks.json b/e2e/utils/responseMocks.json index f7e354d..925d28f 100644 --- a/e2e/utils/responseMocks.json +++ b/e2e/utils/responseMocks.json @@ -1,692 +1,709 @@ { "splitChanges": [ { - "splits": [ - { - "trafficTypeName": "testTT", - "name": "MATIAS_TEST", - "trafficAllocation": 92, - "trafficAllocationSeed": 59120715, - "seed": -2094556730, - "status": "ACTIVE", - "killed": false, - "defaultTreatment": "off", - "changeNumber": 1619720346270, - "algo": 2, - "configurations": {}, - "sets": ["set_a"], - "conditions": [ - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "account", - "attribute": "test" - }, - "matcherType": "MATCHES_STRING", - "negate": false, - "userDefinedSegmentMatcherData": null, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": "/matias/i" - } - ] - }, - "partitions": [ - { - "treatment": "on", - "size": 100 + "ff": { + "d": [ + { + "trafficTypeName": "testTT", + "name": "MATIAS_TEST", + "trafficAllocation": 92, + "trafficAllocationSeed": 59120715, + "seed": -2094556730, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "changeNumber": 1619720346270, + "algo": 2, + "configurations": {}, + "sets": [ + "set_a" + ], + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "account", + "attribute": "test" + }, + "matcherType": "MATCHES_STRING", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": "/matias/i" + } + ] }, - { - "treatment": "off", - "size": 0 - } - ], - "label": "test matches /matias/i" - }, - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "account", - "attribute": null - }, - "matcherType": "ALL_KEYS", - "negate": false, - "userDefinedSegmentMatcherData": null, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 } - ] + ], + "label": "test matches /matias/i" }, - "partitions": [ - { - "treatment": "on", - "size": 0 + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "account", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] }, - { - "treatment": "off", - "size": 100 - } - ], - "label": "default rule" - } - ] - }, - { - "trafficTypeName": "user", - "name": "TEST_MATIAS", - "trafficAllocation": 44, - "trafficAllocationSeed": -1207740278, - "seed": 203792729, - "status": "ACTIVE", - "killed": false, - "defaultTreatment": "off", - "changeNumber": 1619205925116, - "algo": 2, - "configurations": {}, - "sets": ["set_b"], - "conditions": [ - { - "conditionType": "WHITELIST", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": null, - "matcherType": "IN_SEGMENT", - "negate": false, - "userDefinedSegmentMatcherData": { - "segmentName": "test_maldo" - }, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null - } - ] - }, - "partitions": [ - { - "treatment": "on", - "size": 100 - } - ], - "label": "whitelisted segment" - }, - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "user", - "attribute": null - }, - "matcherType": "ALL_KEYS", - "negate": false, - "userDefinedSegmentMatcherData": null, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 } - ] - }, - "partitions": [ - { - "treatment": "on", - "size": 100 - }, - { - "treatment": "off", - "size": 0 - }, - { - "treatment": "PITY_MARTINEZ", - "size": 0 - }, - { - "treatment": "JUANFER_QUINTERO", - "size": 0 - }, - { - "treatment": "LUQUITAS_PRATTO", - "size": 0 - }, - { - "treatment": "ENZO_PEREZ", - "size": 0 - }, - { - "treatment": "REDO", - "size": 0 - } - ], - "label": "default rule" - } - ] - }, - { - "trafficTypeName": "user", - "name": "TEST_DOC", - "trafficAllocation": 100, - "trafficAllocationSeed": -1845986406, - "seed": 255141922, - "status": "ACTIVE", - "killed": false, - "defaultTreatment": "off", - "changeNumber": 1555536480284, - "algo": 2, - "configurations": { - "on": "{\"ojoijoii\":\"oijoijioj\",\"\":\"\"}" + ], + "label": "default rule" + } + ] }, - "sets": ["set_a", "set_b"], - "conditions": [ - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "desded", - "attribute": null - }, - "matcherType": "ALL_KEYS", - "negate": false, - "userDefinedSegmentMatcherData": null, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null + { + "trafficTypeName": "user", + "name": "TEST_MATIAS", + "trafficAllocation": 44, + "trafficAllocationSeed": -1207740278, + "seed": 203792729, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "changeNumber": 1619205925116, + "algo": 2, + "configurations": {}, + "sets": [ + "set_b" + ], + "conditions": [ + { + "conditionType": "WHITELIST", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": null, + "matcherType": "IN_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "test_maldo" + }, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 } - ] + ], + "label": "whitelisted segment" }, - "partitions": [ - { - "treatment": "on", - "size": 0 + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] }, - { - "treatment": "off", - "size": 100 - } - ], - "label": "default rule" - } - ] - }, - { - "trafficTypeName": "user", - "name": "Lucas_Split", - "trafficAllocation": 32, - "trafficAllocationSeed": 2048379668, - "seed": 871802730, - "status": "ACTIVE", - "killed": false, - "defaultTreatment": "v1", - "changeNumber": 1619205566698, - "algo": 2, - "configurations": {}, - "sets": [], - "conditions": [ - { - "conditionType": "WHITELIST", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": null, - "matcherType": "IN_SEGMENT", - "negate": false, - "userDefinedSegmentMatcherData": { - "segmentName": "Lucas_Segments_Tests" - }, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 + }, + { + "treatment": "PITY_MARTINEZ", + "size": 0 + }, + { + "treatment": "JUANFER_QUINTERO", + "size": 0 + }, + { + "treatment": "LUQUITAS_PRATTO", + "size": 0 + }, + { + "treatment": "ENZO_PEREZ", + "size": 0 + }, + { + "treatment": "REDO", + "size": 0 } - ] - }, - "partitions": [ - { - "treatment": "on", - "size": 100 - } - ], - "label": "whitelisted segment" + ], + "label": "default rule" + } + ] + }, + { + "trafficTypeName": "user", + "name": "TEST_DOC", + "trafficAllocation": 100, + "trafficAllocationSeed": -1845986406, + "seed": 255141922, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "changeNumber": 1555536480284, + "algo": 2, + "configurations": { + "on": "{\"ojoijoii\":\"oijoijioj\",\"\":\"\"}" }, - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "user", - "attribute": null - }, - "matcherType": "IN_SEGMENT", - "negate": false, - "userDefinedSegmentMatcherData": { - "segmentName": "Lucas_Segments_Tests" - }, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null - } - ] - }, - "partitions": [ - { - "treatment": "on", - "size": 0 + "sets": [ + "set_a", + "set_b" + ], + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "desded", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] }, - { - "treatment": "off", - "size": 0 + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 + } + ], + "label": "default rule" + } + ] + }, + { + "trafficTypeName": "user", + "name": "Lucas_Split", + "trafficAllocation": 32, + "trafficAllocationSeed": 2048379668, + "seed": 871802730, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "v1", + "changeNumber": 1619205566698, + "algo": 2, + "configurations": {}, + "sets": [], + "conditions": [ + { + "conditionType": "WHITELIST", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": null, + "matcherType": "IN_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "Lucas_Segments_Tests" + }, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] }, - { - "treatment": "v1", - "size": 100 - } - ], - "label": "in segment Lucas_Segments_Tests" - }, - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "user", - "attribute": null - }, - "matcherType": "ALL_KEYS", - "negate": false, - "userDefinedSegmentMatcherData": null, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null + "partitions": [ + { + "treatment": "on", + "size": 100 } - ] + ], + "label": "whitelisted segment" }, - "partitions": [ - { - "treatment": "on", - "size": 0 + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": null + }, + "matcherType": "IN_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "Lucas_Segments_Tests" + }, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] }, - { - "treatment": "off", - "size": 100 + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 0 + }, + { + "treatment": "v1", + "size": 100 + } + ], + "label": "in segment Lucas_Segments_Tests" + }, + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] }, - { - "treatment": "v1", - "size": 0 - } - ], - "label": "default rule" - } - ] - } - ], - "since": -1, - "till": 1619720346271 + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 + }, + { + "treatment": "v1", + "size": 0 + } + ], + "label": "default rule" + } + ] + } + ], + "s": -1, + "t": 1619720346271 + } }, { - "splits": [], - "since": 1619720346271, - "till": 1619720346271 + "ff": { + "d": [], + "s": 1619720346271, + "t": 1619720346271 + } }, { - "splits": [ - { - "trafficTypeName": "account", - "name": "TEST_RULO", - "trafficAllocation": 100, - "trafficAllocationSeed": -1845986406, - "seed": 255141922, - "status": "ACTIVE", - "killed": false, - "defaultTreatment": "off", - "changeNumber": 1555536480284, - "algo": 2, - "configurations": { - "on": "{\"ojoijoii\":\"oijoijioj\",\"\":\"\"}" - }, - "conditions": [ - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "desded", - "attribute": null - }, - "matcherType": "ALL_KEYS", - "negate": false, - "userDefinedSegmentMatcherData": null, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null - } - ] - }, - "partitions": [ - { - "treatment": "on", - "size": 0 + "ff": { + "d": [ + { + "trafficTypeName": "account", + "name": "TEST_RULO", + "trafficAllocation": 100, + "trafficAllocationSeed": -1845986406, + "seed": 255141922, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "changeNumber": 1555536480284, + "algo": 2, + "configurations": { + "on": "{\"ojoijoii\":\"oijoijioj\",\"\":\"\"}" + }, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "desded", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] }, - { - "treatment": "off", - "size": 100 - } - ], - "label": "default rule" - } - ] - }, - { - "trafficTypeName": "account", - "name": "MATIAS_TEST", - "trafficAllocation": 92, - "trafficAllocationSeed": 59120715, - "seed": -2094556730, - "status": "ACTIVE", - "killed": false, - "defaultTreatment": "off", - "changeNumber": 1619720346272, - "algo": 2, - "configurations": {}, - "sets": ["set_c"], - "conditions": [ - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "account", - "attribute": "test" - }, - "matcherType": "MATCHES_STRING", - "negate": false, - "userDefinedSegmentMatcherData": null, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": "/matias/i" + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 } - ] - }, - "partitions": [ - { - "treatment": "on", - "size": 100 + ], + "label": "default rule" + } + ] + }, + { + "trafficTypeName": "account", + "name": "MATIAS_TEST", + "trafficAllocation": 92, + "trafficAllocationSeed": 59120715, + "seed": -2094556730, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "changeNumber": 1619720346272, + "algo": 2, + "configurations": {}, + "sets": [ + "set_c" + ], + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "account", + "attribute": "test" + }, + "matcherType": "MATCHES_STRING", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": "/matias/i" + } + ] }, - { - "treatment": "off", - "size": 0 - } - ], - "label": "test matches /matias/i" - }, - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "account", - "attribute": null - }, - "matcherType": "ALL_KEYS", - "negate": false, - "userDefinedSegmentMatcherData": null, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "off", + "size": 0 } - ] + ], + "label": "test matches /matias/i" }, - "partitions": [ - { - "treatment": "on", - "size": 0 + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "account", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] }, - { - "treatment": "off", - "size": 100 - } - ], - "label": "default rule" - } - ] - }, - { - "trafficTypeName": "user", - "name": "TEST_DOC", - "trafficAllocation": 100, - "trafficAllocationSeed": -1845986406, - "seed": 255141922, - "status": "ACTIVE", - "killed": false, - "defaultTreatment": "on", - "changeNumber": 1619720346272, - "algo": 2, - "configurations": { - "on": "{\"ojoijoii\":\"oijoijioj\",\"\":\"\"}" - }, - "conditions": [ - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "desded", - "attribute": null - }, - "matcherType": "ALL_KEYS", - "negate": false, - "userDefinedSegmentMatcherData": null, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 } - ] - }, - "partitions": [ - { - "treatment": "on", - "size": 0 + ], + "label": "default rule" + } + ] + }, + { + "trafficTypeName": "user", + "name": "TEST_DOC", + "trafficAllocation": 100, + "trafficAllocationSeed": -1845986406, + "seed": 255141922, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "on", + "changeNumber": 1619720346272, + "algo": 2, + "configurations": { + "on": "{\"ojoijoii\":\"oijoijioj\",\"\":\"\"}" + }, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "desded", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] }, - { - "treatment": "off", - "size": 100 - } - ], - "label": "default rule" - } - ] - }, - { - "trafficTypeName": "user", - "name": "Lucas_Split", - "trafficAllocation": 32, - "trafficAllocationSeed": 2048379668, - "seed": 871802730, - "status": "ARCHIVED", - "killed": false, - "defaultTreatment": "v1", - "changeNumber": 1619720346272, - "algo": 2, - "configurations": {}, - "conditions": [ - { - "conditionType": "WHITELIST", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": null, - "matcherType": "IN_SEGMENT", - "negate": false, - "userDefinedSegmentMatcherData": { - "segmentName": "Lucas_Segments_Tests" - }, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 } - ] - }, - "partitions": [ - { - "treatment": "on", - "size": 100 - } - ], - "label": "whitelisted segment" - }, - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "user", - "attribute": null - }, - "matcherType": "IN_SEGMENT", - "negate": false, - "userDefinedSegmentMatcherData": { - "segmentName": "Lucas_Segments_Tests" - }, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null + ], + "label": "default rule" + } + ] + }, + { + "trafficTypeName": "user", + "name": "Lucas_Split", + "trafficAllocation": 32, + "trafficAllocationSeed": 2048379668, + "seed": 871802730, + "status": "ARCHIVED", + "killed": false, + "defaultTreatment": "v1", + "changeNumber": 1619720346272, + "algo": 2, + "configurations": {}, + "conditions": [ + { + "conditionType": "WHITELIST", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": null, + "matcherType": "IN_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "Lucas_Segments_Tests" + }, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 } - ] + ], + "label": "whitelisted segment" }, - "partitions": [ - { - "treatment": "on", - "size": 0 - }, - { - "treatment": "off", - "size": 0 + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": null + }, + "matcherType": "IN_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "Lucas_Segments_Tests" + }, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] }, - { - "treatment": "v1", - "size": 100 - } - ], - "label": "in segment Lucas_Segments_Tests" - }, - { - "conditionType": "ROLLOUT", - "matcherGroup": { - "combiner": "AND", - "matchers": [ - { - "keySelector": { - "trafficType": "user", - "attribute": null - }, - "matcherType": "ALL_KEYS", - "negate": false, - "userDefinedSegmentMatcherData": null, - "whitelistMatcherData": null, - "unaryNumericMatcherData": null, - "betweenMatcherData": null, - "booleanMatcherData": null, - "dependencyMatcherData": null, - "stringMatcherData": null + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 0 + }, + { + "treatment": "v1", + "size": 100 } - ] + ], + "label": "in segment Lucas_Segments_Tests" }, - "partitions": [ - { - "treatment": "on", - "size": 0 - }, - { - "treatment": "off", - "size": 100 + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] }, - { - "treatment": "v1", - "size": 0 - } - ], - "label": "default rule" - } - ] - } - ], - "since": 1619720346271, - "till": 1619720346272 + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 100 + }, + { + "treatment": "v1", + "size": 0 + } + ], + "label": "default rule" + } + ] + } + ], + "s": 1619720346271, + "t": 1619720346272 + } }, { - "splits": [], - "since": 1619720346272, - "till": 1619720346272 + "ff": { + "d": [], + "s": 1619720346272, + "t": 1619720346272 + } } ], "segmentChanges": [ diff --git a/package-lock.json b/package-lock.json index fe2ed06..a61d3f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.7.0", "license": "Apache-2.0", "dependencies": { - "@splitsoftware/splitio-commons": "2.2.0", + "@splitsoftware/splitio-commons": "2.2.1-rc.5", "dotenv": "^9.0.1", "node-fetch": "^2.7.0", "yargs": "^17.0.1" @@ -1693,9 +1693,9 @@ } }, "node_modules/@splitsoftware/splitio-commons": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.2.0.tgz", - "integrity": "sha512-ywWDh2fM4/EqJ1AByjXM13gAal+z/WSGiBQ5OZmjpL/iqFLENy3yo/GwsxR/ataOi27XbRQTeQbE/eD7HVnWiA==", + "version": "2.2.1-rc.5", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.2.1-rc.5.tgz", + "integrity": "sha512-pG9yDnE7uohxf6ufFNu8nyqLG7ZvjxAvg7YHUCFn9/ym/0v3fKFbKXi0PY/Tkvq8FU+zRRuQNO40K8d9wWzjVw==", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", @@ -9208,9 +9208,9 @@ } }, "@splitsoftware/splitio-commons": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.2.0.tgz", - "integrity": "sha512-ywWDh2fM4/EqJ1AByjXM13gAal+z/WSGiBQ5OZmjpL/iqFLENy3yo/GwsxR/ataOi27XbRQTeQbE/eD7HVnWiA==", + "version": "2.2.1-rc.5", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.2.1-rc.5.tgz", + "integrity": "sha512-pG9yDnE7uohxf6ufFNu8nyqLG7ZvjxAvg7YHUCFn9/ym/0v3fKFbKXi0PY/Tkvq8FU+zRRuQNO40K8d9wWzjVw==", "requires": { "@types/ioredis": "^4.28.0", "tslib": "^2.3.1" diff --git a/package.json b/package.json index 634f468..fef10b9 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "prepublishOnly": "npm run check && npm run test && npm run build" }, "dependencies": { - "@splitsoftware/splitio-commons": "2.2.0", + "@splitsoftware/splitio-commons": "2.2.1-rc.5", "dotenv": "^9.0.1", "node-fetch": "^2.7.0", "yargs": "^17.0.1" diff --git a/src/settings/__tests__/index.spec.ts b/src/settings/__tests__/index.spec.ts index 12a1ac4..19d46c2 100644 --- a/src/settings/__tests__/index.spec.ts +++ b/src/settings/__tests__/index.spec.ts @@ -26,7 +26,7 @@ describe('synchronizerSettingsValidator', () => { expect(settings.scheduler.eventsPerPost).toBe(defaults.scheduler.eventsPerPost); expect(settings.scheduler.impressionsPerPost).toBe(defaults.scheduler.impressionsPerPost); expect(settings.scheduler.maxRetries).toBe(config.scheduler!.maxRetries); - expect(settings.sync.flagSpecVersion).toBe('1.1'); + expect(settings.sync.flagSpecVersion).toBe('1.3'); expect(settings.sync.requestOptions).toBe(config.sync!.requestOptions); }); diff --git a/src/settings/defaults.ts b/src/settings/defaults.ts index 5e5428c..6a89b50 100644 --- a/src/settings/defaults.ts +++ b/src/settings/defaults.ts @@ -4,8 +4,6 @@ */ const version = '@VERSION@'; -export const FLAG_SPEC_VERSION = '1.1'; - /** * Default values to create settings for the JavaScript Synchronizer. */ @@ -30,9 +28,6 @@ export const defaults = { // Number of retry attempts for posting impressions and events. maxRetries: 3, }, - sync: { - flagSpecVersion: FLAG_SPEC_VERSION, - }, version: `synctoolsjs-${version}`, streamingEnabled: false, }; diff --git a/src/settings/index.ts b/src/settings/index.ts index fbe533d..7fb4f36 100644 --- a/src/settings/index.ts +++ b/src/settings/index.ts @@ -1,12 +1,13 @@ import { ILogger } from '@splitsoftware/splitio-commons/src/logger/types'; import { ISettings } from '@splitsoftware/splitio-commons/src/types'; +import { FLAG_SPEC_VERSION } from '@splitsoftware/splitio-commons/src/utils/constants'; import { isIntegerNumber } from '@splitsoftware/splitio-commons/src/utils/lang'; import { settingsValidation } from '@splitsoftware/splitio-commons/src/utils/settingsValidation/index'; import { validateLogger } from '@splitsoftware/splitio-commons/src/utils/settingsValidation/logger/builtinLogger'; import { ISynchronizerSettings } from '../../types'; -import { defaults, FLAG_SPEC_VERSION } from './defaults'; +import { defaults } from './defaults'; -const FLAG_SPEC_VERSIONS = ['1.0', FLAG_SPEC_VERSION]; +const FLAG_SPEC_VERSIONS = ['1.0', '1.1', '1.2', FLAG_SPEC_VERSION]; /** * Object with some default values to instantiate the application and fullfil internal diff --git a/src/synchronizers/SplitsSynchronizer.ts b/src/synchronizers/SplitsSynchronizer.ts index e12bbb6..36dd311 100644 --- a/src/synchronizers/SplitsSynchronizer.ts +++ b/src/synchronizers/SplitsSynchronizer.ts @@ -54,7 +54,7 @@ export class SplitsSynchronizer { ) { this._storage = storage; this._settings = settings; - this._fetcher = splitChangesFetcherFactory(splitFetcher); + this._fetcher = splitChangesFetcherFactory(splitFetcher, settings, storage); this._splitUpdater = undefined; // @ts-ignore this._inMemoryStorage = InMemoryStorageFactory({ settings }); // @ts-ignore this._inMemoryStorageSnapshot = InMemoryStorageFactory({ settings }); diff --git a/types/index.d.ts b/types/index.d.ts index b808585..f729b6b 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -171,11 +171,11 @@ declare module JsSyncTools { splitFilters?: SplitFilter[] /** * Feature Flag Spec version. Option to determine which version of the feature flag definitions are fetched and stored. - * Possible values are '1.0' and '1.1'. + * Possible values are `'1.0'`, `'1.1'`, `'1.2'`, and `'1.3'`. * - * @default '1.1' + * @default '1.3' */ - flagSpecVersion?: '1.0' | '1.1' + flagSpecVersion?: '1.0' | '1.1' | '1.2' | '1.3' /** * Impressions Collection Mode. Option to determine how impressions are going to be sent to Split Servers. * From aa08ce9e9d6c8bb08dba495bf81ebd54846c355e Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 20 May 2025 17:55:27 -0300 Subject: [PATCH 2/4] Add tests --- e2e/synchronizer.test.ts | 18 +++++++--- e2e/utils/responseMocks.json | 45 +++++++++++++++++++++++++ src/synchronizers/SplitsSynchronizer.ts | 2 +- 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/e2e/synchronizer.test.ts b/e2e/synchronizer.test.ts index 14329ab..37e3bc9 100644 --- a/e2e/synchronizer.test.ts +++ b/e2e/synchronizer.test.ts @@ -116,6 +116,14 @@ describe('Synchronizer e2e tests', () => { expect(itemsSetB.sort()).toEqual(['TEST_DOC', 'TEST_MATIAS']); expect(itemsInexistentSet).toEqual([]); }); + + test('saves 1 rule-based segment', async () => { + const ruleBasedSegments = await _redisWrapper.getKeysByPrefix(`${REDIS_PREFIX}.rbsegment.*`); + expect(ruleBasedSegments).toHaveLength(1); + + expect(await _redisWrapper.get(`${REDIS_PREFIX}.rbsegments.till`)).toBe('100'); + }); + }); describe('Runs SDK Consumer with DEBUG impressions mode, and', () => { @@ -144,7 +152,7 @@ describe('Synchronizer e2e tests', () => { describe('Runs Synchronizer a [SECOND] time and', () => { beforeAll(async () => { - fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.3&since=1619720346271&rbSince=-1', { status: 200, body: responseMocks.splitChanges[2] }); + fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.3&since=1619720346271&rbSince=100', { status: 200, body: responseMocks.splitChanges[2] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/test_maldo?since=1589906133231', { status: 200, body: responseMocks.segmentChanges[3] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/Lucas_Segments_Tests?since=1617053238061', { status: 200, body: responseMocks.segmentChanges[6] }); @@ -224,7 +232,7 @@ describe('Synchronizer e2e tests', () => { }); test('Run Synchronizer and check that data was popped from Redis and sent to Split BE', async () => { - fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.3&since=1619720346272&rbSince=-1', { status: 200, body: responseMocks.splitChanges[3] }); + fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.3&since=1619720346272&rbSince=100', { status: 200, body: responseMocks.splitChanges[3] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/test_maldo?since=1589906133231', { status: 200, body: responseMocks.segmentChanges[3] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/Lucas_Segments_Tests?since=1617053238061', { status: 200, body: responseMocks.segmentChanges[6] }); @@ -272,7 +280,7 @@ describe('Synchronizer e2e tests', () => { }); test('Run Synchronizer and check that data was popped from Redis and sent to Split BE', async () => { - fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.3&since=1619720346272&rbSince=-1', { status: 200, body: responseMocks.splitChanges[3] }); + fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.3&since=1619720346272&rbSince=100', { status: 200, body: responseMocks.splitChanges[3] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/test_maldo?since=1589906133231', { status: 200, body: responseMocks.segmentChanges[3] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/Lucas_Segments_Tests?since=1617053238061', { status: 200, body: responseMocks.segmentChanges[6] }); @@ -402,7 +410,7 @@ describe('Synchronizer e2e tests - OPTIMIZED impressions mode & Flag Sets filter describe('Synchronizer runs a second time, and', () => { beforeAll(async () => { - fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.3&since=1619720346271&rbSince=-1&sets=set_b', { status: 200, body: responseMocks.splitChanges[2] }); + fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.3&since=1619720346271&rbSince=100&sets=set_b', { status: 200, body: responseMocks.splitChanges[2] }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/test_maldo?since=1589906133231', { status: 200, body: responseMocks.segmentChanges[3] }); await _synchronizer.execute(); @@ -460,7 +468,7 @@ describe('Synchronizer e2e tests - OPTIMIZED impressions mode & Flag Sets filter }, }); - fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.3&since=1619720346272&rbSince=-1&sets=set_b', { status: 500 }); + fetchMock.getOnce(SERVER_MOCK_URL + '/splitChanges?s=1.3&since=1619720346272&rbSince=100&sets=set_b', { status: 500 }); fetchMock.getOnce(SERVER_MOCK_URL + '/segmentChanges/test_maldo?since=1589906133231', { status: 200, body: responseMocks.segmentChanges[3] }); expect(await synchronizer.execute()).toBe(false); diff --git a/e2e/utils/responseMocks.json b/e2e/utils/responseMocks.json index 925d28f..33d51f7 100644 --- a/e2e/utils/responseMocks.json +++ b/e2e/utils/responseMocks.json @@ -371,6 +371,51 @@ ], "s": -1, "t": 1619720346271 + }, + "rbs": { + "s": -1, + "t": 100, + "d": [ + { + "changeNumber": 5, + "name": "test_rule_based_segment", + "status": "ACTIVE", + "trafficTypeName": "user", + "excluded": { + "keys": [ + "mauro@split.io", + "gaston@split.io" + ], + "segments": [ + { + "type": "standard", + "name": "test_maldo" + } + ] + }, + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user" + }, + "matcherType": "ENDS_WITH", + "negate": false, + "whitelistMatcherData": { + "whitelist": [ + "@split.io" + ] + } + } + ] + } + } + ] + } + ] } }, { diff --git a/src/synchronizers/SplitsSynchronizer.ts b/src/synchronizers/SplitsSynchronizer.ts index 36dd311..798f1e5 100644 --- a/src/synchronizers/SplitsSynchronizer.ts +++ b/src/synchronizers/SplitsSynchronizer.ts @@ -142,7 +142,7 @@ export class SplitsSynchronizer { const registeredSegments = this._inMemoryStorage.segments.getRegisteredSegments(); - // @todo: Update segment definitions and change number + // @todo: Update rule-based segments, segment definitions and change number if (registeredSegments.length > 0) await this._storage.segments.registerSegments(registeredSegments); } catch (error) { From 10fd80c9d33af848c71ffccded96cd5335dc3f26 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 21 May 2025 13:31:24 -0300 Subject: [PATCH 3/4] rc --- package-lock.json | 4 ++-- package.json | 2 +- src/Synchronizer.ts | 32 +++++++++++++++++--------------- src/settings/index.ts | 2 +- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index c06b22c..f964c7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio-sync-tools", - "version": "0.7.0", + "version": "1.0.0-rc.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-sync-tools", - "version": "0.7.0", + "version": "1.0.0-rc.0", "license": "Apache-2.0", "dependencies": { "@splitsoftware/splitio-commons": "2.3.1-rc.0", diff --git a/package.json b/package.json index d112599..a822e2b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-sync-tools", - "version": "0.7.0", + "version": "1.0.0-rc.0", "description": "Split JavaScript Sync Tools", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/src/Synchronizer.ts b/src/Synchronizer.ts index b4eae52..3bed790 100644 --- a/src/Synchronizer.ts +++ b/src/Synchronizer.ts @@ -2,8 +2,10 @@ import { splitApiFactory } from '@splitsoftware/splitio-commons/src/services/spl import { ISplitApi } from '@splitsoftware/splitio-commons/src/services/types'; import { IStorageAsync, ITelemetryCacheAsync } from '@splitsoftware/splitio-commons/src/storages/types'; import { ISettings } from '@splitsoftware/splitio-commons/src/types'; -import { SegmentsSynchronizer } from './synchronizers/SegmentsSynchronizer'; -import { SplitsSynchronizer } from './synchronizers/SplitsSynchronizer'; +import { segmentChangesFetcherFactory } from '@splitsoftware/splitio-commons/src/sync/polling/fetchers/segmentChangesFetcher'; +import { segmentChangesUpdaterFactory } from '@splitsoftware/splitio-commons/src/sync/polling/updaters/segmentChangesUpdater'; +import { splitChangesFetcherFactory } from '@splitsoftware/splitio-commons/src/sync/polling/fetchers/splitChangesFetcher'; +import { splitChangesUpdaterFactory } from '@splitsoftware/splitio-commons/src/sync/polling/updaters/splitChangesUpdater'; import { synchronizerStorageFactory } from './storages/synchronizerStorage'; import { eventsSubmitterFactory } from './submitters/eventsSubmitter'; import { impressionsSubmitterFactory } from './submitters/impressionsSubmitter'; @@ -36,13 +38,13 @@ export class Synchronizer { */ private _splitApi: ISplitApi; /** - * The local reference to the SegmentsUpdater instance from `@splitio/javascript-commons`. + * The local reference to the segmentChangesUpdater instance from `@splitio/javascript-commons`. */ - private _segmentsSynchronizer!: SegmentsSynchronizer; + private _segmentChangesUpdater!: ReturnType; /** - * The local reference to the SplitUpdater instance from `@splitio/javascript-commons`. + * The local reference to the splitChangesUpdater instance from `@splitio/javascript-commons`. */ - private _splitsSynchronizer!: SplitsSynchronizer; + private _splitChangesUpdater!: ReturnType; /** * The local reference to the EventsSynchronizer class. */ @@ -137,15 +139,16 @@ export class Synchronizer { new ImpressionCountsCacheInMemory() : undefined; - this._segmentsSynchronizer = new SegmentsSynchronizer( - this._splitApi.fetchSegmentChanges, - this.settings, + this._segmentChangesUpdater = segmentChangesUpdaterFactory( + this.settings.log, + segmentChangesFetcherFactory(this._splitApi.fetchSegmentChanges), this._storage.segments, ); - this._splitsSynchronizer = new SplitsSynchronizer( - this._splitApi.fetchSplitChanges, - this.settings, + this._splitChangesUpdater = splitChangesUpdaterFactory( + this.settings.log, + splitChangesFetcherFactory(this._splitApi.fetchSplitChanges, this.settings, this._storage), this._storage, + this.settings.sync.__splitFiltersValidation ); this._eventsSubmitter = eventsSubmitterFactory( this.settings.log, @@ -260,11 +263,10 @@ export class Synchronizer { private async executeSplitsAndSegments(standalone = true) { if (standalone) await this.preExecute(); - // @TODO optimize SplitChangesUpdater to reduce storage operations ("inMemoryOperation" mode) - const isSplitsSyncSuccessful = await this._splitsSynchronizer.getSplitChanges(); + const isSplitsSyncSuccessful = await this._splitChangesUpdater(); this.settings.log.debug(`Feature flags Synchronizer task: ${isSplitsSyncSuccessful ? 'Successful' : 'Unsuccessful'}`); - const isSegmentsSyncSuccessful = await this._segmentsSynchronizer.getSegmentsChanges(); + const isSegmentsSyncSuccessful = await this._segmentChangesUpdater(); this.settings.log.debug(`Segments Synchronizer task: ${isSegmentsSyncSuccessful ? 'Successful' : 'Unsuccessful'}`); if (standalone) await this.postExecute(); diff --git a/src/settings/index.ts b/src/settings/index.ts index 7fb4f36..e590daa 100644 --- a/src/settings/index.ts +++ b/src/settings/index.ts @@ -53,7 +53,7 @@ export function synchronizerSettingsValidator( const { scheduler, log } = settings; // @TODO validate synchronizerMode eventually - // @TODO: validate minimum and maximum value for config params. + // @TODO validate minimum and maximum value for config params. scheduler.eventsPerPost = validatePositiveInteger(log, 'eventsPerPost', scheduler.eventsPerPost, defaults.scheduler.eventsPerPost); scheduler.impressionsPerPost = validatePositiveInteger(log, 'impressionsPerPost', scheduler.impressionsPerPost, defaults.scheduler.impressionsPerPost); scheduler.maxRetries = validatePositiveInteger(log, 'maxRetries', scheduler.maxRetries, defaults.scheduler.maxRetries); From ff21f473011c660ca809b81f949534b3ff355fdd Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 27 May 2025 17:47:48 -0300 Subject: [PATCH 4/4] Stable version --- CHANGES.txt | 5 +++-- package-lock.json | 18 +++++++++--------- package.json | 4 ++-- src/Synchronizer.ts | 2 +- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 30ccbb2..4dbca39 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,6 @@ -1.0.0 (May XXX, 2025) - - Added support for synchronizing feature flags with prerequisites and targeting rules based on rule-based segments, by updating the default flag spec version to 1.3. If using the Split Proxy, flag spec 1.3 requires Split Proxy v5.11.0 or higher. If an older version is used, the Synchronizer will fallback to the previous flag spec version (1.2). +1.0.0 (May 28, 2025) + - Added support for synchronizing feature flags with rule-based segments. These segments determine membership at runtime by evaluating their configured rules against the user attributes provided to the SDK. + - Added support for synchronizing feature flags with prerequisites. This allows customers to define dependency conditions between flags, which are evaluated before any allowlists or targeting rules. - Added support for synchronizing SDK impressions with properties. - Added `sync.requestOptions.getHeaderOverrides` configuration option to enhance Synchronizer HTTP request Headers for Authorization Frameworks. - Updated @splitsoftware/splitio-commons package to version 2.3.0 and some transitive dependencies for vulnerability fixes and other improvements. diff --git a/package-lock.json b/package-lock.json index f964c7e..056a286 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@splitsoftware/splitio-sync-tools", - "version": "1.0.0-rc.0", + "version": "1.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-sync-tools", - "version": "1.0.0-rc.0", + "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "@splitsoftware/splitio-commons": "2.3.1-rc.0", + "@splitsoftware/splitio-commons": "2.4.0", "dotenv": "^9.0.1", "node-fetch": "^2.7.0", "yargs": "^17.0.1" @@ -1693,9 +1693,9 @@ } }, "node_modules/@splitsoftware/splitio-commons": { - "version": "2.3.1-rc.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.3.1-rc.0.tgz", - "integrity": "sha512-+IQdkvy9FPD/r2v40vew8wpVFMPbFWmYXaQ1uhTgytax3nLI0TokoSjEBizEfE7B6xcQ22AZ4ogAqDnIvQBryw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.4.0.tgz", + "integrity": "sha512-VjrzXe7zDM5oi+VWfNNAu1DtcsZl1he8c/MeC4O2SiNRid+Nurzs0ROziHEcBt/4nnCI7vZMNdM4FCcnZHMccA==", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", @@ -9208,9 +9208,9 @@ } }, "@splitsoftware/splitio-commons": { - "version": "2.3.1-rc.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.3.1-rc.0.tgz", - "integrity": "sha512-+IQdkvy9FPD/r2v40vew8wpVFMPbFWmYXaQ1uhTgytax3nLI0TokoSjEBizEfE7B6xcQ22AZ4ogAqDnIvQBryw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.4.0.tgz", + "integrity": "sha512-VjrzXe7zDM5oi+VWfNNAu1DtcsZl1he8c/MeC4O2SiNRid+Nurzs0ROziHEcBt/4nnCI7vZMNdM4FCcnZHMccA==", "requires": { "@types/ioredis": "^4.28.0", "tslib": "^2.3.1" diff --git a/package.json b/package.json index a822e2b..740ef25 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-sync-tools", - "version": "1.0.0-rc.0", + "version": "1.0.0", "description": "Split JavaScript Sync Tools", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", @@ -50,7 +50,7 @@ "prepublishOnly": "npm run check && npm run test && npm run build" }, "dependencies": { - "@splitsoftware/splitio-commons": "2.3.1-rc.0", + "@splitsoftware/splitio-commons": "2.4.0", "dotenv": "^9.0.1", "node-fetch": "^2.7.0", "yargs": "^17.0.1" diff --git a/src/Synchronizer.ts b/src/Synchronizer.ts index 3bed790..268003c 100644 --- a/src/Synchronizer.ts +++ b/src/Synchronizer.ts @@ -194,7 +194,7 @@ export class Synchronizer { private async preExecute(): Promise { const log = this.settings.log; if (!getFetch()) throw new Error('Global Fetch API is not available'); - log.info('Synchronizer: Execute'); + log.info(`Synchronizer: Execute. Version: ${this.settings.version}`); const areAPIsReady = await this._checkEndpointHealth(); if (!areAPIsReady) throw new Error('Health check of Split API endpoints failed');