Skip to content

Commit

Permalink
feat(core): Add edge config adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
christensenep committed Feb 12, 2024
1 parent 998f8cd commit 4cb15ce
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 10 deletions.
2 changes: 1 addition & 1 deletion apps/core/app/api/revalidate/route/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const handler = async (request: NextRequest) => {

const node = await getRoute(pathname);

const expiryTime = Date.now() + 1000 * 60 * 30; // 30 minutes;
const expiryTime = Date.now() + 1000 * 60 * 60 * 6; // 6 hours;

try {
await kv.set(pathname, { node, expiryTime });
Expand Down
2 changes: 1 addition & 1 deletion apps/core/app/api/revalidate/store-status/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const handler = async () => {
return new NextResponse('Unable to revalidate store status', { status: 500 });
}

const expiryTime = Date.now() + 1000 * 60 * 5; // 5 minutes;
const expiryTime = Date.now() + 1000 * 60 * 60 * 6; // 6 hours;

try {
await kv.set(STORE_STATUS_KEY, { status, expiryTime });
Expand Down
81 changes: 81 additions & 0 deletions apps/core/lib/kv/adapters/vercel-edge-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { get } from '@vercel/edge-config';

import { KvAdapter } from '../types';

export class VercelEdgeConfigAdapter implements KvAdapter {
private config_id = '';

constructor() {
const config_matcher = /(?:https:\/\/edge-config.vercel.com\/)(.*)\?(?:.*)/;

if (
!process.env.EDGE_CONFIG ||
!process.env.VERCEL_TEAM_ID ||
!process.env.VERCEL_ACCESS_TOKEN
) {
// eslint-disable-next-line no-console
console.error(
`EDGE_CONFIG, VERCEL_TEAM_ID, and VERCEL_ACCESS_TOKEN must be defined to use the Edge Config adapter`,
);

return;
}

const match = config_matcher.exec(process.env.EDGE_CONFIG);

if (!match || !match[1]) {
// eslint-disable-next-line no-console
console.error(`EDGE_CONFIG is malformed, unable to initialize Edge Config adapter`);

return;
}

this.config_id = match[1];
}

async get<Data>(key: string) {
const keyHash = await this.hashString(key);

return get<string>(keyHash).then(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
(result) => (result !== undefined ? (JSON.parse(result) as Data) : null),
);
}

async mget<Data>(...keys: string[]) {
return Promise.all(keys.map((key) => this.get<Data>(key)));
}

async set<Data>(key: string, value: Data) {
const keyHash = await this.hashString(key);

const url = `https://api.vercel.com/v1/edge-config/${this.config_id}/items?teamId=${process.env.VERCEL_TEAM_ID ?? ''}`;

void fetch(url, {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.VERCEL_ACCESS_TOKEN ?? ''}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
items: [
{
operation: 'upsert',
key: keyHash,
value: JSON.stringify(value),
},
],
}),
});

return value;
}

private async hashString(key: string) {
return crypto.subtle.digest('SHA-1', new TextEncoder().encode(key)).then((buf) => {
return Array.prototype.map
.call(new Uint8Array(buf), (x: string) => `00${parseInt(x, 10).toString(16)}`.slice(-2))
.join('');
});
}
}
14 changes: 9 additions & 5 deletions apps/core/lib/kv/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,19 @@ class KV<Adapter extends KvAdapter> implements KvAdapter {
}

async function createKVAdapter() {
if (process.env.NODE_ENV === 'development' && !process.env.KV_REST_API_URL) {
const { DevKvAdapter } = await import('./adapters/dev');
if (process.env.ENABLE_EDGE_CONFIG === 'true') {
const { VercelEdgeConfigAdapter } = await import('./adapters/vercel-edge-config');

return new DevKvAdapter();
return new VercelEdgeConfigAdapter();
} else if (process.env.KV_REST_API_URL) {
const { VercelKvAdapter } = await import('./adapters/vercel');

return new VercelKvAdapter();
}

const { VercelKvAdapter } = await import('./adapters/vercel');
const { DevKvAdapter } = await import('./adapters/dev');

return new VercelKvAdapter();
return new DevKvAdapter();
}

const adapterInstance = new KV(createKVAdapter, {
Expand Down
4 changes: 2 additions & 2 deletions apps/core/middlewares/with-custom-urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const getExistingRouteInfo = async (request: NextRequest) => {

const setKvStatus = async (status?: StorefrontStatusType | null) => {
try {
const expiryTime = Date.now() + 1000 * 60 * 5; // 5 minutes;
const expiryTime = Date.now() + 1000 * 60 * 60 * 6; // 6 hours;

await kv.set(STORE_STATUS_KEY, { status, expiryTime });
} catch (error) {
Expand All @@ -103,7 +103,7 @@ const setKvStatus = async (status?: StorefrontStatusType | null) => {

const setKvRoute = async (request: NextRequest, node: Node) => {
try {
const expiryTime = Date.now() + 1000 * 60 * 30; // 30 minutes;
const expiryTime = Date.now() + 1000 * 60 * 60 * 6; // 6 hours;

await kv.set(request.nextUrl.pathname, { node, expiryTime });
} catch (error) {
Expand Down
3 changes: 2 additions & 1 deletion apps/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@graphql-typed-document-node/core": "^3.2.0",
"@icons-pack/react-simple-icons": "^9.2.0",
"@vercel/analytics": "^1.1.1",
"@vercel/edge-config": "^0.4.1",
"@vercel/kv": "^1.0.0",
"@vercel/speed-insights": "^1.0.1",
"clsx": "^2.0.0",
Expand All @@ -29,8 +30,8 @@
"next-auth": "5.0.0-beta.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.49.3",
"react-google-recaptcha": "^3.1.0",
"react-hook-form": "^7.49.3",
"react-hot-toast": "^2.4.1",
"schema-dts": "^1.1.2",
"server-only": "^0.0.1",
Expand Down
14 changes: 14 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 4cb15ce

Please sign in to comment.