@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.
Files changed (185) hide show
  1. package/.cursor/skills/deco-api-call-dedup/SKILL.md +443 -0
  2. package/.cursor/skills/deco-apps-architecture/SKILL.md +255 -0
  3. package/.cursor/skills/deco-apps-architecture/app-pattern.md +288 -0
  4. package/.cursor/skills/deco-apps-architecture/commerce-types.md +239 -0
  5. package/.cursor/skills/deco-apps-architecture/new-app-guide.md +268 -0
  6. package/.cursor/skills/deco-apps-architecture/scripts-codegen.md +148 -0
  7. package/.cursor/skills/deco-apps-architecture/shared-utils.md +181 -0
  8. package/.cursor/skills/deco-apps-architecture/vtex-deep-structure.md +253 -0
  9. package/.cursor/skills/deco-apps-architecture/website-app.md +169 -0
  10. package/.cursor/skills/deco-apps-vtex-porting/SKILL.md +189 -0
  11. package/.cursor/skills/deco-apps-vtex-porting/adaptation-patterns.md +335 -0
  12. package/.cursor/skills/deco-apps-vtex-porting/commerce-porting.md +155 -0
  13. package/.cursor/skills/deco-apps-vtex-porting/cookie-auth-patterns.md +148 -0
  14. package/.cursor/skills/deco-apps-vtex-porting/structure-map.md +234 -0
  15. package/.cursor/skills/deco-apps-vtex-porting/transform-mapping.md +99 -0
  16. package/.cursor/skills/deco-apps-vtex-porting/website-porting.md +194 -0
  17. package/.cursor/skills/deco-apps-vtex-review/SKILL.md +234 -0
  18. package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +270 -0
  19. package/.cursor/skills/deco-async-rendering-site-guide/SKILL.md +417 -0
  20. package/.cursor/skills/deco-cms-layout-caching/SKILL.md +293 -0
  21. package/.cursor/skills/deco-cms-route-config/SKILL.md +388 -0
  22. package/.cursor/skills/deco-core-architecture/SKILL.md +185 -0
  23. package/.cursor/skills/deco-core-architecture/blocks.md +196 -0
  24. package/.cursor/skills/deco-core-architecture/deco-vs-deco-start.md +191 -0
  25. package/.cursor/skills/deco-core-architecture/engine.md +220 -0
  26. package/.cursor/skills/deco-core-architecture/hooks-components.md +157 -0
  27. package/.cursor/skills/deco-core-architecture/plugins-clients.md +136 -0
  28. package/.cursor/skills/deco-core-architecture/runtime.md +116 -0
  29. package/.cursor/skills/deco-core-architecture/site-usage.md +165 -0
  30. package/.cursor/skills/deco-e2e-testing/SKILL.md +372 -0
  31. package/.cursor/skills/deco-e2e-testing/discovery.md +337 -0
  32. package/.cursor/skills/deco-e2e-testing/scripts/scaffold.sh +81 -0
  33. package/.cursor/skills/deco-e2e-testing/selectors.md +175 -0
  34. package/.cursor/skills/deco-e2e-testing/templates/package.json +18 -0
  35. package/.cursor/skills/deco-e2e-testing/templates/playwright.config.ts +65 -0
  36. package/.cursor/skills/deco-e2e-testing/templates/scripts/baseline.ts +279 -0
  37. package/.cursor/skills/deco-e2e-testing/templates/scripts/run-e2e.ts +194 -0
  38. package/.cursor/skills/deco-e2e-testing/templates/specs/ecommerce-flow.spec.ts +612 -0
  39. package/.cursor/skills/deco-e2e-testing/templates/tsconfig.json +12 -0
  40. package/.cursor/skills/deco-e2e-testing/templates/utils/metrics-collector.ts +918 -0
  41. package/.cursor/skills/deco-e2e-testing/troubleshooting.md +602 -0
  42. package/.cursor/skills/deco-edge-caching/SKILL.md +316 -0
  43. package/.cursor/skills/deco-full-analysis/SKILL.md +898 -0
  44. package/.cursor/skills/deco-full-analysis/checklists/asset-optimization.md +251 -0
  45. package/.cursor/skills/deco-full-analysis/checklists/bug-fix.md +189 -0
  46. package/.cursor/skills/deco-full-analysis/checklists/cache-strategy.md +144 -0
  47. package/.cursor/skills/deco-full-analysis/checklists/dependency-update.md +150 -0
  48. package/.cursor/skills/deco-full-analysis/checklists/hydration-fix.md +191 -0
  49. package/.cursor/skills/deco-full-analysis/checklists/image-optimization.md +180 -0
  50. package/.cursor/skills/deco-full-analysis/checklists/loader-optimization.md +165 -0
  51. package/.cursor/skills/deco-full-analysis/checklists/seo-fix.md +183 -0
  52. package/.cursor/skills/deco-full-analysis/checklists/site-cleanup.md +281 -0
  53. package/.cursor/skills/deco-full-analysis/discovery.md +548 -0
  54. package/.cursor/skills/deco-incident-debugging/SKILL.md +378 -0
  55. package/.cursor/skills/deco-incident-debugging/headless-mode.md +510 -0
  56. package/.cursor/skills/deco-incident-debugging/learnings-index.md +227 -0
  57. package/.cursor/skills/deco-incident-debugging/triage-workflow.md +312 -0
  58. package/.cursor/skills/deco-islands-migration/SKILL.md +251 -0
  59. package/.cursor/skills/deco-loader-n-plus-1-detector/SKILL.md +275 -0
  60. package/.cursor/skills/deco-performance-audit/SKILL.md +530 -0
  61. package/.cursor/skills/deco-performance-audit/tools-reference.md +428 -0
  62. package/.cursor/skills/deco-performance-audit/workflow.md +457 -0
  63. package/.cursor/skills/deco-server-functions-invoke/SKILL.md +92 -0
  64. package/.cursor/skills/deco-server-functions-invoke/architecture.md +166 -0
  65. package/.cursor/skills/deco-server-functions-invoke/generator.md +122 -0
  66. package/.cursor/skills/deco-server-functions-invoke/problem.md +98 -0
  67. package/.cursor/skills/deco-server-functions-invoke/troubleshooting.md +110 -0
  68. package/.cursor/skills/deco-site-deployment/SKILL.md +396 -0
  69. package/.cursor/skills/deco-site-memory-debugging/SKILL.md +121 -0
  70. package/.cursor/skills/deco-site-memory-debugging/cdp-connection.md +222 -0
  71. package/.cursor/skills/deco-site-memory-debugging/memory-analysis.md +362 -0
  72. package/.cursor/skills/deco-site-patterns/SKILL.md +124 -0
  73. package/.cursor/skills/deco-site-patterns/app-composition.md +337 -0
  74. package/.cursor/skills/deco-site-patterns/client-patterns.md +341 -0
  75. package/.cursor/skills/deco-site-patterns/cms-wiring.md +230 -0
  76. package/.cursor/skills/deco-site-patterns/section-patterns.md +340 -0
  77. package/.cursor/skills/deco-site-scaling-tuning/SKILL.md +240 -0
  78. package/.cursor/skills/deco-site-scaling-tuning/analysis-scripts.md +267 -0
  79. package/.cursor/skills/deco-start-architecture/SKILL.md +218 -0
  80. package/.cursor/skills/deco-start-architecture/admin-protocol.md +156 -0
  81. package/.cursor/skills/deco-start-architecture/cms-resolution.md +201 -0
  82. package/.cursor/skills/deco-start-architecture/code-quality.md +158 -0
  83. package/.cursor/skills/deco-start-architecture/gap-analysis.md +129 -0
  84. package/.cursor/skills/deco-start-architecture/sdk-utilities.md +197 -0
  85. package/.cursor/skills/deco-start-architecture/worker-entry-caching.md +154 -0
  86. package/.cursor/skills/deco-startup-analysis/SKILL.md +248 -0
  87. package/.cursor/skills/deco-storefront-test-checklist/SKILL.md +369 -0
  88. package/.cursor/skills/deco-tanstack-hydration-fixes/SKILL.md +468 -0
  89. package/.cursor/skills/deco-tanstack-navigation/SKILL.md +681 -0
  90. package/.cursor/skills/deco-tanstack-search/SKILL.md +411 -0
  91. package/.cursor/skills/deco-tanstack-storefront-patterns/SKILL.md +1013 -0
  92. package/.cursor/skills/deco-to-tanstack-migration/SKILL.md +518 -0
  93. package/.cursor/skills/deco-to-tanstack-migration/references/codemod-commands.md +174 -0
  94. package/.cursor/skills/deco-to-tanstack-migration/references/commerce/README.md +78 -0
  95. package/.cursor/skills/deco-to-tanstack-migration/references/deco-framework/README.md +128 -0
  96. package/.cursor/skills/deco-to-tanstack-migration/references/gotchas.md +719 -0
  97. package/.cursor/skills/deco-to-tanstack-migration/references/imports/README.md +70 -0
  98. package/.cursor/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +154 -0
  99. package/.cursor/skills/deco-to-tanstack-migration/references/signals/README.md +220 -0
  100. package/.cursor/skills/deco-to-tanstack-migration/references/vite-config/README.md +78 -0
  101. package/.cursor/skills/deco-to-tanstack-migration/templates/package-json.md +55 -0
  102. package/.cursor/skills/deco-to-tanstack-migration/templates/root-route.md +110 -0
  103. package/.cursor/skills/deco-to-tanstack-migration/templates/router.md +96 -0
  104. package/.cursor/skills/deco-to-tanstack-migration/templates/setup-ts.md +167 -0
  105. package/.cursor/skills/deco-to-tanstack-migration/templates/vite-config.md +122 -0
  106. package/.cursor/skills/deco-to-tanstack-migration/templates/worker-entry.md +67 -0
  107. package/.cursor/skills/deco-typescript-fixes/SKILL.md +178 -0
  108. package/.cursor/skills/deco-typescript-fixes/common-fixes.md +330 -0
  109. package/.cursor/skills/deco-typescript-fixes/strategy.md +148 -0
  110. package/.cursor/skills/deco-variant-selection-perf/SKILL.md +272 -0
  111. package/.cursor/skills/deco-vtex-fetch-cache/SKILL.md +225 -0
  112. package/.cursor/skills/find-skills/SKILL.md +133 -0
  113. package/.cursor/skills/incident-report/SKILL.md +179 -0
  114. package/.cursor/skills/incident-report/references/5-whys.md +75 -0
  115. package/.cursor/skills/incident-report/templates/client-report.md +187 -0
  116. package/.cursor/skills/incident-report/templates/internal-report.md +206 -0
  117. package/.cursor/skills/template-skill/SKILL.md +38 -0
  118. package/.github/workflows/release.yml +32 -0
  119. package/.releaserc.json +25 -0
  120. package/CLAUDE.md +135 -0
  121. package/GAP_ANALYSIS.md +224 -0
  122. package/GAP_ANALYSIS_V2.md +1013 -0
  123. package/biome.json +39 -0
  124. package/knip.json +5 -0
  125. package/package.json +87 -0
  126. package/scripts/generate-blocks.ts +69 -0
  127. package/scripts/generate-invoke.ts +378 -0
  128. package/scripts/generate-schema.ts +657 -0
  129. package/src/admin/cors.ts +29 -0
  130. package/src/admin/decofile.ts +72 -0
  131. package/src/admin/index.ts +24 -0
  132. package/src/admin/invoke.ts +163 -0
  133. package/src/admin/liveControls.ts +29 -0
  134. package/src/admin/meta.ts +70 -0
  135. package/src/admin/render.ts +205 -0
  136. package/src/admin/schema.ts +686 -0
  137. package/src/admin/setup.ts +44 -0
  138. package/src/cms/index.ts +59 -0
  139. package/src/cms/loader.ts +180 -0
  140. package/src/cms/registry.ts +162 -0
  141. package/src/cms/resolve.ts +1005 -0
  142. package/src/cms/sectionLoaders.ts +294 -0
  143. package/src/hooks/DecoPageRenderer.tsx +444 -0
  144. package/src/hooks/LazySection.tsx +109 -0
  145. package/src/hooks/LiveControls.tsx +108 -0
  146. package/src/hooks/SectionErrorFallback.tsx +85 -0
  147. package/src/hooks/index.ts +8 -0
  148. package/src/index.ts +5 -0
  149. package/src/matchers/builtins.ts +184 -0
  150. package/src/matchers/posthog.ts +154 -0
  151. package/src/middleware/decoState.ts +55 -0
  152. package/src/middleware/healthMetrics.ts +131 -0
  153. package/src/middleware/index.ts +80 -0
  154. package/src/middleware/liveness.ts +21 -0
  155. package/src/middleware/observability.ts +205 -0
  156. package/src/routes/adminRoutes.ts +83 -0
  157. package/src/routes/cmsRoute.ts +302 -0
  158. package/src/routes/components.tsx +34 -0
  159. package/src/routes/index.ts +15 -0
  160. package/src/sdk/analytics.ts +72 -0
  161. package/src/sdk/cacheHeaders.ts +268 -0
  162. package/src/sdk/cachedLoader.ts +206 -0
  163. package/src/sdk/clx.ts +3 -0
  164. package/src/sdk/cookie.ts +39 -0
  165. package/src/sdk/createInvoke.ts +57 -0
  166. package/src/sdk/csp.ts +59 -0
  167. package/src/sdk/env.ts +27 -0
  168. package/src/sdk/index.ts +63 -0
  169. package/src/sdk/instrumentedFetch.ts +137 -0
  170. package/src/sdk/invoke.ts +133 -0
  171. package/src/sdk/mergeCacheControl.ts +150 -0
  172. package/src/sdk/redirects.ts +217 -0
  173. package/src/sdk/requestContext.ts +184 -0
  174. package/src/sdk/serverTimings.ts +68 -0
  175. package/src/sdk/signal.ts +41 -0
  176. package/src/sdk/sitemap.ts +143 -0
  177. package/src/sdk/urlUtils.ts +117 -0
  178. package/src/sdk/useDevice.ts +82 -0
  179. package/src/sdk/useId.ts +7 -0
  180. package/src/sdk/useScript.ts +101 -0
  181. package/src/sdk/workerEntry.ts +703 -0
  182. package/src/sdk/wrapCaughtErrors.ts +107 -0
  183. package/src/types/index.ts +39 -0
  184. package/src/types/widgets.ts +13 -0
  185. package/tsconfig.json +13 -0
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Client-safe admin setup functions.
3
+ *
4
+ * These functions only set module-level state (no node: imports,
5
+ * no AsyncLocalStorage). Safe to import in both client and SSR builds.
6
+ *
7
+ * For server-only handlers (handleMeta, handleRender, etc.),
8
+ * import from "@decocms/start/admin" instead.
9
+ */
10
+
11
+ export { type InvokeAction, type InvokeLoader, setInvokeActions, setInvokeLoaders } from "./invoke";
12
+ export { setMetaData } from "./meta";
13
+ export {
14
+ type LoaderConfig,
15
+ type MatcherConfig,
16
+ registerLoaderSchema,
17
+ registerLoaderSchemas,
18
+ registerMatcherSchema,
19
+ registerMatcherSchemas,
20
+ } from "./schema";
21
+
22
+ let cssHref: string | null = null;
23
+ let fontHrefs: string[] = [];
24
+ let themeName = "light";
25
+ let bodyClass = "bg-base-100 text-base-content";
26
+ let htmlLang = "pt-BR";
27
+
28
+ export function setRenderShell(opts: {
29
+ css?: string;
30
+ fonts?: string[];
31
+ theme?: string;
32
+ bodyClass?: string;
33
+ lang?: string;
34
+ }) {
35
+ if (opts.css) cssHref = opts.css;
36
+ if (opts.fonts) fontHrefs = opts.fonts;
37
+ if (opts.theme !== undefined) themeName = opts.theme;
38
+ if (opts.bodyClass !== undefined) bodyClass = opts.bodyClass;
39
+ if (opts.lang !== undefined) htmlLang = opts.lang;
40
+ }
41
+
42
+ export function getRenderShellConfig() {
43
+ return { cssHref, fontHrefs, themeName, bodyClass, htmlLang };
44
+ }
@@ -0,0 +1,59 @@
1
+ export type { DecoPage, Resolvable } from "./loader";
2
+ export {
3
+ findPageByPath,
4
+ getAllPages,
5
+ getRevision,
6
+ loadBlocks,
7
+ onChange,
8
+ setBlocks,
9
+ withBlocksOverride,
10
+ } from "./loader";
11
+ export type { SectionModule, SectionOptions } from "./registry";
12
+ export {
13
+ getResolvedComponent,
14
+ getSection,
15
+ getSectionOptions,
16
+ getSectionRegistry,
17
+ getSyncComponent,
18
+ listRegisteredSections,
19
+ preloadSectionComponents,
20
+ preloadSectionModule,
21
+ registerSection,
22
+ registerSections,
23
+ registerSectionsSync,
24
+ setResolvedComponent,
25
+ } from "./registry";
26
+ export type {
27
+ AsyncRenderingConfig,
28
+ CommerceLoader,
29
+ DanglingReferenceHandler,
30
+ DecoPageResult,
31
+ DeferredSection,
32
+ MatcherContext,
33
+ ResolvedSection,
34
+ ResolveErrorHandler,
35
+ } from "./resolve";
36
+ export {
37
+ addSkipResolveType,
38
+ getAsyncRenderingConfig,
39
+ onBeforeResolve,
40
+ registerCommerceLoader,
41
+ registerCommerceLoaders,
42
+ registerMatcher,
43
+ resolveDecoPage,
44
+ resolveDeferredSection,
45
+ resolveValue,
46
+ setAsyncRenderingConfig,
47
+ setDanglingReferenceHandler,
48
+ setResolveErrorHandler,
49
+ } from "./resolve";
50
+ export type { SectionLoaderFn } from "./sectionLoaders";
51
+ export {
52
+ isLayoutSection,
53
+ registerCacheableSections,
54
+ registerLayoutSections,
55
+ registerSectionLoader,
56
+ registerSectionLoaders,
57
+ runSectionLoaders,
58
+ runSingleSectionLoader,
59
+ } from "./sectionLoaders";
@@ -0,0 +1,180 @@
1
+ import * as asyncHooks from "node:async_hooks";
2
+
3
+ export type Resolvable = {
4
+ __resolveType?: string;
5
+ [key: string]: unknown;
6
+ };
7
+
8
+ export type DecoPage = {
9
+ name: string;
10
+ path?: string;
11
+ sections: Resolvable[] | Resolvable;
12
+ seo?: Record<string, unknown>;
13
+ };
14
+
15
+ // globalThis-backed storage: TanStack Start server function split modules
16
+ // may get isolated module instances. globalThis ensures shared state.
17
+ const G = globalThis as any;
18
+ if (!G.__deco) G.__deco = {};
19
+
20
+ let blockData: Record<string, unknown> = G.__deco.blockData ?? {};
21
+ let revision: string | null = G.__deco.revision ?? null;
22
+
23
+ interface ALSLike<T> {
24
+ getStore(): T | undefined;
25
+ run<R>(store: T, fn: () => R): R;
26
+ }
27
+
28
+ // AsyncLocalStorage might not be available in client builds (Vite replaces
29
+ // node:async_hooks with an empty shim). The namespace import avoids Rollup's
30
+ // named-export validation, and the runtime check prevents construction errors.
31
+ const ALS = (asyncHooks as any).AsyncLocalStorage;
32
+ const blocksOverrideStorage: ALSLike<Record<string, unknown>> = ALS
33
+ ? new ALS()
34
+ : { getStore: () => undefined, run: (_s: any, fn: any) => fn() };
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Change listeners
38
+ // ---------------------------------------------------------------------------
39
+
40
+ type ChangeListener = (blocks: Record<string, unknown>, revision: string) => void;
41
+ const changeListeners: ChangeListener[] = [];
42
+
43
+ /** Register a callback invoked whenever setBlocks() changes the decofile. */
44
+ export function onChange(listener: ChangeListener) {
45
+ changeListeners.push(listener);
46
+ return () => {
47
+ const idx = changeListeners.indexOf(listener);
48
+ if (idx >= 0) changeListeners.splice(idx, 1);
49
+ };
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Revision hashing
54
+ // ---------------------------------------------------------------------------
55
+
56
+ function computeRevision(blocks: Record<string, unknown>): string {
57
+ const str = JSON.stringify(blocks);
58
+ let hash = 5381;
59
+ for (let i = 0; i < str.length; i++) {
60
+ hash = ((hash << 5) + hash + str.charCodeAt(i)) >>> 0;
61
+ }
62
+ return hash.toString(36);
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Block management
67
+ // ---------------------------------------------------------------------------
68
+
69
+ /**
70
+ * Set the blocks data. Called at startup with generated blocks,
71
+ * and by the admin on hot-reload.
72
+ * Notifies all onChange listeners and updates the revision.
73
+ */
74
+ export function setBlocks(blocks: Record<string, unknown>) {
75
+ blockData = blocks;
76
+ revision = computeRevision(blocks);
77
+
78
+ // Persist to globalThis so other module instances see them
79
+ G.__deco.blockData = blockData;
80
+ G.__deco.revision = revision;
81
+
82
+ for (const listener of [...changeListeners]) {
83
+ try {
84
+ listener(blocks, revision);
85
+ } catch (e) {
86
+ console.error("[CMS] onChange listener error:", e);
87
+ }
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Load the current blocks. If running inside a `withBlocksOverride` scope
93
+ * (admin preview), the override is merged on top of the base blocks.
94
+ */
95
+ export function loadBlocks(): Record<string, unknown> {
96
+ // Re-sync from globalThis in case setBlocks was called in another module instance
97
+ if (G.__deco.blockData && G.__deco.blockData !== blockData) {
98
+ blockData = G.__deco.blockData;
99
+ revision = G.__deco.revision ?? null;
100
+ }
101
+
102
+ const override = blocksOverrideStorage.getStore();
103
+ if (override) {
104
+ return { ...blockData, ...override };
105
+ }
106
+ return blockData;
107
+ }
108
+
109
+ /** Get the current decofile revision hash. Changes on each setBlocks(). */
110
+ export function getRevision(): string | null {
111
+ return revision;
112
+ }
113
+
114
+ /**
115
+ * Run a function with a temporary blocks overlay.
116
+ *
117
+ * Used by admin preview: the admin sends a partial decofile (only the
118
+ * blocks that changed), and `loadBlocks()` returns the merged result
119
+ * for the duration of the render. Other concurrent requests are not
120
+ * affected (AsyncLocalStorage is per-request scoped).
121
+ */
122
+ export function withBlocksOverride<T>(override: Record<string, unknown>, fn: () => T): T {
123
+ return blocksOverrideStorage.run(override, fn);
124
+ }
125
+
126
+ export function getAllPages(): Array<{ key: string; page: DecoPage }> {
127
+ const blocks = loadBlocks();
128
+ const pages: Array<{ key: string; page: DecoPage; specificity: number }> = [];
129
+
130
+ for (const [key, block] of Object.entries(blocks)) {
131
+ if (!key.startsWith("pages-")) continue;
132
+ const page = block as DecoPage;
133
+ if (!page.sections) continue;
134
+ if (!page.path) continue;
135
+
136
+ let specificity = 0;
137
+ if (page.path === "/*") specificity = 0;
138
+ else if (page.path.includes(":") || page.path.includes("$")) specificity = 1;
139
+ else specificity = 2;
140
+
141
+ pages.push({ key, page, specificity });
142
+ }
143
+
144
+ return pages
145
+ .sort((a, b) => b.specificity - a.specificity)
146
+ .map(({ key, page }) => ({ key, page }));
147
+ }
148
+
149
+ function matchPath(pattern: string, urlPath: string): Record<string, string> | null {
150
+ if (pattern === "/*") return { _splat: urlPath };
151
+
152
+ const patternParts = pattern.split("/").filter(Boolean);
153
+ const urlParts = urlPath.split("/").filter(Boolean);
154
+
155
+ if (patternParts.length !== urlParts.length) return null;
156
+
157
+ const params: Record<string, string> = {};
158
+ for (let i = 0; i < patternParts.length; i++) {
159
+ const pp = patternParts[i];
160
+ const up = urlParts[i];
161
+ if (pp.startsWith(":")) params[pp.slice(1)] = up;
162
+ else if (pp !== up) return null;
163
+ }
164
+
165
+ return params;
166
+ }
167
+
168
+ export function findPageByPath(
169
+ targetPath: string,
170
+ ): { page: DecoPage; params: Record<string, string> } | null {
171
+ const allPages = getAllPages();
172
+
173
+ for (const { page } of allPages) {
174
+ if (!page.path) continue;
175
+ const params = matchPath(page.path, targetPath);
176
+ if (params !== null) return { page, params };
177
+ }
178
+
179
+ return null;
180
+ }
@@ -0,0 +1,162 @@
1
+ import type { ComponentType } from "react";
2
+
3
+ export type SectionModule = {
4
+ default: ComponentType<any>;
5
+ loader?: (props: any) => Promise<any> | any;
6
+ LoadingFallback?: ComponentType<any>;
7
+ ErrorFallback?: ComponentType<{ error: Error }>;
8
+ };
9
+
10
+ type RegistryEntry = () => Promise<SectionModule>;
11
+
12
+ export interface SectionOptions {
13
+ /** Custom loading fallback component for this section. */
14
+ loadingFallback?: ComponentType<any>;
15
+ /** Custom error fallback component for this section. */
16
+ errorFallback?: ComponentType<{ error: Error }>;
17
+ }
18
+
19
+ // globalThis-backed: server function split modules need access to the registry
20
+ const G = globalThis as any;
21
+ if (!G.__deco) G.__deco = {};
22
+ if (!G.__deco.sectionRegistry) G.__deco.sectionRegistry = {};
23
+ if (!G.__deco.sectionOptions) G.__deco.sectionOptions = {};
24
+ if (!G.__deco.resolvedComponents) G.__deco.resolvedComponents = {};
25
+ if (!G.__deco.syncComponents) G.__deco.syncComponents = {};
26
+
27
+ const registry: Record<string, RegistryEntry> = G.__deco.sectionRegistry;
28
+ const sectionOptions: Record<string, SectionOptions> = G.__deco.sectionOptions;
29
+
30
+ // Cache of already-resolved component references.
31
+ // When a module is loaded (server-side or after first client import),
32
+ // the default export is stored here so subsequent renders can use
33
+ // the component directly WITHOUT React.lazy/Suspense — preventing
34
+ // hydration flash on SSR'd content.
35
+ const resolvedComponents: Record<string, ComponentType<any>> = G.__deco.resolvedComponents;
36
+
37
+ // Static sync registry — components that were statically imported and
38
+ // are guaranteed available on BOTH server and client without any async import.
39
+ // These never need React.lazy/Suspense and render identically on SSR and hydration.
40
+ const syncComponents: Record<string, ComponentType<any>> = G.__deco.syncComponents;
41
+
42
+ export function registerSection(key: string, loader: RegistryEntry, options?: SectionOptions) {
43
+ registry[key] = loader;
44
+ if (options) sectionOptions[key] = options;
45
+ }
46
+
47
+ export function registerSections(
48
+ sections: Record<string, RegistryEntry>,
49
+ defaultOptions?: SectionOptions,
50
+ ) {
51
+ for (const [key, loader] of Object.entries(sections)) {
52
+ registry[key] = loader;
53
+ if (defaultOptions) sectionOptions[key] = { ...defaultOptions };
54
+ }
55
+ }
56
+
57
+ export function getSection(resolveType: string): RegistryEntry | undefined {
58
+ return registry[resolveType];
59
+ }
60
+
61
+ export function getSectionOptions(resolveType: string): SectionOptions | undefined {
62
+ return sectionOptions[resolveType];
63
+ }
64
+
65
+ /**
66
+ * Load a section module eagerly to extract LoadingFallback/ErrorFallback.
67
+ * Used by DeferredSectionWrapper to show custom skeletons before the section
68
+ * scrolls into view and its full props are fetched.
69
+ */
70
+ export async function preloadSectionModule(
71
+ resolveType: string,
72
+ ): Promise<SectionOptions | undefined> {
73
+ const existing = sectionOptions[resolveType];
74
+ if (existing?.loadingFallback) return existing;
75
+
76
+ const loader = registry[resolveType];
77
+ if (!loader) return undefined;
78
+
79
+ try {
80
+ const mod = await loader();
81
+ const opts: SectionOptions = { ...existing };
82
+ if (mod.LoadingFallback) opts.loadingFallback = mod.LoadingFallback;
83
+ if (mod.ErrorFallback) opts.errorFallback = mod.ErrorFallback;
84
+ sectionOptions[resolveType] = opts;
85
+ return opts;
86
+ } catch {
87
+ return existing;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Get a previously resolved component. Returns undefined if the module
93
+ * hasn't been imported yet. Use this to render WITHOUT React.lazy/Suspense
94
+ * for sections whose JS is already loaded — avoids hydration flash.
95
+ */
96
+ export function getResolvedComponent(key: string): ComponentType<any> | undefined {
97
+ return resolvedComponents[key];
98
+ }
99
+
100
+ /**
101
+ * Store a resolved component reference. Called after a successful import()
102
+ * so future renders can skip React.lazy entirely.
103
+ */
104
+ export function setResolvedComponent(key: string, component: ComponentType<any>): void {
105
+ resolvedComponents[key] = component;
106
+ }
107
+
108
+ /**
109
+ * Pre-import section modules and cache their default exports.
110
+ * Called server-side after resolving eager sections so that
111
+ * the SSR render tree uses direct component refs instead of React.lazy.
112
+ */
113
+ export async function preloadSectionComponents(keys: string[]): Promise<void> {
114
+ await Promise.all(
115
+ keys.map(async (key) => {
116
+ if (resolvedComponents[key]) return;
117
+ const loader = registry[key];
118
+ if (!loader) return;
119
+ try {
120
+ const mod = await loader();
121
+ if (mod?.default) {
122
+ resolvedComponents[key] = mod.default;
123
+ }
124
+ const opts: SectionOptions = { ...sectionOptions[key] };
125
+ if (mod.LoadingFallback) opts.loadingFallback = mod.LoadingFallback;
126
+ if (mod.ErrorFallback) opts.errorFallback = mod.ErrorFallback;
127
+ sectionOptions[key] = opts;
128
+ } catch {
129
+ /* ignore — will fall back to React.lazy */
130
+ }
131
+ }),
132
+ );
133
+ }
134
+
135
+ /**
136
+ * Register sections with their already-imported component references.
137
+ * These are available synchronously on both server and client — no dynamic
138
+ * import, no React.lazy, no Suspense. Use for critical above-the-fold
139
+ * sections that must never flash during hydration.
140
+ */
141
+ export function registerSectionsSync(sections: Record<string, ComponentType<any>>): void {
142
+ for (const [key, component] of Object.entries(sections)) {
143
+ syncComponents[key] = component;
144
+ resolvedComponents[key] = component;
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Get a synchronously-registered component. Returns undefined if the
150
+ * section was only registered with a lazy loader (registerSections).
151
+ */
152
+ export function getSyncComponent(key: string): ComponentType<any> | undefined {
153
+ return syncComponents[key];
154
+ }
155
+
156
+ export function listRegisteredSections(): string[] {
157
+ return Object.keys(registry);
158
+ }
159
+
160
+ export function getSectionRegistry(): Record<string, RegistryEntry> {
161
+ return registry;
162
+ }