@grafana-experiments/sdk 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +339 -0
- package/RELEASING.md +36 -0
- package/dist/chunk-W7444I4G.js +792 -0
- package/dist/grafana.cjs +852 -0
- package/dist/grafana.d.cts +22 -0
- package/dist/grafana.d.ts +22 -0
- package/dist/grafana.js +49 -0
- package/dist/index.cjs +846 -0
- package/dist/index.d.cts +35 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +26 -0
- package/dist/react.cjs +63 -0
- package/dist/react.d.cts +14 -0
- package/dist/react.d.ts +14 -0
- package/dist/react.js +36 -0
- package/dist/types-M9Kqiiqw.d.cts +171 -0
- package/dist/types-M9Kqiiqw.d.ts +171 -0
- package/package.json +73 -1
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { E as ExperimentsOptions, e as Experiments, f as EventDefinition, R as ReportOptions, A as Assignment, M as MeasurementContext } from './types-M9Kqiiqw.cjs';
|
|
2
|
+
export { g as AllocationUnit, h as AnalyticsReporter, i as AssignmentMetadata, D as Diagnostic, d as EventManifest, c as Experiment, a as ExperimentConfig, b as ExperimentDefinition, j as ExperimentState, F as FaroEventType, J as Journey, P as Properties, V as Variant } from './types-M9Kqiiqw.cjs';
|
|
3
|
+
import { BrowserConfig } from '@grafana/faro-web-sdk';
|
|
4
|
+
import { TrackingEventDetails } from '@openfeature/web-sdk';
|
|
5
|
+
|
|
6
|
+
declare function createExperiments(options: ExperimentsOptions): Experiments;
|
|
7
|
+
|
|
8
|
+
declare function defineEvent<const T extends EventDefinition>(definition: T): T;
|
|
9
|
+
type Value<T> = T extends {
|
|
10
|
+
type: 'number';
|
|
11
|
+
} ? number : T extends {
|
|
12
|
+
type: 'boolean';
|
|
13
|
+
} ? boolean : string;
|
|
14
|
+
type EventProperties<T extends EventDefinition> = {
|
|
15
|
+
[K in keyof T['properties'] as T['properties'][K] extends {
|
|
16
|
+
required: true;
|
|
17
|
+
} ? K : never]: Value<T['properties'][K]>;
|
|
18
|
+
} & {
|
|
19
|
+
[K in keyof T['properties'] as T['properties'][K] extends {
|
|
20
|
+
required: true;
|
|
21
|
+
} ? never : K]?: Value<T['properties'][K]>;
|
|
22
|
+
};
|
|
23
|
+
declare function createTypedReporter<T extends Record<string, EventDefinition>>(sdk: Experiments, definitions: T): <K extends keyof T & string>(name: K, properties: EventProperties<T[K]>, options?: ReportOptions) => void;
|
|
24
|
+
|
|
25
|
+
/** Install before Faro initialization; bind getAssignments to the SDK when it exists. */
|
|
26
|
+
declare function createExperimentEnricher(options: {
|
|
27
|
+
getAssignments: () => readonly Assignment[];
|
|
28
|
+
isEnabled?: () => boolean;
|
|
29
|
+
beforeSend?: BrowserConfig['beforeSend'];
|
|
30
|
+
}): NonNullable<BrowserConfig['beforeSend']>;
|
|
31
|
+
|
|
32
|
+
/** Explicitly opt in to sharing captured attribution with the flag provider. */
|
|
33
|
+
declare function toOpenFeatureTrackingDetails(context: MeasurementContext): TrackingEventDetails;
|
|
34
|
+
|
|
35
|
+
export { Assignment, EventDefinition, Experiments, ExperimentsOptions, MeasurementContext, ReportOptions, createExperimentEnricher, createExperiments, createTypedReporter, defineEvent, toOpenFeatureTrackingDetails };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { E as ExperimentsOptions, e as Experiments, f as EventDefinition, R as ReportOptions, A as Assignment, M as MeasurementContext } from './types-M9Kqiiqw.js';
|
|
2
|
+
export { g as AllocationUnit, h as AnalyticsReporter, i as AssignmentMetadata, D as Diagnostic, d as EventManifest, c as Experiment, a as ExperimentConfig, b as ExperimentDefinition, j as ExperimentState, F as FaroEventType, J as Journey, P as Properties, V as Variant } from './types-M9Kqiiqw.js';
|
|
3
|
+
import { BrowserConfig } from '@grafana/faro-web-sdk';
|
|
4
|
+
import { TrackingEventDetails } from '@openfeature/web-sdk';
|
|
5
|
+
|
|
6
|
+
declare function createExperiments(options: ExperimentsOptions): Experiments;
|
|
7
|
+
|
|
8
|
+
declare function defineEvent<const T extends EventDefinition>(definition: T): T;
|
|
9
|
+
type Value<T> = T extends {
|
|
10
|
+
type: 'number';
|
|
11
|
+
} ? number : T extends {
|
|
12
|
+
type: 'boolean';
|
|
13
|
+
} ? boolean : string;
|
|
14
|
+
type EventProperties<T extends EventDefinition> = {
|
|
15
|
+
[K in keyof T['properties'] as T['properties'][K] extends {
|
|
16
|
+
required: true;
|
|
17
|
+
} ? K : never]: Value<T['properties'][K]>;
|
|
18
|
+
} & {
|
|
19
|
+
[K in keyof T['properties'] as T['properties'][K] extends {
|
|
20
|
+
required: true;
|
|
21
|
+
} ? never : K]?: Value<T['properties'][K]>;
|
|
22
|
+
};
|
|
23
|
+
declare function createTypedReporter<T extends Record<string, EventDefinition>>(sdk: Experiments, definitions: T): <K extends keyof T & string>(name: K, properties: EventProperties<T[K]>, options?: ReportOptions) => void;
|
|
24
|
+
|
|
25
|
+
/** Install before Faro initialization; bind getAssignments to the SDK when it exists. */
|
|
26
|
+
declare function createExperimentEnricher(options: {
|
|
27
|
+
getAssignments: () => readonly Assignment[];
|
|
28
|
+
isEnabled?: () => boolean;
|
|
29
|
+
beforeSend?: BrowserConfig['beforeSend'];
|
|
30
|
+
}): NonNullable<BrowserConfig['beforeSend']>;
|
|
31
|
+
|
|
32
|
+
/** Explicitly opt in to sharing captured attribution with the flag provider. */
|
|
33
|
+
declare function toOpenFeatureTrackingDetails(context: MeasurementContext): TrackingEventDetails;
|
|
34
|
+
|
|
35
|
+
export { Assignment, EventDefinition, Experiments, ExperimentsOptions, MeasurementContext, ReportOptions, createExperimentEnricher, createExperiments, createTypedReporter, defineEvent, toOpenFeatureTrackingDetails };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createExperimentEnricher,
|
|
3
|
+
createExperiments,
|
|
4
|
+
createTypedReporter,
|
|
5
|
+
defineEvent
|
|
6
|
+
} from "./chunk-W7444I4G.js";
|
|
7
|
+
|
|
8
|
+
// src/tracking.ts
|
|
9
|
+
function toOpenFeatureTrackingDetails(context) {
|
|
10
|
+
return {
|
|
11
|
+
event_id: context.event_id,
|
|
12
|
+
telemetry_schema_version: context.telemetry_schema_version,
|
|
13
|
+
experiments: context.experiments.map(
|
|
14
|
+
(assignment) => Object.fromEntries(Object.entries(assignment).filter(([, value]) => value !== void 0))
|
|
15
|
+
),
|
|
16
|
+
...context.event_schema_version !== void 0 ? { event_schema_version: context.event_schema_version } : {},
|
|
17
|
+
...context.journey_id !== void 0 ? { journey_id: context.journey_id } : {}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
createExperimentEnricher,
|
|
22
|
+
createExperiments,
|
|
23
|
+
createTypedReporter,
|
|
24
|
+
defineEvent,
|
|
25
|
+
toOpenFeatureTrackingDetails
|
|
26
|
+
};
|
package/dist/react.cjs
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/react.tsx
|
|
21
|
+
var react_exports = {};
|
|
22
|
+
__export(react_exports, {
|
|
23
|
+
ExperimentsProvider: () => ExperimentsProvider,
|
|
24
|
+
useExperiment: () => useExperiment,
|
|
25
|
+
useExperiments: () => useExperiments
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(react_exports);
|
|
28
|
+
var import_react = require("react");
|
|
29
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
30
|
+
var Context = (0, import_react.createContext)(null);
|
|
31
|
+
var serverSnapshot = () => ({ status: "inactive" });
|
|
32
|
+
var INACTIVE = serverSnapshot();
|
|
33
|
+
function ExperimentsProvider({
|
|
34
|
+
experiments,
|
|
35
|
+
children
|
|
36
|
+
}) {
|
|
37
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Context.Provider, { value: experiments, children });
|
|
38
|
+
}
|
|
39
|
+
function useExperiments() {
|
|
40
|
+
const experiments = (0, import_react.useContext)(Context);
|
|
41
|
+
if (!experiments) {
|
|
42
|
+
throw new Error("useExperiments requires ExperimentsProvider");
|
|
43
|
+
}
|
|
44
|
+
return experiments;
|
|
45
|
+
}
|
|
46
|
+
function useExperiment(experiment, eligible = true, entryId) {
|
|
47
|
+
const state = (0, import_react.useSyncExternalStore)(experiment.subscribe, experiment.getSnapshot, () => INACTIVE);
|
|
48
|
+
(0, import_react.useEffect)(() => {
|
|
49
|
+
if (!eligible) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const controller = new AbortController();
|
|
53
|
+
void experiment.activate({ signal: controller.signal, entryId });
|
|
54
|
+
return () => controller.abort();
|
|
55
|
+
}, [experiment, eligible, entryId, state.status === "inactive"]);
|
|
56
|
+
return state;
|
|
57
|
+
}
|
|
58
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
59
|
+
0 && (module.exports = {
|
|
60
|
+
ExperimentsProvider,
|
|
61
|
+
useExperiment,
|
|
62
|
+
useExperiments
|
|
63
|
+
});
|
package/dist/react.d.cts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { e as Experiments, a as ExperimentConfig, c as Experiment, j as ExperimentState } from './types-M9Kqiiqw.cjs';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
import '@grafana/faro-web-sdk';
|
|
5
|
+
import '@openfeature/web-sdk';
|
|
6
|
+
|
|
7
|
+
declare function ExperimentsProvider({ experiments, children, }: {
|
|
8
|
+
experiments: Experiments;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
}): react.JSX.Element;
|
|
11
|
+
declare function useExperiments(): Experiments;
|
|
12
|
+
declare function useExperiment<T extends ExperimentConfig>(experiment: Experiment<T>, eligible?: boolean, entryId?: string): ExperimentState<T>;
|
|
13
|
+
|
|
14
|
+
export { ExperimentsProvider, useExperiment, useExperiments };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { e as Experiments, a as ExperimentConfig, c as Experiment, j as ExperimentState } from './types-M9Kqiiqw.js';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
import '@grafana/faro-web-sdk';
|
|
5
|
+
import '@openfeature/web-sdk';
|
|
6
|
+
|
|
7
|
+
declare function ExperimentsProvider({ experiments, children, }: {
|
|
8
|
+
experiments: Experiments;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
}): react.JSX.Element;
|
|
11
|
+
declare function useExperiments(): Experiments;
|
|
12
|
+
declare function useExperiment<T extends ExperimentConfig>(experiment: Experiment<T>, eligible?: boolean, entryId?: string): ExperimentState<T>;
|
|
13
|
+
|
|
14
|
+
export { ExperimentsProvider, useExperiment, useExperiments };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/react.tsx
|
|
2
|
+
import { createContext, useContext, useEffect, useSyncExternalStore } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
var Context = createContext(null);
|
|
5
|
+
var serverSnapshot = () => ({ status: "inactive" });
|
|
6
|
+
var INACTIVE = serverSnapshot();
|
|
7
|
+
function ExperimentsProvider({
|
|
8
|
+
experiments,
|
|
9
|
+
children
|
|
10
|
+
}) {
|
|
11
|
+
return /* @__PURE__ */ jsx(Context.Provider, { value: experiments, children });
|
|
12
|
+
}
|
|
13
|
+
function useExperiments() {
|
|
14
|
+
const experiments = useContext(Context);
|
|
15
|
+
if (!experiments) {
|
|
16
|
+
throw new Error("useExperiments requires ExperimentsProvider");
|
|
17
|
+
}
|
|
18
|
+
return experiments;
|
|
19
|
+
}
|
|
20
|
+
function useExperiment(experiment, eligible = true, entryId) {
|
|
21
|
+
const state = useSyncExternalStore(experiment.subscribe, experiment.getSnapshot, () => INACTIVE);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!eligible) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
void experiment.activate({ signal: controller.signal, entryId });
|
|
28
|
+
return () => controller.abort();
|
|
29
|
+
}, [experiment, eligible, entryId, state.status === "inactive"]);
|
|
30
|
+
return state;
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
ExperimentsProvider,
|
|
34
|
+
useExperiment,
|
|
35
|
+
useExperiments
|
|
36
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { Faro, BrowserConfig } from '@grafana/faro-web-sdk';
|
|
2
|
+
import { Client, TrackingEventDetails } from '@openfeature/web-sdk';
|
|
3
|
+
|
|
4
|
+
type Variant = 'control' | 'treatment' | 'excluded';
|
|
5
|
+
type Properties = Record<string, unknown>;
|
|
6
|
+
type AnalyticsReporter = (name: string, properties: Properties) => void | Promise<void>;
|
|
7
|
+
type FaroEventType = 'custom-event' | 'user-action';
|
|
8
|
+
interface ReportOptions {
|
|
9
|
+
faro?: {
|
|
10
|
+
type: FaroEventType;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
interface ExperimentConfig {
|
|
14
|
+
variant: Variant;
|
|
15
|
+
}
|
|
16
|
+
interface ExperimentDefinition<T extends ExperimentConfig = ExperimentConfig> {
|
|
17
|
+
id: string;
|
|
18
|
+
flagKey: string;
|
|
19
|
+
group?: string;
|
|
20
|
+
metadata?: AssignmentMetadata;
|
|
21
|
+
mapAssignment?: (evaluation: {
|
|
22
|
+
value: unknown;
|
|
23
|
+
variant?: string;
|
|
24
|
+
flagMetadata?: Record<string, unknown>;
|
|
25
|
+
reason?: string;
|
|
26
|
+
}) => AssignmentMetadata;
|
|
27
|
+
flag: {
|
|
28
|
+
type: 'boolean';
|
|
29
|
+
} | {
|
|
30
|
+
type: 'object';
|
|
31
|
+
validate: (value: unknown) => value is T;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
interface Assignment {
|
|
35
|
+
experiment_id: string;
|
|
36
|
+
experiment_revision?: string;
|
|
37
|
+
allocation_unit_type?: AllocationUnit['type'];
|
|
38
|
+
allocation_unit_id?: string;
|
|
39
|
+
assignment_source?: 'provider' | 'host' | 'unknown';
|
|
40
|
+
population?: AssignmentMetadata['population'];
|
|
41
|
+
activated_at?: string;
|
|
42
|
+
experiment_group: string;
|
|
43
|
+
flag_key: string;
|
|
44
|
+
variant: 'control' | 'treatment';
|
|
45
|
+
session_id: string;
|
|
46
|
+
exposure_id: string;
|
|
47
|
+
}
|
|
48
|
+
type ExperimentState<T extends ExperimentConfig = ExperimentConfig> = {
|
|
49
|
+
status: 'inactive';
|
|
50
|
+
} | {
|
|
51
|
+
status: 'unavailable';
|
|
52
|
+
reason: 'disabled' | 'no-session' | 'provider' | 'evaluation' | 'invalid-value' | 'disposed';
|
|
53
|
+
} | {
|
|
54
|
+
status: 'excluded';
|
|
55
|
+
variant: 'excluded';
|
|
56
|
+
config?: Readonly<T>;
|
|
57
|
+
} | {
|
|
58
|
+
status: 'active';
|
|
59
|
+
variant: 'control' | 'treatment';
|
|
60
|
+
assignment: Readonly<Assignment>;
|
|
61
|
+
config?: Readonly<T>;
|
|
62
|
+
};
|
|
63
|
+
interface Experiment<T extends ExperimentConfig = ExperimentConfig> {
|
|
64
|
+
readonly id: string;
|
|
65
|
+
activate: (options?: {
|
|
66
|
+
signal?: AbortSignal;
|
|
67
|
+
entryId?: string;
|
|
68
|
+
}) => Promise<ExperimentState<T>>;
|
|
69
|
+
getSnapshot: () => ExperimentState<T>;
|
|
70
|
+
subscribe: (listener: () => void) => () => void;
|
|
71
|
+
}
|
|
72
|
+
/** Attribution captured once for an outcome, independent of destination serialization. */
|
|
73
|
+
interface MeasurementContext {
|
|
74
|
+
readonly event_id: string;
|
|
75
|
+
readonly experiments: readonly Readonly<Assignment>[];
|
|
76
|
+
readonly telemetry_schema_version: 2;
|
|
77
|
+
readonly event_schema_version?: number;
|
|
78
|
+
readonly journey_id?: string;
|
|
79
|
+
}
|
|
80
|
+
interface ExperimentsOptions {
|
|
81
|
+
/** Stable application identifier; namespaces session storage and exposure identities. */
|
|
82
|
+
scope: string;
|
|
83
|
+
client: Client;
|
|
84
|
+
/** Optional existing analytics destination. Omit for Faro-only reporting. */
|
|
85
|
+
reportAnalytics?: AnalyticsReporter;
|
|
86
|
+
faro: {
|
|
87
|
+
instance: Faro;
|
|
88
|
+
config?: never;
|
|
89
|
+
} | {
|
|
90
|
+
config: BrowserConfig;
|
|
91
|
+
instance?: never;
|
|
92
|
+
};
|
|
93
|
+
getSessionId?: () => string | undefined;
|
|
94
|
+
contextKey?: string;
|
|
95
|
+
/** Gate both destinations and activation; the host owns consent. */
|
|
96
|
+
isEnabled?: () => boolean;
|
|
97
|
+
events?: Record<string, FaroEventType>;
|
|
98
|
+
eventDefinitions?: Record<string, EventDefinition>;
|
|
99
|
+
onDiagnostic?: (diagnostic: Diagnostic) => void;
|
|
100
|
+
enrichFaro?: boolean;
|
|
101
|
+
transformFaroProperties?: (name: string, properties: Properties) => Properties | null;
|
|
102
|
+
reportExposure?: (assignment: Readonly<Assignment>) => void | Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* Opt-in: also send outcome events to the OpenFeature Tracking API, for providers that attribute goals themselves. Return the details to send, or
|
|
105
|
+
* null to skip. Receives isolated original properties and captured attribution; return only fields to share. Providers without `track` no-op.
|
|
106
|
+
*/
|
|
107
|
+
openFeatureTracking?: (name: string, properties: Properties, context: MeasurementContext) => TrackingEventDetails | null | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* Record `feature_flag.result.value` on exposures when the provider supplies no variant, as the
|
|
110
|
+
* OpenTelemetry convention requires. Disable when flag values are sensitive or large. Default true.
|
|
111
|
+
*/
|
|
112
|
+
recordFlagValue?: boolean;
|
|
113
|
+
onError?: (error: unknown) => void;
|
|
114
|
+
readinessTimeoutMs?: number;
|
|
115
|
+
storage?: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'> | null;
|
|
116
|
+
}
|
|
117
|
+
interface Experiments {
|
|
118
|
+
readonly ready: Promise<void>;
|
|
119
|
+
defineExperiment: <T extends ExperimentConfig = ExperimentConfig>(definition: ExperimentDefinition<T>) => Experiment<T>;
|
|
120
|
+
reportAnalytics: (name: string, properties?: Properties, options?: ReportOptions) => void;
|
|
121
|
+
/** Call when a host-provided session ID or consent changes. Faro session changes are observed automatically. */
|
|
122
|
+
refreshSession: () => void;
|
|
123
|
+
/** Supply a stable, non-secret identity/tenant context key before activating in a new context. */
|
|
124
|
+
resetContext: (contextKey: string) => void;
|
|
125
|
+
beginJourney: () => Journey;
|
|
126
|
+
getEventManifest: () => EventManifest;
|
|
127
|
+
getDiagnostics: () => readonly Diagnostic[];
|
|
128
|
+
subscribeDiagnostics: (listener: (diagnostic: Diagnostic) => void) => () => void;
|
|
129
|
+
/** Attribution snapshot for an opt-in Faro enrichment hook. Never activates experiments. */
|
|
130
|
+
getActiveAssignments: () => readonly Assignment[];
|
|
131
|
+
dispose: () => void;
|
|
132
|
+
}
|
|
133
|
+
interface AllocationUnit {
|
|
134
|
+
type: 'stack' | 'organization' | 'user' | 'session';
|
|
135
|
+
id: string;
|
|
136
|
+
}
|
|
137
|
+
interface AssignmentMetadata {
|
|
138
|
+
revision?: string;
|
|
139
|
+
allocationUnit?: AllocationUnit;
|
|
140
|
+
population?: 'randomized' | 'test' | 'targeted';
|
|
141
|
+
}
|
|
142
|
+
interface Diagnostic {
|
|
143
|
+
sequence: number;
|
|
144
|
+
timestamp: string;
|
|
145
|
+
code: string;
|
|
146
|
+
experimentId?: string;
|
|
147
|
+
eventName?: string;
|
|
148
|
+
detail?: string;
|
|
149
|
+
}
|
|
150
|
+
type PropertyType = 'string' | 'number' | 'boolean';
|
|
151
|
+
interface EventDefinition {
|
|
152
|
+
version: number;
|
|
153
|
+
description?: string;
|
|
154
|
+
properties: Record<string, {
|
|
155
|
+
type: PropertyType;
|
|
156
|
+
required?: boolean;
|
|
157
|
+
}>;
|
|
158
|
+
}
|
|
159
|
+
interface EventManifest {
|
|
160
|
+
schemaVersion: 2;
|
|
161
|
+
scope: string;
|
|
162
|
+
events: Record<string, EventDefinition>;
|
|
163
|
+
}
|
|
164
|
+
interface Journey {
|
|
165
|
+
readonly id: string;
|
|
166
|
+
getTelemetryContext: () => Record<string, string>;
|
|
167
|
+
reportAnalytics(name: string, properties?: Properties, options?: ReportOptions): void;
|
|
168
|
+
end(): void;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export type { Assignment as A, Diagnostic as D, ExperimentsOptions as E, FaroEventType as F, Journey as J, MeasurementContext as M, Properties as P, ReportOptions as R, Variant as V, ExperimentConfig as a, ExperimentDefinition as b, Experiment as c, EventManifest as d, Experiments as e, EventDefinition as f, AllocationUnit as g, AnalyticsReporter as h, AssignmentMetadata as i, ExperimentState as j };
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { Faro, BrowserConfig } from '@grafana/faro-web-sdk';
|
|
2
|
+
import { Client, TrackingEventDetails } from '@openfeature/web-sdk';
|
|
3
|
+
|
|
4
|
+
type Variant = 'control' | 'treatment' | 'excluded';
|
|
5
|
+
type Properties = Record<string, unknown>;
|
|
6
|
+
type AnalyticsReporter = (name: string, properties: Properties) => void | Promise<void>;
|
|
7
|
+
type FaroEventType = 'custom-event' | 'user-action';
|
|
8
|
+
interface ReportOptions {
|
|
9
|
+
faro?: {
|
|
10
|
+
type: FaroEventType;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
interface ExperimentConfig {
|
|
14
|
+
variant: Variant;
|
|
15
|
+
}
|
|
16
|
+
interface ExperimentDefinition<T extends ExperimentConfig = ExperimentConfig> {
|
|
17
|
+
id: string;
|
|
18
|
+
flagKey: string;
|
|
19
|
+
group?: string;
|
|
20
|
+
metadata?: AssignmentMetadata;
|
|
21
|
+
mapAssignment?: (evaluation: {
|
|
22
|
+
value: unknown;
|
|
23
|
+
variant?: string;
|
|
24
|
+
flagMetadata?: Record<string, unknown>;
|
|
25
|
+
reason?: string;
|
|
26
|
+
}) => AssignmentMetadata;
|
|
27
|
+
flag: {
|
|
28
|
+
type: 'boolean';
|
|
29
|
+
} | {
|
|
30
|
+
type: 'object';
|
|
31
|
+
validate: (value: unknown) => value is T;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
interface Assignment {
|
|
35
|
+
experiment_id: string;
|
|
36
|
+
experiment_revision?: string;
|
|
37
|
+
allocation_unit_type?: AllocationUnit['type'];
|
|
38
|
+
allocation_unit_id?: string;
|
|
39
|
+
assignment_source?: 'provider' | 'host' | 'unknown';
|
|
40
|
+
population?: AssignmentMetadata['population'];
|
|
41
|
+
activated_at?: string;
|
|
42
|
+
experiment_group: string;
|
|
43
|
+
flag_key: string;
|
|
44
|
+
variant: 'control' | 'treatment';
|
|
45
|
+
session_id: string;
|
|
46
|
+
exposure_id: string;
|
|
47
|
+
}
|
|
48
|
+
type ExperimentState<T extends ExperimentConfig = ExperimentConfig> = {
|
|
49
|
+
status: 'inactive';
|
|
50
|
+
} | {
|
|
51
|
+
status: 'unavailable';
|
|
52
|
+
reason: 'disabled' | 'no-session' | 'provider' | 'evaluation' | 'invalid-value' | 'disposed';
|
|
53
|
+
} | {
|
|
54
|
+
status: 'excluded';
|
|
55
|
+
variant: 'excluded';
|
|
56
|
+
config?: Readonly<T>;
|
|
57
|
+
} | {
|
|
58
|
+
status: 'active';
|
|
59
|
+
variant: 'control' | 'treatment';
|
|
60
|
+
assignment: Readonly<Assignment>;
|
|
61
|
+
config?: Readonly<T>;
|
|
62
|
+
};
|
|
63
|
+
interface Experiment<T extends ExperimentConfig = ExperimentConfig> {
|
|
64
|
+
readonly id: string;
|
|
65
|
+
activate: (options?: {
|
|
66
|
+
signal?: AbortSignal;
|
|
67
|
+
entryId?: string;
|
|
68
|
+
}) => Promise<ExperimentState<T>>;
|
|
69
|
+
getSnapshot: () => ExperimentState<T>;
|
|
70
|
+
subscribe: (listener: () => void) => () => void;
|
|
71
|
+
}
|
|
72
|
+
/** Attribution captured once for an outcome, independent of destination serialization. */
|
|
73
|
+
interface MeasurementContext {
|
|
74
|
+
readonly event_id: string;
|
|
75
|
+
readonly experiments: readonly Readonly<Assignment>[];
|
|
76
|
+
readonly telemetry_schema_version: 2;
|
|
77
|
+
readonly event_schema_version?: number;
|
|
78
|
+
readonly journey_id?: string;
|
|
79
|
+
}
|
|
80
|
+
interface ExperimentsOptions {
|
|
81
|
+
/** Stable application identifier; namespaces session storage and exposure identities. */
|
|
82
|
+
scope: string;
|
|
83
|
+
client: Client;
|
|
84
|
+
/** Optional existing analytics destination. Omit for Faro-only reporting. */
|
|
85
|
+
reportAnalytics?: AnalyticsReporter;
|
|
86
|
+
faro: {
|
|
87
|
+
instance: Faro;
|
|
88
|
+
config?: never;
|
|
89
|
+
} | {
|
|
90
|
+
config: BrowserConfig;
|
|
91
|
+
instance?: never;
|
|
92
|
+
};
|
|
93
|
+
getSessionId?: () => string | undefined;
|
|
94
|
+
contextKey?: string;
|
|
95
|
+
/** Gate both destinations and activation; the host owns consent. */
|
|
96
|
+
isEnabled?: () => boolean;
|
|
97
|
+
events?: Record<string, FaroEventType>;
|
|
98
|
+
eventDefinitions?: Record<string, EventDefinition>;
|
|
99
|
+
onDiagnostic?: (diagnostic: Diagnostic) => void;
|
|
100
|
+
enrichFaro?: boolean;
|
|
101
|
+
transformFaroProperties?: (name: string, properties: Properties) => Properties | null;
|
|
102
|
+
reportExposure?: (assignment: Readonly<Assignment>) => void | Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* Opt-in: also send outcome events to the OpenFeature Tracking API, for providers that attribute goals themselves. Return the details to send, or
|
|
105
|
+
* null to skip. Receives isolated original properties and captured attribution; return only fields to share. Providers without `track` no-op.
|
|
106
|
+
*/
|
|
107
|
+
openFeatureTracking?: (name: string, properties: Properties, context: MeasurementContext) => TrackingEventDetails | null | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* Record `feature_flag.result.value` on exposures when the provider supplies no variant, as the
|
|
110
|
+
* OpenTelemetry convention requires. Disable when flag values are sensitive or large. Default true.
|
|
111
|
+
*/
|
|
112
|
+
recordFlagValue?: boolean;
|
|
113
|
+
onError?: (error: unknown) => void;
|
|
114
|
+
readinessTimeoutMs?: number;
|
|
115
|
+
storage?: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'> | null;
|
|
116
|
+
}
|
|
117
|
+
interface Experiments {
|
|
118
|
+
readonly ready: Promise<void>;
|
|
119
|
+
defineExperiment: <T extends ExperimentConfig = ExperimentConfig>(definition: ExperimentDefinition<T>) => Experiment<T>;
|
|
120
|
+
reportAnalytics: (name: string, properties?: Properties, options?: ReportOptions) => void;
|
|
121
|
+
/** Call when a host-provided session ID or consent changes. Faro session changes are observed automatically. */
|
|
122
|
+
refreshSession: () => void;
|
|
123
|
+
/** Supply a stable, non-secret identity/tenant context key before activating in a new context. */
|
|
124
|
+
resetContext: (contextKey: string) => void;
|
|
125
|
+
beginJourney: () => Journey;
|
|
126
|
+
getEventManifest: () => EventManifest;
|
|
127
|
+
getDiagnostics: () => readonly Diagnostic[];
|
|
128
|
+
subscribeDiagnostics: (listener: (diagnostic: Diagnostic) => void) => () => void;
|
|
129
|
+
/** Attribution snapshot for an opt-in Faro enrichment hook. Never activates experiments. */
|
|
130
|
+
getActiveAssignments: () => readonly Assignment[];
|
|
131
|
+
dispose: () => void;
|
|
132
|
+
}
|
|
133
|
+
interface AllocationUnit {
|
|
134
|
+
type: 'stack' | 'organization' | 'user' | 'session';
|
|
135
|
+
id: string;
|
|
136
|
+
}
|
|
137
|
+
interface AssignmentMetadata {
|
|
138
|
+
revision?: string;
|
|
139
|
+
allocationUnit?: AllocationUnit;
|
|
140
|
+
population?: 'randomized' | 'test' | 'targeted';
|
|
141
|
+
}
|
|
142
|
+
interface Diagnostic {
|
|
143
|
+
sequence: number;
|
|
144
|
+
timestamp: string;
|
|
145
|
+
code: string;
|
|
146
|
+
experimentId?: string;
|
|
147
|
+
eventName?: string;
|
|
148
|
+
detail?: string;
|
|
149
|
+
}
|
|
150
|
+
type PropertyType = 'string' | 'number' | 'boolean';
|
|
151
|
+
interface EventDefinition {
|
|
152
|
+
version: number;
|
|
153
|
+
description?: string;
|
|
154
|
+
properties: Record<string, {
|
|
155
|
+
type: PropertyType;
|
|
156
|
+
required?: boolean;
|
|
157
|
+
}>;
|
|
158
|
+
}
|
|
159
|
+
interface EventManifest {
|
|
160
|
+
schemaVersion: 2;
|
|
161
|
+
scope: string;
|
|
162
|
+
events: Record<string, EventDefinition>;
|
|
163
|
+
}
|
|
164
|
+
interface Journey {
|
|
165
|
+
readonly id: string;
|
|
166
|
+
getTelemetryContext: () => Record<string, string>;
|
|
167
|
+
reportAnalytics(name: string, properties?: Properties, options?: ReportOptions): void;
|
|
168
|
+
end(): void;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export type { Assignment as A, Diagnostic as D, ExperimentsOptions as E, FaroEventType as F, Journey as J, MeasurementContext as M, Properties as P, ReportOptions as R, Variant as V, ExperimentConfig as a, ExperimentDefinition as b, Experiment as c, EventManifest as d, Experiments as e, EventDefinition as f, AllocationUnit as g, AnalyticsReporter as h, AssignmentMetadata as i, ExperimentState as j };
|
package/package.json
CHANGED
|
@@ -1 +1,73 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"name": "@grafana-experiments/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Explicit experiment activation and analytics reporting with OpenFeature and Grafana Faro",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE",
|
|
12
|
+
"RELEASING.md"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"import": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"require": {
|
|
21
|
+
"types": "./dist/index.d.cts",
|
|
22
|
+
"default": "./dist/index.cjs"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"./grafana": {
|
|
26
|
+
"import": {
|
|
27
|
+
"types": "./dist/grafana.d.ts",
|
|
28
|
+
"default": "./dist/grafana.js"
|
|
29
|
+
},
|
|
30
|
+
"require": {
|
|
31
|
+
"types": "./dist/grafana.d.cts",
|
|
32
|
+
"default": "./dist/grafana.cjs"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"./react": {
|
|
36
|
+
"import": {
|
|
37
|
+
"types": "./dist/react.d.ts",
|
|
38
|
+
"default": "./dist/react.js"
|
|
39
|
+
},
|
|
40
|
+
"require": {
|
|
41
|
+
"types": "./dist/react.d.cts",
|
|
42
|
+
"default": "./dist/react.cjs"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=22"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@grafana/faro-web-sdk": ">=2.12.0 <2.13.0",
|
|
51
|
+
"@grafana/runtime": ">=13.2.2 <14",
|
|
52
|
+
"@openfeature/web-sdk": ">=1.9.0 <2",
|
|
53
|
+
"react": "^18.3.0 || ^19.0.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependenciesMeta": {
|
|
56
|
+
"@grafana/runtime": {
|
|
57
|
+
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"react": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public",
|
|
65
|
+
"registry": "https://registry.npmjs.org"
|
|
66
|
+
},
|
|
67
|
+
"repository": {
|
|
68
|
+
"type": "git",
|
|
69
|
+
"url": "git+https://github.com/grafana/grafana-experiments-app.git",
|
|
70
|
+
"directory": "packages/grafana-experiments"
|
|
71
|
+
},
|
|
72
|
+
"gitHead": "ba9348e954f7dc3beddba187e56916139bb76790"
|
|
73
|
+
}
|