@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,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
|
+
}
|
package/src/cms/index.ts
ADDED
|
@@ -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
|
+
}
|