@rsc-kit/core 0.30.42 → 0.30.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache.d.ts +2 -0
- package/dist/cache.js +11 -1
- package/dist/cache.js.map +1 -1
- package/dist/host.d.ts +14 -0
- package/dist/host.js +97 -3
- package/dist/host.js.map +1 -1
- package/dist/pageCache.d.ts +38 -0
- package/dist/pageCache.js +192 -6
- package/dist/pageCache.js.map +1 -1
- package/dist/request.d.ts +10 -0
- package/dist/request.js +28 -0
- package/dist/request.js.map +1 -1
- package/dist/vite.js +3 -0
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
package/dist/cache.d.ts
CHANGED
|
@@ -81,6 +81,8 @@ export declare function memoryCacheStore(options?: {
|
|
|
81
81
|
* settings, never connects). `null` goes back to memory.
|
|
82
82
|
*/
|
|
83
83
|
export declare function installCacheStore(store: CacheStore | (() => CacheStore) | null): void;
|
|
84
|
+
/** The store the app installed, if it did: null when answers are kept in this process's memory. */
|
|
85
|
+
export declare function installedCacheStore(): CacheStore | null;
|
|
84
86
|
/** The store in use: the app's, else this process's memory. */
|
|
85
87
|
export declare function cacheStore(): CacheStore;
|
|
86
88
|
/**
|
package/dist/cache.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// scope is opened by whoever owns the request — the host, the worker, or the
|
|
18
18
|
// prerenderer — and everything below it shares one store.
|
|
19
19
|
import { versionSource } from "./changed.js";
|
|
20
|
-
import { noteAwaiter, withHelper, withoutRequest } from "./request.js";
|
|
20
|
+
import { noteAwaiter, noteCacheTags, withHelper, withoutRequest } from "./request.js";
|
|
21
21
|
import { resolveScope } from "./revalidate.js";
|
|
22
22
|
const SCOPE = Symbol.for("@rsc-kit/core.cache-scope");
|
|
23
23
|
const globals = globalThis;
|
|
@@ -145,6 +145,12 @@ export function memoryCacheStore(options = {}) {
|
|
|
145
145
|
export function installCacheStore(store) {
|
|
146
146
|
globals[STORE] = store;
|
|
147
147
|
}
|
|
148
|
+
/** The store the app installed, if it did: null when answers are kept in this process's memory. */
|
|
149
|
+
export function installedCacheStore() {
|
|
150
|
+
if (typeof globals[STORE] === "function")
|
|
151
|
+
globals[STORE] = globals[STORE]();
|
|
152
|
+
return globals[STORE] ?? null;
|
|
153
|
+
}
|
|
148
154
|
/** The store in use: the app's, else this process's memory. */
|
|
149
155
|
export function cacheStore() {
|
|
150
156
|
if (typeof globals[STORE] === "function")
|
|
@@ -180,6 +186,10 @@ function across(fn, options) {
|
|
|
180
186
|
const versions = tags.length
|
|
181
187
|
? await versionSource().changed(Object.fromEntries(tags.map((tag) => [tag, 0])), 0)
|
|
182
188
|
: {};
|
|
189
|
+
// Before anything is shared: an ask that joins one in flight, or is
|
|
190
|
+
// answered from the store, read the same tags as the one that worked it
|
|
191
|
+
// out - at the versions its key is built from.
|
|
192
|
+
noteCacheTags(tags, versions);
|
|
183
193
|
const key = [
|
|
184
194
|
"rsc-cache",
|
|
185
195
|
globals[BUILD] ?? "",
|
package/dist/cache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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","sourcesContent":["// Asking the same question twice in one request.\n//\n// import { cache } from '@rsc-kit/core/cache'\n//\n// export const currentUser = cache(async () => db.user(await sessionId()))\n//\n// A guard checks who you are; the layout wants their name; the page wants their\n// permissions. Three calls, one request, one answer — without threading the\n// result through props that only exist to carry it.\n//\n// This is the same shape as React's cache(), and deliberately not React's: the\n// engine's own boundaries are not React's. A guard runs before any component\n// does, which is exactly where the duplication starts, and React has no scope\n// open there.\n//\n// Scoped to the request, so two in flight cannot see each other's answers. The\n// scope is opened by whoever owns the request — the host, the worker, or the\n// prerenderer — and everything below it shares one store.\n\nimport { versionSource } from \"./changed.js\";\nimport { noteAwaiter, withHelper, withoutRequest } from \"./request.js\";\nimport { resolveScope } from \"./revalidate.js\";\n\n/** One request's memo table. */\ntype Store = Map<unknown, ArgNode>;\n\n/**\n * Arguments keyed the way React keys them: a tree, one level per argument.\n *\n * Primitives compare by value and objects by identity, which falls out of\n * using a Map for the first and a WeakMap for the second. Serialising the\n * arguments instead would make `f({ a: 1 })` and `f({ a: 1 })` the same call —\n * which they are not, if the object is mutable.\n */\ninterface ArgNode {\n /** The answer for the argument list that ends here. */\n result?: { value: unknown };\n byValue?: Map<unknown, ArgNode>;\n byIdentity?: WeakMap<object, ArgNode>;\n}\n\nconst SCOPE = Symbol.for(\"@rsc-kit/core.cache-scope\");\n\nconst globals = globalThis as Record<symbol | string, unknown>;\n\nlet ready: Promise<void> | null = null;\n\ninterface Scope {\n getStore(): Store | undefined;\n run<T>(store: Store, fn: () => T): T;\n}\n\nfunction scope(): Scope | null {\n return (globals[SCOPE] as Scope | undefined) ?? null;\n}\n\nfunction isObject(value: unknown): value is object {\n return (\n (typeof value === \"object\" && value !== null) || typeof value === \"function\"\n );\n}\n\n/** Walk to the node for this argument list, creating it as we go. */\nfunction nodeFor(root: ArgNode, args: unknown[]): ArgNode {\n let node = root;\n\n for (const arg of args) {\n if (isObject(arg)) {\n node.byIdentity ??= new WeakMap();\n\n let next = node.byIdentity.get(arg);\n\n if (!next) {\n next = {};\n node.byIdentity.set(arg, next);\n }\n\n node = next;\n } else {\n node.byValue ??= new Map();\n\n let next = node.byValue.get(arg);\n\n if (!next) {\n next = {};\n node.byValue.set(arg, next);\n }\n\n node = next;\n }\n }\n\n return node;\n}\n\n/**\n * Memoise a function for the length of one request - or, given a key and a\n * lifetime, across requests too.\n *\n * The *promise* is cached, not the value, so callers that arrive while the\n * first is still in flight wait on it rather than starting a second. A\n * rejection is cached too: within one request the answer to a question does\n * not change because you asked again.\n *\n * Outside a request this calls straight through. Nothing is stored, nothing\n * leaks between requests, and shared code does not have to know which side of\n * the boundary it is on — the same reason revalidate() is a no-op outside an\n * action.\n *\n * With options, the answer is also kept in the cache store (see\n * {@link installCacheStore}) for `revalidate` seconds, shared by every\n * request, and by every instance when the store is shared:\n *\n * export const findPage = cache(\n * async (slug: string) => db.pages.find(slug),\n * { key: 'pages.find', revalidate: 3600, tags: ['pages'] },\n * )\n *\n * // wherever pages are saved\n * await changed('pages') // the next findPage() asks the database again\n *\n * Arguments and answer must be JSON. The function runs as nobody - no\n * cookies(), no headers(), no session on a backend call - because its\n * answer is handed to every visitor; a read throws instead of leaking.\n */\nexport function cache<A extends unknown[], R>(\n fn: (...args: A) => R,\n options?: AcrossRequests<A>,\n): (...args: A) => R {\n const run = options ? (across(fn as never, options) as unknown as (...args: A) => R) : fn;\n\n return (...args: A): R => {\n const store = scope()?.getStore();\n\n if (!store) return run(...args);\n\n let root = store.get(fn);\n\n if (!root) {\n root = {};\n store.set(fn, root);\n }\n\n const node = nodeFor(root, args);\n\n // Who asked, every time: the body runs once, and a read it makes can\n // then name every component that waited on it, not only the first.\n noteAwaiter(fn);\n\n if (node.result) return node.result.value as R;\n\n const value = withHelper(fn, () => run(...args));\n\n node.result = { value };\n\n return value;\n };\n}\n\n// ── Across requests ──────────────────────────────────────────────────────────\n//\n// Where answers are kept is the app's choice, as with the versions behind\n// changed(): memory by default, or anything with a get and a set - Redis,\n// Workers KV, a table. The store keeps strings and nothing else; the engine\n// owns the key, the lifetime and the invalidation.\n//\n// Invalidation is a version, not a delete. A key carries the current version\n// of each of its tags, read from the same source changed() bumps, so after\n// changed('pages') the next ask builds a different key and misses. The old\n// entry is never read again and expires on its own. That is why a store needs\n// no delete, no scan and no purge - and why a change made on one instance is\n// seen by the next ask on every other, when the versions are shared.\n\n/** How a function's answers are kept between requests. */\nexport interface AcrossRequests<A extends unknown[] = unknown[]> {\n /**\n * The function's name in the store, the same on every instance: a function\n * has no identity outside this process. Two functions must not share one.\n */\n key: string;\n /** Seconds an answer is kept before it is asked again. */\n revalidate: number;\n /**\n * Names whose change makes every answer stale at once: `changed('pages')`.\n * The same names `refreshOn` and `changed()` use, so a change that refreshes\n * open tabs also misses here. Given the arguments, for a tag per record.\n */\n tags?: string[] | ((...args: A) => string[]);\n}\n\n/**\n * Where answers kept across requests live. Strings in, strings out.\n *\n * // Redis\n * installCacheStore({\n * get: (key) => redis.get(key),\n * set: (key, value, ttl) => redis.set(key, value, 'EX', ttl),\n * })\n *\n * // Workers KV (60 seconds is KV's shortest expiry)\n * installCacheStore({\n * get: (key) => env.CACHE.get(key),\n * set: (key, value, ttl) => env.CACHE.put(key, value, { expirationTtl: Math.max(60, ttl) }),\n * })\n *\n * A store may forget anything at any time, and may keep it past `ttl`: the\n * engine checks an answer's age itself.\n */\nexport interface CacheStore {\n get(key: string): Promise<string | null | undefined> | string | null | undefined;\n set(key: string, value: string, ttl: number): Promise<unknown> | unknown;\n}\n\nconst STORE = Symbol.for(\"@rsc-kit/core.cache-store\");\nconst LOCAL_STORE = Symbol.for(\"@rsc-kit/core.cache-store-local\");\nconst BUILD = Symbol.for(\"@rsc-kit/core.cache-build\");\nconst IN_FLIGHT = Symbol.for(\"@rsc-kit/core.cache-in-flight\");\n\n/**\n * Answers kept in this process, the most recently used `max` of them (1,000\n * by default). The store without one installed: fast, and one copy per\n * instance, so a change on another instance is seen when the answer expires.\n */\nexport function memoryCacheStore(options: { max?: number } = {}): CacheStore {\n const max = options.max ?? 1000;\n const entries = new Map<string, { value: string; until: number }>();\n\n return {\n get(key) {\n const entry = entries.get(key);\n\n if (!entry) return undefined;\n\n entries.delete(key);\n\n if (entry.until <= Date.now()) return undefined;\n\n // Re-inserted, so the Map's order is least recently used first.\n entries.set(key, entry);\n\n return entry.value;\n },\n set(key, value, ttl) {\n entries.delete(key);\n entries.set(key, { value, until: Date.now() + ttl * 1000 });\n\n while (entries.size > max) entries.delete(entries.keys().next().value!);\n },\n };\n}\n\n/**\n * Choose where answers are kept across requests: a store, or a function that\n * makes one on first use (so the build, which loads the app without its\n * settings, never connects). `null` goes back to memory.\n */\nexport function installCacheStore(\n store: CacheStore | (() => CacheStore) | null,\n): void {\n globals[STORE] = store;\n}\n\n/** The store in use: the app's, else this process's memory. */\nexport function cacheStore(): CacheStore {\n if (typeof globals[STORE] === \"function\")\n globals[STORE] = (globals[STORE] as () => CacheStore)();\n\n return (\n (globals[STORE] as CacheStore | null | undefined) ??\n ((globals[LOCAL_STORE] as CacheStore | undefined) ??= memoryCacheStore())\n );\n}\n\n/**\n * The build answers are kept for. Set by the host once it knows its own, so\n * a deploy never reads answers a previous build kept - their shape may have\n * changed with the code. Not for apps.\n */\nexport function setCacheBuild(build: string | null | undefined): void {\n globals[BUILD] = build ?? \"\";\n}\n\nlet warnedStore = false;\n\n/** A store that fails is a slower page, not a broken one: said once, then asked around. */\nfunction storeFailed(error: unknown): void {\n if (warnedStore) return;\n warnedStore = true;\n console.warn(\n \"[rsc-kit] the cache store failed, so answers are being worked out each time instead:\",\n error,\n );\n}\n\ninterface Kept {\n /** The answer. Absent when it was undefined, which JSON cannot spell. */\n v?: unknown;\n /** When it was worked out, in ms. */\n at: number;\n}\n\nfunction across<A extends unknown[], R>(\n fn: (...args: A) => Promise<R> | R,\n options: AcrossRequests<A>,\n): (...args: A) => Promise<R> {\n if (!options.key) throw new Error(\"[rsc-kit] cache() across requests needs a key\");\n if (!(options.revalidate > 0))\n throw new Error(`[rsc-kit] cache() ${options.key}: revalidate is seconds, above 0`);\n\n // Whole seconds for the store, which may round anyway; the age is checked exactly.\n const ttl = Math.ceil(options.revalidate);\n\n return async (...args: A): Promise<R> => {\n const tags =\n typeof options.tags === \"function\" ? options.tags(...args) : options.tags ?? [];\n const versions = tags.length\n ? await versionSource().changed(Object.fromEntries(tags.map((tag) => [tag, 0])), 0)\n : {};\n const key = [\n \"rsc-cache\",\n (globals[BUILD] as string | undefined) ?? \"\",\n options.key,\n JSON.stringify(args),\n tags.map((tag) => `${tag}@${versions[tag] ?? 0}`).join(\",\"),\n ].join(\"|\");\n\n // One ask per key at a time in this process: a burst of visitors to a\n // page nobody has asked for yet starts one lookup, not one each.\n const inFlight = ((globals[IN_FLIGHT] as Map<string, Promise<unknown>> | undefined) ??=\n new Map());\n const pending = inFlight.get(key);\n\n if (pending) return pending as Promise<R>;\n\n const work = (async (): Promise<R> => {\n const store = cacheStore();\n let raw: string | null | undefined;\n\n try {\n raw = await store.get(key);\n } catch (error) {\n storeFailed(error);\n }\n\n if (raw) {\n const kept = JSON.parse(raw) as Kept;\n\n if (Date.now() - kept.at < options.revalidate * 1000) return kept.v as R;\n }\n\n const value = await withoutRequest(() => fn(...args));\n const kept: Kept = { at: Date.now() };\n\n if (value !== undefined) kept.v = value;\n\n // Every caller gets what the store would give back - a miss as well as\n // a hit - so a Date is a string the first time too, not only after.\n const text = JSON.stringify(kept);\n\n try {\n await store.set(key, text, ttl);\n } catch (error) {\n storeFailed(error);\n }\n\n return (JSON.parse(text) as Kept).v as R;\n })();\n\n inFlight.set(key, work);\n\n try {\n return await work;\n } finally {\n inFlight.delete(key);\n }\n };\n}\n\n/** Whether anything is listening, for code that wants to know. */\nexport function isCaching(): boolean {\n return scope()?.getStore() !== undefined;\n}\n\n/**\n * Run one request with a memo table of its own.\n *\n * Opened by whoever owns the request. Nesting is harmless and does not create a\n * second table — an inner call reuses the one already open, so a host that\n * wraps and an engine that also wraps do not end up with two.\n */\nexport async function withCache<T>(run: () => Promise<T>): Promise<T> {\n if (!globals[SCOPE]) {\n ready ??= resolveScope().then((resolved) => {\n globals[SCOPE] ??= resolved as unknown as Scope;\n });\n\n await ready;\n }\n\n const existing = scope()!.getStore();\n\n if (existing) return await run();\n\n return await scope()!.run(new Map(), run);\n}\n\n/**\n * Forget every answer this request has cached so far.\n *\n * For the render that follows a mutation. An action's request memoises what\n * the middleware and the action read before the row was changed, and a\n * revalidation rendered in the same request would read those answers back:\n * a port renamed its agent and the sidebar the action re-rendered still\n * said the old name, while the row was updated. What was true before the\n * write is not a memo of what is true after it.\n */\nexport function forgetCached(): void {\n scope()?.getStore()?.clear();\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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","sourcesContent":["// Asking the same question twice in one request.\n//\n// import { cache } from '@rsc-kit/core/cache'\n//\n// export const currentUser = cache(async () => db.user(await sessionId()))\n//\n// A guard checks who you are; the layout wants their name; the page wants their\n// permissions. Three calls, one request, one answer — without threading the\n// result through props that only exist to carry it.\n//\n// This is the same shape as React's cache(), and deliberately not React's: the\n// engine's own boundaries are not React's. A guard runs before any component\n// does, which is exactly where the duplication starts, and React has no scope\n// open there.\n//\n// Scoped to the request, so two in flight cannot see each other's answers. The\n// scope is opened by whoever owns the request — the host, the worker, or the\n// prerenderer — and everything below it shares one store.\n\nimport { versionSource } from \"./changed.js\";\nimport { noteAwaiter, noteCacheTags, withHelper, withoutRequest } from \"./request.js\";\nimport { resolveScope } from \"./revalidate.js\";\n\n/** One request's memo table. */\ntype Store = Map<unknown, ArgNode>;\n\n/**\n * Arguments keyed the way React keys them: a tree, one level per argument.\n *\n * Primitives compare by value and objects by identity, which falls out of\n * using a Map for the first and a WeakMap for the second. Serialising the\n * arguments instead would make `f({ a: 1 })` and `f({ a: 1 })` the same call —\n * which they are not, if the object is mutable.\n */\ninterface ArgNode {\n /** The answer for the argument list that ends here. */\n result?: { value: unknown };\n byValue?: Map<unknown, ArgNode>;\n byIdentity?: WeakMap<object, ArgNode>;\n}\n\nconst SCOPE = Symbol.for(\"@rsc-kit/core.cache-scope\");\n\nconst globals = globalThis as Record<symbol | string, unknown>;\n\nlet ready: Promise<void> | null = null;\n\ninterface Scope {\n getStore(): Store | undefined;\n run<T>(store: Store, fn: () => T): T;\n}\n\nfunction scope(): Scope | null {\n return (globals[SCOPE] as Scope | undefined) ?? null;\n}\n\nfunction isObject(value: unknown): value is object {\n return (\n (typeof value === \"object\" && value !== null) || typeof value === \"function\"\n );\n}\n\n/** Walk to the node for this argument list, creating it as we go. */\nfunction nodeFor(root: ArgNode, args: unknown[]): ArgNode {\n let node = root;\n\n for (const arg of args) {\n if (isObject(arg)) {\n node.byIdentity ??= new WeakMap();\n\n let next = node.byIdentity.get(arg);\n\n if (!next) {\n next = {};\n node.byIdentity.set(arg, next);\n }\n\n node = next;\n } else {\n node.byValue ??= new Map();\n\n let next = node.byValue.get(arg);\n\n if (!next) {\n next = {};\n node.byValue.set(arg, next);\n }\n\n node = next;\n }\n }\n\n return node;\n}\n\n/**\n * Memoise a function for the length of one request - or, given a key and a\n * lifetime, across requests too.\n *\n * The *promise* is cached, not the value, so callers that arrive while the\n * first is still in flight wait on it rather than starting a second. A\n * rejection is cached too: within one request the answer to a question does\n * not change because you asked again.\n *\n * Outside a request this calls straight through. Nothing is stored, nothing\n * leaks between requests, and shared code does not have to know which side of\n * the boundary it is on — the same reason revalidate() is a no-op outside an\n * action.\n *\n * With options, the answer is also kept in the cache store (see\n * {@link installCacheStore}) for `revalidate` seconds, shared by every\n * request, and by every instance when the store is shared:\n *\n * export const findPage = cache(\n * async (slug: string) => db.pages.find(slug),\n * { key: 'pages.find', revalidate: 3600, tags: ['pages'] },\n * )\n *\n * // wherever pages are saved\n * await changed('pages') // the next findPage() asks the database again\n *\n * Arguments and answer must be JSON. The function runs as nobody - no\n * cookies(), no headers(), no session on a backend call - because its\n * answer is handed to every visitor; a read throws instead of leaking.\n */\nexport function cache<A extends unknown[], R>(\n fn: (...args: A) => R,\n options?: AcrossRequests<A>,\n): (...args: A) => R {\n const run = options ? (across(fn as never, options) as unknown as (...args: A) => R) : fn;\n\n return (...args: A): R => {\n const store = scope()?.getStore();\n\n if (!store) return run(...args);\n\n let root = store.get(fn);\n\n if (!root) {\n root = {};\n store.set(fn, root);\n }\n\n const node = nodeFor(root, args);\n\n // Who asked, every time: the body runs once, and a read it makes can\n // then name every component that waited on it, not only the first.\n noteAwaiter(fn);\n\n if (node.result) return node.result.value as R;\n\n const value = withHelper(fn, () => run(...args));\n\n node.result = { value };\n\n return value;\n };\n}\n\n// ── Across requests ──────────────────────────────────────────────────────────\n//\n// Where answers are kept is the app's choice, as with the versions behind\n// changed(): memory by default, or anything with a get and a set - Redis,\n// Workers KV, a table. The store keeps strings and nothing else; the engine\n// owns the key, the lifetime and the invalidation.\n//\n// Invalidation is a version, not a delete. A key carries the current version\n// of each of its tags, read from the same source changed() bumps, so after\n// changed('pages') the next ask builds a different key and misses. The old\n// entry is never read again and expires on its own. That is why a store needs\n// no delete, no scan and no purge - and why a change made on one instance is\n// seen by the next ask on every other, when the versions are shared.\n\n/** How a function's answers are kept between requests. */\nexport interface AcrossRequests<A extends unknown[] = unknown[]> {\n /**\n * The function's name in the store, the same on every instance: a function\n * has no identity outside this process. Two functions must not share one.\n */\n key: string;\n /** Seconds an answer is kept before it is asked again. */\n revalidate: number;\n /**\n * Names whose change makes every answer stale at once: `changed('pages')`.\n * The same names `refreshOn` and `changed()` use, so a change that refreshes\n * open tabs also misses here. Given the arguments, for a tag per record.\n */\n tags?: string[] | ((...args: A) => string[]);\n}\n\n/**\n * Where answers kept across requests live. Strings in, strings out.\n *\n * // Redis\n * installCacheStore({\n * get: (key) => redis.get(key),\n * set: (key, value, ttl) => redis.set(key, value, 'EX', ttl),\n * })\n *\n * // Workers KV (60 seconds is KV's shortest expiry)\n * installCacheStore({\n * get: (key) => env.CACHE.get(key),\n * set: (key, value, ttl) => env.CACHE.put(key, value, { expirationTtl: Math.max(60, ttl) }),\n * })\n *\n * A store may forget anything at any time, and may keep it past `ttl`: the\n * engine checks an answer's age itself.\n */\nexport interface CacheStore {\n get(key: string): Promise<string | null | undefined> | string | null | undefined;\n set(key: string, value: string, ttl: number): Promise<unknown> | unknown;\n}\n\nconst STORE = Symbol.for(\"@rsc-kit/core.cache-store\");\nconst LOCAL_STORE = Symbol.for(\"@rsc-kit/core.cache-store-local\");\nconst BUILD = Symbol.for(\"@rsc-kit/core.cache-build\");\nconst IN_FLIGHT = Symbol.for(\"@rsc-kit/core.cache-in-flight\");\n\n/**\n * Answers kept in this process, the most recently used `max` of them (1,000\n * by default). The store without one installed: fast, and one copy per\n * instance, so a change on another instance is seen when the answer expires.\n */\nexport function memoryCacheStore(options: { max?: number } = {}): CacheStore {\n const max = options.max ?? 1000;\n const entries = new Map<string, { value: string; until: number }>();\n\n return {\n get(key) {\n const entry = entries.get(key);\n\n if (!entry) return undefined;\n\n entries.delete(key);\n\n if (entry.until <= Date.now()) return undefined;\n\n // Re-inserted, so the Map's order is least recently used first.\n entries.set(key, entry);\n\n return entry.value;\n },\n set(key, value, ttl) {\n entries.delete(key);\n entries.set(key, { value, until: Date.now() + ttl * 1000 });\n\n while (entries.size > max) entries.delete(entries.keys().next().value!);\n },\n };\n}\n\n/**\n * Choose where answers are kept across requests: a store, or a function that\n * makes one on first use (so the build, which loads the app without its\n * settings, never connects). `null` goes back to memory.\n */\nexport function installCacheStore(\n store: CacheStore | (() => CacheStore) | null,\n): void {\n globals[STORE] = store;\n}\n\n/** The store the app installed, if it did: null when answers are kept in this process's memory. */\nexport function installedCacheStore(): CacheStore | null {\n if (typeof globals[STORE] === \"function\")\n globals[STORE] = (globals[STORE] as () => CacheStore)();\n\n return (globals[STORE] as CacheStore | null | undefined) ?? null;\n}\n\n/** The store in use: the app's, else this process's memory. */\nexport function cacheStore(): CacheStore {\n if (typeof globals[STORE] === \"function\")\n globals[STORE] = (globals[STORE] as () => CacheStore)();\n\n return (\n (globals[STORE] as CacheStore | null | undefined) ??\n ((globals[LOCAL_STORE] as CacheStore | undefined) ??= memoryCacheStore())\n );\n}\n\n/**\n * The build answers are kept for. Set by the host once it knows its own, so\n * a deploy never reads answers a previous build kept - their shape may have\n * changed with the code. Not for apps.\n */\nexport function setCacheBuild(build: string | null | undefined): void {\n globals[BUILD] = build ?? \"\";\n}\n\nlet warnedStore = false;\n\n/** A store that fails is a slower page, not a broken one: said once, then asked around. */\nfunction storeFailed(error: unknown): void {\n if (warnedStore) return;\n warnedStore = true;\n console.warn(\n \"[rsc-kit] the cache store failed, so answers are being worked out each time instead:\",\n error,\n );\n}\n\ninterface Kept {\n /** The answer. Absent when it was undefined, which JSON cannot spell. */\n v?: unknown;\n /** When it was worked out, in ms. */\n at: number;\n}\n\nfunction across<A extends unknown[], R>(\n fn: (...args: A) => Promise<R> | R,\n options: AcrossRequests<A>,\n): (...args: A) => Promise<R> {\n if (!options.key) throw new Error(\"[rsc-kit] cache() across requests needs a key\");\n if (!(options.revalidate > 0))\n throw new Error(`[rsc-kit] cache() ${options.key}: revalidate is seconds, above 0`);\n\n // Whole seconds for the store, which may round anyway; the age is checked exactly.\n const ttl = Math.ceil(options.revalidate);\n\n return async (...args: A): Promise<R> => {\n const tags =\n typeof options.tags === \"function\" ? options.tags(...args) : options.tags ?? [];\n\n const versions = tags.length\n ? await versionSource().changed(Object.fromEntries(tags.map((tag) => [tag, 0])), 0)\n : {};\n\n // Before anything is shared: an ask that joins one in flight, or is\n // answered from the store, read the same tags as the one that worked it\n // out - at the versions its key is built from.\n noteCacheTags(tags, versions);\n const key = [\n \"rsc-cache\",\n (globals[BUILD] as string | undefined) ?? \"\",\n options.key,\n JSON.stringify(args),\n tags.map((tag) => `${tag}@${versions[tag] ?? 0}`).join(\",\"),\n ].join(\"|\");\n\n // One ask per key at a time in this process: a burst of visitors to a\n // page nobody has asked for yet starts one lookup, not one each.\n const inFlight = ((globals[IN_FLIGHT] as Map<string, Promise<unknown>> | undefined) ??=\n new Map());\n const pending = inFlight.get(key);\n\n if (pending) return pending as Promise<R>;\n\n const work = (async (): Promise<R> => {\n const store = cacheStore();\n let raw: string | null | undefined;\n\n try {\n raw = await store.get(key);\n } catch (error) {\n storeFailed(error);\n }\n\n if (raw) {\n const kept = JSON.parse(raw) as Kept;\n\n if (Date.now() - kept.at < options.revalidate * 1000) return kept.v as R;\n }\n\n const value = await withoutRequest(() => fn(...args));\n const kept: Kept = { at: Date.now() };\n\n if (value !== undefined) kept.v = value;\n\n // Every caller gets what the store would give back - a miss as well as\n // a hit - so a Date is a string the first time too, not only after.\n const text = JSON.stringify(kept);\n\n try {\n await store.set(key, text, ttl);\n } catch (error) {\n storeFailed(error);\n }\n\n return (JSON.parse(text) as Kept).v as R;\n })();\n\n inFlight.set(key, work);\n\n try {\n return await work;\n } finally {\n inFlight.delete(key);\n }\n };\n}\n\n/** Whether anything is listening, for code that wants to know. */\nexport function isCaching(): boolean {\n return scope()?.getStore() !== undefined;\n}\n\n/**\n * Run one request with a memo table of its own.\n *\n * Opened by whoever owns the request. Nesting is harmless and does not create a\n * second table — an inner call reuses the one already open, so a host that\n * wraps and an engine that also wraps do not end up with two.\n */\nexport async function withCache<T>(run: () => Promise<T>): Promise<T> {\n if (!globals[SCOPE]) {\n ready ??= resolveScope().then((resolved) => {\n globals[SCOPE] ??= resolved as unknown as Scope;\n });\n\n await ready;\n }\n\n const existing = scope()!.getStore();\n\n if (existing) return await run();\n\n return await scope()!.run(new Map(), run);\n}\n\n/**\n * Forget every answer this request has cached so far.\n *\n * For the render that follows a mutation. An action's request memoises what\n * the middleware and the action read before the row was changed, and a\n * revalidation rendered in the same request would read those answers back:\n * a port renamed its agent and the sidebar the action re-rendered still\n * said the old name, while the row was updated. What was true before the\n * write is not a memo of what is true after it.\n */\nexport function forgetCached(): void {\n scope()?.getStore()?.clear();\n}\n"]}
|
package/dist/host.d.ts
CHANGED
|
@@ -216,6 +216,20 @@ export interface RscHostOptions {
|
|
|
216
216
|
* for a deployment that would rather its proxy did it.
|
|
217
217
|
*/
|
|
218
218
|
compress?: boolean;
|
|
219
|
+
/**
|
|
220
|
+
* Keep a `revalidate` page in the engine, so it is served without a render
|
|
221
|
+
* until it is `revalidate` seconds old or a tag it used changes: ISR on
|
|
222
|
+
* every host, with a CDN in front or not.
|
|
223
|
+
*
|
|
224
|
+
* On by default. Kept in the store `installCacheStore()` installed, shared
|
|
225
|
+
* by every instance, else in this process's memory - 64 MB unless
|
|
226
|
+
* `maxBytes` says otherwise. Off where `HOMEPORT_PURGE_URL` is set: there
|
|
227
|
+
* Homeport's edge keeps the page, and a copy here would sit in the memory
|
|
228
|
+
* of an app that sleeps. Off in development, where a kept page hides edits.
|
|
229
|
+
*/
|
|
230
|
+
pageCache?: boolean | {
|
|
231
|
+
maxBytes?: number;
|
|
232
|
+
};
|
|
219
233
|
}
|
|
220
234
|
/**
|
|
221
235
|
* Whether a request that changes something came from this site.
|
package/dist/host.js
CHANGED
|
@@ -19,7 +19,7 @@ import { apiKey } from "./apiPrerender.js";
|
|
|
19
19
|
import { hostPath, hostSegment, routableHost } from "./hostRouting.js";
|
|
20
20
|
import { withRevalidation } from "./revalidate.js";
|
|
21
21
|
import { withoutPayloadKey } from "./js/payloadKey.js";
|
|
22
|
-
import { cachingFor, finishCacheable, noteReadsSoFar } from "./pageCache.js";
|
|
22
|
+
import { cachingFor, createPageCache, finishCacheable, noteReadsSoFar, pageMemoryStore } from "./pageCache.js";
|
|
23
23
|
/**
|
|
24
24
|
* @internal For a host adapter that embeds the engine. An app imports this
|
|
25
25
|
* from `@rsc-kit/core/revalidate`.
|
|
@@ -69,7 +69,7 @@ import { isCrawler } from "./crawler.js";
|
|
|
69
69
|
import { inlineFlight, inlineFlightStart } from "./inlineFlight.js";
|
|
70
70
|
import { currentNotFound, withRedirect } from "./redirect.js";
|
|
71
71
|
import { compressed } from "./compress.js";
|
|
72
|
-
import { setCacheBuild, withCache } from "./cache.js";
|
|
72
|
+
import { installedCacheStore, setCacheBuild, withCache } from "./cache.js";
|
|
73
73
|
import { notFoundRowSent, renderAsNobody, takeAfterWork, withRequest, withResponseDraft, withUrl } from "./request.js";
|
|
74
74
|
import { criticalAssetsOf, linkHeader, mergeAssets } from "./earlyHints.js";
|
|
75
75
|
/**
|
|
@@ -86,6 +86,19 @@ export { matchIntercept, matchRoute, sharedDepth } from "./routing.js";
|
|
|
86
86
|
import { FLIGHT_TYPE, HEADER, HTML_TYPE, PER_CLIENT, PER_REQUEST, REVALIDATE, EDGE, VARY_ON_RSC, } from "./headers.js";
|
|
87
87
|
import { ServerAuthenticationError, ServerAuthorizationError, } from "./js/errors.js";
|
|
88
88
|
import { assertSigningSecret, serveChanges } from "./changed.js";
|
|
89
|
+
/**
|
|
90
|
+
* Whether instances live for a request or a few: a Worker's isolates, a
|
|
91
|
+
* Vercel function. Memory there is a store few requests ever find.
|
|
92
|
+
*/
|
|
93
|
+
function shortLivedInstances() {
|
|
94
|
+
const env = globalThis.process?.env;
|
|
95
|
+
return globalThis.navigator?.userAgent === "Cloudflare-Workers" || Boolean(env?.VERCEL);
|
|
96
|
+
}
|
|
97
|
+
/** Whether the app runs on Homeport, whose edge keeps pages itself. */
|
|
98
|
+
function onHomeport() {
|
|
99
|
+
const env = globalThis.process?.env;
|
|
100
|
+
return Boolean(env?.HOMEPORT_PURGE_URL);
|
|
101
|
+
}
|
|
89
102
|
/**
|
|
90
103
|
* The answer to a redirect that was decided before anything was written.
|
|
91
104
|
*
|
|
@@ -487,6 +500,10 @@ export function createRscHandler(options) {
|
|
|
487
500
|
const maxActionBody = options.maxActionBody ?? DEFAULT_MAX_ACTION_BODY;
|
|
488
501
|
const trustedOrigins = trustedOriginsOf(options.trustedOrigins);
|
|
489
502
|
const compress = options.compress ?? true;
|
|
503
|
+
// The page cache, made on the first request: the app installs its store in
|
|
504
|
+
// register(), which runs after the handler is made.
|
|
505
|
+
let pages;
|
|
506
|
+
const pageCacheOption = onHomeport() ? false : (options.pageCache ?? true);
|
|
490
507
|
// Annotated rather than inferred: the narrowing below is lost inside the
|
|
491
508
|
// closures that use it, and every one of them runs after the throw.
|
|
492
509
|
const manifest = options.manifest ?? engine.manifest?.();
|
|
@@ -692,6 +709,79 @@ export function createRscHandler(options) {
|
|
|
692
709
|
const caching = cachingFor(arrived, page?.route.revalidate);
|
|
693
710
|
if (caching && (page?.route.hostMiddleware?.length ?? 0) > 0)
|
|
694
711
|
caching.refused ??= "host-middleware";
|
|
712
|
+
if (pages === undefined) {
|
|
713
|
+
const store = pageCacheOption === false ? null : installedCacheStore();
|
|
714
|
+
if (pageCacheOption !== false && !store && shortLivedInstances()) {
|
|
715
|
+
console.warn("[rsc-kit] revalidate pages are kept in this instance's memory, which this platform starts and stops per request: few requests will find one. Install a shared store with installCacheStore() - Workers KV, Redis - or set pageCache: false.");
|
|
716
|
+
}
|
|
717
|
+
pages =
|
|
718
|
+
pageCacheOption === false
|
|
719
|
+
? null
|
|
720
|
+
: createPageCache(store ?? pageMemoryStore(typeof pageCacheOption === "object" ? pageCacheOption.maxBytes : undefined));
|
|
721
|
+
}
|
|
722
|
+
// The url as it arrived names the answer whole: the host, the query, and
|
|
723
|
+
// the ?_rsc= key that tells a document from each payload.
|
|
724
|
+
const pageKey = pages && caching && !caching.refused ? `${version ?? ""}|${arrived.url}` : null;
|
|
725
|
+
if (pages && pageKey) {
|
|
726
|
+
const hit = await pages.lookup(pageKey, arrived);
|
|
727
|
+
if (hit) {
|
|
728
|
+
if (hit.stale)
|
|
729
|
+
refreshKept(pages, pageKey, arrived, page.route);
|
|
730
|
+
return await finishKept(request, hit.response);
|
|
731
|
+
}
|
|
732
|
+
const run = await pages.once(pageKey, () => answer(request, caching, pageKey));
|
|
733
|
+
if (run.led)
|
|
734
|
+
return run.value ?? null;
|
|
735
|
+
// Another request rendered it while this one waited.
|
|
736
|
+
const after = await pages.lookup(pageKey, arrived);
|
|
737
|
+
if (after)
|
|
738
|
+
return await finishKept(request, after.response);
|
|
739
|
+
}
|
|
740
|
+
return await answer(request, caching, pageKey);
|
|
741
|
+
};
|
|
742
|
+
/**
|
|
743
|
+
* Render a kept page again behind a visitor who was just sent the stale
|
|
744
|
+
* copy - once per page at a time, in a request of its own: the visitor's
|
|
745
|
+
* may be cancelled the moment their answer is sent, and a HEAD or a
|
|
746
|
+
* conditional request is not the render to keep. A render that fails, or
|
|
747
|
+
* turns out private, keeps nothing, so the stale copy stays until its
|
|
748
|
+
* window ends.
|
|
749
|
+
*/
|
|
750
|
+
function refreshKept(pages, pageKey, arrived, route) {
|
|
751
|
+
if (pages.busy(pageKey))
|
|
752
|
+
return;
|
|
753
|
+
const headers = new Headers(arrived.headers);
|
|
754
|
+
headers.delete("If-None-Match");
|
|
755
|
+
headers.delete("If-Modified-Since");
|
|
756
|
+
const fresh = new Request(arrived.url, { method: "GET", headers });
|
|
757
|
+
const caching = cachingFor(fresh, route.revalidate);
|
|
758
|
+
if (!caching)
|
|
759
|
+
return;
|
|
760
|
+
const work = pages
|
|
761
|
+
.once(pageKey, () => answer(withoutPayloadKey(fresh), caching, pageKey))
|
|
762
|
+
.then(async (run) => {
|
|
763
|
+
await run.value?.body?.cancel();
|
|
764
|
+
})
|
|
765
|
+
.catch((error) => {
|
|
766
|
+
console.warn(`[rsc-kit] refreshing the kept copy of ${caching.path} failed; the stale copy is served until it is rendered:`, error);
|
|
767
|
+
});
|
|
768
|
+
// A Worker keeps the isolate alive only for what is handed to waitUntil.
|
|
769
|
+
const context = arrived.context;
|
|
770
|
+
if (typeof context?.waitUntil === "function")
|
|
771
|
+
context.waitUntil(work);
|
|
772
|
+
}
|
|
773
|
+
/** A page the engine kept, sent the way a rendered one would be. */
|
|
774
|
+
async function finishKept(request, response) {
|
|
775
|
+
response.headers.set("X-RSC-Kit", "cached");
|
|
776
|
+
if (identify)
|
|
777
|
+
response.headers.set("X-Powered-By", "rsc-kit");
|
|
778
|
+
if (compress) {
|
|
779
|
+
const answer = await compressed(request, response, undefined);
|
|
780
|
+
return answer;
|
|
781
|
+
}
|
|
782
|
+
return response;
|
|
783
|
+
}
|
|
784
|
+
async function answer(request, caching, pageKey) {
|
|
695
785
|
return await withRequest(request, () => withCache(() =>
|
|
696
786
|
// Open for the whole request and sealed the moment an answer exists,
|
|
697
787
|
// so middleware — which runs before any rendering — can put headers on
|
|
@@ -752,6 +842,9 @@ export function createRscHandler(options) {
|
|
|
752
842
|
if (link)
|
|
753
843
|
response.headers.set("Link", link);
|
|
754
844
|
}
|
|
845
|
+
// Kept by the engine, when finishCacheable sent it public.
|
|
846
|
+
if (pages && pageKey)
|
|
847
|
+
await pages.keep(pageKey, response);
|
|
755
848
|
// What built it. The name only, never the version - a version in
|
|
756
849
|
// every response is what a vulnerability scanner filters on - and
|
|
757
850
|
// off for a team whose policy strips every framework identifier.
|
|
@@ -784,7 +877,8 @@ export function createRscHandler(options) {
|
|
|
784
877
|
}
|
|
785
878
|
return response;
|
|
786
879
|
})));
|
|
787
|
-
}
|
|
880
|
+
}
|
|
881
|
+
;
|
|
788
882
|
async function route(request) {
|
|
789
883
|
let url = new URL(request.url);
|
|
790
884
|
// A host other than the site's own is matched with its segment in front
|