Skip to content

Commit 6705996

Browse files
authored
fix: make vercel use EdgeProvider (#114)
1 parent 7b44da7 commit 6705996

File tree

8 files changed

+222
-238
lines changed

8 files changed

+222
-238
lines changed

packages/sdk/vercel/package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,32 @@
2020
"import": "./dist/esm/src/index.js"
2121
},
2222
"main": "./dist/cjs/src/index.js",
23+
"types": "./dist/cjs/src/index.d.ts",
2324
"files": [
2425
"dist"
2526
],
2627
"scripts": {
2728
"doc": "../../../scripts/build-doc.sh .",
2829
"build": "../../../scripts/build-package.sh",
30+
"clean": "rimraf dist",
2931
"tsw": "yarn tsc --watch",
3032
"start": "rimraf dist && yarn tsw",
3133
"lint": "eslint . --ext .ts",
32-
"prettier": "prettier --write '**/*.@(js|ts|tsx|json|css)'",
33-
"test": "jest --ci --runInBand --coverage",
34+
"prettier": "prettier --write '**/*.@(js|ts|tsx|json|css)' --ignore-path ../../../.prettierignore",
35+
"test": "NODE_OPTIONS=\"--experimental-vm-modules --no-warnings\" jest --ci --runInBand",
36+
"coverage": "yarn test --coverage",
3437
"check": "yarn prettier && yarn lint && yarn build && yarn test && yarn doc"
3538
},
3639
"dependencies": {
3740
"@launchdarkly/js-server-sdk-common-edge": "1.0.1",
41+
"@vercel/edge-config": "^0.1.8",
3842
"crypto-js": "^4.1.1"
3943
},
4044
"devDependencies": {
4145
"@types/crypto-js": "^4.1.1",
4246
"@types/jest": "^29.5.0",
4347
"@typescript-eslint/eslint-plugin": "^5.57.0",
4448
"@typescript-eslint/parser": "^5.57.0",
45-
"@vercel/edge-config": "^0.1.7",
4649
"eslint": "^8.37.0",
4750
"eslint-config-airbnb-base": "^15.0.0",
4851
"eslint-config-airbnb-typescript": "^17.0.0",
@@ -52,6 +55,7 @@
5255
"jest": "^29.5.0",
5356
"launchdarkly-js-test-helpers": "^2.2.0",
5457
"prettier": "^2.8.7",
58+
"rimraf": "^5.0.0",
5559
"ts-jest": "^29.1.0",
5660
"typedoc": "0.23.26",
5761
"typescript": "^5.0.3"
Lines changed: 73 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,95 @@
1-
import type { EdgeConfigValue } from '@vercel/edge-config';
2-
import { LDClient } from '@launchdarkly/js-server-sdk-common-edge';
1+
import { LDClient, LDContext } from '@launchdarkly/js-server-sdk-common-edge';
32
import { init } from './index';
43
import * as testData from './utils/testData.json';
5-
import mockEdge from './utils/mockEdge';
4+
import mockEdgeConfigClient from './utils/mockEdgeConfigClient';
65

76
const sdkKey = 'test-sdk-key';
8-
const flagKey = 'testFlag1';
9-
const context = { kind: 'user', key: 'test-user-key-1' };
7+
const flagKey1 = 'testFlag1';
8+
const flagKey2 = 'testFlag2';
9+
const flagKey3 = 'testFlag3';
10+
const context: LDContext = { kind: 'user', key: 'test-user-key-1' };
1011

1112
describe('init', () => {
1213
let ldClient: LDClient;
14+
const mockGet = mockEdgeConfigClient.get as jest.Mock;
1315

1416
beforeAll(async () => {
15-
// I can't figure out a way around the generic types used in @vercel/edge-config causing us type issues here.
16-
// The tests work as expected
17-
// @ts-ignore
18-
mockEdge.get = jest.fn(async () => testData as EdgeConfigValue);
19-
ldClient = init(sdkKey, mockEdge);
17+
mockGet.mockImplementation(() => Promise.resolve(testData));
18+
ldClient = init(sdkKey, mockEdgeConfigClient);
2019
await ldClient.waitForInitialization();
2120
});
2221

2322
afterAll(() => {
2423
ldClient.close();
2524
});
2625

27-
test('variation', async () => {
28-
const flagDetail = await ldClient.variation(flagKey, context, false);
29-
expect(flagDetail).toBeTruthy();
30-
});
26+
describe('flags', () => {
27+
test('variation default', async () => {
28+
const value = await ldClient.variation(flagKey1, context, false);
29+
expect(value).toBeTruthy();
30+
});
31+
32+
test('variation default rollout', async () => {
33+
const contextWithEmail = { ...context, email: 'test@yahoo.com' };
34+
const value = await ldClient.variation(flagKey2, contextWithEmail, false);
35+
const detail = await ldClient.variationDetail(flagKey2, contextWithEmail, false);
36+
37+
expect(detail).toEqual({ reason: { kind: 'FALLTHROUGH' }, value: true, variationIndex: 0 });
38+
expect(value).toBeTruthy();
39+
});
40+
41+
test('rule match', async () => {
42+
const contextWithEmail = { ...context, email: 'test@gmail.com' };
43+
const value = await ldClient.variation(flagKey1, contextWithEmail, false);
44+
const detail = await ldClient.variationDetail(flagKey1, contextWithEmail, false);
45+
46+
expect(detail).toEqual({
47+
reason: { kind: 'RULE_MATCH', ruleId: 'rule1', ruleIndex: 0 },
48+
value: false,
49+
variationIndex: 1,
50+
});
51+
expect(value).toBeFalsy();
52+
});
53+
54+
test('fallthrough', async () => {
55+
const contextWithEmail = { ...context, email: 'test@yahoo.com' };
56+
const value = await ldClient.variation(flagKey1, contextWithEmail, false);
57+
const detail = await ldClient.variationDetail(flagKey1, contextWithEmail, false);
58+
59+
expect(detail).toEqual({ reason: { kind: 'FALLTHROUGH' }, value: true, variationIndex: 0 });
60+
expect(value).toBeTruthy();
61+
});
3162

32-
test('variationDetail', async () => {
33-
const flagDetail = await ldClient.variationDetail(flagKey, context, false);
34-
expect(flagDetail).toEqual({ reason: { kind: 'FALLTHROUGH' }, value: true, variationIndex: 0 });
63+
test('allFlags fallthrough', async () => {
64+
const allFlags = await ldClient.allFlagsState(context);
65+
66+
expect(allFlags).toBeDefined();
67+
expect(allFlags.toJSON()).toEqual({
68+
$flagsState: {
69+
testFlag1: { debugEventsUntilDate: 2000, variation: 0, version: 2 },
70+
testFlag2: { debugEventsUntilDate: 2000, variation: 0, version: 2 },
71+
testFlag3: { debugEventsUntilDate: 2000, variation: 0, version: 2 },
72+
},
73+
$valid: true,
74+
testFlag1: true,
75+
testFlag2: true,
76+
testFlag3: true,
77+
});
78+
});
3579
});
3680

37-
test('allFlags', async () => {
38-
const allFlags = await ldClient.allFlagsState(context);
39-
40-
expect(allFlags).toBeDefined();
41-
expect(allFlags.toJSON()).toEqual({
42-
$flagsState: {
43-
testFlag1: { debugEventsUntilDate: null, variation: 0, version: 2 },
44-
testFlag2: { debugEventsUntilDate: null, variation: 1, version: 2 },
45-
},
46-
$valid: true,
47-
testFlag1: true,
48-
testFlag2: false,
81+
describe('segments', () => {
82+
test('segment by country', async () => {
83+
const contextWithCountry = { ...context, country: 'australia' };
84+
const value = await ldClient.variation(flagKey3, contextWithCountry, false);
85+
const detail = await ldClient.variationDetail(flagKey3, contextWithCountry, false);
86+
87+
expect(detail).toEqual({
88+
reason: { kind: 'RULE_MATCH', ruleId: 'rule1', ruleIndex: 0 },
89+
value: false,
90+
variationIndex: 1,
91+
});
92+
expect(value).toBeFalsy();
4993
});
5094
});
5195
});

packages/sdk/vercel/src/index.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@
1111
import type { EdgeConfigClient } from '@vercel/edge-config';
1212
import {
1313
BasicLogger,
14+
EdgeFeatureStore,
15+
EdgeProvider,
1416
init as initEdge,
1517
LDClient,
1618
LDOptions,
1719
} from '@launchdarkly/js-server-sdk-common-edge';
18-
import VercelFeatureStore from './vercelFeatureStore';
1920
import createPlatformInfo from './createPlatformInfo';
2021

2122
export * from '@launchdarkly/js-server-sdk-common-edge';
@@ -33,20 +34,29 @@ export type { LDClient };
3334
* (`new LDClient()/new LDClientImpl()/new LDClient()`); the SDK does not currently support
3435
* this.
3536
*
36-
* @param edgeConfig
37-
* The Vercel Edge Config client configured for LaunchDarkly.
3837
* @param sdkKey
3938
* The client side SDK key. This is only used to query the edgeConfig above,
4039
* not to connect with LaunchDarkly servers.
40+
* @param edgeConfig
41+
* The Vercel Edge Config client configured for LaunchDarkly.
4142
* @param options
4243
* Optional configuration settings. The only supported option is logger.
4344
* @return
4445
* The new {@link LDClient} instance.
4546
*/
4647
export const init = (sdkKey: string, edgeConfig: EdgeConfigClient, options: LDOptions = {}) => {
4748
const logger = options.logger ?? BasicLogger.get();
49+
50+
// vercel does not support string gets so we have to patch it
51+
const edgeProvider: EdgeProvider = {
52+
get: async (rootKey: string) => {
53+
const json = await edgeConfig.get(rootKey);
54+
return json ? JSON.stringify(json) : null;
55+
},
56+
};
57+
4858
return initEdge(sdkKey, createPlatformInfo(), {
49-
featureStore: new VercelFeatureStore(edgeConfig, sdkKey, logger),
59+
featureStore: new EdgeFeatureStore(edgeProvider, sdkKey, 'Vercel', logger),
5060
logger,
5161
...options,
5262
});
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { EdgeConfigClient } from '@vercel/edge-config';
22

3-
const mockEdge: EdgeConfigClient = {
3+
const mockEdgeConfigClient: EdgeConfigClient = {
44
get: jest.fn(),
55
getAll: jest.fn(),
66
digest: jest.fn(),
77
has: jest.fn(),
88
};
99

10-
export default mockEdge;
10+
export default mockEdgeConfigClient;

packages/sdk/vercel/src/utils/testData.json

Lines changed: 125 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,26 @@
55
"on": true,
66
"prerequisites": [],
77
"targets": [],
8-
"rules": [],
8+
"rules": [
9+
{
10+
"variation": 1,
11+
"id": "rule1",
12+
"clauses": [
13+
{
14+
"contextKind": "user",
15+
"attribute": "/email",
16+
"op": "contains",
17+
"values": ["gmail"],
18+
"negate": false
19+
}
20+
],
21+
"trackEvents": false,
22+
"rollout": {
23+
"bucketBy": "bucket",
24+
"variations": [{ "variation": 1, "weight": 100 }]
25+
}
26+
}
27+
],
928
"fallthrough": {
1029
"variation": 0
1130
},
@@ -19,16 +38,58 @@
1938
"salt": "aef830243d6640d0a973be89988e008d",
2039
"trackEvents": false,
2140
"trackEventsFallthrough": false,
22-
"debugEventsUntilDate": null,
41+
"debugEventsUntilDate": 2000,
2342
"version": 2,
2443
"deleted": false
2544
},
2645
"testFlag2": {
2746
"key": "testFlag2",
28-
"on": false,
47+
"on": true,
2948
"prerequisites": [],
3049
"targets": [],
3150
"rules": [],
51+
"fallthrough": {
52+
"variation": 0,
53+
"rollout": {
54+
"bucketBy": "bucket",
55+
"variations": [{ "variation": 1, "weight": 100 }],
56+
"contextKind:": "user",
57+
"attribute": "/email"
58+
}
59+
},
60+
"offVariation": 1,
61+
"variations": [true, false],
62+
"clientSideAvailability": {
63+
"usingMobileKey": true,
64+
"usingEnvironmentId": true
65+
},
66+
"clientSide": true,
67+
"salt": "aef830243d6640d0a973be89988e008d",
68+
"trackEvents": false,
69+
"trackEventsFallthrough": false,
70+
"debugEventsUntilDate": 2000,
71+
"version": 2,
72+
"deleted": false
73+
},
74+
"testFlag3": {
75+
"key": "testFlag3",
76+
"on": true,
77+
"prerequisites": [],
78+
"targets": [],
79+
"rules": [
80+
{
81+
"variation": 1,
82+
"id": "rule1",
83+
"clauses": [
84+
{
85+
"op": "segmentMatch",
86+
"values": ["testSegment1"],
87+
"negate": false
88+
}
89+
],
90+
"trackEvents": false
91+
}
92+
],
3293
"fallthrough": {
3394
"variation": 0
3495
},
@@ -42,9 +103,69 @@
42103
"salt": "aef830243d6640d0a973be89988e008d",
43104
"trackEvents": false,
44105
"trackEventsFallthrough": false,
45-
"debugEventsUntilDate": null,
106+
"debugEventsUntilDate": 2000,
46107
"version": 2,
47108
"deleted": false
48109
}
110+
},
111+
"segments": {
112+
"testSegment1": {
113+
"name": "testSegment1",
114+
"tags": [],
115+
"creationDate": 1676063792158,
116+
"key": "testSegment1",
117+
"included": [],
118+
"excluded": [],
119+
"includedContexts": [],
120+
"excludedContexts": [],
121+
"_links": {
122+
"parent": { "href": "/api/v2/segments/default/test", "type": "application/json" },
123+
"self": {
124+
"href": "/api/v2/segments/default/test/beta-users-1",
125+
"type": "application/json"
126+
},
127+
"site": { "href": "/default/test/segments/beta-users-1", "type": "text/html" }
128+
},
129+
"rules": [
130+
{
131+
"id": "rule-country",
132+
"clauses": [
133+
{
134+
"attribute": "country",
135+
"op": "in",
136+
"values": ["australia"],
137+
"negate": false
138+
}
139+
]
140+
}
141+
],
142+
"version": 1,
143+
"deleted": false,
144+
"_access": { "denied": [], "allowed": [] },
145+
"generation": 1
146+
},
147+
"testSegment2": {
148+
"name": "testSegment2",
149+
"tags": [],
150+
"creationDate": 1676063792158,
151+
"key": "testSegment2",
152+
"included": [],
153+
"excluded": [],
154+
"includedContexts": [],
155+
"excludedContexts": [],
156+
"_links": {
157+
"parent": { "href": "/api/v2/segments/default/test", "type": "application/json" },
158+
"self": {
159+
"href": "/api/v2/segments/default/test/beta-users-1",
160+
"type": "application/json"
161+
},
162+
"site": { "href": "/default/test/segments/beta-users-1", "type": "text/html" }
163+
},
164+
"rules": [],
165+
"version": 1,
166+
"deleted": false,
167+
"_access": { "denied": [], "allowed": [] },
168+
"generation": 1
169+
}
49170
}
50171
}

0 commit comments

Comments
 (0)