From 98c4b96aea91f8e22f102095e9d8f2d969114815 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 22 May 2026 20:29:49 -0300 Subject: [PATCH] Separate configs endpoints from SDK endpoints AI-Session-Id: 352b40fc-38b4-44a3-bc19-de0bef30504c AI-Tool: claude-code AI-Model: unknown --- src/services/__tests__/splitApi.spec.ts | 19 ++++++++++++------- src/services/splitApi.ts | 13 +++++++++---- src/services/types.ts | 3 ++- src/sync/polling/pollingManagerSS.ts | 9 +++++---- .../polling/syncTasks/segmentsSyncTask.ts | 7 +++---- src/sync/syncManagerOnline.ts | 12 +++++++----- .../__tests__/index.spec.ts | 2 ++ .../__tests__/settings.mocks.ts | 6 ++++-- src/utils/settingsValidation/index.ts | 2 ++ src/utils/settingsValidation/url.ts | 6 +++++- types/splitio.d.ts | 7 ++++--- 11 files changed, 55 insertions(+), 31 deletions(-) diff --git a/src/services/__tests__/splitApi.spec.ts b/src/services/__tests__/splitApi.spec.ts index bfb59f26..cd311cb9 100644 --- a/src/services/__tests__/splitApi.spec.ts +++ b/src/services/__tests__/splitApi.spec.ts @@ -50,20 +50,25 @@ describe('splitApi', () => { assertHeaders(settings, headers); expect(url).toBe(expectedConfigsUrl(-1, 100, settings.validateFilters || false, settings)); + splitApi.fetchConfigsSegmentChanges(-1, 'segmentName', false, 100); + [url, { headers }] = fetchMock.mock.calls[5]; + assertHeaders(settings, headers); + expect(url).toBe('configs/v1/segmentChanges/segmentName?since=-1&till=100'); + splitApi.postEventsBulk('fake-body'); - assertHeaders(settings, fetchMock.mock.calls[5][1].headers); + assertHeaders(settings, fetchMock.mock.calls[6][1].headers); splitApi.postTestImpressionsBulk('fake-body'); - assertHeaders(settings, fetchMock.mock.calls[6][1].headers); - expect(fetchMock.mock.calls[6][1].headers['SplitSDKImpressionsMode']).toBe(settings.sync.impressionsMode); + assertHeaders(settings, fetchMock.mock.calls[7][1].headers); + expect(fetchMock.mock.calls[7][1].headers['SplitSDKImpressionsMode']).toBe(settings.sync.impressionsMode); splitApi.postTestImpressionsCount('fake-body'); - assertHeaders(settings, fetchMock.mock.calls[7][1].headers); + assertHeaders(settings, fetchMock.mock.calls[8][1].headers); splitApi.postMetricsConfig('fake-body'); - assertHeaders(settings, fetchMock.mock.calls[8][1].headers); - splitApi.postMetricsUsage('fake-body'); assertHeaders(settings, fetchMock.mock.calls[9][1].headers); + splitApi.postMetricsUsage('fake-body'); + assertHeaders(settings, fetchMock.mock.calls[10][1].headers); expect(telemetryTrackerMock.trackHttp).toBeCalledTimes(9); @@ -78,7 +83,7 @@ describe('splitApi', () => { function expectedConfigsUrl(since: number, till: number, usesFilter: boolean, settings: ISettings) { const filterQueryString = settings.sync.__splitFiltersValidation && settings.sync.__splitFiltersValidation.queryString; - return `sdk/v1/configs?since=${since}${usesFilter ? filterQueryString : ''}${till ? '&till=' + till : ''}`; + return `configs/v1/configs?since=${since}${usesFilter ? filterQueryString : ''}${till ? '&till=' + till : ''}`; } }); diff --git a/src/services/splitApi.ts b/src/services/splitApi.ts index b9fe142f..d90b1e2d 100644 --- a/src/services/splitApi.ts +++ b/src/services/splitApi.ts @@ -63,15 +63,20 @@ export function splitApiFactory( }); }, + fetchSegmentChanges(since: number, segmentName: string, noCache?: boolean, till?: number) { + const url = `${urls.sdk}/segmentChanges/${segmentName}?since=${since}${till ? '&till=' + till : ''}`; + return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(SEGMENT)); + }, + // @TODO support filterQueryString and handle ERROR_TOO_MANY_SETS error fetchConfigs(since: number, noCache?: boolean, till?: number) { - const url = `${urls.sdk}/v1/configs?since=${since}${filterQueryString || ''}${till ? '&till=' + till : ''}`; + const url = `${urls.configs}/v1/configs?since=${since}${filterQueryString || ''}${till ? '&till=' + till : ''}`; return (secureSplitHttpClient || splitHttpClient)(url, noCache ? noCacheHeaderOptions : undefined); }, - fetchSegmentChanges(since: number, segmentName: string, noCache?: boolean, till?: number) { - const url = `${urls.sdk}/segmentChanges/${segmentName}?since=${since}${till ? '&till=' + till : ''}`; - return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(SEGMENT)); + fetchConfigsSegmentChanges(since: number, segmentName: string, noCache?: boolean, till?: number) { + const url = `${urls.configs}/v1/segmentChanges/${segmentName}?since=${since}${till ? '&till=' + till : ''}`; + return (secureSplitHttpClient || splitHttpClient)(url, noCache ? noCacheHeaderOptions : undefined); }, fetchMemberships(userMatchingKey: string, noCache?: boolean, till?: number) { diff --git a/src/services/types.ts b/src/services/types.ts index e89c3a66..230859a1 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -62,8 +62,9 @@ export interface ISplitApi { getEventsAPIHealthCheck: IHealthCheckAPI fetchAuth: IFetchAuth fetchSplitChanges: IFetchDefinitionChanges - fetchConfigs: IFetchDefinitionChanges fetchSegmentChanges: IFetchSegmentChanges + fetchConfigs: IFetchDefinitionChanges + fetchConfigsSegmentChanges: IFetchSegmentChanges fetchMemberships: IFetchMemberships postEventsBulk: IPostEventsBulk postUniqueKeysBulkCs: IPostUniqueKeysBulkCs diff --git a/src/sync/polling/pollingManagerSS.ts b/src/sync/polling/pollingManagerSS.ts index 028162ca..712f6858 100644 --- a/src/sync/polling/pollingManagerSS.ts +++ b/src/sync/polling/pollingManagerSS.ts @@ -3,21 +3,22 @@ import { segmentsSyncTaskFactory } from './syncTasks/segmentsSyncTask'; import { IPollingManager, ISegmentsSyncTask, IDefinitionsSyncTask } from './types'; import { POLLING_START, POLLING_STOP, LOG_PREFIX_SYNC_POLLING } from '../../logger/constants'; import { ISdkFactoryContextSync } from '../../sdkFactory/types'; -import { IDefinitionChangesFetcher } from './fetchers/types'; +import { IDefinitionChangesFetcher, ISegmentChangesFetcher } from './fetchers/types'; /** * Expose start / stop mechanism for pulling data from services. */ export function pollingManagerSSFactory( params: ISdkFactoryContextSync, - definitionChangesFetcher: IDefinitionChangesFetcher + definitionChangesFetcher: IDefinitionChangesFetcher, + segmentChangesFetcher: ISegmentChangesFetcher ): IPollingManager { - const { splitApi, storage, readiness, settings } = params; + const { storage, readiness, settings } = params; const log = settings.log; const definitionsSyncTask: IDefinitionsSyncTask = definitionsSyncTaskFactory(definitionChangesFetcher, storage, readiness, settings); - const segmentsSyncTask: ISegmentsSyncTask = segmentsSyncTaskFactory(splitApi.fetchSegmentChanges, storage, readiness, settings); + const segmentsSyncTask: ISegmentsSyncTask = segmentsSyncTaskFactory(segmentChangesFetcher, storage, readiness, settings); return { definitionsSyncTask, diff --git a/src/sync/polling/syncTasks/segmentsSyncTask.ts b/src/sync/polling/syncTasks/segmentsSyncTask.ts index b69d7087..b1ab3b9f 100644 --- a/src/sync/polling/syncTasks/segmentsSyncTask.ts +++ b/src/sync/polling/syncTasks/segmentsSyncTask.ts @@ -2,16 +2,15 @@ import { IStorageSync } from '../../../storages/types'; import { IReadinessManager } from '../../../readiness/types'; import { syncTaskFactory } from '../../syncTask'; import { ISegmentsSyncTask } from '../types'; -import { segmentChangesFetcherFactory } from '../fetchers/segmentChangesFetcher'; -import { IFetchSegmentChanges } from '../../../services/types'; import { ISettings } from '../../../types'; import { segmentChangesUpdaterFactory } from '../updaters/segmentChangesUpdater'; +import { ISegmentChangesFetcher } from '../fetchers/types'; /** * Creates a sync task that periodically executes a `segmentChangesUpdater` task */ export function segmentsSyncTaskFactory( - fetchSegmentChanges: IFetchSegmentChanges, + segmentChangesFetcher: ISegmentChangesFetcher, storage: IStorageSync, readiness: IReadinessManager, settings: ISettings, @@ -20,7 +19,7 @@ export function segmentsSyncTaskFactory( settings.log, segmentChangesUpdaterFactory( settings.log, - segmentChangesFetcherFactory(fetchSegmentChanges), + segmentChangesFetcher, storage.segments, readiness, settings.startup.requestTimeoutBeforeReady, diff --git a/src/sync/syncManagerOnline.ts b/src/sync/syncManagerOnline.ts index e2f24747..caa0abf7 100644 --- a/src/sync/syncManagerOnline.ts +++ b/src/sync/syncManagerOnline.ts @@ -12,7 +12,8 @@ import { ISdkFactoryContextSync } from '../sdkFactory/types'; import { SDK_DEFINITIONS_CACHE_LOADED } from '../readiness/constants'; import { usesSegmentsSync } from '../storages/AbstractDefinitionsCacheSync'; import { splitChangesFetcherFactory } from './polling/fetchers/splitChangesFetcher'; -import { IDefinitionChangesFetcher } from './polling/fetchers/types'; +import { IDefinitionChangesFetcher, ISegmentChangesFetcher } from './polling/fetchers/types'; +import { segmentChangesFetcherFactory } from './polling/fetchers/segmentChangesFetcher'; /** * Online SyncManager factory. @@ -24,9 +25,10 @@ import { IDefinitionChangesFetcher } from './polling/fetchers/types'; * @param definitionChangesFetcherFactory - optional to replace the default split changes fetcher */ export function syncManagerOnlineFactory( - pollingManagerFactory?: (params: ISdkFactoryContextSync, definitionChangesFetcher: IDefinitionChangesFetcher) => IPollingManager, + pollingManagerFactory?: (params: ISdkFactoryContextSync, definitionChangesFetcher: IDefinitionChangesFetcher, segmentChangesFetcher: ISegmentChangesFetcher) => IPollingManager, pushManagerFactory?: (params: ISdkFactoryContextSync, pollingManager: IPollingManager) => IPushManager | undefined, - definitionChangesFetcherFactory = splitChangesFetcherFactory + definitionFetcherFactory = splitChangesFetcherFactory, + segmentFetcherFactory = (params: ISdkFactoryContextSync) => segmentChangesFetcherFactory(params.splitApi.fetchSegmentChanges) ): (params: ISdkFactoryContextSync) => ISyncManagerCS { /** @@ -37,7 +39,7 @@ export function syncManagerOnlineFactory( const { settings, settings: { log, streamingEnabled, sync: { enabled: syncEnabled } }, telemetryTracker, storage, readiness } = params; /** Polling Manager */ - const pollingManager = pollingManagerFactory && pollingManagerFactory(params, definitionChangesFetcherFactory(params)); + const pollingManager = pollingManagerFactory && pollingManagerFactory(params, definitionFetcherFactory(params), segmentFetcherFactory(params)); /** Push Manager */ const pushManager = syncEnabled && streamingEnabled && pollingManager && pushManagerFactory ? @@ -45,7 +47,7 @@ export function syncManagerOnlineFactory( undefined; /** Submitter Manager */ - // It is not inyected as push and polling managers, because at the moment it is required + // It is not injected as push and polling managers, because at the moment it is required const submitterManager = submitterManagerFactory(params); /** Sync Manager logic */ diff --git a/src/utils/settingsValidation/__tests__/index.spec.ts b/src/utils/settingsValidation/__tests__/index.spec.ts index a230fdf3..756a9029 100644 --- a/src/utils/settingsValidation/__tests__/index.spec.ts +++ b/src/utils/settingsValidation/__tests__/index.spec.ts @@ -38,6 +38,7 @@ describe('settingsValidation', () => { auth: 'https://auth.split.io/api', streaming: 'https://streaming.split.io', telemetry: 'https://telemetry.split.io/api', + configs: 'https://configs.split.io/api', }); expect(settings.sync.impressionsMode).toBe(OPTIMIZED); expect(settings.sync.enabled).toBe(true); @@ -82,6 +83,7 @@ describe('settingsValidation', () => { auth: 'auth-url', streaming: 'streaming-url', telemetry: 'telemetry-url', + configs: 'configs-url', }; const settings = settingsValidation({ diff --git a/src/utils/settingsValidation/__tests__/settings.mocks.ts b/src/utils/settingsValidation/__tests__/settings.mocks.ts index 7bce6249..0fd52f6b 100644 --- a/src/utils/settingsValidation/__tests__/settings.mocks.ts +++ b/src/utils/settingsValidation/__tests__/settings.mocks.ts @@ -80,7 +80,8 @@ export const fullSettings: ISettings = { events: 'https://events.split.io/api', auth: 'https://auth.split.io/api', streaming: 'https://streaming.split.io', - telemetry: 'https://telemetry.split.io/api' + telemetry: 'https://telemetry.split.io/api', + configs: 'https://configs.split.io/api' }, log: loggerMock, userConsent: undefined @@ -105,7 +106,8 @@ export const settingsSplitApi = { sdk: 'sdk', auth: 'auth', streaming: 'streaming', - telemetry: 'telemetry' + telemetry: 'telemetry', + configs: 'configs' }, sync: { impressionsMode: 'DEBUG', diff --git a/src/utils/settingsValidation/index.ts b/src/utils/settingsValidation/index.ts index 8f070082..3a3cdab6 100644 --- a/src/utils/settingsValidation/index.ts +++ b/src/utils/settingsValidation/index.ts @@ -58,6 +58,8 @@ export const base = { streaming: 'https://streaming.split.io', // Telemetry Server telemetry: 'https://telemetry.split.io/api', + // Configs Server + configs: 'https://configs.split.io/api', }, // Defines which kind of storage we should instantiate. diff --git a/src/utils/settingsValidation/url.ts b/src/utils/settingsValidation/url.ts index cd2fbbab..ba04db93 100644 --- a/src/utils/settingsValidation/url.ts +++ b/src/utils/settingsValidation/url.ts @@ -1,9 +1,10 @@ import { ISettings } from '../../types'; -const telemetryEndpointMatcher = /^\/v1\/metrics\/(config|usage)/; +const telemetryEndpointMatcher = /^\/v1\/(metrics|keys)\/(config|usage|ss|cs)/; const eventsEndpointMatcher = /^\/(testImpressions|metrics|events)/; const authEndpointMatcher = /^\/(v2|v3)\/auth/; const streamingEndpointMatcher = /^\/(sse|event-stream)/; +const configsEndpointMatcher = /^\/v1\/(configs|segmentChanges)/; /** * Get URL based on a given target (path). @@ -26,5 +27,8 @@ export function url(settings: ISettings, target: string) { if (streamingEndpointMatcher.test(target)) { return `${settings.urls.streaming}${target}`; } + if (configsEndpointMatcher.test(target)) { + return `${settings.urls.configs}${target}`; + } return `${settings.urls.sdk}${target}`; } diff --git a/types/splitio.d.ts b/types/splitio.d.ts index e222d896..b721242e 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -644,6 +644,7 @@ declare namespace SplitIO { auth: string; streaming: string; telemetry: string; + configs: string; }; readonly integrations?: IntegrationFactory[]; readonly logger?: Logger; @@ -2356,11 +2357,11 @@ declare namespace SplitIO { */ auth?: string; /** - * String property to override the base URL where the SDK will get rollout plan related data, like feature flags and segments definitions. + * String property to override the base URL where the SDK will get rollout plan related data, like configs and segments definitions. * - * @defaultValue `'https://appconfig.split.io/api'` + * @defaultValue `'https://configs.split.io/api'` */ - sdk?: string; + configs?: string; /** * String property to override the base URL where the SDK will post event-related information like impressions. *