diff --git a/packages/client/src/client/index.ts b/packages/client/src/client/index.ts index 991883635..4f1cce44f 100644 --- a/packages/client/src/client/index.ts +++ b/packages/client/src/client/index.ts @@ -1,2 +1 @@ export * from './client'; -export * from './open-feature-client'; diff --git a/packages/client/src/open-feature.ts b/packages/client/src/open-feature.ts index 08fc424a0..82f1a4b35 100644 --- a/packages/client/src/open-feature.ts +++ b/packages/client/src/open-feature.ts @@ -8,10 +8,11 @@ import { objectOrUndefined, stringOrUndefined, } from '@openfeature/core'; -import { Client, OpenFeatureClient } from './client'; +import { Client } from './client'; import { OpenFeatureEventEmitter, ProviderEvents } from './events'; import { Hook } from './hooks'; import { NOOP_PROVIDER, Provider, ProviderStatus } from './provider'; +import { OpenFeatureClient } from './client/open-feature-client'; // use a symbol as a key for the global singleton const GLOBAL_OPENFEATURE_API_KEY = Symbol.for('@openfeature/web-sdk/api'); @@ -26,10 +27,17 @@ type DomainRecord = { const _globalThis = globalThis as OpenFeatureGlobal; -export class OpenFeatureAPI extends OpenFeatureCommonAPI implements ManageContext> { +export class OpenFeatureAPI + extends OpenFeatureCommonAPI + implements ManageContext> +{ protected _statusEnumType: typeof ProviderStatus = ProviderStatus; protected _apiEmitter: GenericEventEmitter = new OpenFeatureEventEmitter(); - protected _defaultProvider: ProviderWrapper = new ProviderWrapper(NOOP_PROVIDER, ProviderStatus.NOT_READY, this._statusEnumType); + protected _defaultProvider: ProviderWrapper = new ProviderWrapper( + NOOP_PROVIDER, + ProviderStatus.NOT_READY, + this._statusEnumType, + ); protected _domainScopedProviders: Map> = new Map(); protected _createEventEmitter = () => new OpenFeatureEventEmitter(); @@ -111,9 +119,7 @@ export class OpenFeatureAPI extends OpenFeatureCommonAPI - this.runProviderContextChangeHandler(dm.domain, dm.wrapper, oldContext, context), - ), + allDomainRecords.map((dm) => this.runProviderContextChangeHandler(dm.domain, dm.wrapper, oldContext, context)), ); } } @@ -222,7 +228,7 @@ export class OpenFeatureAPI extends OpenFeatureCommonAPI { // this should always be set according to the typings, but let's be defensive considering JS const providerName = wrapper.provider?.metadata?.name || 'unnamed-provider'; - + try { if (typeof wrapper.provider.onContextChange === 'function') { wrapper.incrementPendingContextChanges(); diff --git a/packages/client/test/client.spec.ts b/packages/client/test/client.spec.ts index 34a7fdf36..ee06bae1d 100644 --- a/packages/client/test/client.spec.ts +++ b/packages/client/test/client.spec.ts @@ -8,13 +8,13 @@ import { JsonObject, JsonValue, OpenFeature, - OpenFeatureClient, Provider, ProviderFatalError, ProviderStatus, ResolutionDetails, StandardResolutionReasons, } from '../src'; +import { OpenFeatureClient } from '../src/client/open-feature-client'; const BOOLEAN_VALUE = true; const STRING_VALUE = 'val'; @@ -382,7 +382,7 @@ describe('OpenFeatureClient', () => { // No generic information exists at runtime, but this test has some value in ensuring the generic args still exist in the typings. const client = OpenFeature.getClient(); const details: ResolutionDetails = client.getObjectDetails('flag', { key: 'value' }); - + expect(details).toBeDefined(); }); }); @@ -464,45 +464,45 @@ describe('OpenFeatureClient', () => { expect(details.errorCode).toEqual(ErrorCode.PROVIDER_NOT_READY); }); }); - + describe('Evaluation details structure', () => { const flagKey = 'number-details'; const defaultValue = 1970; let details: EvaluationDetails; - + describe('Normal execution', () => { beforeEach(() => { const client = OpenFeature.getClient(); details = client.getNumberDetails(flagKey, defaultValue); - + expect(details).toBeDefined(); }); - + describe('Requirement 1.4.2, 1.4.3', () => { it('should contain flag value', () => { expect(details.value).toEqual(NUMBER_VALUE); }); }); - + describe('Requirement 1.4.4', () => { it('should contain flag key', () => { expect(details.flagKey).toEqual(flagKey); }); }); - + describe('Requirement 1.4.5', () => { it('should contain flag variant', () => { expect(details.variant).toEqual(NUMBER_VARIANT); }); }); - + describe('Requirement 1.4.6', () => { it('should contain reason', () => { expect(details.reason).toEqual(REASON); }); }); }); - + describe('Abnormal execution', () => { const NON_OPEN_FEATURE_ERROR_MESSAGE = 'A null dereference or something, I dunno.'; const OPEN_FEATURE_ERROR_MESSAGE = "This ain't the flag you're looking for."; @@ -522,14 +522,14 @@ describe('OpenFeatureClient', () => { } as unknown as Provider; const defaultNumberValue = 123; const defaultStringValue = 'hey!'; - + beforeEach(() => { OpenFeature.setProvider(errorProvider); client = OpenFeature.getClient(); nonOpenFeatureErrorDetails = client.getNumberDetails('some-flag', defaultNumberValue); openFeatureErrorDetails = client.getStringDetails('some-flag', defaultStringValue); }); - + describe('Requirement 1.4.7', () => { describe('OpenFeatureError', () => { it('should contain error code', () => { @@ -537,7 +537,7 @@ describe('OpenFeatureClient', () => { expect(openFeatureErrorDetails.errorCode).toEqual(ErrorCode.FLAG_NOT_FOUND); // should get code from thrown OpenFeatureError }); }); - + describe('Non-OpenFeatureError', () => { it('should contain error code', () => { expect(nonOpenFeatureErrorDetails.errorCode).toBeTruthy(); @@ -545,30 +545,30 @@ describe('OpenFeatureClient', () => { }); }); }); - + describe('Requirement 1.4.8', () => { it('should contain error reason', () => { expect(nonOpenFeatureErrorDetails.reason).toEqual(StandardResolutionReasons.ERROR); expect(openFeatureErrorDetails.reason).toEqual(StandardResolutionReasons.ERROR); }); }); - + describe('Requirement 1.4.9', () => { it('must not throw, must return default', () => { nonOpenFeatureErrorDetails = client.getNumberDetails('some-flag', defaultNumberValue); - + expect(nonOpenFeatureErrorDetails).toBeTruthy(); expect(nonOpenFeatureErrorDetails.value).toEqual(defaultNumberValue); }); }); - + describe('Requirement 1.4.12', () => { describe('OpenFeatureError', () => { it('should contain "error" message', () => { expect(openFeatureErrorDetails.errorMessage).toEqual(OPEN_FEATURE_ERROR_MESSAGE); }); }); - + describe('Non-OpenFeatureError', () => { it('should contain "error" message', () => { expect(nonOpenFeatureErrorDetails.errorMessage).toEqual(NON_OPEN_FEATURE_ERROR_MESSAGE); @@ -576,14 +576,14 @@ describe('OpenFeatureClient', () => { }); }); }); - + describe('Requirement 1.4.13, Requirement 1.4.14', () => { it('should return immutable `flag metadata` as defined by the provider', () => { const flagMetadata = { url: 'https://test.dev', version: '1', }; - + const flagMetadataProvider = { metadata: { name: 'flag-metadata', @@ -595,14 +595,14 @@ describe('OpenFeatureClient', () => { }; }), } as unknown as Provider; - + OpenFeature.setProvider(flagMetadataProvider); const client = OpenFeature.getClient(); const response = client.getBooleanDetails('some-flag', false); expect(response.flagMetadata).toBe(flagMetadata); expect(Object.isFrozen(response.flagMetadata)).toBeTruthy(); }); - + it('should return empty `flag metadata` because it was not set by the provider', () => { // The mock provider doesn't contain flag metadata OpenFeature.setProvider(MOCK_PROVIDER); diff --git a/packages/client/test/open-feature.spec.ts b/packages/client/test/open-feature.spec.ts index d8e4f8374..51ac20887 100644 --- a/packages/client/test/open-feature.spec.ts +++ b/packages/client/test/open-feature.spec.ts @@ -1,5 +1,6 @@ import { Paradigm } from '@openfeature/core'; -import { OpenFeature, OpenFeatureAPI, OpenFeatureClient, Provider, ProviderStatus } from '../src'; +import { OpenFeature, OpenFeatureAPI, Provider, ProviderStatus } from '../src'; +import { OpenFeatureClient } from '../src/client/open-feature-client'; const mockProvider = (config?: { initialStatus?: ProviderStatus; runsOn?: Paradigm }) => { return { diff --git a/packages/nest/test/open-feature.module.spec.ts b/packages/nest/test/open-feature.module.spec.ts index d0a7111ea..cb4453e2a 100644 --- a/packages/nest/test/open-feature.module.spec.ts +++ b/packages/nest/test/open-feature.module.spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getOpenFeatureClientToken, OpenFeatureModule, ServerProviderEvents } from '../src'; -import { OpenFeature, OpenFeatureClient } from '@openfeature/server-sdk'; +import { Client, OpenFeature } from '@openfeature/server-sdk'; import { getOpenFeatureDefaultTestModule } from './fixtures'; describe('OpenFeatureModule', () => { @@ -31,19 +31,19 @@ describe('OpenFeatureModule', () => { it('should return the SDKs default provider and not throw', async () => { expect(() => { - moduleWithoutProvidersRef.get(getOpenFeatureClientToken()); + moduleWithoutProvidersRef.get(getOpenFeatureClientToken()); }).not.toThrow(); }); }); it('should return the default provider', async () => { - const client = moduleRef.get(getOpenFeatureClientToken()); + const client = moduleRef.get(getOpenFeatureClientToken()); expect(client).toBeDefined(); expect(await client.getStringValue('testStringFlag', '')).toEqual('expected-string-value-default'); }); it('should inject the client with the given scope', async () => { - const client = moduleRef.get(getOpenFeatureClientToken('domainScopedClient')); + const client = moduleRef.get(getOpenFeatureClientToken('domainScopedClient')); expect(client).toBeDefined(); expect(await client.getStringValue('testStringFlag', '')).toEqual('expected-string-value-scoped'); }); diff --git a/packages/nest/test/test-app.ts b/packages/nest/test/test-app.ts index e64070fbe..dd3d7ec85 100644 --- a/packages/nest/test/test-app.ts +++ b/packages/nest/test/test-app.ts @@ -1,14 +1,14 @@ import { Controller, Get, Injectable, UseInterceptors } from '@nestjs/common'; import { Observable, map } from 'rxjs'; import { BooleanFeatureFlag, ObjectFeatureFlag, NumberFeatureFlag, FeatureClient, StringFeatureFlag } from '../src'; -import { OpenFeatureClient, EvaluationDetails, FlagValue } from '@openfeature/server-sdk'; +import { Client, EvaluationDetails, FlagValue } from '@openfeature/server-sdk'; import { EvaluationContextInterceptor } from '../src'; @Injectable() export class OpenFeatureTestService { constructor( - @FeatureClient() public defaultClient: OpenFeatureClient, - @FeatureClient({ domain: 'domainScopedClient' }) public domainScopedClient: OpenFeatureClient, + @FeatureClient() public defaultClient: Client, + @FeatureClient({ domain: 'domainScopedClient' }) public domainScopedClient: Client, ) {} public async serviceMethod(flag: EvaluationDetails) { diff --git a/packages/server/src/client/index.ts b/packages/server/src/client/index.ts index 991883635..4f1cce44f 100644 --- a/packages/server/src/client/index.ts +++ b/packages/server/src/client/index.ts @@ -1,2 +1 @@ export * from './client'; -export * from './open-feature-client'; diff --git a/packages/server/src/open-feature.ts b/packages/server/src/open-feature.ts index 4cd0fb461..09b00250d 100644 --- a/packages/server/src/open-feature.ts +++ b/packages/server/src/open-feature.ts @@ -6,7 +6,7 @@ import { objectOrUndefined, stringOrUndefined, } from '@openfeature/core'; -import { Client, OpenFeatureClient } from './client'; +import { Client } from './client'; import { OpenFeatureEventEmitter } from './events'; import { Hook } from './hooks'; import { NOOP_PROVIDER, Provider, ProviderStatus } from './provider'; @@ -17,6 +17,7 @@ import { TransactionContextPropagator, } from './transaction-context'; import { ServerProviderStatus } from '@openfeature/core'; +import { OpenFeatureClient } from './client/open-feature-client'; // use a symbol as a key for the global singleton const GLOBAL_OPENFEATURE_API_KEY = Symbol.for('@openfeature/js-sdk/api'); @@ -28,11 +29,17 @@ const _globalThis = globalThis as OpenFeatureGlobal; export class OpenFeatureAPI extends OpenFeatureCommonAPI - implements ManageContext, ManageTransactionContextPropagator> + implements + ManageContext, + ManageTransactionContextPropagator> { protected _statusEnumType: typeof ProviderStatus = ProviderStatus; protected _apiEmitter = new OpenFeatureEventEmitter(); - protected _defaultProvider: ProviderWrapper = new ProviderWrapper(NOOP_PROVIDER, ProviderStatus.NOT_READY, this._statusEnumType); + protected _defaultProvider: ProviderWrapper = new ProviderWrapper( + NOOP_PROVIDER, + ProviderStatus.NOT_READY, + this._statusEnumType, + ); protected _domainScopedProviders: Map> = new Map(); protected _createEventEmitter = () => new OpenFeatureEventEmitter(); diff --git a/packages/server/test/client.spec.ts b/packages/server/test/client.spec.ts index 1ce45e17f..fb163511e 100644 --- a/packages/server/test/client.spec.ts +++ b/packages/server/test/client.spec.ts @@ -9,7 +9,6 @@ import { JsonObject, JsonValue, OpenFeature, - OpenFeatureClient, Provider, ProviderFatalError, ProviderStatus, @@ -18,6 +17,7 @@ import { TransactionContext, TransactionContextPropagator, } from '../src'; +import { OpenFeatureClient } from '../src/client/open-feature-client'; const BOOLEAN_VALUE = true; const STRING_VALUE = 'val'; diff --git a/packages/server/test/open-feature.spec.ts b/packages/server/test/open-feature.spec.ts index 803ed14f7..7c5943a86 100644 --- a/packages/server/test/open-feature.spec.ts +++ b/packages/server/test/open-feature.spec.ts @@ -1,5 +1,6 @@ import { Paradigm } from '@openfeature/core'; -import { OpenFeature, OpenFeatureAPI, OpenFeatureClient, Provider, ProviderStatus } from '../src'; +import { OpenFeature, OpenFeatureAPI, Provider, ProviderStatus } from '../src'; +import { OpenFeatureClient } from '../src/client/open-feature-client'; const mockProvider = (config?: { initialStatus?: ProviderStatus; runsOn?: Paradigm }) => { return { diff --git a/packages/shared/src/events/eventing.ts b/packages/shared/src/events/eventing.ts index 753bfebb7..1bb5936fb 100644 --- a/packages/shared/src/events/eventing.ts +++ b/packages/shared/src/events/eventing.ts @@ -6,24 +6,24 @@ export type EventMetadata = { }; export type CommonEventDetails = { - providerName: string; + readonly providerName: string; /** * @deprecated alias of "domain", use domain instead */ - clientName?: string; + readonly clientName?: string; readonly domain?: string; }; type CommonEventProps = { - message?: string; - metadata?: EventMetadata; + readonly message?: string; + readonly metadata?: EventMetadata; }; export type ReadyEvent = CommonEventProps; export type ErrorEvent = CommonEventProps; export type StaleEvent = CommonEventProps; -export type ReconcilingEvent = CommonEventProps & { errorCode: ErrorCode }; -export type ConfigChangeEvent = CommonEventProps & { flagsChanged?: string[] }; +export type ReconcilingEvent = CommonEventProps & { readonly errorCode: ErrorCode }; +export type ConfigChangeEvent = CommonEventProps & { readonly flagsChanged?: string[] }; type ServerEventMap = { [ServerProviderEvents.Ready]: ReadyEvent;