Skip to content

Commit 50ab7cf

Browse files
authored
Pavel/add async (#3)
* Add prefetchConfig function * Make running of vercel function async * Adjust vercion of sdk * Adjust example * Fix for test * Adjust README * Add check for config expiration to avoid running Vercel Function too much * Add section about using cron job to readme * Adjust readme
1 parent c731753 commit 50ab7cf

File tree

5 files changed

+129
-8
lines changed

5 files changed

+129
-8
lines changed

README.md

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ npm install @eppo/vercel-edge-sdk
2323

2424
## Quick start
2525

26-
This SDK is inteded to be used in [Vercel Edge Middleware](https://vercel.com/docs/functions/edge-middleware)
26+
This SDK is inteded to be used in [Vercel Edge Middleware](https://vercel.com/docs/functions/edge-middleware) and in [Vercel Function](https://vercel.com/docs/functions/quickstart) for hydration and keeping stored config up-to-date.
27+
2728
[Vercel Edge Config Store ](https://vercel.com/docs/storage/edge-config/using-edge-config) is required for storing Eppo configs.
2829

2930
#### Example of usage in middleware.ts file
@@ -67,6 +68,8 @@ export async function middleware(request: NextRequest) {
6768
edgeConfig: process.env.EDGE_CONFIG,
6869
edgeConfigStoreId: process.env.EDGE_CONFIG_STORE_ID,
6970
vercelApiToken: process.env.EDGE_CONFIG_TOKEN,
71+
vercelFunctionUrl: process.env.VERCEL_FUNCTION_URL // e.g. https://domain/api/eppo-prefetch
72+
edgeConfigExpirationSeconds: 1000,
7073
}
7174
});
7275

@@ -95,6 +98,86 @@ export const config = {
9598

9699
```
97100

101+
This script will not fetch configs from Eppo, only from Vercel Config Store.
102+
103+
To fetch configs from Eppo and store them in Vercel Config Store, you need to create a Vercel Function.
104+
105+
Example:
106+
107+
`pages/api/eppo-prefetch.ts`
108+
109+
```ts
110+
export const runtime = 'nodejs';
111+
112+
import { IAssignmentLogger, prefetchConfig } from '@eppo/vercel-edge-sdk';
113+
import { NextApiRequest, NextApiResponse } from 'next';
114+
115+
const assignmentLogger: IAssignmentLogger = {
116+
logAssignment(assignment) {
117+
console.log('assignement', assignment)
118+
},
119+
};
120+
121+
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
122+
123+
try {
124+
if (!process.env.EDGE_CONFIG) {
125+
throw new Error('Define EDGE_CONFIG env variable');
126+
}
127+
128+
if (!process.env.EDGE_CONFIG_STORE_ID) {
129+
throw new Error('Define EDGE_CONFIG_STORE_ID env variable')
130+
}
131+
132+
if (!process.env.EDGE_CONFIG_TOKEN) {
133+
throw new Error('Define EDGE_CONFIG_TOKEN env variable')
134+
}
135+
136+
if (!process.env.INTERNAL_FEATURE_FLAG_API_KEY) {
137+
throw new Error('Define INTERNAL_FEATURE_FLAG_API_KEY env variable')
138+
}
139+
140+
prefetchConfig({
141+
apiKey: process.env.INTERNAL_FEATURE_FLAG_API_KEY,
142+
assignmentLogger,
143+
vercelParams: {
144+
edgeConfig: process.env.EDGE_CONFIG,
145+
edgeConfigStoreId: process.env.EDGE_CONFIG_STORE_ID,
146+
vercelApiToken: process.env.VERCEL_API_TOKEN,
147+
},
148+
});
149+
150+
res.status(200).json({ message: 'Prefetch success' });
151+
} catch(e) {
152+
res.status(500).json({ message: 'Prefetch error'});
153+
}
154+
}
155+
```
156+
157+
Your middleware, each time running, will start this cloud function (by doing an async request to the url specidifed in `vercelParams.vercelFunctionUrl`), and it will fetch and store configs.
158+
159+
The flow is next:
160+
- if config stored in Vercel Config Store is not outdated, middleware will give return up-to-date assignment;
161+
- if config stored in Vercel Config Store is outdated, middleware will still give an assignment requested, just outdated, and send a request to start Vercel Function to prefetch up-to-date config; Next run of the middleware will give an updated result;
162+
163+
164+
### Vercel Cron Job
165+
166+
You can hydrate data using [Vercel Cron Job](https://vercel.com/guides/how-to-setup-cron-jobs-on-vercel).
167+
For this, in your middleware, do not provide a URL to Vercel Function.
168+
Create a Vercel Function and as in the example above, and create a cron job like:
169+
170+
```ts
171+
{
172+
"crons": [
173+
{
174+
"path": "/api/eppo-prefetch",
175+
"schedule": "0 5 * * *"
176+
}
177+
]
178+
}
179+
```
180+
98181
## Assignment functions
99182

100183
Every Eppo flag has a return type that is set once on creation in the dashboard. Once a flag is created, assignments in code should be made using the corresponding typed function:

example.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { IAssignmentLogger } from '@eppo/js-client-sdk-common';
22

3-
import { init } from './src/index';
3+
import { init, prefetchConfig } from './src/index';
44

55
const assignmentLogger: IAssignmentLogger = {
66
logAssignment(assignment) {
@@ -9,13 +9,24 @@ const assignmentLogger: IAssignmentLogger = {
99
};
1010

1111
async function main() {
12+
await prefetchConfig({
13+
apiKey: '...',
14+
assignmentLogger,
15+
vercelParams: {
16+
edgeConfig: 'https://edge-config.vercel.com/...',
17+
edgeConfigStoreId: '...',
18+
vercelApiToken: '..',
19+
},
20+
});
21+
1222
const eppoClient = await init({
13-
apiKey: '<your-eppo-sdk-key>',
23+
apiKey: '...',
1424
assignmentLogger,
1525
vercelParams: {
1626
edgeConfig: 'https://edge-config.vercel.com/...',
1727
edgeConfigStoreId: '...',
1828
vercelApiToken: '...',
29+
vercelFunctionUrl: 'http://localhost:3001/api/eppo-prefetch',
1930
},
2031
});
2132

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@eppo/vercel-edge-sdk",
3-
"version": "3.0.0",
3+
"version": "3.0.1",
44
"description": "Eppo SDK for use in Vercel Edge Middleware",
55
"main": "dist/index.js",
66
"files": [

src/index.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ describe('EppoJSClient E2E test', () => {
275275
'test variation assignment splits',
276276
async ({ flag, variationType, defaultValue, subjects }: IAssignmentTestCase) => {
277277
const client = getInstance();
278+
await client.fetchFlagConfigurations();
278279

279280
const typeAssignmentFunctions = {
280281
[VariationType.BOOLEAN]: client.getBoolAssignment.bind(client),

src/index.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export interface IClientConfig {
4343
edgeConfigStoreId: string;
4444
vercelApiToken: string;
4545
edgeConfigExpirationSeconds?: number;
46+
vercelFunctionUrl?: string;
4647
};
4748

4849
/**
@@ -153,7 +154,13 @@ export class EppoJSClient extends EppoClient {
153154
* @public
154155
*/
155156
export async function init(config: IClientConfig): Promise<IEppoClient> {
156-
validation.validateNotBlank(config.apiKey, 'API key required');
157+
await initClient(config);
158+
EppoJSClient.initialized = true;
159+
return EppoJSClient.instance;
160+
}
161+
162+
async function initClient(config: IClientConfig) {
163+
validateConfig(config);
157164
try {
158165
// If any existing instances; ensure they are not polling
159166
if (EppoJSClient.instance) {
@@ -190,7 +197,12 @@ export async function init(config: IClientConfig): Promise<IEppoClient> {
190197
EppoJSClient.instance.setLogger(config.assignmentLogger);
191198
EppoJSClient.instance.setConfigurationRequestParameters(requestConfiguration);
192199

193-
await EppoJSClient.instance.fetchFlagConfigurations();
200+
if (config.vercelParams.vercelFunctionUrl) {
201+
const isConfigExpired = await configurationStore.isExpired();
202+
if (isConfigExpired) {
203+
fetch(config.vercelParams.vercelFunctionUrl);
204+
}
205+
}
194206
} catch (error) {
195207
console.warn(
196208
'Eppo SDK encountered an error initializing, assignment calls will return the default value and not be logged',
@@ -199,8 +211,22 @@ export async function init(config: IClientConfig): Promise<IEppoClient> {
199211
throw error;
200212
}
201213
}
202-
EppoJSClient.initialized = true;
203-
return EppoJSClient.instance;
214+
}
215+
216+
export async function prefetchConfig(config: IClientConfig) {
217+
await initClient(config);
218+
219+
await EppoJSClient.instance.fetchFlagConfigurations();
220+
}
221+
222+
function validateConfig(config: IClientConfig) {
223+
validation.validateNotBlank(config.apiKey, 'API key required');
224+
validation.validateNotBlank(config.vercelParams.edgeConfig, 'EDGE_CONFIG is required');
225+
validation.validateNotBlank(config.vercelParams.vercelApiToken, 'Vercel api token is required');
226+
validation.validateNotBlank(
227+
config.vercelParams.edgeConfigStoreId,
228+
'Edge Config Store Id is required',
229+
);
204230
}
205231

206232
/**

0 commit comments

Comments
 (0)