Skip to content

Commit 8518ef6

Browse files
committed
fix: run auto linter fix
Signed-off-by: JK Gunnink <[email protected]>
1 parent f4b1afb commit 8518ef6

File tree

3 files changed

+111
-138
lines changed

3 files changed

+111
-138
lines changed

libs/providers/rocketflag/project.json

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,14 @@
2323
},
2424
"test": {
2525
"executor": "@nx/jest:jest",
26-
"outputs": [
27-
"{workspaceRoot}/coverage/{projectRoot}"
28-
],
26+
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
2927
"options": {
3028
"jestConfig": "{projectRoot}/jest.config.ts"
3129
}
3230
},
3331
"package": {
3432
"executor": "@nx/rollup:rollup",
35-
"outputs": [
36-
"{options.outputPath}"
37-
],
33+
"outputs": ["{options.outputPath}"],
3834
"options": {
3935
"project": "libs/providers/rocketflag/package.json",
4036
"outputPath": "dist/libs/providers/rocketflag",
@@ -44,10 +40,7 @@
4440
"generateExportsField": true,
4541
"umdName": "RocketFlag",
4642
"external": "all",
47-
"format": [
48-
"cjs",
49-
"esm"
50-
],
43+
"format": ["cjs", "esm"],
5144
"assets": [
5245
{
5346
"glob": "package.json",

libs/providers/rocketflag/src/lib/rocketflag-provider.spec.ts

Lines changed: 9 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { EvaluationContext } from '@openfeature/web-sdk';
22
import { OpenFeature, StandardResolutionReasons, ErrorCode } from '@openfeature/web-sdk';
33
import type { FlagStatus, UserContext } from './rocketflag-provider';
4-
import { RocketFlagProvider } from './rocketflag-provider';
4+
import { createRocketFlagProvider } from './rocketflag-provider';
55

66
// Create a mock RocketFlag client for testing
77
const mockClient = {
@@ -20,13 +20,13 @@ describe('RocketFlagProvider', () => {
2020
beforeEach(() => jest.clearAllMocks());
2121

2222
it('should have the correct metadata name', () => {
23-
const provider = new RocketFlagProvider(mockClient);
23+
const provider = createRocketFlagProvider(mockClient);
2424
expect(provider.metadata.name).toBe('RocketFlagProvider');
2525
});
2626

2727
describe('resolveBooleanEvaluation', () => {
2828
it('should return STALE initially, then resolve to the correct value with TARGETING_MATCH', async () => {
29-
const provider = new RocketFlagProvider(mockClient);
29+
const provider = createRocketFlagProvider(mockClient);
3030
const flagKey = 'test-flag-targeting';
3131
const targetingContext: EvaluationContext = { targetingKey: '[email protected]' };
3232

@@ -47,7 +47,7 @@ describe('RocketFlagProvider', () => {
4747
});
4848

4949
it('should return STALE initially, then resolve with DEFAULT reason when no targetingKey is provided', async () => {
50-
const provider = new RocketFlagProvider(mockClient);
50+
const provider = createRocketFlagProvider(mockClient);
5151
const flagKey = 'test-flag-default';
5252

5353
mockClient.getFlag.mockResolvedValue({ enabled: true });
@@ -65,7 +65,7 @@ describe('RocketFlagProvider', () => {
6565
});
6666

6767
it('should return STALE initially, then resolve with an ERROR if the client rejects', async () => {
68-
const provider = new RocketFlagProvider(mockClient);
68+
const provider = createRocketFlagProvider(mockClient);
6969
OpenFeature.setProvider(provider);
7070
const client = OpenFeature.getClient();
7171
const flagKey = 'test-flag-error';
@@ -85,48 +85,29 @@ describe('RocketFlagProvider', () => {
8585
expect(finalDetails.errorCode).toBe(ErrorCode.GENERAL);
8686
expect(finalDetails.errorMessage).toBe(errorMessage);
8787
});
88-
89-
it('should return from cache on subsequent calls for the same context', () => {
90-
const provider = new RocketFlagProvider(mockClient);
91-
const flagKey = 'cached-flag';
92-
const targetingContext: EvaluationContext = { targetingKey: 'cached-user' };
93-
const cacheKey = JSON.stringify({ flagKey, context: targetingContext });
94-
const cachedDetails = {
95-
value: true,
96-
reason: StandardResolutionReasons.TARGETING_MATCH,
97-
};
98-
99-
// @ts-expect-error - setting private property for test purposes
100-
provider.cache.set(cacheKey, cachedDetails);
101-
102-
const result = provider.resolveBooleanEvaluation(flagKey, false, targetingContext, mockLogger);
103-
104-
expect(result).toEqual(cachedDetails);
105-
expect(mockClient.getFlag).toHaveBeenCalledTimes(1);
106-
});
10788
});
10889

10990
// Tests for other evaluation types to ensure they return TYPE_MISMATCH
11091
describe('Unsupported Evaluations', () => {
111-
const provider = new RocketFlagProvider(mockClient);
92+
const provider = createRocketFlagProvider(mockClient);
11293

11394
it('resolveStringEvaluation should return TYPE_MISMATCH error', () => {
114-
const details = provider.resolveStringEvaluation('flag', 'default');
95+
const details = provider.resolveStringEvaluation('flag', 'default', {}, mockLogger);
11596
expect(details.reason).toBe(StandardResolutionReasons.ERROR);
11697
expect(details.errorCode).toBe(ErrorCode.TYPE_MISMATCH);
11798
expect(details.value).toBe('default');
11899
});
119100

120101
it('resolveNumberEvaluation should return TYPE_MISMATCH error', () => {
121-
const details = provider.resolveNumberEvaluation('flag', 123);
102+
const details = provider.resolveNumberEvaluation('flag', 123, {}, mockLogger);
122103
expect(details.reason).toBe(StandardResolutionReasons.ERROR);
123104
expect(details.errorCode).toBe(ErrorCode.TYPE_MISMATCH);
124105
expect(details.value).toBe(123);
125106
});
126107

127108
it('resolveObjectEvaluation should return TYPE_MISMATCH error', () => {
128109
const defaultValue = { key: 'value' };
129-
const details = provider.resolveObjectEvaluation('flag', defaultValue);
110+
const details = provider.resolveObjectEvaluation('flag', defaultValue, {}, mockLogger);
130111
expect(details.reason).toBe(StandardResolutionReasons.ERROR);
131112
expect(details.errorCode).toBe(ErrorCode.TYPE_MISMATCH);
132113
expect(details.value).toEqual(defaultValue);

libs/providers/rocketflag/src/lib/rocketflag-provider.ts

Lines changed: 99 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type { EvaluationContext, Provider, JsonValue, ResolutionDetails, Logger
22
import { ErrorCode, ProviderEvents, StandardResolutionReasons } from '@openfeature/web-sdk';
33
import { EventEmitter } from 'events';
44

5-
// Interfaces for RocketFlag SDK
65
export interface UserContext {
76
cohort?: string;
87
}
@@ -16,126 +15,126 @@ interface RocketFlagClient {
1615
}
1716

1817
/**
19-
* The RocketFlagProvider implements the OpenFeature Provider interface
20-
* to resolve feature flags from the RocketFlag service.
18+
* A helper function to fetch a flag, update the cache, and notify OpenFeature of changes.
19+
* It's defined within the factory's scope to access the client, cache, emitter, and logger.
2120
*/
22-
export class RocketFlagProvider extends EventEmitter implements Provider {
23-
metadata = {
24-
name: RocketFlagProvider.name,
25-
};
26-
27-
readonly runsOn = 'client';
28-
hooks = [];
29-
private client: RocketFlagClient;
30-
private cache: Map<string, ResolutionDetails<boolean>> = new Map();
31-
private logger?: Logger;
32-
33-
constructor(client: RocketFlagClient) {
34-
super();
35-
this.client = client;
36-
}
37-
38-
/**
39-
* Initialises the provider and can be used to pre-fetch flags.
40-
*/
41-
async initialize(): Promise<void> {
42-
this.logger?.debug('Initialising RocketFlagProvider...');
21+
const fetchFlagAndUpdateCache = (
22+
// Parameters required for the fetch operation
23+
flagKey: string,
24+
defaultValue: boolean,
25+
context: EvaluationContext,
26+
cacheKey: string,
27+
// State managed by the factory's closure
28+
client: RocketFlagClient,
29+
cache: Map<string, ResolutionDetails<boolean>>,
30+
emitter: EventEmitter,
31+
logger?: Logger,
32+
) => {
33+
const userContext: UserContext = {};
34+
const { targetingKey } = context;
35+
36+
if (targetingKey && typeof targetingKey === 'string' && targetingKey !== '') {
37+
userContext.cohort = targetingKey;
4338
}
4439

45-
resolveBooleanEvaluation(
46-
flagKey: string,
47-
defaultValue: boolean,
48-
context: EvaluationContext,
49-
logger: Logger,
50-
): ResolutionDetails<boolean> {
51-
this.logger = logger;
52-
const cacheKey = JSON.stringify({ flagKey, context });
53-
54-
// The SDK expects a synchronous return, so we fetch in the background and notify OpenFeature when the value is ready.
55-
this.fetchFlagAndUpdateCache(flagKey, defaultValue, context, cacheKey);
56-
57-
// Immediately return a value from the cache if available.
58-
if (this.cache.has(cacheKey)) {
59-
const invalidCacheContent: ResolutionDetails<boolean> = {
40+
client
41+
.getFlag(flagKey, userContext)
42+
.then((flagStatus) => {
43+
const details: ResolutionDetails<boolean> = {
44+
value: flagStatus.enabled,
45+
reason: userContext.cohort ? StandardResolutionReasons.TARGETING_MATCH : StandardResolutionReasons.DEFAULT,
46+
};
47+
cache.set(cacheKey, details);
48+
emitter.emit(ProviderEvents.ConfigurationChanged, { flagsChanged: [flagKey] });
49+
logger?.debug(`Successfully fetched flag: ${flagKey}`);
50+
})
51+
.catch((error: unknown) => {
52+
const err = error instanceof Error ? error : new Error(String(error));
53+
const details: ResolutionDetails<boolean> = {
6054
value: defaultValue,
6155
reason: StandardResolutionReasons.ERROR,
6256
errorCode: ErrorCode.GENERAL,
63-
errorMessage: 'Invalid content in cache',
57+
errorMessage: err.message,
6458
};
59+
cache.set(cacheKey, details);
60+
emitter.emit(ProviderEvents.ConfigurationChanged, { flagsChanged: [flagKey] });
61+
logger?.error(`Error fetching flag: ${flagKey}`, err);
62+
});
63+
};
6564

66-
// Since the .get method can return undefined, we handle that with a default "invalid cache" value.
67-
return this.cache.get(cacheKey) || invalidCacheContent;
68-
}
69-
70-
// Return the default value immediately. The cache will be updated later.
71-
return {
72-
value: defaultValue,
73-
reason: StandardResolutionReasons.STALE,
74-
};
75-
}
76-
77-
private fetchFlagAndUpdateCache(
78-
flagKey: string,
79-
defaultValue: boolean,
80-
context: EvaluationContext,
81-
cacheKey: string,
82-
) {
83-
const userContext: UserContext = {};
84-
const targetingKey = context.targetingKey;
85-
86-
if (targetingKey && typeof targetingKey === 'string' && targetingKey !== '') {
87-
userContext.cohort = targetingKey;
88-
}
89-
90-
this.client
91-
.getFlag(flagKey, userContext)
92-
.then((flagStatus) => {
93-
const details: ResolutionDetails<boolean> = {
94-
value: flagStatus.enabled,
95-
reason: userContext.cohort ? StandardResolutionReasons.TARGETING_MATCH : StandardResolutionReasons.DEFAULT,
96-
};
97-
this.cache.set(cacheKey, details);
98-
this.emit(ProviderEvents.ConfigurationChanged, { flagsChanged: [flagKey] });
99-
this.logger?.debug(`Successfully fetched flag: ${flagKey}`);
100-
})
101-
.catch((error: unknown) => {
102-
const err = error instanceof Error ? error : new Error(String(error));
103-
const details: ResolutionDetails<boolean> = {
104-
value: defaultValue,
105-
reason: StandardResolutionReasons.ERROR,
106-
errorCode: ErrorCode.GENERAL,
107-
errorMessage: err.message,
108-
};
109-
this.cache.set(cacheKey, details);
110-
this.emit(ProviderEvents.ConfigurationChanged, { flagsChanged: [flagKey] });
111-
this.logger?.error(`Error fetching flag: ${flagKey}`, err);
112-
});
113-
}
65+
/**
66+
* Creates a functional OpenFeature provider for RocketFlag.
67+
* This provider resolves boolean flags from the RocketFlag service.
68+
*
69+
* @param {RocketFlagClient} client - An instance of the RocketFlag client.
70+
* @returns {Provider & EventEmitter} A provider instance that can be used with the OpenFeature SDK.
71+
*/
72+
export function createRocketFlagProvider(client: RocketFlagClient): Provider & EventEmitter {
73+
const emitter = new EventEmitter();
74+
const cache = new Map<string, ResolutionDetails<boolean>>();
75+
let logger: Logger | undefined;
76+
77+
// Define the provider's logic in a plain object.
78+
const providerLogic = {
79+
metadata: {
80+
name: 'RocketFlagProvider',
81+
},
82+
runsOn: 'client' as const,
83+
hooks: [],
84+
85+
initialize: async (): Promise<void> => {
86+
logger?.debug('Initialising RocketFlagProvider...');
87+
},
88+
89+
resolveBooleanEvaluation: (
90+
flagKey: string,
91+
defaultValue: boolean,
92+
context: EvaluationContext,
93+
evalLogger: Logger,
94+
): ResolutionDetails<boolean> => {
95+
logger = evalLogger; // Capture the logger for async operations.
96+
const cacheKey = JSON.stringify({ flagKey, context });
97+
98+
// Fetch in the background.
99+
fetchFlagAndUpdateCache(flagKey, defaultValue, context, cacheKey, client, cache, emitter, logger);
100+
101+
// Immediately return a cached value if available.
102+
if (cache.has(cacheKey)) {
103+
// The .get() method can return undefined, so we handle that case.
104+
return cache.get(cacheKey) as ResolutionDetails<boolean>;
105+
}
106+
107+
// Return a STALE value while the fetch is in progress.
108+
return {
109+
value: defaultValue,
110+
reason: StandardResolutionReasons.STALE,
111+
};
112+
},
114113

115-
resolveStringEvaluation(flagKey: string, defaultValue: string): ResolutionDetails<string> {
116-
return {
114+
// The other evaluation methods simply return an error.
115+
resolveStringEvaluation: (flagKey: string, defaultValue: string): ResolutionDetails<string> => ({
117116
value: defaultValue,
118117
reason: StandardResolutionReasons.ERROR,
119118
errorCode: ErrorCode.TYPE_MISMATCH,
120119
errorMessage: 'RocketFlag: String flags are not yet supported.',
121-
};
122-
}
120+
}),
123121

124-
resolveNumberEvaluation(flagKey: string, defaultValue: number): ResolutionDetails<number> {
125-
return {
122+
resolveNumberEvaluation: (flagKey: string, defaultValue: number): ResolutionDetails<number> => ({
126123
value: defaultValue,
127124
reason: StandardResolutionReasons.ERROR,
128125
errorCode: ErrorCode.TYPE_MISMATCH,
129126
errorMessage: 'RocketFlag: Number flags are not yet supported.',
130-
};
131-
}
127+
}),
132128

133-
resolveObjectEvaluation<U extends JsonValue>(flagKey: string, defaultValue: U): ResolutionDetails<U> {
134-
return {
129+
resolveObjectEvaluation: <U extends JsonValue>(flagKey: string, defaultValue: U): ResolutionDetails<U> => ({
135130
value: defaultValue,
136131
reason: StandardResolutionReasons.ERROR,
137132
errorCode: ErrorCode.TYPE_MISMATCH,
138133
errorMessage: 'RocketFlag: Object flags are not yet supported.',
139-
};
140-
}
134+
}),
135+
};
136+
137+
// The OpenFeature SDK expects the provider itself to be an event emitter.
138+
// We merge the EventEmitter instance with our provider logic.
139+
return Object.assign(emitter, providerLogic);
141140
}

0 commit comments

Comments
 (0)