Skip to content

Commit ff94197

Browse files
feat: moving duplicate Multi-Provider resources to shared pacakge, WIP
# Conflicts: # packages/web/src/provider/multi-provider/status-tracker.ts
1 parent 8686dbf commit ff94197

24 files changed

+148
-478
lines changed
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
11
export * from './multi-provider';
2-
export * from './errors';
3-
export * from './strategies';

packages/server/src/provider/multi-provider/multi-provider.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,25 @@ import type {
1313
ProviderMetadata,
1414
ResolutionDetails,
1515
TrackingEventDetails,
16+
BaseEvaluationStrategy,
17+
ProviderResolutionResult,
18+
ProviderEntryInput,
19+
RegisteredProvider,
20+
} from '@openfeature/core';
21+
import {
22+
DefaultLogger,
23+
ErrorCode,
24+
GeneralError,
25+
StandardResolutionReasons,
26+
constructAggregateError,
27+
FirstMatchStrategy,
28+
throwAggregateErrorFromPromiseResults,
29+
StatusTracker,
1630
} from '@openfeature/core';
17-
import { DefaultLogger, ErrorCode, GeneralError, StandardResolutionReasons } from '@openfeature/core';
1831
import type { Provider } from '../provider';
1932
import type { Hook } from '../../hooks';
2033
import { OpenFeatureEventEmitter } from '../../events/open-feature-event-emitter';
21-
import { constructAggregateError, throwAggregateErrorFromPromiseResults } from './errors';
2234
import { HookExecutor } from './hook-executor';
23-
import { StatusTracker } from './status-tracker';
24-
import type { BaseEvaluationStrategy, ProviderResolutionResult } from './strategies';
25-
import { FirstMatchStrategy } from './strategies';
26-
import type { ProviderEntryInput, RegisteredProvider } from './types';
2735

