@growthbook/proxy-eval 1.0.8 → 1.1.1

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/index.js CHANGED
@@ -52,21 +52,46 @@ function evaluateFeatures(_a) {
52
52
  }
53
53
  const gbFeatures = gb.getFeatures();
54
54
  for (const key in gbFeatures) {
55
- const result = gb.evalFeature(key);
56
- if (result.on || result.experiment) {
55
+ const featureResult = gb.evalFeature(key);
56
+ // Check if we have any deferred tracking calls (including prerequisite experiments)
57
+ const deferredCalls = gb.getDeferredTrackingCalls();
58
+ const hasDeferredCalls = deferredCalls && deferredCalls.length > 0;
59
+ const hasValue = featureResult.value !== undefined;
60
+ // legacy check (if deferred calls are missing)
61
+ const hasExperiment = featureResult.source === "experiment" && featureResult.experimentResult !== undefined;
62
+ if (hasValue || hasDeferredCalls) {
57
63
  // reduced feature definition
58
64
  evaluatedFeatures[key] = {
59
- defaultValue: result.value,
65
+ defaultValue: featureResult.value,
60
66
  };
61
- if (result.source === "experiment") {
62
- // reduced experiment definition for tracking
63
- const scrubbedResultExperiment = ((_b = result === null || result === void 0 ? void 0 : result.experimentResult) === null || _b === void 0 ? void 0 : _b.variationId) !== undefined
64
- ? scrubExperiment(result.experiment, result.experimentResult.variationId)
65
- : result.experiment;
67
+ if (hasDeferredCalls) {
68
+ // Process all experiment exposures (including prerequisites)
69
+ const tracks = deferredCalls
70
+ .filter(call => call.experiment && call.result) // Defensive: ensure call has required properties
71
+ .map(call => ({
72
+ experiment: scrubExperiment(call.experiment, call.result.variationId),
73
+ result: call.result,
74
+ }));
66
75
  evaluatedFeatures[key].rules = [
67
76
  {
68
- force: result.value,
69
- tracks: [{ experiment: scrubbedResultExperiment, result }],
77
+ force: featureResult.value,
78
+ tracks,
79
+ },
80
+ ];
81
+ gb.setDeferredTrackingCalls([]);
82
+ }
83
+ else if (hasExperiment) {
84
+ // Fallback for direct experiments when no deferred calls
85
+ const scrubbedResultExperiment = ((_b = featureResult === null || featureResult === void 0 ? void 0 : featureResult.experimentResult) === null || _b === void 0 ? void 0 : _b.variationId) !== undefined
86
+ ? scrubExperiment(featureResult.experiment, featureResult.experimentResult.variationId)
87
+ : featureResult.experiment;
88
+ evaluatedFeatures[key].rules = [
89
+ {
90
+ force: featureResult.value,
91
+ tracks: [{
92
+ experiment: scrubbedResultExperiment,
93
+ result: featureResult.experimentResult,
94
+ }],
70
95
  },
71
96
  ];
72
97
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "growthbook-edge-eval-cloudflare",
3
+ "version": "0.1.0",
4
+ "description": "GrowthBook edge evaluation for Cloudflare Workers",
5
+ "main": "src/index.ts",
6
+ "scripts": {
7
+ "dev": "wrangler dev",
8
+ "deploy": "wrangler deploy",
9
+ "build": "wrangler build"
10
+ },
11
+ "dependencies": {
12
+ "@growthbook/proxy-eval": "^1.1.1"
13
+ },
14
+ "devDependencies": {
15
+ "@cloudflare/workers-types": "^4.20250926.0",
16
+ "typescript": "^5.8.2",
17
+ "wrangler": "^4.40.1"
18
+ }
19
+ }
@@ -0,0 +1,119 @@
1
+ import { evaluateFeatures } from "@growthbook/proxy-eval";
2
+ import { StickyBucketService } from "@growthbook/growthbook";
3
+
4
+ interface Env {
5
+ ENVIRONMENT: string;
6
+
7
+ KV_GB_PAYLOAD: KVNamespace;
8
+ // NOTE: Be sure to connect a GrowthBook SDK Webhook to your KV store.
9
+ // Use the webhook type "Cloudflare KV" in the SDK webhook settings.
10
+ }
11
+
12
+ interface PostBody {
13
+ payload: any;
14
+ attributes: Record<string, any>;
15
+ forcedVariations?: Record<string, number>;
16
+ forcedFeatures?: Map<string, any>;
17
+ url?: string;
18
+ // NOTE: For advanced experimentation, you may want to connect a KV or cookie Sticky Bucket service
19
+ stickyBucketService?:
20
+ | (StickyBucketService & {
21
+ // For cookie-based service, should be a no-op:
22
+ connect: () => Promise<void>;
23
+ // For write-buffer flushes (ex: GB Proxy's implementation of RedisStickyBucketService):
24
+ onEvaluate?: () => Promise<void>;
25
+ })
26
+ | null;
27
+ ctx?: { verboseDebugging?: boolean };
28
+ }
29
+
30
+ const KV_KEY = "gb_payload";
31
+ const CACHE_TTL = 60 * 1000; // 1 min
32
+
33
+ // Cache payload from KV
34
+ let cachedPayload: any = null;
35
+ let lastFetch = 0;
36
+
37
+
38
+ export default {
39
+ fetch: async function (
40
+ request: Request,
41
+ env: Env,
42
+ ctx: ExecutionContext,
43
+ ): Promise<Response> {
44
+ // Handle CORS preflight requests
45
+ if (request.method === 'OPTIONS') {
46
+ return new Response(null, {
47
+ status: 204,
48
+ headers: getCORSHeaders(),
49
+ });
50
+ }
51
+
52
+ // Only allow POST requests
53
+ if (request.method !== 'POST') {
54
+ return new Response(null, {
55
+ status: 405,
56
+ headers: {
57
+ 'Allow': 'POST',
58
+ ...getCORSHeaders(),
59
+ }
60
+ });
61
+ }
62
+
63
+ try {
64
+ const body = await request.json<PostBody>().catch(() => null);
65
+ if (!body || typeof body !== "object") {
66
+ return handleInvalidRequest();
67
+ }
68
+
69
+ if (!cachedPayload || Date.now() - lastFetch > CACHE_TTL) {
70
+ cachedPayload = await env.KV_GB_PAYLOAD.get(KV_KEY, 'json');
71
+ lastFetch = Date.now();
72
+ }
73
+
74
+ const { attributes = {}, forcedVariations = {}, forcedFeatures = [], url = "" } = body;
75
+ const forcedFeaturesMap = new Map(forcedFeatures);
76
+
77
+ const evalResponse = await evaluateFeatures({
78
+ payload: cachedPayload,
79
+ attributes,
80
+ forcedVariations,
81
+ forcedFeatures: forcedFeaturesMap,
82
+ url,
83
+ // stickyBucketService,
84
+ });
85
+
86
+ // Return success response
87
+ return new Response(
88
+ JSON.stringify(evalResponse),
89
+ {
90
+ status: 200,
91
+ headers: {
92
+ 'Content-Type': 'application/json',
93
+ ...getCORSHeaders(),
94
+ }
95
+ }
96
+ );
97
+
98
+ } catch (error) {
99
+ console.error(error);
100
+ return handleInvalidRequest();
101
+ }
102
+ }
103
+ }
104
+
105
+ function getCORSHeaders(): Record<string, string> {
106
+ return {
107
+ 'Access-Control-Allow-Origin': '*', // Configure this appropriately for production
108
+ 'Access-Control-Allow-Methods': 'POST, OPTIONS',
109
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
110
+ 'Access-Control-Max-Age': '86400',
111
+ };
112
+ }
113
+
114
+ function handleInvalidRequest(): Response {
115
+ return new Response(null, {
116
+ status: 500,
117
+ headers: getCORSHeaders(),
118
+ });
119
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022"],
5
+ "module": "ES2022",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true,
8
+ "esModuleInterop": true,
9
+ "allowJs": true,
10
+ "strict": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "noEmit": true,
16
+ "types": ["@cloudflare/workers-types"]
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }
@@ -0,0 +1,14 @@
1
+ name = "growthbook-edge-evaluator"
2
+ main = "src/index.ts"
3
+ compatibility_date = "2025-09-25"
4
+
5
+ # Variable bindings. These are arbitrary, plaintext strings (similar to environment variables)
6
+ # Note: Use secrets to store sensitive data.
7
+ # Docs: https://developers.cloudflare.com/workers/platform/environment-variables
8
+
9
+ kv_namespaces = [
10
+ { binding = "KV_GB_PAYLOAD", id = "qwerty123" }
11
+ ]
12
+
13
+ [vars]
14
+ NODE_ENV="production"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@growthbook/proxy-eval",
3
3
  "description": "Remote evaluation service for proxies, edge workers, or backend APIs",
4
- "version": "1.0.8",
4
+ "version": "1.1.1",
5
5
  "main": "dist/index.js",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -18,7 +18,7 @@
18
18
  "dev": "tsc --watch"
19
19
  },
20
20
  "dependencies": {
21
- "@growthbook/growthbook": "^1.6.1"
21
+ "@growthbook/growthbook": "^1.6.3"
22
22
  },
23
23
  "devDependencies": {
24
24
  "rimraf": "^6.0.1",
package/src/index.ts CHANGED
@@ -3,6 +3,9 @@ import {
3
3
  GrowthBook,
4
4
  Context as GBContext,
5
5
  StickyBucketService,
6
+ FeatureDefinition,
7
+ FeatureRule,
8
+ AutoExperiment,
6
9
  } from "@growthbook/growthbook";
7
10
 
8
11
  export async function evaluateFeatures({
@@ -27,8 +30,8 @@ export async function evaluateFeatures({
27
30
  | null;
28
31
  ctx?: any;
29
32
  }) {
30
- const evaluatedFeatures: Record<string, any> = {};
31
- const evaluatedExperiments: any[] = [];
33
+ const evaluatedFeatures: Record<string, FeatureDefinition> = {};
34
+ const evaluatedExperiments: AutoExperiment[] = [];
32
35
 
33
36
  const features = payload?.features;
34
37
  const experiments = payload?.experiments;
@@ -67,25 +70,56 @@ export async function evaluateFeatures({
67
70
 
68
71
  const gbFeatures = gb.getFeatures();
69
72
  for (const key in gbFeatures) {
70
- const result = gb.evalFeature(key);
71
- if (result.on || result.experiment) {
73
+ const featureResult = gb.evalFeature(key);
74
+
75
+ // Check if we have any deferred tracking calls (including prerequisite experiments)
76
+ const deferredCalls = gb.getDeferredTrackingCalls();
77
+ const hasDeferredCalls = deferredCalls && deferredCalls.length > 0;
78
+ const hasValue = featureResult.value !== undefined;
79
+
80
+ // legacy check (if deferred calls are missing)
81
+ const hasExperiment = featureResult.source === "experiment" && featureResult.experimentResult !== undefined;
82
+
83
+ if (hasValue || hasDeferredCalls) {
72
84
  // reduced feature definition
73
85
  evaluatedFeatures[key] = {
74
- defaultValue: result.value,
86
+ defaultValue: featureResult.value,
75
87
  };
76
- if (result.source === "experiment") {
77
- // reduced experiment definition for tracking
88
+
89
+ if (hasDeferredCalls) {
90
+ // Process all experiment exposures (including prerequisites)
91
+ const tracks: FeatureRule['tracks'] = deferredCalls
92
+ .filter(call => call.experiment && call.result) // Defensive: ensure call has required properties
93
+ .map(call => ({
94
+ experiment: scrubExperiment(call.experiment, call.result.variationId),
95
+ result: call.result,
96
+ }));
97
+
98
+ evaluatedFeatures[key].rules = [
99
+ {
100
+ force: featureResult.value,
101
+ tracks,
102
+ },
103
+ ];
104
+ gb.setDeferredTrackingCalls([]);
105
+
106
+ } else if (hasExperiment) {
107
+ // Fallback for direct experiments when no deferred calls
78
108
  const scrubbedResultExperiment =
79
- result?.experimentResult?.variationId !== undefined
109
+ featureResult?.experimentResult?.variationId !== undefined
80
110
  ? scrubExperiment(
81
- result.experiment,
82
- result.experimentResult.variationId,
111
+ featureResult.experiment,
112
+ featureResult.experimentResult.variationId,
83
113
  )
84
- : result.experiment;
114
+ : featureResult.experiment;
115
+
85
116
  evaluatedFeatures[key].rules = [
86
117
  {
87
- force: result.value,
88
- tracks: [{ experiment: scrubbedResultExperiment, result }],
118
+ force: featureResult.value,
119
+ tracks: [{
120
+ experiment: scrubbedResultExperiment,
121
+ result: featureResult.experimentResult!,
122
+ }],
89
123
  },
90
124
  ];
91
125
  }