From 0d1ba870d2329d07e0eee480735622aad93f1901 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 5 Sep 2025 15:01:01 -0300 Subject: [PATCH 1/2] refactor: add getAll method to segment caches and refactor datetime transformation to not mutate FF definitions --- src/evaluator/convertions/index.ts | 10 +++++++ src/evaluator/matchersTransform/index.ts | 7 ++--- .../inLocalStorage/RBSegmentsCacheInLocal.ts | 4 +++ .../inMemory/RBSegmentsCacheInMemory.ts | 4 +++ src/storages/types.ts | 28 ++++++++++++++++--- 5 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/evaluator/convertions/index.ts b/src/evaluator/convertions/index.ts index 7d7384d7..acad8017 100644 --- a/src/evaluator/convertions/index.ts +++ b/src/evaluator/convertions/index.ts @@ -1,3 +1,5 @@ +import { IBetweenMatcherData } from '../../dtos/types'; + export function zeroSinceHH(millisSinceEpoch: number): number { return new Date(millisSinceEpoch).setUTCHours(0, 0, 0, 0); } @@ -5,3 +7,11 @@ export function zeroSinceHH(millisSinceEpoch: number): number { export function zeroSinceSS(millisSinceEpoch: number): number { return new Date(millisSinceEpoch).setUTCSeconds(0, 0); } + +export function betweenDateTimeTransform(betweenMatcherData: IBetweenMatcherData): IBetweenMatcherData { + return { + dataType: betweenMatcherData.dataType, + start: zeroSinceSS(betweenMatcherData.start), + end: zeroSinceSS(betweenMatcherData.end) + }; +} diff --git a/src/evaluator/matchersTransform/index.ts b/src/evaluator/matchersTransform/index.ts index 6219c4dc..075ea9f0 100644 --- a/src/evaluator/matchersTransform/index.ts +++ b/src/evaluator/matchersTransform/index.ts @@ -3,7 +3,7 @@ import { matcherTypes, matcherTypesMapper, matcherDataTypes } from '../matchers/ import { segmentTransform } from './segment'; import { whitelistTransform } from './whitelist'; import { numericTransform } from './unaryNumeric'; -import { zeroSinceHH, zeroSinceSS } from '../convertions'; +import { zeroSinceHH, zeroSinceSS, betweenDateTimeTransform } from '../convertions'; import { IBetweenMatcherData, IInLargeSegmentMatcherData, IInSegmentMatcherData, ISplitMatcher, IUnaryNumericMatcherData } from '../../dtos/types'; import { IMatcherDto } from '../types'; @@ -32,7 +32,7 @@ export function matchersTransform(matchers: ISplitMatcher[]): IMatcherDto[] { let type = matcherTypesMapper(matcherType); // As default input data type we use string (even for ALL_KEYS) let dataType = matcherDataTypes.STRING; - let value = undefined; + let value; if (type === matcherTypes.IN_SEGMENT) { value = segmentTransform(userDefinedSegmentMatcherData as IInSegmentMatcherData); @@ -60,8 +60,7 @@ export function matchersTransform(matchers: ISplitMatcher[]): IMatcherDto[] { dataType = matcherDataTypes.NUMBER; if (value.dataType === 'DATETIME') { - value.start = zeroSinceSS(value.start); - value.end = zeroSinceSS(value.end); + value = betweenDateTimeTransform(value); dataType = matcherDataTypes.DATETIME; } } else if (type === matcherTypes.BETWEEN_SEMVER) { diff --git a/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts b/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts index 37f6ad8e..cfc68cf5 100644 --- a/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +++ b/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts @@ -105,6 +105,10 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync { return item && JSON.parse(item); } + getAll(): IRBSegment[] { + return this.getNames().map(key => this.get(key)!); + } + contains(names: Set): boolean { const namesArray = setToArray(names); const namesInStorage = this.getNames(); diff --git a/src/storages/inMemory/RBSegmentsCacheInMemory.ts b/src/storages/inMemory/RBSegmentsCacheInMemory.ts index 568b0deb..2b876202 100644 --- a/src/storages/inMemory/RBSegmentsCacheInMemory.ts +++ b/src/storages/inMemory/RBSegmentsCacheInMemory.ts @@ -51,6 +51,10 @@ export class RBSegmentsCacheInMemory implements IRBSegmentsCacheSync { return this.cache[name] || null; } + getAll(): IRBSegment[] { + return this.getNames().map(key => this.get(key)!); + } + contains(names: Set): boolean { const namesArray = setToArray(names); const namesInStorage = this.getNames(); diff --git a/src/storages/types.ts b/src/storages/types.ts index 8e93daca..2737da40 100644 --- a/src/storages/types.ts +++ b/src/storages/types.ts @@ -1,5 +1,5 @@ import SplitIO from '../../types/splitio'; -import { MaybeThenable, ISplit, IRBSegment, IMySegmentsResponse } from '../dtos/types'; +import { MaybeThenable, ISplit, IRBSegment, IMySegmentsResponse, IMembershipsResponse, ISegmentChangesResponse, ISplitChangesResponse } from '../dtos/types'; import { MySegmentsData } from '../sync/polling/types'; import { EventDataType, HttpErrors, HttpLatencies, ImpressionDataType, LastSync, Method, MethodExceptions, MethodLatencies, MultiMethodExceptions, MultiMethodLatencies, MultiConfigs, OperationType, StoredEventWithMetadata, StoredImpressionWithMetadata, StreamingEvent, UniqueKeysPayloadCs, UniqueKeysPayloadSs, TelemetryUsageStatsPayload, UpdatesFromSSEEnum } from '../sync/submitters/types'; import { ISettings } from '../types'; @@ -235,6 +235,7 @@ export interface IRBSegmentsCacheSync extends IRBSegmentsCacheBase { update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean, get(name: string): IRBSegment | null, getChangeNumber(): number, + getAll(): IRBSegment[], clear(): void, contains(names: Set): boolean, // Used only for smart pausing in client-side standalone. Returns true if the storage contains a RBSegment using segments or large segments matchers @@ -465,7 +466,7 @@ export interface IStorageBase< telemetry?: TTelemetryCache, uniqueKeys: TUniqueKeysCache, destroy(): void | Promise, - shared?: (matchingKey: string, onReadyCb: (error?: any) => void) => this + shared?: (matchingKey: string, onReadyCb?: (error?: any) => void) => this } export interface IStorageSync extends IStorageBase< @@ -496,8 +497,6 @@ export interface IStorageAsync extends IStorageBase< /** StorageFactory */ -export type DataLoader = (storage: IStorageSync, matchingKey: string) => void - export interface IStorageFactoryParams { settings: ISettings, /** @@ -505,6 +504,9 @@ export interface IStorageFactoryParams { * It is meant for emitting SDK_READY event in consumer mode, and waiting before using the storage in the synchronizer. */ onReadyCb: (error?: any) => void, + /** + * For emitting SDK_READY_FROM_CACHE event in consumer mode with Redis to allow immediate evaluations + */ onReadyFromCacheCb: () => void, } @@ -518,3 +520,21 @@ export type IStorageAsyncFactory = SplitIO.StorageAsyncFactory & { readonly type: SplitIO.StorageType, (params: IStorageFactoryParams): IStorageAsync } + +export type RolloutPlan = { + /** + * Feature flags and rule-based segments. + */ + splitChanges: ISplitChangesResponse; + /** + * Optional map of matching keys to their memberships. + */ + memberships?: { + [matchingKey: string]: IMembershipsResponse; + }; + /** + * Optional list of standard segments. + * This property is ignored if `memberships` is provided. + */ + segmentChanges?: ISegmentChangesResponse[]; +}; From 3d5c5e4a9d36a64d591488718703d44b75316fd6 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 5 Sep 2025 15:07:33 -0300 Subject: [PATCH 2/2] Fix types --- src/storages/types.ts | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/src/storages/types.ts b/src/storages/types.ts index 2737da40..97664de5 100644 --- a/src/storages/types.ts +++ b/src/storages/types.ts @@ -1,5 +1,5 @@ import SplitIO from '../../types/splitio'; -import { MaybeThenable, ISplit, IRBSegment, IMySegmentsResponse, IMembershipsResponse, ISegmentChangesResponse, ISplitChangesResponse } from '../dtos/types'; +import { MaybeThenable, ISplit, IRBSegment, IMySegmentsResponse } from '../dtos/types'; import { MySegmentsData } from '../sync/polling/types'; import { EventDataType, HttpErrors, HttpLatencies, ImpressionDataType, LastSync, Method, MethodExceptions, MethodLatencies, MultiMethodExceptions, MultiMethodLatencies, MultiConfigs, OperationType, StoredEventWithMetadata, StoredImpressionWithMetadata, StreamingEvent, UniqueKeysPayloadCs, UniqueKeysPayloadSs, TelemetryUsageStatsPayload, UpdatesFromSSEEnum } from '../sync/submitters/types'; import { ISettings } from '../types'; @@ -497,6 +497,8 @@ export interface IStorageAsync extends IStorageBase< /** StorageFactory */ +export type DataLoader = (storage: IStorageSync, matchingKey: string) => void + export interface IStorageFactoryParams { settings: ISettings, /** @@ -520,21 +522,3 @@ export type IStorageAsyncFactory = SplitIO.StorageAsyncFactory & { readonly type: SplitIO.StorageType, (params: IStorageFactoryParams): IStorageAsync } - -export type RolloutPlan = { - /** - * Feature flags and rule-based segments. - */ - splitChanges: ISplitChangesResponse; - /** - * Optional map of matching keys to their memberships. - */ - memberships?: { - [matchingKey: string]: IMembershipsResponse; - }; - /** - * Optional list of standard segments. - * This property is ignored if `memberships` is provided. - */ - segmentChanges?: ISegmentChangesResponse[]; -};