@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.
Files changed (111) hide show
  1. package/package.json +97 -0
  2. package/src/cms/applySectionConventions.ts +128 -0
  3. package/src/cms/blockSource.test.ts +67 -0
  4. package/src/cms/blockSource.ts +124 -0
  5. package/src/cms/index.ts +104 -0
  6. package/src/cms/layoutCacheRace.test.ts +146 -0
  7. package/src/cms/loadDecofileDirectory.test.ts +52 -0
  8. package/src/cms/loadDecofileDirectory.ts +48 -0
  9. package/src/cms/loader.test.ts +184 -0
  10. package/src/cms/loader.ts +284 -0
  11. package/src/cms/registry.test.ts +118 -0
  12. package/src/cms/registry.ts +252 -0
  13. package/src/cms/resolve.test.ts +547 -0
  14. package/src/cms/resolve.ts +2003 -0
  15. package/src/cms/schema.ts +993 -0
  16. package/src/cms/sectionLoaders.test.ts +409 -0
  17. package/src/cms/sectionLoaders.ts +562 -0
  18. package/src/cms/sectionMixins.test.ts +163 -0
  19. package/src/cms/sectionMixins.ts +153 -0
  20. package/src/hooks/LazySection.tsx +121 -0
  21. package/src/hooks/LiveControls.tsx +122 -0
  22. package/src/hooks/RenderSection.test.tsx +81 -0
  23. package/src/hooks/RenderSection.tsx +91 -0
  24. package/src/hooks/SectionErrorFallback.tsx +97 -0
  25. package/src/hooks/index.ts +4 -0
  26. package/src/index.ts +8 -0
  27. package/src/matchers/builtins.test.ts +251 -0
  28. package/src/matchers/builtins.ts +437 -0
  29. package/src/matchers/countryNames.ts +104 -0
  30. package/src/matchers/override.test.ts +205 -0
  31. package/src/matchers/override.ts +136 -0
  32. package/src/matchers/posthog.ts +154 -0
  33. package/src/middleware/decoState.ts +55 -0
  34. package/src/middleware/healthMetrics.ts +133 -0
  35. package/src/middleware/hydrationContext.test.ts +61 -0
  36. package/src/middleware/hydrationContext.ts +79 -0
  37. package/src/middleware/index.ts +88 -0
  38. package/src/middleware/liveness.ts +21 -0
  39. package/src/middleware/observability.test.ts +238 -0
  40. package/src/middleware/observability.ts +620 -0
  41. package/src/middleware/validateSection.test.ts +147 -0
  42. package/src/middleware/validateSection.ts +100 -0
  43. package/src/sdk/abTesting.test.ts +326 -0
  44. package/src/sdk/abTesting.ts +499 -0
  45. package/src/sdk/analytics.ts +77 -0
  46. package/src/sdk/cacheHeaders.test.ts +115 -0
  47. package/src/sdk/cacheHeaders.ts +424 -0
  48. package/src/sdk/cachedLoader.ts +364 -0
  49. package/src/sdk/clx.ts +5 -0
  50. package/src/sdk/cn.test.ts +34 -0
  51. package/src/sdk/cn.ts +28 -0
  52. package/src/sdk/composite.test.ts +121 -0
  53. package/src/sdk/composite.ts +114 -0
  54. package/src/sdk/cookie.test.ts +108 -0
  55. package/src/sdk/cookie.ts +129 -0
  56. package/src/sdk/crypto.ts +185 -0
  57. package/src/sdk/csp.ts +59 -0
  58. package/src/sdk/djb2.ts +20 -0
  59. package/src/sdk/encoding.test.ts +71 -0
  60. package/src/sdk/encoding.ts +47 -0
  61. package/src/sdk/env.ts +31 -0
  62. package/src/sdk/http.test.ts +71 -0
  63. package/src/sdk/http.ts +124 -0
  64. package/src/sdk/index.ts +90 -0
  65. package/src/sdk/inflightTimeout.test.ts +35 -0
  66. package/src/sdk/inflightTimeout.ts +53 -0
  67. package/src/sdk/instrumentedFetch.test.ts +559 -0
  68. package/src/sdk/instrumentedFetch.ts +339 -0
  69. package/src/sdk/invoke.test.ts +115 -0
  70. package/src/sdk/invoke.ts +260 -0
  71. package/src/sdk/logger.test.ts +432 -0
  72. package/src/sdk/logger.ts +304 -0
  73. package/src/sdk/mergeCacheControl.ts +150 -0
  74. package/src/sdk/normalizeUrls.ts +91 -0
  75. package/src/sdk/observability.ts +109 -0
  76. package/src/sdk/otel.test.ts +526 -0
  77. package/src/sdk/otel.ts +981 -0
  78. package/src/sdk/otelAdapters/clickhouseCollector.ts +65 -0
  79. package/src/sdk/otelAdapters.test.ts +89 -0
  80. package/src/sdk/otelAdapters.ts +144 -0
  81. package/src/sdk/otelHttpLog.test.ts +457 -0
  82. package/src/sdk/otelHttpLog.ts +419 -0
  83. package/src/sdk/otelHttpMeter.test.ts +292 -0
  84. package/src/sdk/otelHttpMeter.ts +506 -0
  85. package/src/sdk/otelHttpTracer.test.ts +474 -0
  86. package/src/sdk/otelHttpTracer.ts +543 -0
  87. package/src/sdk/redirects.ts +225 -0
  88. package/src/sdk/requestContext.ts +281 -0
  89. package/src/sdk/requestContextStorage.browser.test.ts +29 -0
  90. package/src/sdk/requestContextStorage.browser.ts +43 -0
  91. package/src/sdk/requestContextStorage.ts +35 -0
  92. package/src/sdk/retry.ts +45 -0
  93. package/src/sdk/serverTimings.ts +68 -0
  94. package/src/sdk/signal.ts +42 -0
  95. package/src/sdk/sitemap.ts +160 -0
  96. package/src/sdk/urlRedaction.test.ts +73 -0
  97. package/src/sdk/urlRedaction.ts +82 -0
  98. package/src/sdk/urlUtils.ts +134 -0
  99. package/src/sdk/useDevice.test.ts +130 -0
  100. package/src/sdk/useDevice.ts +109 -0
  101. package/src/sdk/useDeviceContext.tsx +108 -0
  102. package/src/sdk/useId.ts +7 -0
  103. package/src/sdk/useScript.test.ts +128 -0
  104. package/src/sdk/useScript.ts +210 -0
  105. package/src/sdk/useSuggestions.test.ts +230 -0
  106. package/src/sdk/useSuggestions.ts +188 -0
  107. package/src/sdk/wrapCaughtErrors.ts +107 -0
  108. package/src/setup.ts +119 -0
  109. package/src/types/index.ts +39 -0
  110. package/src/types/widgets.ts +14 -0
  111. 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
+ }