Skip to content

Commit

Permalink
Pavel/add async (#3)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
pavelflux authored May 31, 2024
1 parent c731753 commit 50ab7cf
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 8 deletions.
85 changes: 84 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ npm install @eppo/vercel-edge-sdk

## Quick start

This SDK is inteded to be used in [Vercel Edge Middleware](https://vercel.com/docs/functions/edge-middleware)
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.

[Vercel Edge Config Store ](https://vercel.com/docs/storage/edge-config/using-edge-config) is required for storing Eppo configs.

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

Expand Down Expand Up @@ -95,6 +98,86 @@ export const config = {

```

This script will not fetch configs from Eppo, only from Vercel Config Store.

To fetch configs from Eppo and store them in Vercel Config Store, you need to create a Vercel Function.

Example:

`pages/api/eppo-prefetch.ts`

```ts
export const runtime = 'nodejs';

import { IAssignmentLogger, prefetchConfig } from '@eppo/vercel-edge-sdk';
import { NextApiRequest, NextApiResponse } from 'next';

const assignmentLogger: IAssignmentLogger = {
logAssignment(assignment) {
console.log('assignement', assignment)
},
};

export default async function handler(req: NextApiRequest, res: NextApiResponse) {

try {
if (!process.env.EDGE_CONFIG) {
throw new Error('Define EDGE_CONFIG env variable');
}

if (!process.env.EDGE_CONFIG_STORE_ID) {
throw new Error('Define EDGE_CONFIG_STORE_ID env variable')
}

if (!process.env.EDGE_CONFIG_TOKEN) {
throw new Error('Define EDGE_CONFIG_TOKEN env variable')
}

if (!process.env.INTERNAL_FEATURE_FLAG_API_KEY) {
throw new Error('Define INTERNAL_FEATURE_FLAG_API_KEY env variable')
}

prefetchConfig({
apiKey: process.env.INTERNAL_FEATURE_FLAG_API_KEY,
assignmentLogger,
vercelParams: {
edgeConfig: process.env.EDGE_CONFIG,
edgeConfigStoreId: process.env.EDGE_CONFIG_STORE_ID,
vercelApiToken: process.env.VERCEL_API_TOKEN,
},
});

res.status(200).json({ message: 'Prefetch success' });
} catch(e) {
res.status(500).json({ message: 'Prefetch error'});
}
}
```

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.

The flow is next:
- if config stored in Vercel Config Store is not outdated, middleware will give return up-to-date assignment;
- 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;


### Vercel Cron Job

You can hydrate data using [Vercel Cron Job](https://vercel.com/guides/how-to-setup-cron-jobs-on-vercel).
For this, in your middleware, do not provide a URL to Vercel Function.
Create a Vercel Function and as in the example above, and create a cron job like:

```ts
{
"crons": [
{
"path": "/api/eppo-prefetch",
"schedule": "0 5 * * *"
}
]
}
```

## Assignment functions

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:
Expand Down
15 changes: 13 additions & 2 deletions example.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IAssignmentLogger } from '@eppo/js-client-sdk-common';

import { init } from './src/index';
import { init, prefetchConfig } from './src/index';

const assignmentLogger: IAssignmentLogger = {
logAssignment(assignment) {
Expand All @@ -9,13 +9,24 @@ const assignmentLogger: IAssignmentLogger = {
};

async function main() {
await prefetchConfig({
apiKey: '...',
assignmentLogger,
vercelParams: {
edgeConfig: 'https://edge-config.vercel.com/...',
edgeConfigStoreId: '...',
vercelApiToken: '..',
},
});

const eppoClient = await init({
apiKey: '<your-eppo-sdk-key>',
apiKey: '...',
assignmentLogger,
vercelParams: {
edgeConfig: 'https://edge-config.vercel.com/...',
edgeConfigStoreId: '...',
vercelApiToken: '...',
vercelFunctionUrl: 'http://localhost:3001/api/eppo-prefetch',
},
});

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@eppo/vercel-edge-sdk",
"version": "3.0.0",
"version": "3.0.1",
"description": "Eppo SDK for use in Vercel Edge Middleware",
"main": "dist/index.js",
"files": [
Expand Down
1 change: 1 addition & 0 deletions src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ describe('EppoJSClient E2E test', () => {
'test variation assignment splits',
async ({ flag, variationType, defaultValue, subjects }: IAssignmentTestCase) => {
const client = getInstance();
await client.fetchFlagConfigurations();

const typeAssignmentFunctions = {
[VariationType.BOOLEAN]: client.getBoolAssignment.bind(client),
Expand Down
34 changes: 30 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface IClientConfig {
edgeConfigStoreId: string;
vercelApiToken: string;
edgeConfigExpirationSeconds?: number;
vercelFunctionUrl?: string;
};

/**
Expand Down Expand Up @@ -153,7 +154,13 @@ export class EppoJSClient extends EppoClient {
* @public
*/
export async function init(config: IClientConfig): Promise<IEppoClient> {
validation.validateNotBlank(config.apiKey, 'API key required');
await initClient(config);
EppoJSClient.initialized = true;
return EppoJSClient.instance;
}

async function initClient(config: IClientConfig) {
validateConfig(config);
try {
// If any existing instances; ensure they are not polling
if (EppoJSClient.instance) {
Expand Down Expand Up @@ -190,7 +197,12 @@ export async function init(config: IClientConfig): Promise<IEppoClient> {
EppoJSClient.instance.setLogger(config.assignmentLogger);
EppoJSClient.instance.setConfigurationRequestParameters(requestConfiguration);

await EppoJSClient.instance.fetchFlagConfigurations();
if (config.vercelParams.vercelFunctionUrl) {
const isConfigExpired = await configurationStore.isExpired();
if (isConfigExpired) {
fetch(config.vercelParams.vercelFunctionUrl);
}
}
} catch (error) {
console.warn(
'Eppo SDK encountered an error initializing, assignment calls will return the default value and not be logged',
Expand All @@ -199,8 +211,22 @@ export async function init(config: IClientConfig): Promise<IEppoClient> {
throw error;
}
}
EppoJSClient.initialized = true;
return EppoJSClient.instance;
}

export async function prefetchConfig(config: IClientConfig) {
await initClient(config);

await EppoJSClient.instance.fetchFlagConfigurations();
}

function validateConfig(config: IClientConfig) {
validation.validateNotBlank(config.apiKey, 'API key required');
validation.validateNotBlank(config.vercelParams.edgeConfig, 'EDGE_CONFIG is required');
validation.validateNotBlank(config.vercelParams.vercelApiToken, 'Vercel api token is required');
validation.validateNotBlank(
config.vercelParams.edgeConfigStoreId,
'Edge Config Store Id is required',
);
}

/**
Expand Down

0 comments on commit 50ab7cf

Please sign in to comment.