Skip to content

Commit 5d55ea1

Browse files
lukas-reiningbeeme1mrtoddbaert
authored
feat: add init/shutdown and events (#436)
Signed-off-by: Lukas Reining <[email protected]> Co-authored-by: Michael Beemer <[email protected]> Co-authored-by: Todd Baert <[email protected]>
1 parent 310c6ac commit 5d55ea1

File tree

20 files changed

+1239
-324
lines changed

20 files changed

+1239
-324
lines changed

jest.config.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ export default {
108108
preset: 'ts-jest',
109109
// Run tests from one or more projects
110110
projects: [
111+
{
112+
displayName: 'shared',
113+
testEnvironment: 'node',
114+
preset: 'ts-jest',
115+
testMatch: ['<rootDir>/packages/shared/test/**/*.spec.ts'],
116+
},
111117
{
112118
displayName: 'server',
113119
testEnvironment: 'node',
@@ -137,7 +143,7 @@ export default {
137143
setupFiles: ['<rootDir>/packages/client/e2e/step-definitions/setup.ts'],
138144
moduleNameMapper: {
139145
'^uuid$': require.resolve('uuid'),
140-
'^(.*)\\.js$': ['$1', '$1.js']
146+
'^(.*)\\.js$': ['$1', '$1.js'],
141147
},
142148
},
143149
],

package.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,5 @@
7272
"packages/server",
7373
"packages/client",
7474
"packages/shared"
75-
],
76-
"dependencies": {
77-
78-
}
75+
]
7976
}

