@revturbine/sdk 0.7.7 → 0.7.9
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/dist/growthbook.d.ts +119 -2
- package/dist/growthbook.js +1 -1
- package/dist/growthbook.js.map +1 -1
- package/dist/headless.d.ts +162 -99
- package/dist/headless.js +2 -2
- package/dist/headless.js.map +1 -1
- package/dist/index.d.ts +162 -99
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/optimizely.d.ts +416 -0
- package/dist/optimizely.js +2 -0
- package/dist/optimizely.js.map +1 -0
- package/dist/types/web-sdk/customer-side.d.ts +27 -1
- package/dist/types/web-sdk/customer-side.d.ts.map +1 -1
- package/dist/types/web-sdk/optimizely.d.ts +8 -0
- package/dist/types/web-sdk/optimizely.d.ts.map +1 -0
- package/dist/types/web-sdk/providers/basic-experiment-provider.d.ts.map +1 -1
- package/dist/types/web-sdk/providers/experiment-context.d.ts +20 -0
- package/dist/types/web-sdk/providers/experiment-context.d.ts.map +1 -0
- package/dist/types/web-sdk/providers/growthbook-experiment-provider.d.ts +3 -1
- package/dist/types/web-sdk/providers/growthbook-experiment-provider.d.ts.map +1 -1
- package/dist/types/web-sdk/providers/index.d.ts +3 -1
- package/dist/types/web-sdk/providers/index.d.ts.map +1 -1
- package/dist/types/web-sdk/providers/optimizely-experiment-provider.d.ts +50 -0
- package/dist/types/web-sdk/providers/optimizely-experiment-provider.d.ts.map +1 -0
- package/dist/types/web-sdk/providers/types.d.ts +1 -1
- package/dist/types/web-sdk/providers/types.d.ts.map +1 -1
- package/package.json +15 -5
package/dist/growthbook.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GrowthBook } from '@growthbook/growthbook';
|
|
2
2
|
import { toJSONSchema, JSONSchemaMeta } from 'zod/v4/core';
|
|
3
|
+
import { z } from 'zod';
|
|
3
4
|
|
|
4
5
|
type OverrideParameters = Parameters<NonNullable<NonNullable<Parameters<typeof toJSONSchema>[1]>['override']>>[0];
|
|
5
6
|
type OverrideSchemaParameters = Omit<OverrideParameters, 'zodSchema'>;
|
|
@@ -180,6 +181,84 @@ declare module 'zod/v4' {
|
|
|
180
181
|
interface GlobalMeta extends ZodOpenApiMetadata {}
|
|
181
182
|
}
|
|
182
183
|
|
|
184
|
+
/**
|
|
185
|
+
* User schemas — Zod definitions for user context, trial status, and usage.
|
|
186
|
+
*
|
|
187
|
+
* These are external/SDK-facing types that describe an identified user's
|
|
188
|
+
* current state (plan, entitlements, usage, personalization tokens).
|
|
189
|
+
*/
|
|
190
|
+
|
|
191
|
+
/** A single usage entry derived from a credits / usage_limit entitlement. */
|
|
192
|
+
declare const UserUsageEntrySchema: z.ZodObject<{
|
|
193
|
+
entitlement_handle: z.ZodString;
|
|
194
|
+
unit: z.ZodString;
|
|
195
|
+
amount: z.ZodNumber;
|
|
196
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
197
|
+
reset_date: z.ZodOptional<z.ZodString>;
|
|
198
|
+
}, z.core.$strip>;
|
|
199
|
+
/**
|
|
200
|
+
* Plan context for the identified user (plan 191 Q-1 amendment): `handle` is
|
|
201
|
+
* the plan's `unique_handle` — the same matching identity as the flat
|
|
202
|
+
* top-level `plan_handle` — renamed from `id`, which in practice was always
|
|
203
|
+
* populated with the handle anyway. There is no `id` in plan context.
|
|
204
|
+
*/
|
|
205
|
+
declare const UserPlanContextSchema: z.ZodObject<{
|
|
206
|
+
handle: z.ZodString;
|
|
207
|
+
name: z.ZodString;
|
|
208
|
+
price: z.ZodOptional<z.ZodString>;
|
|
209
|
+
billing_period: z.ZodOptional<z.ZodEnum<{
|
|
210
|
+
monthly: "monthly";
|
|
211
|
+
annual: "annual";
|
|
212
|
+
none: "none";
|
|
213
|
+
}>>;
|
|
214
|
+
}, z.core.$strip>;
|
|
215
|
+
|
|
216
|
+
// AUTO-GENERATED by scripts/generate-zod-outputs.ts - do not edit.
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
type UserPlanContext = z.infer<typeof UserPlanContextSchema>;
|
|
220
|
+
type UserUsageEntry = z.infer<typeof UserUsageEntrySchema>;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Pure utility functions extracted from web-sdk/customer-side.ts.
|
|
224
|
+
*
|
|
225
|
+
* No side effects, no browser APIs, no `this` — just transforms.
|
|
226
|
+
*/
|
|
227
|
+
|
|
228
|
+
/** JSON-serializable value for SDK data bags. */
|
|
229
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
230
|
+
[key: string]: JsonValue;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* User context lifecycle — pure functions for building, merging,
|
|
235
|
+
* and deriving targeting state from user context + RevTurbineConfig.
|
|
236
|
+
*/
|
|
237
|
+
|
|
238
|
+
/** SDK-facing user context (widened for customer convenience). */
|
|
239
|
+
interface UserContextSnapshot {
|
|
240
|
+
id?: string;
|
|
241
|
+
/**
|
|
242
|
+
* THE plan matching identity — the plan's `unique_handle` (plan 191 Q-1).
|
|
243
|
+
* Rule targets, segment plan predicates, and plan resolution match on this
|
|
244
|
+
* value alone.
|
|
245
|
+
*/
|
|
246
|
+
plan_handle?: string;
|
|
247
|
+
/**
|
|
248
|
+
* The plan as a bare handle string, or as `{ handle, name, … }` (plan 191
|
|
249
|
+
* Q-1 amendment — the object's identity symbol is `handle`; there is no
|
|
250
|
+
* `id` in plan context). Both shapes carry the same matching identity as
|
|
251
|
+
* the flat `plan_handle` above, which wins when both are set.
|
|
252
|
+
*/
|
|
253
|
+
plan?: string | UserPlanContext;
|
|
254
|
+
custom?: Record<string, JsonValue>;
|
|
255
|
+
entitlements?: Record<string, boolean>;
|
|
256
|
+
usage?: Record<string, UserUsageEntry>;
|
|
257
|
+
personalization?: Record<string, JsonValue>;
|
|
258
|
+
/** Canonical experiment-handle to variant-handle assignments. */
|
|
259
|
+
experiments?: Record<string, string>;
|
|
260
|
+
}
|
|
261
|
+
|
|
183
262
|
/**
|
|
184
263
|
* Trait namespace — allows multiple trait-contributing providers to register
|
|
185
264
|
* under `traits:<namespace>` and merge into the unified traits bag.
|
|
@@ -197,6 +276,15 @@ type TraitsNamespace = `traits:${string}`;
|
|
|
197
276
|
* into a single `traits` slot in `ResolvedProviderContext`.
|
|
198
277
|
*/
|
|
199
278
|
type DomainProviderName = 'plan' | 'entitlements' | 'segments' | 'experiments' | 'content' | 'rules' | 'traits' | 'theme' | 'events' | 'cta' | TraitsNamespace;
|
|
279
|
+
/** Immutable inputs available while resolving a domain provider. */
|
|
280
|
+
interface DomainProviderResolutionInput {
|
|
281
|
+
/** Current caller-supplied and trusted server-enriched context snapshot. */
|
|
282
|
+
readonly userContext: Readonly<UserContextSnapshot>;
|
|
283
|
+
/** Changes whenever identity or user context changes. */
|
|
284
|
+
readonly contextRevision: string;
|
|
285
|
+
/** Aborted when this resolution is no longer valid for the current identity. */
|
|
286
|
+
readonly signal: AbortSignal;
|
|
287
|
+
}
|
|
200
288
|
/**
|
|
201
289
|
* Base interface all domain providers implement.
|
|
202
290
|
*
|
|
@@ -211,7 +299,7 @@ interface DomainProvider<D extends DomainProviderName = DomainProviderName, T =
|
|
|
211
299
|
* Resolve the current value. Called before placement decisions.
|
|
212
300
|
* May be async (e.g. reading from a database or API).
|
|
213
301
|
*/
|
|
214
|
-
resolve(): T | Promise<T>;
|
|
302
|
+
resolve(input?: DomainProviderResolutionInput): T | Promise<T>;
|
|
215
303
|
/**
|
|
216
304
|
* Optional: subscribe to changes (enables push-based invalidation).
|
|
217
305
|
* Returns an unsubscribe function.
|
|
@@ -219,6 +307,8 @@ interface DomainProvider<D extends DomainProviderName = DomainProviderName, T =
|
|
|
219
307
|
subscribe?(listener: (next: T) => void): () => void;
|
|
220
308
|
/** Cache TTL in ms. 0 = re-resolve every call (default). */
|
|
221
309
|
cacheTtlMs?: number;
|
|
310
|
+
/** Customer-controlled revision used to invalidate cached provider output. */
|
|
311
|
+
readonly providerRevision?: string | number;
|
|
222
312
|
}
|
|
223
313
|
/**
|
|
224
314
|
* The user's experiment assignments, keyed by **experiment handle** with the
|
|
@@ -235,7 +325,28 @@ interface DomainProvider<D extends DomainProviderName = DomainProviderName, T =
|
|
|
235
325
|
interface ExperimentProviderState {
|
|
236
326
|
/** `{ [experimentHandle]: variantHandle }`. */
|
|
237
327
|
assignments: Record<string, string>;
|
|
328
|
+
/** Structured outcomes keyed by experiment handle, when supplied by the provider. */
|
|
329
|
+
selections?: Record<string, ExperimentVariantSelection>;
|
|
238
330
|
}
|
|
331
|
+
/** Named reasons for a provider not returning an assignment. */
|
|
332
|
+
type ExperimentVariantNonAssignmentReason = 'not_enrolled' | 'provider_not_ready' | 'provider_unavailable' | 'provider_error' | 'timeout' | 'aborted' | 'unknown_variant' | 'invalid_handle' | 'mapping_missing' | 'provider_missing' | 'ownership_conflict' | 'assignment_conflict';
|
|
333
|
+
/**
|
|
334
|
+
* Normalized result returned for one canonical experiment handle.
|
|
335
|
+
* Missing and failed vendor values remain explicit and never become control.
|
|
336
|
+
*/
|
|
337
|
+
type ExperimentVariantSelection = {
|
|
338
|
+
status: 'assigned';
|
|
339
|
+
experimentHandle: string;
|
|
340
|
+
variantHandle: string;
|
|
341
|
+
providerHandle?: string;
|
|
342
|
+
providerRevision?: string | number;
|
|
343
|
+
} | {
|
|
344
|
+
status: 'not_assigned' | 'unavailable' | 'unsupported';
|
|
345
|
+
experimentHandle: string;
|
|
346
|
+
reason: ExperimentVariantNonAssignmentReason;
|
|
347
|
+
providerHandle?: string;
|
|
348
|
+
providerRevision?: string | number;
|
|
349
|
+
};
|
|
239
350
|
/**
|
|
240
351
|
* Supplies experiment enrollment and variant assignment.
|
|
241
352
|
*
|
|
@@ -246,6 +357,10 @@ interface ExperimentProviderState {
|
|
|
246
357
|
*/
|
|
247
358
|
interface ExperimentAssignmentProvider extends DomainProvider<'experiments', ExperimentProviderState> {
|
|
248
359
|
readonly domain: 'experiments';
|
|
360
|
+
/** Stable diagnostic identifier; vendor credentials never belong here. */
|
|
361
|
+
readonly providerHandle?: string;
|
|
362
|
+
/** Canonical RevTurbine experiment handles this provider owns. */
|
|
363
|
+
readonly ownedExperimentHandles?: readonly string[];
|
|
249
364
|
}
|
|
250
365
|
|
|
251
366
|
/**
|
|
@@ -271,9 +386,11 @@ interface GrowthBookExperimentAssignmentOptions {
|
|
|
271
386
|
client: Pick<GrowthBook<Record<string, string>>, 'evalFeature'>;
|
|
272
387
|
/** Explicit experiment-to-feature mappings; no naming convention is assumed. */
|
|
273
388
|
bindings: readonly GrowthBookExperimentBinding[];
|
|
389
|
+
/** Customer-controlled revision for feature/config changes. */
|
|
390
|
+
providerRevision?: string | number;
|
|
274
391
|
}
|
|
275
392
|
/** Machine-readable reasons a GrowthBook assignment cannot be normalized. */
|
|
276
|
-
type GrowthBookAssignmentErrorReason = 'duplicate_experiment_handle' | 'unknown_feature' | 'invalid_variant' | 'unexpected_variant';
|
|
393
|
+
type GrowthBookAssignmentErrorReason = 'duplicate_experiment_handle' | 'invalid_binding' | 'unknown_feature' | 'invalid_variant' | 'unexpected_variant';
|
|
277
394
|
/** Raised when GrowthBook cannot produce a valid normalized assignment. */
|
|
278
395
|
declare class GrowthBookAssignmentError extends Error {
|
|
279
396
|
/** Stable reason suitable for logs and error handling. */
|
package/dist/growthbook.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var s=Object.defineProperty;var d=(r,n,e)=>n in r?s(r,n,{enumerable:true,configurable:true,writable:true,value:e}):r[n]=e;var i=(r,n,e)=>d(r,typeof n!="symbol"?n+"":n,e);var o=class extends Error{constructor(e,t,a){super(`GrowthBook assignment for experiment "${t.experimentHandle}" (feature "${t.featureKey}") failed: ${a}`);i(this,"reason");i(this,"experimentHandle");i(this,"featureKey");this.name="GrowthBookAssignmentError",this.reason=e,this.experimentHandle=t.experimentHandle,this.featureKey=t.featureKey;}};function m(r){let n=new Set;for(let e of r){if(n.has(e.experimentHandle))throw new o("duplicate_experiment_handle",e,"the experiment handle is mapped more than once");n.add(e.experimentHandle);}}function p(r){return m(r.bindings),{domain:"experiments",resolve(){let n={};for(let e of r.bindings){let t=r.client.evalFeature(e.featureKey);if(t.source==="unknownFeature")throw new o("unknown_feature",e,"GrowthBook reported an unknown feature");if(typeof t.value!="string"||t.value.length===0)throw new o("invalid_variant",e,"the evaluated value must be a non-empty string variant handle");if(e.allowedVariants&&!e.allowedVariants.includes(t.value))throw new o("unexpected_variant",e,`variant "${t.value}" is outside the configured allowlist`);n[e.experimentHandle]=t.value;}return {assignments:n}}}}export{o as GrowthBookAssignmentError,p as createGrowthBookExperimentAssignmentProvider};//# sourceMappingURL=growthbook.js.map
|
|
1
|
+
var s=Object.defineProperty;var d=(r,n,e)=>n in r?s(r,n,{enumerable:true,configurable:true,writable:true,value:e}):r[n]=e;var i=(r,n,e)=>d(r,typeof n!="symbol"?n+"":n,e);var o=class extends Error{constructor(e,t,a){super(`GrowthBook assignment for experiment "${t.experimentHandle}" (feature "${t.featureKey}") failed: ${a}`);i(this,"reason");i(this,"experimentHandle");i(this,"featureKey");this.name="GrowthBookAssignmentError",this.reason=e,this.experimentHandle=t.experimentHandle,this.featureKey=t.featureKey;}};function m(r){let n=new Set;for(let e of r){if(!e.experimentHandle.trim()||!e.featureKey.trim())throw new o("invalid_binding",e,"experiment handles and GrowthBook feature keys must be non-empty");if(n.has(e.experimentHandle))throw new o("duplicate_experiment_handle",e,"the experiment handle is mapped more than once");n.add(e.experimentHandle);}}function p(r){return m(r.bindings),{domain:"experiments",providerHandle:"growthbook",providerRevision:r.providerRevision,ownedExperimentHandles:r.bindings.map(n=>n.experimentHandle),resolve(){let n={};for(let e of r.bindings){let t=r.client.evalFeature(e.featureKey);if(t.source==="unknownFeature")throw new o("unknown_feature",e,"GrowthBook reported an unknown feature");if(typeof t.value!="string"||t.value.length===0)throw new o("invalid_variant",e,"the evaluated value must be a non-empty string variant handle");if(e.allowedVariants&&!e.allowedVariants.includes(t.value))throw new o("unexpected_variant",e,`variant "${t.value}" is outside the configured allowlist`);n[e.experimentHandle]=t.value;}return {assignments:n}}}}export{o as GrowthBookAssignmentError,p as createGrowthBookExperimentAssignmentProvider};//# sourceMappingURL=growthbook.js.map
|
|
2
2
|
//# sourceMappingURL=growthbook.js.map
|
package/dist/growthbook.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../providers/growthbook-experiment-provider.ts"],"names":["GrowthBookAssignmentError","reason","binding","detail","__publicField","validateBindings","bindings","handles","createGrowthBookExperimentAssignmentProvider","options","assignments","result"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../providers/growthbook-experiment-provider.ts"],"names":["GrowthBookAssignmentError","reason","binding","detail","__publicField","validateBindings","bindings","handles","createGrowthBookExperimentAssignmentProvider","options","assignments","result"],"mappings":"0KA0CO,IAAMA,CAAAA,CAAN,cAAwC,KAAM,CASnD,YACEC,CAAAA,CACAC,CAAAA,CACAC,EACA,CACA,KAAA,CACE,yCAAyCD,CAAAA,CAAQ,gBAAgB,eAClDA,CAAAA,CAAQ,UAAU,cAAcC,CAAM,CAAA,CACvD,CAAA,CAfFC,CAAAA,CAAA,IAAA,CAAS,QAAA,CAAA,CAETA,EAAA,IAAA,CAAS,kBAAA,CAAA,CAETA,EAAA,IAAA,CAAS,YAAA,CAAA,CAYP,KAAK,IAAA,CAAO,2BAAA,CACZ,KAAK,MAAA,CAASH,CAAAA,CACd,KAAK,gBAAA,CAAmBC,CAAAA,CAAQ,iBAChC,IAAA,CAAK,UAAA,CAAaA,EAAQ,WAC5B,CACF,EAEA,SAASG,CAAAA,CAAiBC,CAAAA,CAAwD,CAChF,IAAMC,CAAAA,CAAU,IAAI,GAAA,CACpB,IAAA,IAAWL,KAAWI,CAAAA,CAAU,CAC9B,GAAI,CAACJ,CAAAA,CAAQ,iBAAiB,IAAA,EAAK,EAAK,CAACA,CAAAA,CAAQ,UAAA,CAAW,MAAK,CAC/D,MAAM,IAAIF,CAAAA,CACR,iBAAA,CACAE,CAAAA,CACA,kEACF,CAAA,CAEF,GAAIK,EAAQ,GAAA,CAAIL,CAAAA,CAAQ,gBAAgB,CAAA,CACtC,MAAM,IAAIF,CAAAA,CACR,6BAAA,CACAE,CAAAA,CACA,gDACF,CAAA,CAEFK,CAAAA,CAAQ,IAAIL,CAAAA,CAAQ,gBAAgB,EACtC,CACF,CASO,SAASM,CAAAA,CACdC,CAAAA,CAC8B,CAC9B,OAAAJ,CAAAA,CAAiBI,CAAAA,CAAQ,QAAQ,CAAA,CAE1B,CACL,OAAQ,aAAA,CACR,cAAA,CAAgB,aAChB,gBAAA,CAAkBA,CAAAA,CAAQ,iBAC1B,sBAAA,CAAwBA,CAAAA,CAAQ,SAAS,GAAA,CAAKP,CAAAA,EAAYA,EAAQ,gBAAgB,CAAA,CAClF,OAAA,EAAmC,CACjC,IAAMQ,CAAAA,CAAsC,EAAC,CAE7C,IAAA,IAAWR,KAAWO,CAAAA,CAAQ,QAAA,CAAU,CACtC,IAAME,CAAAA,CAASF,CAAAA,CAAQ,MAAA,CAAO,WAAA,CAAYP,CAAAA,CAAQ,UAAU,CAAA,CAC5D,GAAIS,EAAO,MAAA,GAAW,gBAAA,CACpB,MAAM,IAAIX,CAAAA,CACR,iBAAA,CACAE,CAAAA,CACA,wCACF,CAAA,CAGF,GAAI,OAAOS,CAAAA,CAAO,OAAU,QAAA,EAAYA,CAAAA,CAAO,MAAM,MAAA,GAAW,CAAA,CAC9D,MAAM,IAAIX,CAAAA,CACR,kBACAE,CAAAA,CACA,+DACF,EAGF,GAAIA,CAAAA,CAAQ,iBAAmB,CAACA,CAAAA,CAAQ,eAAA,CAAgB,QAAA,CAASS,CAAAA,CAAO,KAAK,EAC3E,MAAM,IAAIX,EACR,oBAAA,CACAE,CAAAA,CACA,YAAYS,CAAAA,CAAO,KAAK,CAAA,qCAAA,CAC1B,CAAA,CAGFD,CAAAA,CAAYR,CAAAA,CAAQ,gBAAgB,CAAA,CAAIS,CAAAA,CAAO,MACjD,CAEA,OAAO,CAAE,WAAA,CAAAD,CAAY,CACvB,CACF,CACF","file":"growthbook.js","sourcesContent":["/**\n * GrowthBook-backed experiment assignment provider.\n *\n * The GrowthBook client is injected by the customer. This adapter therefore\n * evaluates the customer's real GrowthBook instance without bundling its SDK,\n * fetching credentials, or accepting executable customer code.\n */\nimport type { GrowthBook } from '@growthbook/growthbook';\nimport type {\n ExperimentAssignmentProvider,\n ExperimentProviderState,\n} from '@revt-eng/core';\n\n/** One RevTurbine experiment handle mapped to its GrowthBook feature key. */\nexport interface GrowthBookExperimentBinding {\n /** Stable RevTurbine experiment handle emitted in normalized assignments. */\n experimentHandle: string;\n /** GrowthBook feature key whose evaluated string value is the variant handle. */\n featureKey: string;\n /** Optional allowlist that rejects unexpected GrowthBook variant values. */\n allowedVariants?: readonly string[];\n}\n\n/** Options for the separately bundled GrowthBook assignment adapter. */\nexport interface GrowthBookExperimentAssignmentOptions {\n /** An initialized customer-owned GrowthBook client. */\n client: Pick<GrowthBook<Record<string, string>>, 'evalFeature'>;\n /** Explicit experiment-to-feature mappings; no naming convention is assumed. */\n bindings: readonly GrowthBookExperimentBinding[];\n /** Customer-controlled revision for feature/config changes. */\n providerRevision?: string | number;\n}\n\n/** Machine-readable reasons a GrowthBook assignment cannot be normalized. */\nexport type GrowthBookAssignmentErrorReason =\n | 'duplicate_experiment_handle'\n | 'invalid_binding'\n | 'unknown_feature'\n | 'invalid_variant'\n | 'unexpected_variant';\n\n/** Raised when GrowthBook cannot produce a valid normalized assignment. */\nexport class GrowthBookAssignmentError extends Error {\n /** Stable reason suitable for logs and error handling. */\n readonly reason: GrowthBookAssignmentErrorReason;\n /** RevTurbine experiment whose assignment failed. */\n readonly experimentHandle: string;\n /** GrowthBook feature evaluated for the assignment. */\n readonly featureKey: string;\n\n /** Construct a named GrowthBook normalization failure. */\n constructor(\n reason: GrowthBookAssignmentErrorReason,\n binding: GrowthBookExperimentBinding,\n detail: string,\n ) {\n super(\n `GrowthBook assignment for experiment \"${binding.experimentHandle}\" `\n + `(feature \"${binding.featureKey}\") failed: ${detail}`,\n );\n this.name = 'GrowthBookAssignmentError';\n this.reason = reason;\n this.experimentHandle = binding.experimentHandle;\n this.featureKey = binding.featureKey;\n }\n}\n\nfunction validateBindings(bindings: readonly GrowthBookExperimentBinding[]): void {\n const handles = new Set<string>();\n for (const binding of bindings) {\n if (!binding.experimentHandle.trim() || !binding.featureKey.trim()) {\n throw new GrowthBookAssignmentError(\n 'invalid_binding',\n binding,\n 'experiment handles and GrowthBook feature keys must be non-empty',\n );\n }\n if (handles.has(binding.experimentHandle)) {\n throw new GrowthBookAssignmentError(\n 'duplicate_experiment_handle',\n binding,\n 'the experiment handle is mapped more than once',\n );\n }\n handles.add(binding.experimentHandle);\n }\n}\n\n/**\n * Create an assignment provider backed by an initialized GrowthBook client.\n *\n * Each GrowthBook feature must evaluate to a non-empty string variant handle.\n * Unknown features and invalid values fail explicitly instead of being\n * converted to an empty assignment or fabricated fallback.\n */\nexport function createGrowthBookExperimentAssignmentProvider(\n options: GrowthBookExperimentAssignmentOptions,\n): ExperimentAssignmentProvider {\n validateBindings(options.bindings);\n\n return {\n domain: 'experiments',\n providerHandle: 'growthbook',\n providerRevision: options.providerRevision,\n ownedExperimentHandles: options.bindings.map((binding) => binding.experimentHandle),\n resolve(): ExperimentProviderState {\n const assignments: Record<string, string> = {};\n\n for (const binding of options.bindings) {\n const result = options.client.evalFeature(binding.featureKey);\n if (result.source === 'unknownFeature') {\n throw new GrowthBookAssignmentError(\n 'unknown_feature',\n binding,\n 'GrowthBook reported an unknown feature',\n );\n }\n\n if (typeof result.value !== 'string' || result.value.length === 0) {\n throw new GrowthBookAssignmentError(\n 'invalid_variant',\n binding,\n 'the evaluated value must be a non-empty string variant handle',\n );\n }\n\n if (binding.allowedVariants && !binding.allowedVariants.includes(result.value)) {\n throw new GrowthBookAssignmentError(\n 'unexpected_variant',\n binding,\n `variant \"${result.value}\" is outside the configured allowlist`,\n );\n }\n\n assignments[binding.experimentHandle] = result.value;\n }\n\n return { assignments };\n },\n };\n}\n"]}
|