Skip to content

Commit 10bf95a

Browse files
author
Nathan Booker
committed
Replace Vercel KV adapter with Vercel Runtime Cache API
1 parent 560f950 commit 10bf95a

8 files changed

Lines changed: 222 additions & 166 deletions

File tree

.changeset/chatty-forks-sniff.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@bigcommerce/catalyst-core": minor
3+
---
4+
5+
Implement Vercel Runtime Cache API as replacement for Vercel KV adapter

core/lib/kv/adapters/bc.ts

Lines changed: 0 additions & 101 deletions
This file was deleted.

core/lib/kv/adapters/upstash.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,14 @@ import { Redis } from '@upstash/redis';
22

33
import { KvAdapter, SetCommandOptions } from '../types';
44

5-
import { MemoryKvAdapter } from './memory';
6-
7-
const memoryKv = new MemoryKvAdapter();
8-
95
export class UpstashKvAdapter implements KvAdapter {
106
private upstashKv = Redis.fromEnv();
11-
private memoryKv = memoryKv;
127

138
async mget<Data>(...keys: string[]) {
14-
const memoryValues = await this.memoryKv.mget<Data>(...keys);
15-
16-
return memoryValues.length ? memoryValues : this.upstashKv.mget<Data[]>(keys);
9+
return this.upstashKv.mget<Data[]>(keys);
1710
}
1811

1912
async set<Data>(key: string, value: Data, opts?: SetCommandOptions) {
20-
await this.memoryKv.set(key, value, opts);
21-
2213
const response = await this.upstashKv.set(key, value, opts);
2314

2415
if (response === 'OK') {
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { getCache } from '@vercel/functions';
2+
3+
import { KvAdapter, SetCommandOptions } from '../types';
4+
5+
export class RuntimeCacheAdapter implements KvAdapter {
6+
private cache = getCache();
7+
8+
async mget<Data>(...keys: string[]): Promise<Array<Data | null>> {
9+
this.logger(
10+
`MGET - Keys: ${keys.toString()} - Source: RUNTIME_CACHE - Fetching ${keys.length} keys`,
11+
);
12+
13+
try {
14+
const values = await Promise.all(
15+
keys.map(async (key) => {
16+
try {
17+
// Use the Runtime Cache API
18+
const cachedValue = await this.cache.get(key);
19+
20+
if (cachedValue !== null) {
21+
this.logger(`RUNTIME_CACHE GET - Key: ${key} - Found: true`);
22+
return cachedValue;
23+
}
24+
25+
this.logger(`RUNTIME_CACHE GET - Key: ${key} - Found: false`);
26+
return null;
27+
} catch (error) {
28+
this.logger(
29+
`RUNTIME_CACHE GET ERROR - Key: ${key} - Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
30+
);
31+
return null;
32+
}
33+
}),
34+
);
35+
36+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
37+
return values as Array<Data | null>;
38+
} catch (error) {
39+
this.logger(
40+
`RUNTIME_CACHE ACCESS ERROR - Returning null values: ${error instanceof Error ? error.message : 'Unknown error'}`,
41+
);
42+
43+
// Return null for all keys if Runtime Cache is unavailable
44+
return keys.map(() => null);
45+
}
46+
}
47+
48+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
49+
async set<Data>(key: string, value: Data, _opts?: SetCommandOptions): Promise<Data | null> {
50+
this.logger(`SET - Key: ${key} - Setting in runtime cache`);
51+
52+
try {
53+
await this.cache.set(key, value);
54+
this.logger(`RUNTIME_CACHE SET - Key: ${key} - Success`);
55+
} catch (error) {
56+
this.logger(
57+
`RUNTIME_CACHE SET ERROR - Key: ${key} - Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
58+
);
59+
}
60+
61+
return value;
62+
}
63+
64+
private logger(message: string) {
65+
// Check if logging is enabled using the same logic as the main KV class
66+
const loggingEnabled =
67+
(process.env.NODE_ENV !== 'production' && process.env.KV_LOGGER !== 'false') ||
68+
process.env.KV_LOGGER === 'true';
69+
70+
if (loggingEnabled) {
71+
// eslint-disable-next-line no-console
72+
console.log(`[BigCommerce] Runtime Cache ${message}`);
73+
}
74+
}
75+
}

core/lib/kv/adapters/vercel.ts

Lines changed: 0 additions & 30 deletions
This file was deleted.

core/lib/kv/index.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { KvAdapter, SetCommandOptions } from './types';
33

44
interface Config {
55
logger?: boolean;
6+
disableMemory?: boolean;
67
}
78

89
const memoryKv = new MemoryKvAdapter();
@@ -25,6 +26,19 @@ class KV<Adapter extends KvAdapter> implements KvAdapter {
2526
async mget<Data>(...keys: string[]) {
2627
const kv = await this.getKv();
2728

29+
// Skip memory cache if disabled (useful for testing)
30+
if (this.config.disableMemory) {
31+
this.logger(`MGET - Keys: ${keys.toString()} - MEMORY DISABLED - Direct adapter access`);
32+
33+
const directValues = await kv.mget<Data>(...keys);
34+
35+
this.logger(
36+
`MGET - Keys: ${keys.toString()} - Value: ${JSON.stringify(directValues, null, 2)}`,
37+
);
38+
39+
return directValues;
40+
}
41+
2842
const memoryValues = (await this.memoryKv.mget<Data>(...keys)).filter(Boolean);
2943

3044
if (memoryValues.length === keys.length) {
@@ -60,6 +74,13 @@ class KV<Adapter extends KvAdapter> implements KvAdapter {
6074

6175
this.logger(`SET - Key: ${key} - Value: ${JSON.stringify(value, null, 2)}`);
6276

77+
// Skip memory cache if disabled (useful for testing)
78+
if (this.config.disableMemory) {
79+
this.logger(`SET - Key: ${key} - MEMORY DISABLED - Direct adapter only`);
80+
81+
return kv.set(key, value, opts);
82+
}
83+
6384
await Promise.all([this.memoryKv.set(key, value, opts), kv.set(key, value, opts)]);
6485

6586
return value;
@@ -82,16 +103,11 @@ class KV<Adapter extends KvAdapter> implements KvAdapter {
82103
}
83104

84105
async function createKVAdapter() {
85-
if (process.env.BC_KV_REST_API_URL && process.env.BC_KV_REST_API_TOKEN) {
86-
const { BcKvAdapter } = await import('./adapters/bc');
87-
88-
return new BcKvAdapter();
89-
}
90-
91-
if (process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN) {
92-
const { VercelKvAdapter } = await import('./adapters/vercel');
106+
// Prioritize Runtime Cache for Vercel environments
107+
if (process.env.VERCEL === '1') {
108+
const { RuntimeCacheAdapter } = await import('./adapters/vercel-runtime-cache');
93109

94-
return new VercelKvAdapter();
110+
return new RuntimeCacheAdapter();
95111
}
96112

97113
if (process.env.UPSTASH_REDIS_REST_URL && process.env.UPSTASH_REDIS_REST_TOKEN) {
@@ -107,6 +123,7 @@ const adapterInstance = new KV(createKVAdapter, {
107123
logger:
108124
(process.env.NODE_ENV !== 'production' && process.env.KV_LOGGER !== 'false') ||
109125
process.env.KV_LOGGER === 'true',
126+
disableMemory: process.env.KV_DISABLE_MEMORY === 'true',
110127
});
111128

112129
export { adapterInstance as kv };

core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"@t3-oss/env-core": "^0.13.6",
3535
"@upstash/redis": "^1.35.0",
3636
"@vercel/analytics": "^1.5.0",
37-
"@vercel/kv": "^3.0.0",
37+
"@vercel/functions": "^2.2.3",
3838
"@vercel/speed-insights": "^1.2.0",
3939
"clsx": "^2.1.1",
4040
"content-security-policy-builder": "^2.3.0",

0 commit comments

Comments
 (0)