packages/client/e2e/step-definitions/evaluation.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ const givenAnOpenfeatureClientIsRegisteredWithCacheDisabled = (
2323

2424
defineFeature(feature, (test) => {
2525
beforeAll((done) => {
26-
client.addHandler(ProviderEvents.Ready, () => {
26+
client.addHandler(ProviderEvents.Ready, async () => {
2727
setTimeout(() => {
2828
done(); // TODO remove this once flagd provider properly implements readiness (for now, we add a 2s wait).
2929
}, 2000);
3030
});
3131
});
3232

33-
afterAll(() => {
34-
OpenFeature.close();
33+
afterAll(async () => {
34+
await OpenFeature.close();
3535
});
3636

3737
test('Resolves boolean value', ({ given, when, then }) => {

packages/client/src/client.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,22 @@ import {
33
ErrorCode,
44
EvaluationContext,
55
EvaluationDetails,
6+
EventHandler,
67
FlagValue,
78
FlagValueType,
89
HookContext,
910
JsonValue,
1011
Logger,
1112
OpenFeatureError,
13+
OpenFeatureEventEmitter,
14+
ProviderEvents,
1215
ProviderStatus,
1316
ResolutionDetails,
1417
SafeLogger,
1518
StandardResolutionReasons,
1619
} from '@openfeature/shared';
1720
import { OpenFeature } from './open-feature';
18-
import {
19-
Client,
20-
FlagEvaluationOptions,
21-
Handler,
22-
Hook,
23-
OpenFeatureEventEmitter,
24-
Provider,
25-
ProviderEvents,
26-
} from './types';
21+
import { Client, FlagEvaluationOptions, Hook, Provider } from './types';
2722

2823
type OpenFeatureClientOptions = {
2924
name?: string;
@@ -51,16 +46,24 @@ export class OpenFeatureClient implements Client {
5146
};
5247
}
5348

54-
addHandler(eventType: ProviderEvents, handler: Handler): void {
55-
this.events().on(eventType, handler);
49+
addHandler(eventType: ProviderEvents, handler: EventHandler): void {
50+
this.events().addHandler(eventType, handler);
5651
const providerReady = !this._provider.status || this._provider.status === ProviderStatus.READY;
5752

5853
if (eventType === ProviderEvents.Ready && providerReady) {
5954
// run immediately, we're ready.
60-
handler();
55+
handler({ clientName: this.metadata.name });
6156
}
6257
}
6358

59+
removeHandler(notificationType: ProviderEvents, handler: EventHandler) {
60+
this.events().removeHandler(notificationType, handler);
61+
}
62+
63+
getHandlers(eventType: ProviderEvents) {
64+
return this.events().getHandlers(eventType);
65+
}
66+
6467
setLogger(logger: Logger): OpenFeatureClient {
6568
this._clientLogger = new SafeLogger(logger);
6669
return this;

packages/client/src/open-feature.ts

Lines changed: 3 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
1-
import {
2-
EvaluationContext,
3-
FlagValue,
4-
Logger,
5-
OpenFeatureCommonAPI,
6-
ProviderMetadata,
7-
SafeLogger,
8-
} from '@openfeature/shared';
1+
import { EvaluationContext, FlagValue, Logger, OpenFeatureCommonAPI, SafeLogger } from '@openfeature/shared';
92
import { OpenFeatureClient } from './client';
103
import { NOOP_PROVIDER } from './no-op-provider';
11-
import { Client, Hook, OpenFeatureEventEmitter, Provider, ProviderEvents } from './types';
12-
import { objectOrUndefined, stringOrUndefined } from '@openfeature/shared/src/type-guards';
4+
import { Client, Hook, Provider } from './types';
135

146
// use a symbol as a key for the global singleton
157
const GLOBAL_OPENFEATURE_API_KEY = Symbol.for('@openfeature/js.api');
@@ -19,12 +11,9 @@ type OpenFeatureGlobal = {
1911
};
2012
const _globalThis = globalThis as OpenFeatureGlobal;
2113

22-
export class OpenFeatureAPI extends OpenFeatureCommonAPI {
14+
export class OpenFeatureAPI extends OpenFeatureCommonAPI<Provider> {
2315
protected _hooks: Hook[] = [];
24-
private readonly _events = new OpenFeatureEventEmitter();
2516
protected _defaultProvider: Provider = NOOP_PROVIDER;
26-
protected _clientProviders: Map<string, Provider> = new Map();
27-
protected _clientEvents: Map<string | undefined, OpenFeatureEventEmitter> = new Map();
2817

2918
// eslint-disable-next-line @typescript-eslint/no-empty-function
3019
private constructor() {
@@ -47,14 +36,6 @@ export class OpenFeatureAPI extends OpenFeatureCommonAPI {
4736
return instance;
4837
}
4938

50-
/**
51-
* Get metadata about registered provider.
52-
* @returns {ProviderMetadata} Provider Metadata
53-
*/
54-
get providerMetadata(): ProviderMetadata {
55-
return this._defaultProvider.metadata;
56-
}
57-
5839
setLogger(logger: Logger): this {
5940
this._logger = new SafeLogger(logger);
6041
return this;
@@ -80,70 +61,6 @@ export class OpenFeatureAPI extends OpenFeatureCommonAPI {
8061
await this._defaultProvider?.onContextChange?.(oldContext, context);
8162
}
8263

83-
/**
84-
* Sets the default provider for flag evaluations.
85-
* This provider will be used by unnamed clients and named clients to which no provider is bound.
86-
* Setting a provider supersedes the current provider used in new and existing clients without a name.
87-
* @param {Provider} provider The provider responsible for flag evaluations.
88-
* @returns {OpenFeatureAPI} OpenFeature API
89-
*/
90-
setProvider(provider: Provider): this;
91-
/**
92-
* Sets the provider that OpenFeature will use for flag evaluations of providers with the given name.
93-
* Setting a provider supersedes the current provider used in new and existing clients with that name.
94-
* @param {string} clientName The name to identify the client
95-
* @param {Provider} provider The provider responsible for flag evaluations.
96-
* @returns {OpenFeatureAPI} OpenFeature API
97-
*/
98-
setProvider(clientName: string, provider: Provider): this;
99-
setProvider(clientOrProvider?: string | Provider, providerOrUndefined?: Provider): this {
100-
const clientName = stringOrUndefined(clientOrProvider);
101-
const provider = objectOrUndefined<Provider>(clientOrProvider) ?? objectOrUndefined<Provider>(providerOrUndefined);
102-
103-
if (!provider) {
104-
return this;
105-
}
106-
107-
const oldProvider = this.getProviderForClient(clientName);
108-
109-
// ignore no-ops
110-
if (oldProvider === provider) {
111-
return this;
112-
}
113-
114-
if (clientName) {
115-
this._clientProviders.set(clientName, provider);
116-
} else {
117-
this._defaultProvider = provider;
118-
}
119-
120-
const clientEmitter = this.getEventEmitterForClient(clientName);
121-
this.transferListeners(oldProvider, provider, clientEmitter);
122-
123-
if (typeof provider.initialize === 'function') {
124-
provider
125-
.initialize?.(this._context)
126-
?.then(() => {
127-
clientEmitter.emit(ProviderEvents.Ready);
128-
this._events?.emit(ProviderEvents.Ready);
129-
})
130-
?.catch(() => {
131-
clientEmitter.emit(ProviderEvents.Error);
132-
this._events?.emit(ProviderEvents.Error);
133-
});
134-
} else {
135-
clientEmitter.emit(ProviderEvents.Ready);
136-
this._events?.emit(ProviderEvents.Ready);
137-
}
138-
139-
oldProvider?.onClose?.();
140-
return this;
141-
}
142-
143-
async close(): Promise<void> {
144-
await this?._defaultProvider?.onClose?.();
145-
}
146-
14764
/**
14865
* A factory function for creating new named OpenFeature clients. Clients can contain
14966
* their own state (e.g. logger, hook, context). Multiple clients can be used
@@ -165,39 +82,6 @@ export class OpenFeatureAPI extends OpenFeatureCommonAPI {
16582
{ name, version }
16683
);
16784
}
168-
169-
private getProviderForClient(name?: string): Provider {
170-
if (!name) {
171-
return this._defaultProvider;
172-
}
173-
174-
return this._clientProviders.get(name) ?? this._defaultProvider;
175-
}
176-
177-
private getEventEmitterForClient(name?: string): OpenFeatureEventEmitter {
178-
const emitter = this._clientEvents.get(name);
179-
180-
if (emitter) {
181-
return emitter;
182-
}
183-
184-
const newEmitter = new OpenFeatureEventEmitter({});
185-
this._clientEvents.set(name, newEmitter);
186-
return newEmitter;
187-
}
188-
189-
private transferListeners(oldProvider: Provider, newProvider: Provider, clientEmitter: OpenFeatureEventEmitter) {
190-
oldProvider.events?.removeAllListeners();
191-
192-
// iterate over the event types
193-
Object.values(ProviderEvents).forEach((eventType) =>
194-
newProvider.events?.on(eventType, () => {
195-
// on each event type, fire the associated handlers
196-
clientEmitter.emit(eventType);
197-
this._events.emit(eventType);
198-
})
199-
);
200-
}
20185
}
20286

20387
/**

packages/client/src/types.ts

Lines changed: 1 addition & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
CommonProvider,
55
EvaluationContext,
66
EvaluationDetails,
7+
Eventing,
78
FlagValue,
89
HookContext,
910
HookHints,
@@ -15,48 +16,6 @@ import {
1516
ProviderMetadata,
1617
ResolutionDetails,
1718
} from '@openfeature/shared';
18-
import { EventEmitter as OpenFeatureEventEmitter } from 'events';
19-
export { OpenFeatureEventEmitter };
20-
21-
export enum ProviderEvents {
22-
/**
23-
* The provider is ready to evaluate flags.
24-
*/
25-
Ready = 'PROVIDER_READY',
26-
27-
/**
28-
* The provider is in an error state.
29-
*/
30-
Error = 'PROVIDER_ERROR',
31-
32-
/**
33-
* The flag configuration in the source-of-truth has changed.
34-
*/
35-
ConfigurationChanged = 'PROVIDER_CONFIGURATION_CHANGED',
36-
37-
/**
38-
* The provider's cached state is not longer valid and may not be up-to-date with the source of truth.
39-
*/
40-
Stale = 'PROVIDER_STALE',
41-
}
42-
43-
export interface EventData {
44-
flagKeysChanged?: string[];
45-
changeMetadata?: { [key: string]: boolean | string }; // similar to flag metadata
46-
}
47-
48-
export interface Eventing {
49-
addHandler(notificationType: string, handler: Handler): void;
50-
}
51-
52-
export type EventContext = {
53-
notificationType: string;
54-
[key: string]: unknown;
55-
};
56-
57-
export type Handler = (eventContext?: EventContext) => void;
58-
59-
export type EventCallbackMessage = (eventContext: EventContext) => void;
6019

6120
/**
6221
* Interface that providers must implement to resolve flag values for their particular
@@ -73,12 +32,6 @@ export interface Provider extends CommonProvider {
7332
*/
7433
readonly hooks?: Hook[];
7534

76-
/**
77-
* An event emitter for ProviderEvents.
78-
* @see ProviderEvents
79-
*/
80-
events?: OpenFeatureEventEmitter;
81-
8235
/**
8336
* A handler function to reconcile changes when the static context.
8437
* Called by the SDK when the context is changed.
@@ -87,20 +40,6 @@ export interface Provider extends CommonProvider {
8740
*/
8841
onContextChange?(oldContext: EvaluationContext, newContext: EvaluationContext): Promise<void>;
8942

90-
// TODO: move to common Provider type when we want close in server
91-
onClose?(): Promise<void>;
92-
93-
// TODO: move to common Provider type when we want close in server
94-
/**
95-
* A handler function used to setup the provider.
96-
* Called by the SDK after the provider is set.
97-
* When the returned promise resolves, the SDK fires the ProviderEvents.Ready event.
98-
* If the returned promise rejects, the SDK fires the ProviderEvents.Error event.
99-
* Use this function to perform any context-dependent setup within the provider.
100-
* @param context
101-
*/
102-
initialize?(context: EvaluationContext): Promise<void>;
103-
10443
/**
10544
* Resolve a boolean flag and its evaluation details.
10645
*/

0 commit comments

Comments
 (0)