@decocms/blocks 7.0.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/package.json +97 -0
- package/src/cms/applySectionConventions.ts +128 -0
- package/src/cms/blockSource.test.ts +67 -0
- package/src/cms/blockSource.ts +124 -0
- package/src/cms/index.ts +104 -0
- package/src/cms/layoutCacheRace.test.ts +146 -0
- package/src/cms/loadDecofileDirectory.test.ts +52 -0
- package/src/cms/loadDecofileDirectory.ts +48 -0
- package/src/cms/loader.test.ts +184 -0
- package/src/cms/loader.ts +284 -0
- package/src/cms/registry.test.ts +118 -0
- package/src/cms/registry.ts +252 -0
- package/src/cms/resolve.test.ts +547 -0
- package/src/cms/resolve.ts +2003 -0
- package/src/cms/schema.ts +993 -0
- package/src/cms/sectionLoaders.test.ts +409 -0
- package/src/cms/sectionLoaders.ts +562 -0
- package/src/cms/sectionMixins.test.ts +163 -0
- package/src/cms/sectionMixins.ts +153 -0
- package/src/hooks/LazySection.tsx +121 -0
- package/src/hooks/LiveControls.tsx +122 -0
- package/src/hooks/RenderSection.test.tsx +81 -0
- package/src/hooks/RenderSection.tsx +91 -0
- package/src/hooks/SectionErrorFallback.tsx +97 -0
- package/src/hooks/index.ts +4 -0
- package/src/index.ts +8 -0
- package/src/matchers/builtins.test.ts +251 -0
- package/src/matchers/builtins.ts +437 -0
- package/src/matchers/countryNames.ts +104 -0
- package/src/matchers/override.test.ts +205 -0
- package/src/matchers/override.ts +136 -0
- package/src/matchers/posthog.ts +154 -0
- package/src/middleware/decoState.ts +55 -0
- package/src/middleware/healthMetrics.ts +133 -0
- package/src/middleware/hydrationContext.test.ts +61 -0
- package/src/middleware/hydrationContext.ts +79 -0
- package/src/middleware/index.ts +88 -0
- package/src/middleware/liveness.ts +21 -0
- package/src/middleware/observability.test.ts +238 -0
- package/src/middleware/observability.ts +620 -0
- package/src/middleware/validateSection.test.ts +147 -0
- package/src/middleware/validateSection.ts +100 -0
- package/src/sdk/abTesting.test.ts +326 -0
- package/src/sdk/abTesting.ts +499 -0
- package/src/sdk/analytics.ts +77 -0
- package/src/sdk/cacheHeaders.test.ts +115 -0
- package/src/sdk/cacheHeaders.ts +424 -0
- package/src/sdk/cachedLoader.ts +364 -0
- package/src/sdk/clx.ts +5 -0
- package/src/sdk/cn.test.ts +34 -0
- package/src/sdk/cn.ts +28 -0
- package/src/sdk/composite.test.ts +121 -0
- package/src/sdk/composite.ts +114 -0
- package/src/sdk/cookie.test.ts +108 -0
- package/src/sdk/cookie.ts +129 -0
- package/src/sdk/crypto.ts +185 -0
- package/src/sdk/csp.ts +59 -0
- package/src/sdk/djb2.ts +20 -0
- package/src/sdk/encoding.test.ts +71 -0
- package/src/sdk/encoding.ts +47 -0
- package/src/sdk/env.ts +31 -0
- package/src/sdk/http.test.ts +71 -0
- package/src/sdk/http.ts +124 -0
- package/src/sdk/index.ts +90 -0
- package/src/sdk/inflightTimeout.test.ts +35 -0
- package/src/sdk/inflightTimeout.ts +53 -0
- package/src/sdk/instrumentedFetch.test.ts +559 -0
- package/src/sdk/instrumentedFetch.ts +339 -0
- package/src/sdk/invoke.test.ts +115 -0
- package/src/sdk/invoke.ts +260 -0
- package/src/sdk/logger.test.ts +432 -0
- package/src/sdk/logger.ts +304 -0
- package/src/sdk/mergeCacheControl.ts +150 -0
- package/src/sdk/normalizeUrls.ts +91 -0
- package/src/sdk/observability.ts +109 -0
- package/src/sdk/otel.test.ts +526 -0
- package/src/sdk/otel.ts +981 -0
- package/src/sdk/otelAdapters/clickhouseCollector.ts +65 -0
- package/src/sdk/otelAdapters.test.ts +89 -0
- package/src/sdk/otelAdapters.ts +144 -0
- package/src/sdk/otelHttpLog.test.ts +457 -0
- package/src/sdk/otelHttpLog.ts +419 -0
- package/src/sdk/otelHttpMeter.test.ts +292 -0
- package/src/sdk/otelHttpMeter.ts +506 -0
- package/src/sdk/otelHttpTracer.test.ts +474 -0
- package/src/sdk/otelHttpTracer.ts +543 -0
- package/src/sdk/redirects.ts +225 -0
- package/src/sdk/requestContext.ts +281 -0
- package/src/sdk/requestContextStorage.browser.test.ts +29 -0
- package/src/sdk/requestContextStorage.browser.ts +43 -0
- package/src/sdk/requestContextStorage.ts +35 -0
- package/src/sdk/retry.ts +45 -0
- package/src/sdk/serverTimings.ts +68 -0
- package/src/sdk/signal.ts +42 -0
- package/src/sdk/sitemap.ts +160 -0
- package/src/sdk/urlRedaction.test.ts +73 -0
- package/src/sdk/urlRedaction.ts +82 -0
- package/src/sdk/urlUtils.ts +134 -0
- package/src/sdk/useDevice.test.ts +130 -0
- package/src/sdk/useDevice.ts +109 -0
- package/src/sdk/useDeviceContext.tsx +108 -0
- package/src/sdk/useId.ts +7 -0
- package/src/sdk/useScript.test.ts +128 -0
- package/src/sdk/useScript.ts +210 -0
- package/src/sdk/useSuggestions.test.ts +230 -0
- package/src/sdk/useSuggestions.ts +188 -0
- package/src/sdk/wrapCaughtErrors.ts +107 -0
- package/src/setup.ts +119 -0
- package/src/types/index.ts +39 -0
- package/src/types/widgets.ts +14 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import type { ComponentType } from "react";
|
|
2
|
+
|
|
3
|
+
export type OnBeforeResolveProps = (
|
|
4
|
+
props: Record<string, unknown>,
|
|
5
|
+
) => Record<string, unknown>;
|
|
6
|
+
|
|
7
|
+
export type SectionModule = {
|
|
8
|
+
default: ComponentType<any>;
|
|
9
|
+
loader?: (props: any) => Promise<any> | any;
|
|
10
|
+
onBeforeResolveProps?: OnBeforeResolveProps;
|
|
11
|
+
LoadingFallback?: ComponentType<any>;
|
|
12
|
+
ErrorFallback?: ComponentType<{ error: Error }>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type RegistryEntry = () => Promise<SectionModule>;
|
|
16
|
+
|
|
17
|
+
export interface SectionOptions {
|
|
18
|
+
/** Custom loading fallback component for this section. */
|
|
19
|
+
loadingFallback?: ComponentType<any>;
|
|
20
|
+
/** Custom error fallback component for this section. */
|
|
21
|
+
errorFallback?: ComponentType<{ error: Error }>;
|
|
22
|
+
/**
|
|
23
|
+
* When true, the section is wrapped in `<ClientOnly>` from TanStack Router.
|
|
24
|
+
* It renders only on the client — no SSR, no hydration mismatch.
|
|
25
|
+
* Use for analytics scripts, GTM, third-party widgets, and other
|
|
26
|
+
* browser-dependent components.
|
|
27
|
+
*/
|
|
28
|
+
clientOnly?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// globalThis-backed: server function split modules need access to the registry
|
|
32
|
+
const G = globalThis as any;
|
|
33
|
+
if (!G.__deco) G.__deco = {};
|
|
34
|
+
if (!G.__deco.sectionRegistry) G.__deco.sectionRegistry = {};
|
|
35
|
+
if (!G.__deco.sectionOptions) G.__deco.sectionOptions = {};
|
|
36
|
+
if (!G.__deco.resolvedComponents) G.__deco.resolvedComponents = {};
|
|
37
|
+
if (!G.__deco.syncComponents) G.__deco.syncComponents = {};
|
|
38
|
+
if (!G.__deco.onBeforeResolvePropsRegistry)
|
|
39
|
+
G.__deco.onBeforeResolvePropsRegistry = {};
|
|
40
|
+
|
|
41
|
+
const registry: Record<string, RegistryEntry> = G.__deco.sectionRegistry;
|
|
42
|
+
const sectionOptions: Record<string, SectionOptions> = G.__deco.sectionOptions;
|
|
43
|
+
|
|
44
|
+
// Cache of already-resolved component references.
|
|
45
|
+
// When a module is loaded (server-side or after first client import),
|
|
46
|
+
// the default export is stored here so subsequent renders can use
|
|
47
|
+
// the component directly WITHOUT React.lazy/Suspense — preventing
|
|
48
|
+
// hydration flash on SSR'd content.
|
|
49
|
+
const resolvedComponents: Record<string, ComponentType<any>> = G.__deco.resolvedComponents;
|
|
50
|
+
|
|
51
|
+
// Static sync registry — components that were statically imported and
|
|
52
|
+
// are guaranteed available on BOTH server and client without any async import.
|
|
53
|
+
// These never need React.lazy/Suspense and render identically on SSR and hydration.
|
|
54
|
+
const syncComponents: Record<string, ComponentType<any>> = G.__deco.syncComponents;
|
|
55
|
+
|
|
56
|
+
// onBeforeResolveProps registry — functions that transform raw CMS props
|
|
57
|
+
// BEFORE resolvables are resolved. Allows sections to extract data from
|
|
58
|
+
// raw resolvable structures (e.g., collection IDs from loader refs) that
|
|
59
|
+
// would be lost after resolution.
|
|
60
|
+
const onBeforeResolvePropsRegistry: Record<string, OnBeforeResolveProps> =
|
|
61
|
+
G.__deco.onBeforeResolvePropsRegistry;
|
|
62
|
+
|
|
63
|
+
export function registerSection(key: string, loader: RegistryEntry, options?: SectionOptions) {
|
|
64
|
+
registry[key] = loader;
|
|
65
|
+
if (options) sectionOptions[key] = options;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function registerSections(
|
|
69
|
+
sections: Record<string, RegistryEntry>,
|
|
70
|
+
defaultOptions?: SectionOptions,
|
|
71
|
+
) {
|
|
72
|
+
for (const [key, loader] of Object.entries(sections)) {
|
|
73
|
+
registry[key] = loader;
|
|
74
|
+
if (defaultOptions) sectionOptions[key] = { ...defaultOptions };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function getSection(resolveType: string): RegistryEntry | undefined {
|
|
79
|
+
return registry[resolveType];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function getSectionOptions(resolveType: string): SectionOptions | undefined {
|
|
83
|
+
return sectionOptions[resolveType];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Load a section module eagerly to extract LoadingFallback/ErrorFallback.
|
|
88
|
+
* Used by DeferredSectionWrapper to show custom skeletons before the section
|
|
89
|
+
* scrolls into view and its full props are fetched.
|
|
90
|
+
*/
|
|
91
|
+
export async function preloadSectionModule(
|
|
92
|
+
resolveType: string,
|
|
93
|
+
): Promise<SectionOptions | undefined> {
|
|
94
|
+
const existing = sectionOptions[resolveType];
|
|
95
|
+
if (existing?.loadingFallback) return existing;
|
|
96
|
+
|
|
97
|
+
const loader = registry[resolveType];
|
|
98
|
+
if (!loader) return undefined;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const mod = await loader();
|
|
102
|
+
const opts: SectionOptions = { ...existing };
|
|
103
|
+
if (mod.LoadingFallback) opts.loadingFallback = mod.LoadingFallback;
|
|
104
|
+
if (mod.ErrorFallback) opts.errorFallback = mod.ErrorFallback;
|
|
105
|
+
sectionOptions[resolveType] = opts;
|
|
106
|
+
return opts;
|
|
107
|
+
} catch (e) {
|
|
108
|
+
console.warn(`[Registry] Failed to preload section module "${resolveType}":`, e);
|
|
109
|
+
return existing;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get a previously resolved component. Returns undefined if the module
|
|
115
|
+
* hasn't been imported yet. Use this to render WITHOUT React.lazy/Suspense
|
|
116
|
+
* for sections whose JS is already loaded — avoids hydration flash.
|
|
117
|
+
*/
|
|
118
|
+
export function getResolvedComponent(key: string): ComponentType<any> | undefined {
|
|
119
|
+
return resolvedComponents[key];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Store a resolved component reference. Called after a successful import()
|
|
124
|
+
* so future renders can skip React.lazy entirely.
|
|
125
|
+
*/
|
|
126
|
+
export function setResolvedComponent(key: string, component: ComponentType<any>): void {
|
|
127
|
+
resolvedComponents[key] = component;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Pre-import section modules and cache their default exports.
|
|
132
|
+
* Called server-side after resolving eager sections so that
|
|
133
|
+
* the SSR render tree uses direct component refs instead of React.lazy.
|
|
134
|
+
*/
|
|
135
|
+
export async function preloadSectionComponents(keys: string[]): Promise<void> {
|
|
136
|
+
await Promise.all(
|
|
137
|
+
keys.map(async (key) => {
|
|
138
|
+
if (resolvedComponents[key]) return;
|
|
139
|
+
const loader = registry[key];
|
|
140
|
+
if (!loader) return;
|
|
141
|
+
try {
|
|
142
|
+
const mod = await loader();
|
|
143
|
+
if (mod?.default) {
|
|
144
|
+
resolvedComponents[key] = mod.default;
|
|
145
|
+
}
|
|
146
|
+
if (mod?.onBeforeResolveProps && !onBeforeResolvePropsRegistry[key]) {
|
|
147
|
+
onBeforeResolvePropsRegistry[key] = mod.onBeforeResolveProps;
|
|
148
|
+
}
|
|
149
|
+
const opts: SectionOptions = { ...sectionOptions[key] };
|
|
150
|
+
if (mod.LoadingFallback) opts.loadingFallback = mod.LoadingFallback;
|
|
151
|
+
if (mod.ErrorFallback) opts.errorFallback = mod.ErrorFallback;
|
|
152
|
+
sectionOptions[key] = opts;
|
|
153
|
+
} catch (e) {
|
|
154
|
+
console.warn(`[Registry] Failed to preload component "${key}":`, e);
|
|
155
|
+
}
|
|
156
|
+
}),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* A sync section entry: either a plain component reference or a full module
|
|
162
|
+
* object with optional LoadingFallback and ErrorFallback.
|
|
163
|
+
* Providing the full module allows DeferredSectionWrapper to show the correct
|
|
164
|
+
* skeleton immediately (optionsReady=true on first render) without waiting for
|
|
165
|
+
* the async preloadSectionModule() call.
|
|
166
|
+
*/
|
|
167
|
+
export type SyncSectionEntry =
|
|
168
|
+
| ComponentType<any>
|
|
169
|
+
| {
|
|
170
|
+
default: ComponentType<any>;
|
|
171
|
+
LoadingFallback?: ComponentType<any>;
|
|
172
|
+
ErrorFallback?: ComponentType<{ error: Error }>;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Register sections with their already-imported component references.
|
|
177
|
+
* These are available synchronously on both server and client — no dynamic
|
|
178
|
+
* import, no React.lazy, no Suspense. Use for critical above-the-fold
|
|
179
|
+
* sections that must never flash during hydration.
|
|
180
|
+
*
|
|
181
|
+
* Accepts either a plain component or a full module object (with optional
|
|
182
|
+
* LoadingFallback / ErrorFallback). Providing the module object populates
|
|
183
|
+
* sectionOptions immediately, so DeferredSectionWrapper can show the correct
|
|
184
|
+
* skeleton without an extra async preloadSectionModule() round-trip.
|
|
185
|
+
*/
|
|
186
|
+
export function registerSectionsSync(sections: Record<string, SyncSectionEntry>): void {
|
|
187
|
+
for (const [key, entry] of Object.entries(sections)) {
|
|
188
|
+
const raw = typeof entry === "function" ? entry : (entry as any).default;
|
|
189
|
+
// Accept functions and React wrapper objects (React.memo, forwardRef, lazy)
|
|
190
|
+
const REACT_WRAPPERS = [
|
|
191
|
+
Symbol.for("react.memo"),
|
|
192
|
+
Symbol.for("react.forward_ref"),
|
|
193
|
+
Symbol.for("react.lazy"),
|
|
194
|
+
];
|
|
195
|
+
const component =
|
|
196
|
+
typeof raw === "function" ||
|
|
197
|
+
(raw != null &&
|
|
198
|
+
typeof raw === "object" &&
|
|
199
|
+
REACT_WRAPPERS.includes((raw as any).$$typeof))
|
|
200
|
+
? raw
|
|
201
|
+
: undefined;
|
|
202
|
+
if (!component) {
|
|
203
|
+
console.warn(`[registerSectionsSync] "${key}" has no callable default export — skipping`);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
syncComponents[key] = component;
|
|
207
|
+
resolvedComponents[key] = component;
|
|
208
|
+
|
|
209
|
+
if (typeof entry !== "function") {
|
|
210
|
+
const opts: SectionOptions = { ...sectionOptions[key] };
|
|
211
|
+
if (entry.LoadingFallback) opts.loadingFallback = entry.LoadingFallback;
|
|
212
|
+
if (entry.ErrorFallback) opts.errorFallback = entry.ErrorFallback;
|
|
213
|
+
sectionOptions[key] = opts;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get a synchronously-registered component. Returns undefined if the
|
|
220
|
+
* section was only registered with a lazy loader (registerSections).
|
|
221
|
+
*/
|
|
222
|
+
export function getSyncComponent(key: string): ComponentType<any> | undefined {
|
|
223
|
+
return syncComponents[key];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Register an onBeforeResolveProps function for a section.
|
|
228
|
+
* Called with raw CMS props (containing unresolved `__resolveType` references)
|
|
229
|
+
* BEFORE the resolution engine resolves them. Use to extract metadata from
|
|
230
|
+
* resolvable structures that would be lost after resolution.
|
|
231
|
+
*/
|
|
232
|
+
export function registerOnBeforeResolveProps(
|
|
233
|
+
sectionKey: string,
|
|
234
|
+
fn: OnBeforeResolveProps,
|
|
235
|
+
): void {
|
|
236
|
+
onBeforeResolvePropsRegistry[sectionKey] = fn;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Get the registered onBeforeResolveProps for a section, if any. */
|
|
240
|
+
export function getOnBeforeResolveProps(
|
|
241
|
+
sectionKey: string,
|
|
242
|
+
): OnBeforeResolveProps | undefined {
|
|
243
|
+
return onBeforeResolvePropsRegistry[sectionKey];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function listRegisteredSections(): string[] {
|
|
247
|
+
return Object.keys(registry);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function getSectionRegistry(): Record<string, RegistryEntry> {
|
|
251
|
+
return registry;
|
|
252
|
+
}
|