@decocms/start 0.19.0
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/.cursor/skills/deco-api-call-dedup/SKILL.md +443 -0
- package/.cursor/skills/deco-apps-architecture/SKILL.md +255 -0
- package/.cursor/skills/deco-apps-architecture/app-pattern.md +288 -0
- package/.cursor/skills/deco-apps-architecture/commerce-types.md +239 -0
- package/.cursor/skills/deco-apps-architecture/new-app-guide.md +268 -0
- package/.cursor/skills/deco-apps-architecture/scripts-codegen.md +148 -0
- package/.cursor/skills/deco-apps-architecture/shared-utils.md +181 -0
- package/.cursor/skills/deco-apps-architecture/vtex-deep-structure.md +253 -0
- package/.cursor/skills/deco-apps-architecture/website-app.md +169 -0
- package/.cursor/skills/deco-apps-vtex-porting/SKILL.md +189 -0
- package/.cursor/skills/deco-apps-vtex-porting/adaptation-patterns.md +335 -0
- package/.cursor/skills/deco-apps-vtex-porting/commerce-porting.md +155 -0
- package/.cursor/skills/deco-apps-vtex-porting/cookie-auth-patterns.md +148 -0
- package/.cursor/skills/deco-apps-vtex-porting/structure-map.md +234 -0
- package/.cursor/skills/deco-apps-vtex-porting/transform-mapping.md +99 -0
- package/.cursor/skills/deco-apps-vtex-porting/website-porting.md +194 -0
- package/.cursor/skills/deco-apps-vtex-review/SKILL.md +234 -0
- package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +270 -0
- package/.cursor/skills/deco-async-rendering-site-guide/SKILL.md +417 -0
- package/.cursor/skills/deco-cms-layout-caching/SKILL.md +293 -0
- package/.cursor/skills/deco-cms-route-config/SKILL.md +388 -0
- package/.cursor/skills/deco-core-architecture/SKILL.md +185 -0
- package/.cursor/skills/deco-core-architecture/blocks.md +196 -0
- package/.cursor/skills/deco-core-architecture/deco-vs-deco-start.md +191 -0
- package/.cursor/skills/deco-core-architecture/engine.md +220 -0
- package/.cursor/skills/deco-core-architecture/hooks-components.md +157 -0
- package/.cursor/skills/deco-core-architecture/plugins-clients.md +136 -0
- package/.cursor/skills/deco-core-architecture/runtime.md +116 -0
- package/.cursor/skills/deco-core-architecture/site-usage.md +165 -0
- package/.cursor/skills/deco-e2e-testing/SKILL.md +372 -0
- package/.cursor/skills/deco-e2e-testing/discovery.md +337 -0
- package/.cursor/skills/deco-e2e-testing/scripts/scaffold.sh +81 -0
- package/.cursor/skills/deco-e2e-testing/selectors.md +175 -0
- package/.cursor/skills/deco-e2e-testing/templates/package.json +18 -0
- package/.cursor/skills/deco-e2e-testing/templates/playwright.config.ts +65 -0
- package/.cursor/skills/deco-e2e-testing/templates/scripts/baseline.ts +279 -0
- package/.cursor/skills/deco-e2e-testing/templates/scripts/run-e2e.ts +194 -0
- package/.cursor/skills/deco-e2e-testing/templates/specs/ecommerce-flow.spec.ts +612 -0
- package/.cursor/skills/deco-e2e-testing/templates/tsconfig.json +12 -0
- package/.cursor/skills/deco-e2e-testing/templates/utils/metrics-collector.ts +918 -0
- package/.cursor/skills/deco-e2e-testing/troubleshooting.md +602 -0
- package/.cursor/skills/deco-edge-caching/SKILL.md +316 -0
- package/.cursor/skills/deco-full-analysis/SKILL.md +898 -0
- package/.cursor/skills/deco-full-analysis/checklists/asset-optimization.md +251 -0
- package/.cursor/skills/deco-full-analysis/checklists/bug-fix.md +189 -0
- package/.cursor/skills/deco-full-analysis/checklists/cache-strategy.md +144 -0
- package/.cursor/skills/deco-full-analysis/checklists/dependency-update.md +150 -0
- package/.cursor/skills/deco-full-analysis/checklists/hydration-fix.md +191 -0
- package/.cursor/skills/deco-full-analysis/checklists/image-optimization.md +180 -0
- package/.cursor/skills/deco-full-analysis/checklists/loader-optimization.md +165 -0
- package/.cursor/skills/deco-full-analysis/checklists/seo-fix.md +183 -0
- package/.cursor/skills/deco-full-analysis/checklists/site-cleanup.md +281 -0
- package/.cursor/skills/deco-full-analysis/discovery.md +548 -0
- package/.cursor/skills/deco-incident-debugging/SKILL.md +378 -0
- package/.cursor/skills/deco-incident-debugging/headless-mode.md +510 -0
- package/.cursor/skills/deco-incident-debugging/learnings-index.md +227 -0
- package/.cursor/skills/deco-incident-debugging/triage-workflow.md +312 -0
- package/.cursor/skills/deco-islands-migration/SKILL.md +251 -0
- package/.cursor/skills/deco-loader-n-plus-1-detector/SKILL.md +275 -0
- package/.cursor/skills/deco-performance-audit/SKILL.md +530 -0
- package/.cursor/skills/deco-performance-audit/tools-reference.md +428 -0
- package/.cursor/skills/deco-performance-audit/workflow.md +457 -0
- package/.cursor/skills/deco-server-functions-invoke/SKILL.md +92 -0
- package/.cursor/skills/deco-server-functions-invoke/architecture.md +166 -0
- package/.cursor/skills/deco-server-functions-invoke/generator.md +122 -0
- package/.cursor/skills/deco-server-functions-invoke/problem.md +98 -0
- package/.cursor/skills/deco-server-functions-invoke/troubleshooting.md +110 -0
- package/.cursor/skills/deco-site-deployment/SKILL.md +396 -0
- package/.cursor/skills/deco-site-memory-debugging/SKILL.md +121 -0
- package/.cursor/skills/deco-site-memory-debugging/cdp-connection.md +222 -0
- package/.cursor/skills/deco-site-memory-debugging/memory-analysis.md +362 -0
- package/.cursor/skills/deco-site-patterns/SKILL.md +124 -0
- package/.cursor/skills/deco-site-patterns/app-composition.md +337 -0
- package/.cursor/skills/deco-site-patterns/client-patterns.md +341 -0
- package/.cursor/skills/deco-site-patterns/cms-wiring.md +230 -0
- package/.cursor/skills/deco-site-patterns/section-patterns.md +340 -0
- package/.cursor/skills/deco-site-scaling-tuning/SKILL.md +240 -0
- package/.cursor/skills/deco-site-scaling-tuning/analysis-scripts.md +267 -0
- package/.cursor/skills/deco-start-architecture/SKILL.md +218 -0
- package/.cursor/skills/deco-start-architecture/admin-protocol.md +156 -0
- package/.cursor/skills/deco-start-architecture/cms-resolution.md +201 -0
- package/.cursor/skills/deco-start-architecture/code-quality.md +158 -0
- package/.cursor/skills/deco-start-architecture/gap-analysis.md +129 -0
- package/.cursor/skills/deco-start-architecture/sdk-utilities.md +197 -0
- package/.cursor/skills/deco-start-architecture/worker-entry-caching.md +154 -0
- package/.cursor/skills/deco-startup-analysis/SKILL.md +248 -0
- package/.cursor/skills/deco-storefront-test-checklist/SKILL.md +369 -0
- package/.cursor/skills/deco-tanstack-hydration-fixes/SKILL.md +468 -0
- package/.cursor/skills/deco-tanstack-navigation/SKILL.md +681 -0
- package/.cursor/skills/deco-tanstack-search/SKILL.md +411 -0
- package/.cursor/skills/deco-tanstack-storefront-patterns/SKILL.md +1013 -0
- package/.cursor/skills/deco-to-tanstack-migration/SKILL.md +518 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/codemod-commands.md +174 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/commerce/README.md +78 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/deco-framework/README.md +128 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/gotchas.md +719 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/imports/README.md +70 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +154 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/signals/README.md +220 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/vite-config/README.md +78 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/package-json.md +55 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/root-route.md +110 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/router.md +96 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/setup-ts.md +167 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/vite-config.md +122 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/worker-entry.md +67 -0
- package/.cursor/skills/deco-typescript-fixes/SKILL.md +178 -0
- package/.cursor/skills/deco-typescript-fixes/common-fixes.md +330 -0
- package/.cursor/skills/deco-typescript-fixes/strategy.md +148 -0
- package/.cursor/skills/deco-variant-selection-perf/SKILL.md +272 -0
- package/.cursor/skills/deco-vtex-fetch-cache/SKILL.md +225 -0
- package/.cursor/skills/find-skills/SKILL.md +133 -0
- package/.cursor/skills/incident-report/SKILL.md +179 -0
- package/.cursor/skills/incident-report/references/5-whys.md +75 -0
- package/.cursor/skills/incident-report/templates/client-report.md +187 -0
- package/.cursor/skills/incident-report/templates/internal-report.md +206 -0
- package/.cursor/skills/template-skill/SKILL.md +38 -0
- package/.github/workflows/release.yml +32 -0
- package/.releaserc.json +25 -0
- package/CLAUDE.md +135 -0
- package/GAP_ANALYSIS.md +224 -0
- package/GAP_ANALYSIS_V2.md +1013 -0
- package/biome.json +39 -0
- package/knip.json +5 -0
- package/package.json +87 -0
- package/scripts/generate-blocks.ts +69 -0
- package/scripts/generate-invoke.ts +378 -0
- package/scripts/generate-schema.ts +657 -0
- package/src/admin/cors.ts +29 -0
- package/src/admin/decofile.ts +72 -0
- package/src/admin/index.ts +24 -0
- package/src/admin/invoke.ts +163 -0
- package/src/admin/liveControls.ts +29 -0
- package/src/admin/meta.ts +70 -0
- package/src/admin/render.ts +205 -0
- package/src/admin/schema.ts +686 -0
- package/src/admin/setup.ts +44 -0
- package/src/cms/index.ts +59 -0
- package/src/cms/loader.ts +180 -0
- package/src/cms/registry.ts +162 -0
- package/src/cms/resolve.ts +1005 -0
- package/src/cms/sectionLoaders.ts +294 -0
- package/src/hooks/DecoPageRenderer.tsx +444 -0
- package/src/hooks/LazySection.tsx +109 -0
- package/src/hooks/LiveControls.tsx +108 -0
- package/src/hooks/SectionErrorFallback.tsx +85 -0
- package/src/hooks/index.ts +8 -0
- package/src/index.ts +5 -0
- package/src/matchers/builtins.ts +184 -0
- package/src/matchers/posthog.ts +154 -0
- package/src/middleware/decoState.ts +55 -0
- package/src/middleware/healthMetrics.ts +131 -0
- package/src/middleware/index.ts +80 -0
- package/src/middleware/liveness.ts +21 -0
- package/src/middleware/observability.ts +205 -0
- package/src/routes/adminRoutes.ts +83 -0
- package/src/routes/cmsRoute.ts +302 -0
- package/src/routes/components.tsx +34 -0
- package/src/routes/index.ts +15 -0
- package/src/sdk/analytics.ts +72 -0
- package/src/sdk/cacheHeaders.ts +268 -0
- package/src/sdk/cachedLoader.ts +206 -0
- package/src/sdk/clx.ts +3 -0
- package/src/sdk/cookie.ts +39 -0
- package/src/sdk/createInvoke.ts +57 -0
- package/src/sdk/csp.ts +59 -0
- package/src/sdk/env.ts +27 -0
- package/src/sdk/index.ts +63 -0
- package/src/sdk/instrumentedFetch.ts +137 -0
- package/src/sdk/invoke.ts +133 -0
- package/src/sdk/mergeCacheControl.ts +150 -0
- package/src/sdk/redirects.ts +217 -0
- package/src/sdk/requestContext.ts +184 -0
- package/src/sdk/serverTimings.ts +68 -0
- package/src/sdk/signal.ts +41 -0
- package/src/sdk/sitemap.ts +143 -0
- package/src/sdk/urlUtils.ts +117 -0
- package/src/sdk/useDevice.ts +82 -0
- package/src/sdk/useId.ts +7 -0
- package/src/sdk/useScript.ts +101 -0
- package/src/sdk/workerEntry.ts +703 -0
- package/src/sdk/wrapCaughtErrors.ts +107 -0
- package/src/types/index.ts +39 -0
- package/src/types/widgets.ts +13 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section Loader Registry
|
|
3
|
+
*
|
|
4
|
+
* Section loaders enrich resolved CMS props with server-side data
|
|
5
|
+
* (e.g., price simulations, device detection) that can't be done
|
|
6
|
+
* at the CMS resolution level.
|
|
7
|
+
*
|
|
8
|
+
* This runs AFTER resolveDecoPage and BEFORE React rendering,
|
|
9
|
+
* inside the TanStack Start server function.
|
|
10
|
+
*/
|
|
11
|
+
import type { ResolvedSection } from "./resolve";
|
|
12
|
+
|
|
13
|
+
export type SectionLoaderFn = (
|
|
14
|
+
props: Record<string, unknown>,
|
|
15
|
+
req: Request,
|
|
16
|
+
) => Promise<Record<string, unknown>> | Record<string, unknown>;
|
|
17
|
+
|
|
18
|
+
// globalThis-backed: server function split modules need access
|
|
19
|
+
const G = globalThis as any;
|
|
20
|
+
if (!G.__deco) G.__deco = {};
|
|
21
|
+
if (!G.__deco.sectionLoaderRegistry) G.__deco.sectionLoaderRegistry = new Map();
|
|
22
|
+
if (!G.__deco.layoutSections) G.__deco.layoutSections = new Set();
|
|
23
|
+
if (!G.__deco.cacheableSections) G.__deco.cacheableSections = new Map();
|
|
24
|
+
|
|
25
|
+
const loaderRegistry: Map<string, SectionLoaderFn> = G.__deco.sectionLoaderRegistry;
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Cacheable section loaders — SWR cache for section loader results
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
interface CacheableSectionConfig {
|
|
32
|
+
maxAge: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const cacheableSections: Map<string, CacheableSectionConfig> = G.__deco.cacheableSections;
|
|
36
|
+
|
|
37
|
+
interface SectionCacheEntry {
|
|
38
|
+
section: ResolvedSection;
|
|
39
|
+
createdAt: number;
|
|
40
|
+
refreshing: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const sectionLoaderCache = new Map<string, SectionCacheEntry>();
|
|
44
|
+
const sectionLoaderInflight = new Map<string, Promise<ResolvedSection>>();
|
|
45
|
+
const MAX_SECTION_CACHE_ENTRIES = 200;
|
|
46
|
+
|
|
47
|
+
function evictSectionCacheIfNeeded() {
|
|
48
|
+
if (sectionLoaderCache.size <= MAX_SECTION_CACHE_ENTRIES) return;
|
|
49
|
+
const oldest = [...sectionLoaderCache.entries()].sort((a, b) => a[1].createdAt - b[1].createdAt);
|
|
50
|
+
const toDelete = oldest.slice(0, sectionLoaderCache.size - MAX_SECTION_CACHE_ENTRIES);
|
|
51
|
+
for (const [key] of toDelete) sectionLoaderCache.delete(key);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function djb2Hash(str: string): number {
|
|
55
|
+
let hash = 5381;
|
|
56
|
+
for (let i = 0; i < str.length; i++) {
|
|
57
|
+
hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0;
|
|
58
|
+
}
|
|
59
|
+
return hash >>> 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function sectionCacheKey(component: string, props: Record<string, unknown>): string {
|
|
63
|
+
return `${component}::${djb2Hash(JSON.stringify(props))}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Register section components whose loader results should be cached.
|
|
68
|
+
* Uses SWR (stale-while-revalidate) semantics: stale results are returned
|
|
69
|
+
* immediately while a background refresh runs.
|
|
70
|
+
*
|
|
71
|
+
* Works for both eager sections (speeds up SSR) and deferred sections
|
|
72
|
+
* (speeds up individual fetch on scroll).
|
|
73
|
+
*/
|
|
74
|
+
export function registerCacheableSections(configs: Record<string, CacheableSectionConfig>): void {
|
|
75
|
+
for (const [key, config] of Object.entries(configs)) {
|
|
76
|
+
cacheableSections.set(key, config);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function runCacheableSectionLoader(
|
|
81
|
+
section: ResolvedSection,
|
|
82
|
+
loader: SectionLoaderFn,
|
|
83
|
+
request: Request,
|
|
84
|
+
config: CacheableSectionConfig,
|
|
85
|
+
): Promise<ResolvedSection> {
|
|
86
|
+
const key = sectionCacheKey(section.component, section.props as Record<string, unknown>);
|
|
87
|
+
|
|
88
|
+
const existing = sectionLoaderInflight.get(key);
|
|
89
|
+
if (existing) return existing;
|
|
90
|
+
|
|
91
|
+
const entry = sectionLoaderCache.get(key);
|
|
92
|
+
const now = Date.now();
|
|
93
|
+
const isStale = entry ? now - entry.createdAt > config.maxAge : true;
|
|
94
|
+
|
|
95
|
+
if (entry && !isStale) {
|
|
96
|
+
return Promise.resolve(entry.section);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (entry && isStale && !entry.refreshing) {
|
|
100
|
+
entry.refreshing = true;
|
|
101
|
+
void Promise.resolve(loader(section.props as Record<string, unknown>, request))
|
|
102
|
+
.then((enrichedProps) => {
|
|
103
|
+
const enriched = { ...section, props: enrichedProps };
|
|
104
|
+
sectionLoaderCache.set(key, {
|
|
105
|
+
section: enriched,
|
|
106
|
+
createdAt: Date.now(),
|
|
107
|
+
refreshing: false,
|
|
108
|
+
});
|
|
109
|
+
})
|
|
110
|
+
.catch(() => {
|
|
111
|
+
entry.refreshing = false;
|
|
112
|
+
});
|
|
113
|
+
return Promise.resolve(entry.section);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (entry) return Promise.resolve(entry.section);
|
|
117
|
+
|
|
118
|
+
const promise = (async () => {
|
|
119
|
+
const enrichedProps = await loader(section.props as Record<string, unknown>, request);
|
|
120
|
+
const enriched = { ...section, props: enrichedProps };
|
|
121
|
+
sectionLoaderCache.set(key, {
|
|
122
|
+
section: enriched,
|
|
123
|
+
createdAt: Date.now(),
|
|
124
|
+
refreshing: false,
|
|
125
|
+
});
|
|
126
|
+
evictSectionCacheIfNeeded();
|
|
127
|
+
return enriched;
|
|
128
|
+
})();
|
|
129
|
+
|
|
130
|
+
sectionLoaderInflight.set(key, promise);
|
|
131
|
+
promise.finally(() => sectionLoaderInflight.delete(key));
|
|
132
|
+
return promise;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Register a server-side loader for a specific section.
|
|
137
|
+
* The loader receives the CMS-resolved props and a Request,
|
|
138
|
+
* and returns enriched props.
|
|
139
|
+
*/
|
|
140
|
+
export function registerSectionLoader(sectionKey: string, loader: SectionLoaderFn): void {
|
|
141
|
+
loaderRegistry.set(sectionKey, loader);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Register multiple section loaders at once.
|
|
146
|
+
*/
|
|
147
|
+
export function registerSectionLoaders(loaders: Record<string, SectionLoaderFn>): void {
|
|
148
|
+
for (const [key, loader] of Object.entries(loaders)) {
|
|
149
|
+
loaderRegistry.set(key, loader);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Layout section cache — sections whose output doesn't change per-page
|
|
155
|
+
// (Header, Footer, Theme, etc.) are cached server-side so they aren't
|
|
156
|
+
// re-resolved and re-enriched on every navigation.
|
|
157
|
+
//
|
|
158
|
+
// Uses in-flight deduplication: if two requests try to resolve the same
|
|
159
|
+
// layout section concurrently, the second shares the first's Promise.
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
const layoutSections: Set<string> = G.__deco.layoutSections;
|
|
163
|
+
|
|
164
|
+
const LAYOUT_CACHE_TTL = 5 * 60_000; // 5 minutes
|
|
165
|
+
|
|
166
|
+
interface CachedSection {
|
|
167
|
+
section: ResolvedSection;
|
|
168
|
+
expiresAt: number;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const layoutCache = new Map<string, CachedSection>();
|
|
172
|
+
const layoutInflight = new Map<string, Promise<ResolvedSection>>();
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Register section keys that should be cached as layout sections.
|
|
176
|
+
* Layout sections (Header, Footer, etc.) are cached server-side
|
|
177
|
+
* for LAYOUT_CACHE_TTL to avoid redundant enrichment on every navigation.
|
|
178
|
+
*/
|
|
179
|
+
export function registerLayoutSections(keys: string[]): void {
|
|
180
|
+
for (const key of keys) {
|
|
181
|
+
layoutSections.add(key);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Check if a section key is registered as a layout section. */
|
|
186
|
+
export function isLayoutSection(key: string): boolean {
|
|
187
|
+
return layoutSections.has(key);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function getCachedLayout(component: string): ResolvedSection | null {
|
|
191
|
+
const entry = layoutCache.get(component);
|
|
192
|
+
if (!entry) return null;
|
|
193
|
+
if (Date.now() > entry.expiresAt) {
|
|
194
|
+
layoutCache.delete(component);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
return entry.section;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function setCachedLayout(component: string, section: ResolvedSection): void {
|
|
201
|
+
layoutCache.set(component, {
|
|
202
|
+
section,
|
|
203
|
+
expiresAt: Date.now() + LAYOUT_CACHE_TTL,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Run a layout section's loader with in-flight dedup + TTL cache.
|
|
209
|
+
*/
|
|
210
|
+
function resolveLayoutSection(
|
|
211
|
+
section: ResolvedSection,
|
|
212
|
+
loader: SectionLoaderFn,
|
|
213
|
+
request: Request,
|
|
214
|
+
): Promise<ResolvedSection> {
|
|
215
|
+
const key = section.component;
|
|
216
|
+
|
|
217
|
+
const cached = getCachedLayout(key);
|
|
218
|
+
if (cached) return Promise.resolve(cached);
|
|
219
|
+
|
|
220
|
+
const existing = layoutInflight.get(key);
|
|
221
|
+
if (existing) return existing;
|
|
222
|
+
|
|
223
|
+
const promise = (async () => {
|
|
224
|
+
const enrichedProps = await loader(section.props as Record<string, unknown>, request);
|
|
225
|
+
const enriched = { ...section, props: enrichedProps };
|
|
226
|
+
setCachedLayout(key, enriched);
|
|
227
|
+
return enriched;
|
|
228
|
+
})();
|
|
229
|
+
|
|
230
|
+
layoutInflight.set(key, promise);
|
|
231
|
+
promise.finally(() => layoutInflight.delete(key));
|
|
232
|
+
|
|
233
|
+
return promise;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Run registered section loaders against resolved sections.
|
|
238
|
+
* Sections without a registered loader pass through unchanged.
|
|
239
|
+
*
|
|
240
|
+
* Layout sections use in-flight dedup + TTL cache to avoid
|
|
241
|
+
* redundant enrichment across concurrent and sequential requests.
|
|
242
|
+
*
|
|
243
|
+
* Runs all loaders in parallel for performance.
|
|
244
|
+
*/
|
|
245
|
+
export async function runSectionLoaders(
|
|
246
|
+
sections: ResolvedSection[],
|
|
247
|
+
request: Request,
|
|
248
|
+
): Promise<ResolvedSection[]> {
|
|
249
|
+
return Promise.all(sections.map((section) => runSingleSectionLoader(section, request)));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Run a single section's registered loader.
|
|
254
|
+
* Used by both `runSectionLoaders` (batch) and `loadDeferredSection` (individual).
|
|
255
|
+
*
|
|
256
|
+
* Respects three cache tiers:
|
|
257
|
+
* 1. Layout sections (Header/Footer) — 5min TTL + in-flight dedup
|
|
258
|
+
* 2. Cacheable sections (ProductShelf, FAQ) — SWR with configurable maxAge
|
|
259
|
+
* 3. Regular sections — no cache, always fresh
|
|
260
|
+
*/
|
|
261
|
+
export async function runSingleSectionLoader(
|
|
262
|
+
section: ResolvedSection,
|
|
263
|
+
request: Request,
|
|
264
|
+
): Promise<ResolvedSection> {
|
|
265
|
+
const loader = loaderRegistry.get(section.component);
|
|
266
|
+
if (!loader) return section;
|
|
267
|
+
|
|
268
|
+
if (layoutSections.has(section.component)) {
|
|
269
|
+
try {
|
|
270
|
+
return await resolveLayoutSection(section, loader, request);
|
|
271
|
+
} catch (error) {
|
|
272
|
+
console.error(`[SectionLoader] Error in layout "${section.component}":`, error);
|
|
273
|
+
return section;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const cacheConfig = cacheableSections.get(section.component);
|
|
278
|
+
if (cacheConfig) {
|
|
279
|
+
try {
|
|
280
|
+
return await runCacheableSectionLoader(section, loader, request, cacheConfig);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
console.error(`[SectionLoader] Error in cacheable "${section.component}":`, error);
|
|
283
|
+
return section;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
const enrichedProps = await loader(section.props as Record<string, unknown>, request);
|
|
289
|
+
return { ...section, props: enrichedProps };
|
|
290
|
+
} catch (error) {
|
|
291
|
+
console.error(`[SectionLoader] Error in "${section.component}":`, error);
|
|
292
|
+
return section;
|
|
293
|
+
}
|
|
294
|
+
}
|