Skip to content

Commit 9a15354

Browse files
committed
wip
1 parent 01fcb93 commit 9a15354

File tree

19 files changed

+156
-35
lines changed

19 files changed

+156
-35
lines changed

packages/server/src/client/client.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import type {
88
import type { Features } from '../evaluation';
99
import type { ProviderStatus } from '../provider';
1010
import type { ProviderEvents } from '../events';
11+
import type { Tracking } from '../tracking';
1112

1213
export interface Client
1314
extends EvaluationLifeCycle<Client>,
1415
Features,
1516
ManageContext<Client>,
1617
ManageLogger<Client>,
18+
Tracking,
1719
Eventing<ProviderEvents> {
1820
readonly metadata: ClientMetadata;
1921
/**

packages/server/src/client/internal/open-feature-client.ts

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
HookContext,
99
JsonValue,
1010
Logger,
11+
TrackingEventDetails,
1112
OpenFeatureError,
1213
ResolutionDetails} from '@openfeature/core';
1314
import {
@@ -223,6 +224,19 @@ export class OpenFeatureClient implements Client {
223224
return this.evaluate<T>(flagKey, this._provider.resolveObjectEvaluation, defaultValue, 'object', context, options);
224225
}
225226

227+
track(occurrenceKey: string, context: EvaluationContext, occurrenceDetails: TrackingEventDetails): void {
228+
229+
this.shortCircuitIfNotReady();
230+
231+
const mergedContext = this.mergeContexts(context);
232+
233+
if (typeof this._provider.track === 'function') {
234+
return this._provider.track?.(occurrenceKey, mergedContext, occurrenceDetails);
235+
} else {
236+
this._logger.debug('Provider does not implement track function: will no-op.');
237+
}
238+
}
239+
226240
private async evaluate<T extends FlagValue>(
227241
flagKey: string,
228242
resolver: (
@@ -246,13 +260,7 @@ export class OpenFeatureClient implements Client {
246260
];
247261
const allHooksReversed = [...allHooks].reverse();
248262

249-
// merge global and client contexts
250-
const mergedContext = {
251-
...OpenFeature.getContext(),
252-
...OpenFeature.getTransactionContext(),
253-
...this._context,
254-
...invocationContext,
255-
};
263+
const mergedContext = this.mergeContexts(invocationContext);
256264

257265
// this reference cannot change during the course of evaluation
258266
// it may be used as a key in WeakMaps
@@ -269,12 +277,7 @@ export class OpenFeatureClient implements Client {
269277
try {
270278
const frozenContext = await this.beforeHooks(allHooks, hookContext, options);
271279

272-
// short circuit evaluation entirely if provider is in a bad state
273-
if (this.providerStatus === ProviderStatus.NOT_READY) {
274-
throw new ProviderNotReadyError('provider has not yet initialized');
275-
} else if (this.providerStatus === ProviderStatus.FATAL) {
276-
throw new ProviderFatalError('provider is in an irrecoverable error state');
277-
}
280+
this.shortCircuitIfNotReady();
278281

279282
// run the referenced resolver, binding the provider.
280283
const resolution = await resolver.call(this._provider, flagKey, defaultValue, frozenContext, this._logger);
@@ -380,4 +383,23 @@ export class OpenFeatureClient implements Client {
380383
private get _logger() {
381384
return this._clientLogger || this.globalLogger();
382385
}
386+
387+
private mergeContexts(invocationContext: EvaluationContext) {
388+
// merge global and client contexts
389+
return {
390+
...OpenFeature.getContext(),
391+
...OpenFeature.getTransactionContext(),
392+
...this._context,
393+
...invocationContext,
394+
};
395+
}
396+
397+
private shortCircuitIfNotReady() {
398+
// short circuit evaluation entirely if provider is in a bad state
399+
if (this.providerStatus === ProviderStatus.NOT_READY) {
400+
throw new ProviderNotReadyError('provider has not yet initialized');
401+
} else if (this.providerStatus === ProviderStatus.FATAL) {
402+
throw new ProviderFatalError('provider is in an irrecoverable error state');
403+
}
404+
}
383405
}

packages/server/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ export * from './open-feature';
55
export * from './transaction-context';
66
export * from './events';
77
export * from './hooks';
8+
export * from './tracking';
89
export * from '@openfeature/core';

packages/server/src/provider/provider.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
import type { CommonProvider, EvaluationContext, JsonValue, Logger, ResolutionDetails} from '@openfeature/core';
2-
import { ServerProviderStatus } from '@openfeature/core';
1+
import type {
2+
CommonProvider,
3+
EvaluationContext,
4+
JsonValue,
5+
Logger,
6+
TrackingEventDetails,
7+
ResolutionDetails} from '@openfeature/core';
8+
import {
9+
ServerProviderStatus,
10+
} from '@openfeature/core';
311
import type { Hook } from '../hooks';
412

513
export { ServerProviderStatus as ProviderStatus };
@@ -58,4 +66,12 @@ export interface Provider extends CommonProvider<ServerProviderStatus> {
5866
context: EvaluationContext,
5967
logger: Logger,
6068
): Promise<ResolutionDetails<T>>;
69+
70+
/**
71+
* Track a user action or application state, usually representing a business objective or outcome.
72+
* @param trackingEventName
73+
* @param context
74+
* @param trackingEventDetails
75+
*/
76+
track?(trackingEventName: string, context?: EvaluationContext, trackingEventDetails?: TrackingEventDetails): void;
6177
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './tracking';
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { EvaluationContext, TrackingEventDetails } from '@openfeature/core';
2+
3+
export interface Tracking {
4+
5+
/**
6+
* Track a user action or application state, usually representing a business objective or outcome.
7+
* @param trackingEventName an identifier for the event
8+
* @param context the evaluation context
9+
* @param trackingEventDetails the details of the tracking event
10+
*/
11+
track(trackingEventName: string, context?: EvaluationContext, trackingEventDetails?: TrackingEventDetails): void;
12+
}

packages/shared/src/evaluation/context.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { PrimitiveValue } from './evaluation';
1+
import type { PrimitiveValue } from '../types';
22

33
export type EvaluationContextValue =
44
| PrimitiveValue

packages/shared/src/evaluation/evaluation.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
1-
export type FlagValueType = 'boolean' | 'string' | 'number' | 'object';
2-
3-
export type PrimitiveValue = null | boolean | string | number;
4-
export type JsonObject = { [key: string]: JsonValue };
5-
export type JsonArray = JsonValue[];
1+
import type { JsonValue } from '../types/structure';
62

7-
/**
8-
* Represents a JSON node value.
9-
*/
10-
export type JsonValue = PrimitiveValue | JsonObject | JsonArray;
3+
export type FlagValueType = 'boolean' | 'string' | 'number' | 'object';
114

125
/**
136
* Represents a JSON node value, or Date.

packages/shared/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ export * from './logger';
77
export * from './provider';
88
export * from './evaluation';
99
export * from './type-guards';
10+
export * from './tracking';
1011
export * from './open-feature';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './tracking-event';

0 commit comments

Comments
 (0)