2836
export class MultiProvider implements Provider {
2937
readonly runsOn = 'server';

packages/server/src/provider/multi-provider/status-tracker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { EventDetails } from '@openfeature/core';
2-
import type { OpenFeatureEventEmitter } from '../../events';
32
import { ProviderEvents } from '../../events';
3+
import type { OpenFeatureEventEmitter } from '../../events/open-feature-event-emitter';
44
import { ProviderStatus } from '../provider';
55
import type { RegisteredProvider } from './types';
66

packages/server/src/provider/multi-provider/strategies/comparison-strategy.ts

Lines changed: 0 additions & 72 deletions
This file was deleted.

packages/server/src/provider/multi-provider/strategies/first-match-strategy.ts

Lines changed: 0 additions & 36 deletions
This file was deleted.
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
// Represents an entry in the constructor's provider array which may or may not have a name set
21
import type { Provider } from '../provider';
32

43
export type ProviderEntryInput = {
54
provider: Provider;
65
name?: string;
76
};
87

9-
// Represents a processed and "registered" provider entry where a name has been chosen
108
export type RegisteredProvider = Required<ProviderEntryInput>;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
11
export * from './provider';
2+
export * from './multi-provider/errors';
3+
export * from './multi-provider/status-tracker';
4+
export * from './multi-provider/types';
5+
export * from './multi-provider/strategies';

packages/server/src/provider/multi-provider/errors.ts renamed to packages/shared/src/provider/multi-provider/errors.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { ErrorCode } from '@openfeature/core';
2-
import { GeneralError, OpenFeatureError } from '@openfeature/core';
1+
import type { ErrorCode } from '../../evaluation';
2+
import { GeneralError, OpenFeatureError } from '../../errors';
33
import type { RegisteredProvider } from './types';
44

55
export class ErrorWithCode extends OpenFeatureError {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import type { EventDetails, ProviderEventEmitter } from '../../events';
2+
import { AllProviderEvents } from '../../events';
3+
import { AllProviderStatus } from '../../provider';
4+
import type { RegisteredProvider } from './types';
5+
6+
/**
7+
* Tracks each individual provider's status by listening to emitted events
8+
* Maintains an overall "status" for the multi provider which represents the "most critical" status out of all providers
9+
*/
10+
export class StatusTracker {
11+
private readonly providerStatuses: Record<string, AllProviderStatus> = {};
12+
13+
constructor(private events: ProviderEventEmitter<AllProviderEvents>) {}
14+
15+
wrapEventHandler(providerEntry: RegisteredProvider) {
16+
const provider = providerEntry.provider;
17+
provider.events?.addHandler(AllProviderEvents.Error, (details?: EventDetails) => {
18+
this.changeProviderStatus(providerEntry.name, AllProviderStatus.ERROR, details);
19+
});
20+
21+
provider.events?.addHandler(AllProviderEvents.Stale, (details?: EventDetails) => {
22+
this.changeProviderStatus(providerEntry.name, AllProviderStatus.STALE, details);
23+
});
24+
25+
provider.events?.addHandler(AllProviderEvents.ConfigurationChanged, (details?: EventDetails) => {
26+
this.events.emit(AllProviderEvents.ConfigurationChanged, details);
27+
});
28+
29+
provider.events?.addHandler(AllProviderEvents.Ready, (details?: EventDetails) => {
30+
this.changeProviderStatus(providerEntry.name, AllProviderStatus.READY, details);
31+
});
32+
}
33+
34+
providerStatus(name: string) {
35+
return this.providerStatuses[name];
36+
}
37+
38+
private getStatusFromProviderStatuses() {
39+
const statuses = Object.values(this.providerStatuses);
40+
if (statuses.includes(AllProviderStatus.FATAL)) {
41+
return AllProviderStatus.FATAL;
42+
} else if (statuses.includes(AllProviderStatus.NOT_READY)) {
43+
return AllProviderStatus.NOT_READY;
44+
} else if (statuses.includes(AllProviderStatus.ERROR)) {
45+
return AllProviderStatus.ERROR;
46+
} else if (statuses.includes(AllProviderStatus.STALE)) {
47+
return AllProviderStatus.STALE;
48+
}
49+
return AllProviderStatus.READY;
50+
}
51+
52+
private changeProviderStatus(name: string, status: AllProviderStatus, details?: EventDetails) {
53+
const currentStatus = this.getStatusFromProviderStatuses();
54+
this.providerStatuses[name] = status;
55+
const newStatus = this.getStatusFromProviderStatuses();
56+
if (currentStatus !== newStatus) {
57+
if (newStatus === AllProviderStatus.FATAL || newStatus === AllProviderStatus.ERROR) {
58+
this.events.emit(AllProviderEvents.Error, details);
59+
} else if (newStatus === AllProviderStatus.STALE) {
60+
this.events.emit(AllProviderEvents.Stale, details);
61+
} else if (newStatus === AllProviderStatus.READY) {
62+
this.events.emit(AllProviderEvents.Ready, details);
63+
}
64+
}
65+
}
66+
}

packages/server/src/provider/multi-provider/strategies/base-evaluation-strategy.ts renamed to packages/shared/src/provider/multi-provider/strategies/base-evaluation-strategy.ts

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,25 @@
1-
import type {
2-
ErrorCode,
3-
EvaluationContext,
4-
FlagValue,
5-
FlagValueType,
6-
OpenFeatureError,
7-
ResolutionDetails,
8-
TrackingEventDetails,
9-
} from '@openfeature/core';
10-
import type { Provider } from '../../provider';
11-
import { ProviderStatus } from '../../provider';
1+
import type { ErrorCode, EvaluationContext, FlagValue, FlagValueType, ResolutionDetails } from '../../../evaluation';
2+
import type { OpenFeatureError } from '../../../errors';
3+
import type { CommonProvider } from '../../../provider';
4+
import { AllProviderStatus } from '../../../provider';
125
import { ErrorWithCode } from '../errors';
6+
import type { TrackingEventDetails } from '../../../tracking';
137

148
export type StrategyEvaluationContext = {
159
flagKey: string;
1610
flagType: FlagValueType;
1711
};
12+
1813
export type StrategyProviderContext = {
19-
provider: Provider;
14+
provider: CommonProvider<AllProviderStatus>;
2015
providerName: string;
21-
providerStatus: ProviderStatus;
16+
providerStatus: AllProviderStatus;
2217
};
18+
2319
export type StrategyPerProviderContext = StrategyEvaluationContext & StrategyProviderContext;
2420

2521
type ProviderResolutionResultBase = {
26-
provider: Provider;
22+
provider: CommonProvider<AllProviderStatus>;
2723
providerName: string;
2824
};
2925

@@ -41,25 +37,21 @@ export type ProviderResolutionResult<T extends FlagValue> =
4137

4238
export type FinalResult<T extends FlagValue> = {
4339
details?: ResolutionDetails<T>;
44-
provider?: Provider;
40+
provider?: CommonProvider<AllProviderStatus>;
4541
providerName?: string;
4642
errors?: {
4743
providerName: string;
4844
error: unknown;
4945
}[];
5046
};
5147

52-
/**
53-
* Base strategy to inherit from. Not directly usable, as strategies must implement the "determineResult" method
54-
* Contains default implementations for `shouldEvaluateThisProvider` and `shouldEvaluateNextProvider`
55-
*/
5648
export abstract class BaseEvaluationStrategy {
5749
public runMode: 'parallel' | 'sequential' = 'sequential';
5850

5951
shouldEvaluateThisProvider(strategyContext: StrategyPerProviderContext, _evalContext?: EvaluationContext): boolean {
6052
if (
61-
strategyContext.providerStatus === ProviderStatus.NOT_READY ||
62-
strategyContext.providerStatus === ProviderStatus.FATAL
53+
strategyContext.providerStatus === AllProviderStatus.NOT_READY ||
54+
strategyContext.providerStatus === AllProviderStatus.FATAL
6355
) {
6456
return false;
6557
}
@@ -81,8 +73,8 @@ export abstract class BaseEvaluationStrategy {
8173
_trackingEventDetails?: TrackingEventDetails,
8274
): boolean {
8375
if (
84-
strategyContext.providerStatus === ProviderStatus.NOT_READY ||
85-
strategyContext.providerStatus === ProviderStatus.FATAL
76+
strategyContext.providerStatus === AllProviderStatus.NOT_READY ||
77+
strategyContext.providerStatus === AllProviderStatus.FATAL
8678
) {
8779
return false;
8880
}

0 commit comments

Comments
 (0)