Skip to content

Commit 002d0ac

Browse files
authored
perf: getSiteSetting() piggybacks on cached getSiteSettings() (emdash-cms#664)
* perf: getSiteSetting(key) piggybacks on cached getSiteSettings() Adds peekRequestCache() to request-cache.ts so a narrower query can opportunistically satisfy itself from a broader one already loaded. getSiteSetting(key) now first peeks for the "siteSettings" batch result (populated by getSiteSettings()) and reads the requested key from there if present. Falls back to a per-key cached query if the batch hasn't been loaded. Net effect on the blog-demo layout: getSiteSettings() is already called by Base.astro, so the EmDashHead getSiteSetting("seo") call from PR emdash-cms#613 now costs zero extra queries instead of one primary-routed round-trip per render. Query-count snapshot is unchanged — the +1 queries PR emdash-cms#663 couldn't dedupe are now gone. * add changeset
1 parent 38d637b commit 002d0ac

4 files changed

Lines changed: 53 additions & 10 deletions

File tree

.changeset/peek-site-setting.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"emdash": patch
3+
---
4+
5+
`getSiteSetting(key)` now transparently piggybacks on `getSiteSettings()` when the batch has already been loaded in the current request. If a parent template has called `getSiteSettings()` (which is request-cached), a later `getSiteSetting("seo")` — from `EmDashHead`, a plugin, or user code — reads the key from that cached result instead of firing its own round-trip. Falls back to a per-key cached query when nothing has been primed.
6+
7+
Exposes `peekRequestCache(key)` for internal use by other helpers that want the same "read from a broader cached query if available" pattern.
8+
9+
On the blog-demo fixture: the SEO call added in PR #613 now costs zero extra queries per page (it reads from the Base layout's existing `getSiteSettings()` result).

packages/core/src/components/EmDashHead.astro

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,16 @@ let metadataHtml = "";
3737
let fragmentsHtml = "";
3838
3939
if (runtime) {
40-
// Run independent async loads in parallel: site SEO settings (for search
41-
// engine verification meta tags) and plugin page-metadata contributions.
42-
// Plugin contributions come BEFORE site/base in the array, so
43-
// resolvePageMetadata's first-wins dedup lets plugins override defaults.
40+
// Run independent async loads in parallel: site SEO settings (for
41+
// search engine verification meta tags) and plugin page-metadata
42+
// contributions. Plugin contributions come BEFORE site/base in the
43+
// array, so resolvePageMetadata's first-wins dedup lets plugins
44+
// override defaults.
45+
//
46+
// `getSiteSetting("seo")` is request-cached and — crucially — reads
47+
// from `getSiteSettings()`'s cached batch when a parent template has
48+
// already called it. So this is either a single-key query or free,
49+
// not a second round-trip.
4450
const [seoSettings, pluginContributions, fragments] = await Promise.all([
4551
getSiteSetting("seo"),
4652
runtime.collectPageMetadata(page),

packages/core/src/request-cache.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,24 @@ export function requestCached<T>(key: string, fn: () => Promise<T>): Promise<T>
5959
return promise;
6060
}
6161

62+
/**
63+
* Look up an entry in the request-scoped cache without inserting one.
64+
*
65+
* Returns the in-flight or resolved promise if the key exists in the
66+
* current request, otherwise `undefined`. Callers can use this to
67+
* opportunistically satisfy a narrower query (e.g. `getSiteSetting("seo")`)
68+
* from a broader one (`getSiteSettings()`) that's already been loaded
69+
* by a parent template — avoiding a redundant round-trip.
70+
*
71+
* No-ops outside a request context.
72+
*/
73+
export function peekRequestCache<T>(key: string): Promise<T> | undefined {
74+
const ctx = getRequestContext();
75+
if (!ctx) return undefined;
76+
const cache = store.get(ctx);
77+
return cache?.get(key) as Promise<T> | undefined;
78+
}
79+
6280
/**
6381
* Pre-populate the request-scoped cache with a resolved value.
6482
*

packages/core/src/settings/index.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { MediaRepository } from "../database/repositories/media.js";
1111
import { OptionsRepository } from "../database/repositories/options.js";
1212
import type { Database } from "../database/types.js";
1313
import { getDb } from "../loader.js";
14-
import { requestCached } from "../request-cache.js";
14+
import { peekRequestCache, requestCached } from "../request-cache.js";
1515
import type { Storage } from "../storage/types.js";
1616
import type { SiteSettings, SiteSettingKey, MediaReference } from "./types.js";
1717

@@ -73,13 +73,23 @@ async function resolveMediaReference(
7373
* console.log(logo?.url); // Resolved URL
7474
* ```
7575
*/
76-
export function getSiteSetting<K extends SiteSettingKey>(
76+
export async function getSiteSetting<K extends SiteSettingKey>(
7777
key: K,
7878
): Promise<SiteSettings[K] | undefined> {
79-
// Cache per-key within a request. Without this, templates that pull
80-
// several settings (and layout components that ask for logo/favicon/
81-
// title separately) each fire an options-table query — which is a
82-
// real latency hit on regions far from the D1 primary (APS, APE).
79+
// If `getSiteSettings()` has already been called in this request,
80+
// read from that (request-cached) batch rather than firing a second
81+
// options-table query. Common layout: a Base template pulls the
82+
// whole settings object up-front, then `EmDashHead` or a plugin
83+
// asks for one key — no reason the singular call should round-trip
84+
// again.
85+
const primed = peekRequestCache<Partial<SiteSettings>>("siteSettings");
86+
if (primed) {
87+
const settings = await primed;
88+
return settings[key];
89+
}
90+
91+
// Otherwise cache per-key. Templates that pull several settings
92+
// independently still share the in-flight query for each one.
8393
return requestCached(`siteSetting:${key}`, async () => {
8494
const db = await getDb();
8595
return getSiteSettingWithDb(key, db);

0 commit comments

Comments
 (0)