Skip to content

Commit db45c6d

Browse files
authored
Rework context to actually share memory cache between middleware and handlers for all requests (#2358)
1 parent e3f5f81 commit db45c6d

File tree

3 files changed

+43
-18
lines changed

3 files changed

+43
-18
lines changed

src/lib/async.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { MaybePromise } from 'p-map';
22

3-
import { waitUntil, getGlobalContext } from './waitUntil';
3+
import { waitUntil, getRequestContext } from './waitUntil';
44

55
/**
66
* Execute a function for each input in parallel and return the first result.
@@ -238,7 +238,7 @@ export function singleton<R>(execute: () => Promise<R>): () => Promise<R> {
238238
}
239239

240240
// Promises are not shared between requests in Cloudflare Workers
241-
const ctx = await getGlobalContext();
241+
const ctx = await getRequestContext();
242242
const current = states.get(ctx);
243243
if (current) {
244244
return current;
@@ -269,7 +269,7 @@ export function singletonMap<Key extends string, Args extends any[], Result>(
269269
const states = new WeakMap<object, Map<string, Promise<Result>>>();
270270

271271
const fn: SingletonFunction<Key, Args, Result> = async (key, ...args) => {
272-
const ctx = await getGlobalContext();
272+
const ctx = await getRequestContext();
273273
let current = states.get(ctx);
274274
if (current) {
275275
const existing = current.get(key);
@@ -292,7 +292,7 @@ export function singletonMap<Key extends string, Args extends any[], Result>(
292292
};
293293

294294
fn.isRunning = async (key: string) => {
295-
const ctx = await getGlobalContext();
295+
const ctx = await getRequestContext();
296296
const current = states.get(ctx);
297297
return current?.has(key) ?? false;
298298
};

src/lib/cache/memory.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { CacheBackend } from './types';
1+
import { CacheBackend, CacheEntry } from './types';
22
import { NON_IMMUTABLE_LOCAL_CACHE_MAX_AGE_SECONDS, isCacheEntryImmutable } from './utils';
3-
import { singleton } from '../async';
3+
import { getGlobalContext } from '../waitUntil';
44

55
export const memoryCache: CacheBackend = {
66
name: 'memory',
@@ -64,9 +64,19 @@ export const memoryCache: CacheBackend = {
6464
};
6565

6666
/**
67-
* With next-on-pages, the code seems to be isolated between the middleware and the handler.
68-
* To share the cache between the two, we use a global variable.
69-
* By using a singleton, we ensure that the cache is only created once and stored in the
70-
* current request context.
67+
* In memory cache shared globally.
7168
*/
72-
const getMemoryCache = singleton(async () => new Map());
69+
async function getMemoryCache(): Promise<Map<string, CacheEntry>> {
70+
const ctx: Awaited<ReturnType<typeof getGlobalContext>> & {
71+
gitbookMemoryCache?: Map<string, CacheEntry>;
72+
} = await getGlobalContext();
73+
74+
if (ctx.gitbookMemoryCache) {
75+
return ctx.gitbookMemoryCache;
76+
}
77+
78+
const gitbookMemoryCache = new Map<string, CacheEntry>();
79+
ctx.gitbookMemoryCache = gitbookMemoryCache;
80+
81+
return gitbookMemoryCache;
82+
}

src/lib/waitUntil.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,28 @@
1+
import type { ExecutionContext, IncomingRequestCfProperties } from '@cloudflare/workers-types';
2+
13
let pendings: Array<Promise<unknown>> = [];
24

5+
/**
6+
* Get a global context object for the current execution.
7+
* This object can be used to store data that should be shared between the middleware and the handler
8+
* and re-used for all requests.
9+
*/
10+
export async function getGlobalContext(): Promise<ExecutionContext | object> {
11+
if (process.env.NODE_ENV === 'test') {
12+
// Do not try loading the next-on-pages package in tests as it'll fail
13+
return globalThis;
14+
}
15+
16+
// We lazy-load the next-on-pages package to avoid errors when running tests because of 'server-only'.
17+
const { getOptionalRequestContext } = await import('@cloudflare/next-on-pages');
18+
return getOptionalRequestContext()?.ctx ?? globalThis;
19+
}
20+
321
/**
422
* Get a global context object for the current request.
523
* This object can be used as a key to store request-specific data in a WeakMap.
624
*/
7-
export async function getGlobalContext(): Promise<object> {
25+
export async function getRequestContext(): Promise<IncomingRequestCfProperties | object> {
826
if (process.env.NODE_ENV === 'test') {
927
// Do not try loading the next-on-pages package in tests as it'll fail
1028
return globalThis;
@@ -29,12 +47,9 @@ export async function waitUntil(promise: Promise<unknown>) {
2947
return;
3048
}
3149

32-
// We lazy-load the next-on-pages package to avoid errors when running tests because of 'server-only'.
33-
const { getOptionalRequestContext } = await import('@cloudflare/next-on-pages');
34-
35-
const cloudflare = getOptionalRequestContext();
36-
if (cloudflare) {
37-
cloudflare.ctx.waitUntil(promise);
50+
const cloudflareContext = await getGlobalContext();
51+
if ('waitUntil' in cloudflareContext) {
52+
cloudflareContext.waitUntil(promise);
3853
} else {
3954
await promise;
4055
}

0 commit comments

Comments
 (0)