Skip to content

Commit 847278f

Browse files
fix: resolve TypeScript errors in multi-provider with proper generic types
Signed-off-by: Jonathan Norris <[email protected]>
1 parent ff94197 commit 847278f

File tree

10 files changed

+177
-141
lines changed

10 files changed

+177
-141
lines changed

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

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ import type {
1313
ProviderMetadata,
1414
ResolutionDetails,
1515
TrackingEventDetails,
16-
BaseEvaluationStrategy,
1716
ProviderResolutionResult,
1817
ProviderEntryInput,
1918
RegisteredProvider,
19+
BaseEvaluationStrategy,
2020
} from '@openfeature/core';
2121
import {
2222
DefaultLogger,
@@ -29,8 +29,10 @@ import {
2929
StatusTracker,
3030
} from '@openfeature/core';
3131
import type { Provider } from '../provider';
32+
import { ProviderStatus } from '../provider';
3233
import type { Hook } from '../../hooks';
3334
import { OpenFeatureEventEmitter } from '../../events/open-feature-event-emitter';
35+
import { ProviderEvents } from '../../events';
3436
import { HookExecutor } from './hook-executor';
3537

3638
export class MultiProvider implements Provider {
@@ -43,15 +45,21 @@ export class MultiProvider implements Provider {
4345

4446
metadata: ProviderMetadata;
4547

46-
providerEntries: RegisteredProvider[] = [];
47-
private providerEntriesByName: Record<string, RegisteredProvider> = {};
48+
providerEntries: RegisteredProvider<Provider>[] = [];
49+
private providerEntriesByName: Record<string, RegisteredProvider<Provider>> = {};
4850

4951
private hookExecutor: HookExecutor;
50-
private statusTracker = new StatusTracker(this.events);
52+
private statusTracker = new StatusTracker<
53+
(typeof ProviderEvents)[keyof typeof ProviderEvents],
54+
ProviderStatus,
55+
Provider
56+
>(this.events, ProviderStatus, ProviderEvents);
5157

5258
constructor(
53-
readonly constructorProviders: ProviderEntryInput[],
54-
private readonly evaluationStrategy: BaseEvaluationStrategy = new FirstMatchStrategy(),
59+
readonly constructorProviders: ProviderEntryInput<Provider>[],
60+
private readonly evaluationStrategy: BaseEvaluationStrategy<ProviderStatus, Provider> = new FirstMatchStrategy(
61+
ProviderStatus,
62+
),
5563
private readonly logger: Logger = new DefaultLogger(),
5664
) {
5765
this.hookExecutor = new HookExecutor(this.logger);
@@ -68,7 +76,7 @@ export class MultiProvider implements Provider {
6876
};
6977
}
7078

71-
private registerProviders(constructorProviders: ProviderEntryInput[]) {
79+
private registerProviders(constructorProviders: ProviderEntryInput<Provider>[]) {
7280
const providersByName: Record<string, Provider[]> = {};
7381

7482
for (const constructorProvider of constructorProviders) {
@@ -155,7 +163,7 @@ export class MultiProvider implements Provider {
155163
throw new GeneralError('Hook context not available for evaluation');
156164
}
157165

158-
const tasks: Promise<[boolean, ProviderResolutionResult<T> | null]>[] = [];
166+
const tasks: Promise<[boolean, ProviderResolutionResult<T, ProviderStatus, Provider> | null]>[] = [];
159167

160168
for (const providerEntry of this.providerEntries) {
161169
const task = this.evaluateProviderEntry(
@@ -181,7 +189,7 @@ export class MultiProvider implements Provider {
181189
const results = await Promise.all(tasks);
182190
const resolutions = results
183191
.map(([, resolution]) => resolution)
184-
.filter((r): r is ProviderResolutionResult<T> => Boolean(r));
192+
.filter((r): r is ProviderResolutionResult<T, ProviderStatus, Provider> => Boolean(r));
185193

186194
const finalResult = this.evaluationStrategy.determineFinalResult({ flagKey, flagType }, context, resolutions);
187195

@@ -200,17 +208,17 @@ export class MultiProvider implements Provider {
200208
flagKey: string,
201209
flagType: FlagValueType,
202210
defaultValue: T,
203-
providerEntry: RegisteredProvider,
211+
providerEntry: RegisteredProvider<Provider>,
204212
hookContext: HookContext,
205213
hookHints: HookHints,
206214
context: EvaluationContext,
207-
): Promise<[boolean, ProviderResolutionResult<T> | null]> {
215+
): Promise<[boolean, ProviderResolutionResult<T, ProviderStatus, Provider> | null]> {
208216
let evaluationResult: ResolutionDetails<T> | undefined = undefined;
209217
const provider = providerEntry.provider;
210218
const strategyContext = {
211219
flagKey,
212220
flagType,
213-
provider,
221+
provider: provider as Provider,
214222
providerName: providerEntry.name,
215223
providerStatus: this.statusTracker.providerStatus(providerEntry.name),
216224
};
@@ -219,19 +227,19 @@ export class MultiProvider implements Provider {
219227
return [true, null];
220228
}
221229

222-
let resolution: ProviderResolutionResult<T>;
230+
let resolution: ProviderResolutionResult<T, ProviderStatus, Provider>;
223231

224232
try {
225233
evaluationResult = await this.evaluateProviderAndHooks(flagKey, defaultValue, provider, hookContext, hookHints);
226234
resolution = {
227235
details: evaluationResult,
228-
provider: provider,
236+
provider: provider as Provider,
229237
providerName: providerEntry.name,
230238
};
231239
} catch (error: unknown) {
232240
resolution = {
233241
thrownError: error,
234-
provider: provider,
242+
provider: provider as Provider,
235243
providerName: providerEntry.name,
236244
};
237245
}
@@ -322,7 +330,7 @@ export class MultiProvider implements Provider {
322330
}
323331

324332
const strategyContext = {
325-
provider: providerEntry.provider,
333+
provider: providerEntry.provider as Provider,
326334
providerName: providerEntry.name,
327335
providerStatus: this.statusTracker.providerStatus(providerEntry.name),
328336
};

packages/shared/src/provider/multi-provider/errors.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ export const constructAggregateError = (providerErrors: { error: unknown; provid
3434
);
3535
};
3636

37-
export const throwAggregateErrorFromPromiseResults = (
37+
export const throwAggregateErrorFromPromiseResults = <TProvider>(
3838
result: PromiseSettledResult<unknown>[],
39-
providerEntries: RegisteredProvider[],
39+
providerEntries: RegisteredProvider<TProvider>[],
4040
) => {
4141
const errors = result
4242
.map((r, i) => {

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

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,39 @@
1-
import type { EventDetails, ProviderEventEmitter } from '../../events';
2-
import { AllProviderEvents } from '../../events';
3-
import { AllProviderStatus } from '../../provider';
1+
import type { EventDetails, ProviderEventEmitter, AnyProviderEvent } from '../../events';
42
import type { RegisteredProvider } from './types';
53

64
/**
75
* Tracks each individual provider's status by listening to emitted events
86
* Maintains an overall "status" for the multi provider which represents the "most critical" status out of all providers
97
*/
10-
export class StatusTracker {
11-
private readonly providerStatuses: Record<string, AllProviderStatus> = {};
8+
export class StatusTracker<
9+
TProviderEvents extends AnyProviderEvent,
10+
TProviderStatus,
11+
TProvider extends { events?: ProviderEventEmitter<TProviderEvents> },
12+
> {
13+
private readonly providerStatuses: Record<string, TProviderStatus> = {};
1214

13-
constructor(private events: ProviderEventEmitter<AllProviderEvents>) {}
15+
constructor(
16+
private events: ProviderEventEmitter<TProviderEvents>,
17+
private statusEnum: Record<string, TProviderStatus>,
18+
private eventEnum: Record<string, TProviderEvents>,
19+
) {}
1420

15-
wrapEventHandler(providerEntry: RegisteredProvider) {
21+
wrapEventHandler(providerEntry: RegisteredProvider<TProvider>) {
1622
const provider = providerEntry.provider;
17-
provider.events?.addHandler(AllProviderEvents.Error, (details?: EventDetails) => {
18-
this.changeProviderStatus(providerEntry.name, AllProviderStatus.ERROR, details);
23+
provider.events?.addHandler(this.eventEnum.Error as TProviderEvents, (details?: EventDetails) => {
24+
this.changeProviderStatus(providerEntry.name, this.statusEnum.ERROR, details);
1925
});
2026

21-
provider.events?.addHandler(AllProviderEvents.Stale, (details?: EventDetails) => {
22-
this.changeProviderStatus(providerEntry.name, AllProviderStatus.STALE, details);
27+
provider.events?.addHandler(this.eventEnum.Stale as TProviderEvents, (details?: EventDetails) => {
28+
this.changeProviderStatus(providerEntry.name, this.statusEnum.STALE, details);
2329
});
2430

25-
provider.events?.addHandler(AllProviderEvents.ConfigurationChanged, (details?: EventDetails) => {
26-
this.events.emit(AllProviderEvents.ConfigurationChanged, details);
31+
provider.events?.addHandler(this.eventEnum.ConfigurationChanged as TProviderEvents, (details?: EventDetails) => {
32+
this.events.emit(this.eventEnum.ConfigurationChanged as TProviderEvents, details);
2733
});
2834

29-
provider.events?.addHandler(AllProviderEvents.Ready, (details?: EventDetails) => {
30-
this.changeProviderStatus(providerEntry.name, AllProviderStatus.READY, details);
35+
provider.events?.addHandler(this.eventEnum.Ready as TProviderEvents, (details?: EventDetails) => {
36+
this.changeProviderStatus(providerEntry.name, this.statusEnum.READY, details);
3137
});
3238
}
3339

@@ -37,29 +43,29 @@ export class StatusTracker {
3743

3844
private getStatusFromProviderStatuses() {
3945
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;
46+
if (statuses.includes(this.statusEnum.FATAL)) {
47+
return this.statusEnum.FATAL;
48+
} else if (statuses.includes(this.statusEnum.NOT_READY)) {
49+
return this.statusEnum.NOT_READY;
50+
} else if (statuses.includes(this.statusEnum.ERROR)) {
51+
return this.statusEnum.ERROR;
52+
} else if (statuses.includes(this.statusEnum.STALE)) {
53+
return this.statusEnum.STALE;
4854
}
49-
return AllProviderStatus.READY;
55+
return this.statusEnum.READY;
5056
}
5157

52-
private changeProviderStatus(name: string, status: AllProviderStatus, details?: EventDetails) {
58+
private changeProviderStatus(name: string, status: TProviderStatus, details?: EventDetails) {
5359
const currentStatus = this.getStatusFromProviderStatuses();
5460
this.providerStatuses[name] = status;
5561
const newStatus = this.getStatusFromProviderStatuses();
5662
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+
if (newStatus === this.statusEnum.FATAL || newStatus === this.statusEnum.ERROR) {
64+
this.events.emit(this.eventEnum.Error as TProviderEvents, details);
65+
} else if (newStatus === this.statusEnum.STALE) {
66+
this.events.emit(this.eventEnum.Stale as TProviderEvents, details);
67+
} else if (newStatus === this.statusEnum.READY) {
68+
this.events.emit(this.eventEnum.Ready as TProviderEvents, details);
6369
}
6470
}
6571
}

0 commit comments

Comments
 (0)