@traffical/openfeature-web 0.2.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/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # @traffical/openfeature-web
2
+
3
+ OpenFeature **web** (static-context) [provider](https://openfeature.dev/) backed by the Traffical browser SDK (`@traffical/js-client`).
4
+
5
+ Resolvers are synchronous and evaluate against a single **bound static context** (the current user/session). Each resolution runs one Traffical `decide()` (the intent-to-treat / ITT decision); exposure is a separate, explicit render-time signal — so treatment-on-the-treated (ToT) metrics, the SRM health gate, and bandit optimization measure what the user actually saw.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @traffical/openfeature-web @openfeature/web-sdk @traffical/js-client
11
+ ```
12
+
13
+ `@openfeature/web-sdk` and `@traffical/js-client` are peer dependencies.
14
+
15
+ ## Register the provider
16
+
17
+ Construct your Traffical browser client, wrap it in the provider, and set it on OpenFeature with the initial context. The caller owns the client lifecycle.
18
+
19
+ ```ts
20
+ import { OpenFeature } from "@openfeature/web-sdk";
21
+ import { TrafficalClient } from "@traffical/js-client";
22
+ import { TrafficalWebProvider } from "@traffical/openfeature-web";
23
+
24
+ const client = new TrafficalClient({ apiKey: "pk_...", /* ... */ });
25
+
26
+ await OpenFeature.setContext({ targetingKey: user.id, plan: user.plan });
27
+ await OpenFeature.setProviderAndWait(new TrafficalWebProvider(client));
28
+
29
+ const of = OpenFeature.getClient();
30
+ ```
31
+
32
+ ### Static context
33
+
34
+ The provider evaluates against the context bound via `OpenFeature.setContext(...)` — resolvers ignore any per-call context argument. On a context change (e.g. login/logout) the provider re-binds the context, **clears its decision memo** (so a decision made under the old identity is never served or exposed under the new one), flows the new identity into the client, and returns — letting the web SDK emit the reconcile lifecycle events.
35
+
36
+ ## Resolve a flag
37
+
38
+ ```ts
39
+ const enabled = of.getBooleanValue("checkout.newFlow", false);
40
+ const color = of.getStringValue("ui.color", "blue");
41
+ ```
42
+
43
+ Resolvers are synchronous. The `variant`, `reason` (`SPLIT` when a variant was assigned, else `DEFAULT`), and scalar `traffical.*` `flagMetadata` come from the decision.
44
+
45
+ ## Exposure — the `$traffical.exposure` convention
46
+
47
+ Exposure (ToT) is **explicit**: fire it at your render site once the user actually sees the treatment. Use the reserved event name `$traffical.exposure` (exported as `EXPOSURE_EVENT_NAME`) and echo the `flagKey` in the details:
48
+
49
+ ```ts
50
+ import { EXPOSURE_EVENT_NAME } from "@traffical/openfeature-web";
51
+
52
+ of.track(EXPOSURE_EVENT_NAME, { flagKey: "checkout.newFlow" });
53
+ ```
54
+
55
+ The provider stitches this to the memoized decision for that flag in the current context and calls the native `trackExposure()`. It **never re-decides** in the exposure path — a miss (flag not resolved in this context) warns once and no-ops.
56
+
57
+ Every other `track(name, details)` is a business/reward event, forwarded to the client's `track()` joined on the bound targeting key (falling back to the client's anonymous stable id). A numeric `details.value` is lifted out as the reward value.
58
+
59
+ > Web `Tracking` takes **no context argument** — identity comes from the bound static context.
60
+
61
+ ### No-exposure alarm
62
+
63
+ If the provider records many decisions but zero exposures, it fires a one-shot warning and a non-fatal provider `Error` event, since ToT/SRM/optimization would be silently empty. Instrument `$traffical.exposure`, or set `exposureOnResolve`.
64
+
65
+ ## Options
66
+
67
+ `new TrafficalWebProvider(client, options)`:
68
+
69
+ | Option | Description |
70
+ | --- | --- |
71
+ | `exposureOnResolve` | When `true`, the resolver fires `trackExposure()` on the just-made decision (collapsing ToT toward ITT). |
72
+ | `unitKey` | Override the context field the bundle buckets on. Defaults to `client.getUnitKeyField()` (the bundle's `hashing.unitKey`). |
73
+ | `exposureEventName` | Override the reserved exposure event name. Defaults to `$traffical.exposure`. |
74
+ | `gatePropensity` | Omit `traffical.propensity` from `flagMetadata`. **Defaults to `true` on web.** |
75
+
76
+ > On web, `flagMetadata` is visible in browser devtools, so the provider always gates **both** `traffical.propensity` and `traffical.modelVersion` out — bandit selection internals never leak to the client.
77
+
78
+ ## targetingKey → unit-key mapping
79
+
80
+ The bound `targetingKey` is written under the bundle's **actual** bucketing field (`hashing.unitKey`), not a literal `"targetingKey"` field — otherwise the client's context enrichment would overwrite it with the anonymous stable id and silently mis-bucket the unit. A missing/empty targeting key throws `TargetingKeyMissingError`, which the OpenFeature SDK maps to the default with `reason: ERROR`.
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @traffical/openfeature-web
3
+ *
4
+ * OpenFeature WEB (static-context) provider backed by the Traffical browser SDK
5
+ * (`@traffical/js-client`). Milestone M3.
6
+ *
7
+ * See the OpenFeature provider design in the Traffical SDK spec, §2, §3, §5, §6, §7, §9, §13.
8
+ */
9
+ export { TrafficalWebProvider, default } from "./provider.js";
10
+ export type { TrafficalWebClient } from "./provider.js";
11
+ export type { TrafficalProviderOptions, TrafficalClientLike, OFFlagType, } from "@traffical/openfeature-core";
12
+ export { EXPOSURE_EVENT_NAME, FLAG_METADATA_PREFIX } from "@traffical/openfeature-core";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC9D,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAIxD,YAAY,EACV,wBAAwB,EACxB,mBAAmB,EACnB,UAAU,GACX,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @traffical/openfeature-web
3
+ *
4
+ * OpenFeature WEB (static-context) provider backed by the Traffical browser SDK
5
+ * (`@traffical/js-client`). Milestone M3.
6
+ *
7
+ * See the OpenFeature provider design in the Traffical SDK spec, §2, §3, §5, §6, §7, §9, §13.
8
+ */
9
+ export { TrafficalWebProvider, default } from "./provider.js";
10
+ export { EXPOSURE_EVENT_NAME, FLAG_METADATA_PREFIX } from "@traffical/openfeature-core";
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAU9D,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * TrafficalWebProvider — the OpenFeature WEB (static-context) provider backed by
3
+ * the Traffical browser SDK (`@traffical/js-client`).
4
+ *
5
+ * Design: see the OpenFeature provider design in the Traffical SDK spec, §2, §3, §5, §6, §7, §9, §13
6
+ * (this is milestone M3). It is a thin, SYNC translation membrane over an
7
+ * already-constructed native client (constructor injection — one client
8
+ * instance, the caller owns lifecycle).
9
+ *
10
+ * Web specifics (vs the server provider):
11
+ * - Resolvers are SYNCHRONOUS and receive NO per-invocation context; the
12
+ * provider evaluates against a single BOUND static context (§9).
13
+ * - `onContextChange` re-binds the context and clears the per-context decision
14
+ * memo, then RETURNS — the web SDK (not the provider) emits the reconcile
15
+ * lifecycle events (§9, finding V2).
16
+ * - `track(name, details)` takes NO context argument (web `Tracking`).
17
+ * - `flagMetadata` gates BOTH `traffical.propensity` AND `traffical.modelVersion`
18
+ * out — browser devtools must not leak bandit selection internals (§3.5).
19
+ */
20
+ import { OpenFeatureEventEmitter, type EvaluationContext, type JsonValue, type Logger, type Paradigm, type Provider, type ProviderMetadata, type ResolutionDetails, type Tracking, type TrackingEventDetails } from "@openfeature/web-sdk";
21
+ import { type TrafficalClientLike, type TrafficalProviderOptions } from "@traffical/openfeature-core";
22
+ /**
23
+ * The optional lifecycle surface a browser client may expose on top of the
24
+ * frozen `TrafficalClientLike` contract. All optional so the provider can be
25
+ * driven by a bare structural stub in tests, and so it never depends on hooks
26
+ * that may not exist on every client.
27
+ */
28
+ export interface TrafficalWebClient extends TrafficalClientLike {
29
+ /** Async config fetch + background refresh start. */
30
+ initialize?(): Promise<void>;
31
+ /** Sync, unload-aware teardown (chooses `sendBeacon` on unload). */
32
+ destroy?(): void;
33
+ /** The anonymous stable id used when no explicit identity is set. */
34
+ getStableId?(): string;
35
+ /** Change identity + notify listeners (framework providers, plugins). */
36
+ identify?(unitKey: string): void;
37
+ /**
38
+ * Register a plugin. Used (if present) to hang a config-change listener off
39
+ * the client's EXISTING `runConfigUpdate` hook so a changed refresh can emit
40
+ * `PROVIDER_CONFIGURATION_CHANGED`. Never required.
41
+ */
42
+ use?(plugin: {
43
+ name: string;
44
+ onConfigUpdate?: (bundle: unknown) => void;
45
+ }): unknown;
46
+ }
47
+ export declare class TrafficalWebProvider implements Provider, Tracking {
48
+ readonly metadata: ProviderMetadata;
49
+ /** The web SDK enforces this paradigm at runtime (prevents server/web mixups). */
50
+ readonly runsOn: Paradigm;
51
+ /** The provider owns its emitter; the SDK does not synthesize lifecycle events. */
52
+ readonly events: OpenFeatureEventEmitter;
53
+ private readonly client;
54
+ private readonly options;
55
+ /** The single bound static context. Starts empty; set in `initialize`. */
56
+ private boundContext;
57
+ /**
58
+ * Per-context decision memo (flagKey → DecisionResult). Pure optimization and
59
+ * the source the exposure route stitches against; CLEARED on every context
60
+ * change so a stale-identity decision can never be exposed (§9). Inert
61
+ * decisions are never stored.
62
+ */
63
+ private decisionMemo;
64
+ /** No-exposure alarm counters + one-shot guards (§5). */
65
+ private decisionCount;
66
+ private exposureCount;
67
+ private noExposureAlarmFired;
68
+ private exposureMissWarned;
69
+ private closed;
70
+ constructor(client: TrafficalWebClient, options?: TrafficalProviderOptions);
71
+ initialize(context?: EvaluationContext): Promise<void>;
72
+ onClose(): Promise<void>;
73
+ /**
74
+ * Re-bind the context and clear the per-context decision memo, then RETURN.
75
+ *
76
+ * Returning `void` makes the web SDK emit only `PROVIDER_CONTEXT_CHANGED`
77
+ * (returning a Promise would make it emit `PROVIDER_RECONCILING` first). The
78
+ * provider MUST NOT emit `Reconciling`/`ContextChanged`/`Stale` itself — that
79
+ * would double-emit / race the SDK.
80
+ */
81
+ onContextChange(_oldContext: EvaluationContext, newContext: EvaluationContext): void;
82
+ resolveBooleanEvaluation(flagKey: string, defaultValue: boolean, _context?: EvaluationContext, _logger?: Logger): ResolutionDetails<boolean>;
83
+ resolveStringEvaluation(flagKey: string, defaultValue: string, _context?: EvaluationContext, _logger?: Logger): ResolutionDetails<string>;
84
+ resolveNumberEvaluation(flagKey: string, defaultValue: number, _context?: EvaluationContext, _logger?: Logger): ResolutionDetails<number>;
85
+ resolveObjectEvaluation<T extends JsonValue>(flagKey: string, defaultValue: T, _context?: EvaluationContext, _logger?: Logger): ResolutionDetails<T>;
86
+ /**
87
+ * The single translation path shared by all four resolvers.
88
+ *
89
+ * A thrown `TargetingKeyMissingError`/`TypeMismatchError` propagates: the web
90
+ * SDK is the never-throw boundary and maps typed errors to the default +
91
+ * `reason: ERROR` (§4.4). We do NOT blanket-catch to `GENERAL`.
92
+ */
93
+ private resolve;
94
+ track(trackingEventName: string, details?: TrackingEventDetails): void;
95
+ /**
96
+ * Exposure route: stitch to the memoized decision and call `trackExposure`.
97
+ * NEVER re-decide. A miss (no memoized decision for `flagKey`) warns once and
98
+ * no-ops (§5).
99
+ */
100
+ private routeExposure;
101
+ /**
102
+ * Reward route: identity comes from the bound targeting key (falling back to
103
+ * the client's anonymous stableId). `value` is lifted out of `details`; the
104
+ * rest ride as properties.
105
+ */
106
+ private routeReward;
107
+ private maybeFireNoExposureAlarm;
108
+ private wireConfigChangeSignal;
109
+ }
110
+ export default TrafficalWebProvider;
111
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,300 @@
1
+ /**
2
+ * TrafficalWebProvider — the OpenFeature WEB (static-context) provider backed by
3
+ * the Traffical browser SDK (`@traffical/js-client`).
4
+ *
5
+ * Design: see the OpenFeature provider design in the Traffical SDK spec, §2, §3, §5, §6, §7, §9, §13
6
+ * (this is milestone M3). It is a thin, SYNC translation membrane over an
7
+ * already-constructed native client (constructor injection — one client
8
+ * instance, the caller owns lifecycle).
9
+ *
10
+ * Web specifics (vs the server provider):
11
+ * - Resolvers are SYNCHRONOUS and receive NO per-invocation context; the
12
+ * provider evaluates against a single BOUND static context (§9).
13
+ * - `onContextChange` re-binds the context and clears the per-context decision
14
+ * memo, then RETURNS — the web SDK (not the provider) emits the reconcile
15
+ * lifecycle events (§9, finding V2).
16
+ * - `track(name, details)` takes NO context argument (web `Tracking`).
17
+ * - `flagMetadata` gates BOTH `traffical.propensity` AND `traffical.modelVersion`
18
+ * out — browser devtools must not leak bandit selection internals (§3.5).
19
+ */
20
+ import { OpenFeatureEventEmitter, ProviderEvents, ProviderFatalError, StandardResolutionReasons, } from "@openfeature/web-sdk";
21
+ import { EXPOSURE_EVENT_NAME, FLAG_METADATA_PREFIX, buildTrafficalContext, toResolutionDetails, } from "@traffical/openfeature-core";
22
+ const PROVIDER_NAME = "traffical-provider";
23
+ /**
24
+ * How many decisions may accumulate with zero exposures before the
25
+ * "decisions-without-exposures" alarm fires (once). Mirrors the server
26
+ * provider's footgun defense (§5/D2).
27
+ */
28
+ const NO_EXPOSURE_ALARM_THRESHOLD = 10;
29
+ /**
30
+ * A decision is INERT when the web client's `decide()` error boundary returned
31
+ * its fallback (`unitKeyValue: ""`, `layers: []`, `js-client:560`). An inert
32
+ * decision must be treated as DEFAULT and never fed to exposure/attribution —
33
+ * downstream it silently no-ops (§4.4).
34
+ */
35
+ function isInertDecision(decision) {
36
+ return decision.metadata.unitKeyValue === "" || decision.metadata.layers.length === 0;
37
+ }
38
+ export class TrafficalWebProvider {
39
+ metadata = { name: PROVIDER_NAME };
40
+ /** The web SDK enforces this paradigm at runtime (prevents server/web mixups). */
41
+ runsOn = "client";
42
+ /** The provider owns its emitter; the SDK does not synthesize lifecycle events. */
43
+ events = new OpenFeatureEventEmitter();
44
+ client;
45
+ options;
46
+ /** The single bound static context. Starts empty; set in `initialize`. */
47
+ boundContext = {};
48
+ /**
49
+ * Per-context decision memo (flagKey → DecisionResult). Pure optimization and
50
+ * the source the exposure route stitches against; CLEARED on every context
51
+ * change so a stale-identity decision can never be exposed (§9). Inert
52
+ * decisions are never stored.
53
+ */
54
+ decisionMemo = new Map();
55
+ /** No-exposure alarm counters + one-shot guards (§5). */
56
+ decisionCount = 0;
57
+ exposureCount = 0;
58
+ noExposureAlarmFired = false;
59
+ exposureMissWarned = false;
60
+ closed = false;
61
+ constructor(client, options) {
62
+ this.client = client;
63
+ // Web always gates propensity/modelVersion out of flagMetadata (browser
64
+ // devtools are user-visible). We additionally strip modelVersion below,
65
+ // because core gates propensity but NOT modelVersion.
66
+ this.options = { gatePropensity: true, ...options };
67
+ }
68
+ // ===========================================================================
69
+ // Lifecycle
70
+ // ===========================================================================
71
+ async initialize(context) {
72
+ this.closed = false;
73
+ this.boundContext = context ?? {};
74
+ try {
75
+ await this.client.initialize?.();
76
+ this.wireConfigChangeSignal();
77
+ this.events.emit(ProviderEvents.Ready);
78
+ }
79
+ catch (err) {
80
+ // Any failure to initialize is treated as irrecoverable: the client
81
+ // could not load a bundle, so every resolve would degrade to DEFAULT.
82
+ this.events.emit(ProviderEvents.Error, {
83
+ message: err instanceof Error ? err.message : String(err),
84
+ errorCode: new ProviderFatalError().code,
85
+ });
86
+ // Rethrow so the SDK also transitions the provider to a FATAL state.
87
+ throw err;
88
+ }
89
+ }
90
+ async onClose() {
91
+ if (this.closed)
92
+ return; // idempotent (guard double-close)
93
+ this.closed = true;
94
+ // The browser client's destroy() is sync and unload-aware (it chooses
95
+ // sendBeacon on unload). Do NOT await an async flush on web (§7.2).
96
+ this.client.destroy?.();
97
+ this.decisionMemo.clear();
98
+ }
99
+ // ===========================================================================
100
+ // Static-context reconcile (§9, finding V2)
101
+ // ===========================================================================
102
+ /**
103
+ * Re-bind the context and clear the per-context decision memo, then RETURN.
104
+ *
105
+ * Returning `void` makes the web SDK emit only `PROVIDER_CONTEXT_CHANGED`
106
+ * (returning a Promise would make it emit `PROVIDER_RECONCILING` first). The
107
+ * provider MUST NOT emit `Reconciling`/`ContextChanged`/`Stale` itself — that
108
+ * would double-emit / race the SDK.
109
+ */
110
+ onContextChange(_oldContext, newContext) {
111
+ this.boundContext = newContext ?? {};
112
+ // Clear the memo so a decision made under the old identity can never be
113
+ // served (or exposed) under the new one.
114
+ this.decisionMemo.clear();
115
+ // Flow identity through the client so its stable-id / exposure-dedup state
116
+ // tracks the new identity (rapid login/logout). Best-effort: identify() is
117
+ // optional on the structural client.
118
+ const targetingKey = newContext?.targetingKey;
119
+ if (typeof targetingKey === "string" && targetingKey !== "") {
120
+ this.client.identify?.(targetingKey);
121
+ }
122
+ // Return void → SDK emits CONTEXT_CHANGED only.
123
+ }
124
+ // ===========================================================================
125
+ // Resolvers (SYNC — evaluate against the BOUND context, ignore the arg)
126
+ // ===========================================================================
127
+ resolveBooleanEvaluation(flagKey, defaultValue, _context, _logger) {
128
+ return this.resolve(flagKey, defaultValue, "boolean");
129
+ }
130
+ resolveStringEvaluation(flagKey, defaultValue, _context, _logger) {
131
+ return this.resolve(flagKey, defaultValue, "string");
132
+ }
133
+ resolveNumberEvaluation(flagKey, defaultValue, _context, _logger) {
134
+ return this.resolve(flagKey, defaultValue, "number");
135
+ }
136
+ resolveObjectEvaluation(flagKey, defaultValue, _context, _logger) {
137
+ return this.resolve(flagKey, defaultValue, "object");
138
+ }
139
+ /**
140
+ * The single translation path shared by all four resolvers.
141
+ *
142
+ * A thrown `TargetingKeyMissingError`/`TypeMismatchError` propagates: the web
143
+ * SDK is the never-throw boundary and maps typed errors to the default +
144
+ * `reason: ERROR` (§4.4). We do NOT blanket-catch to `GENERAL`.
145
+ */
146
+ resolve(flagKey, defaultValue, expectedType) {
147
+ // (1) Map the targeting key onto EXACTLY the bundle's unit-key field so the
148
+ // web client's _enrichContext does not override it with the anonymous
149
+ // stableId (which would silently mis-bucket).
150
+ const unitKeyField = this.options.unitKey ?? this.client.getUnitKeyField();
151
+ // (2) Build the Traffical context; a missing targeting key throws
152
+ // TargetingKeyMissingError, which we let propagate to the SDK boundary.
153
+ const { targetingKey, ...attributes } = this.boundContext;
154
+ const ctx = buildTrafficalContext({
155
+ targetingKey: typeof targetingKey === "string" ? targetingKey : undefined,
156
+ attributes,
157
+ unitKeyField,
158
+ });
159
+ // (3) Owning-layer id — robust selection (never positional layers[0]).
160
+ const ownerLayerId = this.client.getParameterLayerId(flagKey);
161
+ // OpenFeature `JsonValue` admits `null`; Traffical's `ParameterValue` does
162
+ // not. The default is only a fallback the engine echoes back untouched, so
163
+ // the boundary cast is sound (the strict type check happens in
164
+ // toResolutionDetails, which accepts any JSON value for object flags).
165
+ const decision = this.client.decide({
166
+ context: ctx,
167
+ defaults: { [flagKey]: defaultValue },
168
+ });
169
+ // Inert decision (error-boundary fallback) → DEFAULT. Never stored, never
170
+ // exposed, never fed to attribution downstream.
171
+ if (isInertDecision(decision)) {
172
+ return { value: defaultValue, reason: StandardResolutionReasons.DEFAULT };
173
+ }
174
+ this.decisionCount++;
175
+ // (4) Memoize the real decision so the exposure route can stitch to it
176
+ // without ever re-deciding (D3/A2).
177
+ this.decisionMemo.set(flagKey, decision);
178
+ // Opt-in escape hatch: fire the exposure on the just-made decision
179
+ // (collapsing ToT toward ITT). Uses the SAME decision — never re-decides —
180
+ // and relies on the client's exposure dedup + attributionOnly skip (§5).
181
+ if (this.options.exposureOnResolve) {
182
+ this.exposureCount++;
183
+ this.client.trackExposure(decision);
184
+ }
185
+ this.maybeFireNoExposureAlarm();
186
+ // (5) Translate. `toResolutionDetails` throws TypeMismatchError on a strict
187
+ // type mismatch (no coercion) — let it propagate.
188
+ const details = toResolutionDetails({
189
+ flagKey,
190
+ defaultValue,
191
+ expectedType,
192
+ decision,
193
+ ownerLayerId,
194
+ gatePropensity: this.options.gatePropensity,
195
+ });
196
+ // Web hardening: core gates propensity but NOT modelVersion. On web,
197
+ // flagMetadata is visible in browser devtools, so strip the model version
198
+ // too (§3.5) — never leak the model version to the client.
199
+ if (details.flagMetadata) {
200
+ delete details.flagMetadata[`${FLAG_METADATA_PREFIX}.modelVersion`];
201
+ }
202
+ return details;
203
+ }
204
+ // ===========================================================================
205
+ // Tracking (web `Tracking` — NO context arg)
206
+ // ===========================================================================
207
+ track(trackingEventName, details) {
208
+ const exposureEventName = this.options.exposureEventName ?? EXPOSURE_EVENT_NAME;
209
+ if (trackingEventName === exposureEventName) {
210
+ this.routeExposure(details);
211
+ return;
212
+ }
213
+ this.routeReward(trackingEventName, details);
214
+ }
215
+ /**
216
+ * Exposure route: stitch to the memoized decision and call `trackExposure`.
217
+ * NEVER re-decide. A miss (no memoized decision for `flagKey`) warns once and
218
+ * no-ops (§5).
219
+ */
220
+ routeExposure(details) {
221
+ const flagKey = details && typeof details.flagKey === "string" ? details.flagKey : undefined;
222
+ const decision = flagKey ? this.decisionMemo.get(flagKey) : undefined;
223
+ if (!decision) {
224
+ if (!this.exposureMissWarned) {
225
+ this.exposureMissWarned = true;
226
+ console.warn(`[Traffical][OpenFeature] Exposure fired for flag "${flagKey ?? "<missing flagKey>"}" ` +
227
+ "but no matching decision was found in the current context. " +
228
+ "Resolve the flag before firing its exposure; never re-decide in the exposure path.");
229
+ }
230
+ return;
231
+ }
232
+ this.exposureCount++;
233
+ this.client.trackExposure(decision);
234
+ }
235
+ /**
236
+ * Reward route: identity comes from the bound targeting key (falling back to
237
+ * the client's anonymous stableId). `value` is lifted out of `details`; the
238
+ * rest ride as properties.
239
+ */
240
+ routeReward(eventName, details) {
241
+ const boundKey = this.boundContext.targetingKey;
242
+ const unitKey = typeof boundKey === "string" && boundKey !== "" ? boundKey : this.client.getStableId?.();
243
+ const value = details && typeof details.value === "number" ? details.value : undefined;
244
+ const rest = {};
245
+ if (details) {
246
+ for (const [k, v] of Object.entries(details)) {
247
+ if (k === "value")
248
+ continue;
249
+ rest[k] = v;
250
+ }
251
+ }
252
+ this.client.track(eventName, { value, ...rest }, { unitKey });
253
+ }
254
+ // ===========================================================================
255
+ // No-exposure alarm (§5/D2)
256
+ // ===========================================================================
257
+ maybeFireNoExposureAlarm() {
258
+ if (this.noExposureAlarmFired)
259
+ return;
260
+ if (this.decisionCount > NO_EXPOSURE_ALARM_THRESHOLD && this.exposureCount === 0) {
261
+ this.noExposureAlarmFired = true;
262
+ const message = "recording decisions but no exposures; ToT/SRM/optimization will be empty. " +
263
+ `Fire "${this.options.exposureEventName ?? EXPOSURE_EVENT_NAME}", or set exposureOnResolve.`;
264
+ console.warn(`[Traffical][OpenFeature] ${message}`);
265
+ // Non-fatal — the provider is still usable, this is a footgun alarm.
266
+ this.events.emit(ProviderEvents.Error, { message });
267
+ }
268
+ }
269
+ // ===========================================================================
270
+ // PROVIDER_CONFIGURATION_CHANGED (§7.3) — optional, via the EXISTING
271
+ // js-client runConfigUpdate plugin hook. No new js-client hooks are added.
272
+ // ===========================================================================
273
+ wireConfigChangeSignal() {
274
+ if (typeof this.client.use !== "function") {
275
+ // TODO: no plugin surface on this client — config-change signalling is
276
+ // skipped. The bundle still refreshes silently in the background.
277
+ return;
278
+ }
279
+ let lastVersion = this.client.getConfigVersion();
280
+ this.client.use({
281
+ name: "openfeature-config-change",
282
+ onConfigUpdate: () => {
283
+ const next = this.client.getConfigVersion();
284
+ if (next !== null && next !== lastVersion) {
285
+ lastVersion = next;
286
+ // The memo is a pure optimization, safe-to-miss, and MUST be
287
+ // configVersion-invalidated (design §9, A1/A5): a background refresh
288
+ // can change the served value / decisionId / propensity / configVersion,
289
+ // so any memoized decision is now stale. Clearing it turns a
290
+ // post-refresh exposure into a safe drop+warn (memo miss) rather than
291
+ // stitching to a decision that no longer matches what the user is served.
292
+ this.decisionMemo.clear();
293
+ this.events.emit(ProviderEvents.ConfigurationChanged);
294
+ }
295
+ },
296
+ });
297
+ }
298
+ }
299
+ export default TrafficalWebProvider;
300
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@traffical/openfeature-web",
3
+ "version": "0.2.0",
4
+ "description": "OpenFeature web (static-context) provider backed by the Traffical browser SDK",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "dev": "tsc --watch",
22
+ "test": "bun test",
23
+ "typecheck": "tsc --noEmit"
24
+ },
25
+ "dependencies": {
26
+ "@traffical/core": "0.11.0",
27
+ "@traffical/openfeature-core": "0.2.0",
28
+ "@traffical/js-client": "0.16.0"
29
+ },
30
+ "peerDependencies": {
31
+ "@openfeature/web-sdk": "^1.9.0"
32
+ },
33
+ "devDependencies": {
34
+ "@openfeature/core": "^1.11.0",
35
+ "@openfeature/web-sdk": "^1.9.0",
36
+ "@traffical/sdk-spec": "^0.7.0",
37
+ "@types/bun": "latest",
38
+ "typescript": "^5.3.0"
39
+ },
40
+ "keywords": [
41
+ "traffical",
42
+ "feature-flags",
43
+ "experimentation",
44
+ "a/b-testing",
45
+ "openfeature",
46
+ "provider"
47
+ ],
48
+ "license": "MIT",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "https://github.com/traffical/js-sdk"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ }
56
+ }