@uniweb/runtime 0.2.15 → 0.2.16

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/dist/ssr.js CHANGED
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { useState, useEffect } from "react";
2
2
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
3
3
  function guaranteeItemStructure(item) {
4
4
  return {
@@ -106,10 +106,33 @@ function applyDefaults(params, defaults) {
106
106
  ...params || {}
107
107
  };
108
108
  }
109
+ function applyCascadedData(localData, cascadedData, inheritData) {
110
+ if (!inheritData || !cascadedData || Object.keys(cascadedData).length === 0) {
111
+ return localData;
112
+ }
113
+ if (inheritData === true) {
114
+ return { ...cascadedData, ...localData };
115
+ }
116
+ if (Array.isArray(inheritData)) {
117
+ const result = { ...localData };
118
+ for (const key of inheritData) {
119
+ if (cascadedData[key] !== void 0 && result[key] === void 0) {
120
+ result[key] = cascadedData[key];
121
+ }
122
+ }
123
+ return result;
124
+ }
125
+ return localData;
126
+ }
109
127
  function prepareProps(block, meta) {
110
128
  const defaults = meta?.defaults || {};
111
129
  const params = applyDefaults(block.properties, defaults);
112
130
  const content = guaranteeContentStructure(block.parsedContent);
131
+ const inheritData = meta?.inheritData;
132
+ const cascadedData = block.cascadedData || {};
133
+ if (inheritData) {
134
+ content.data = applyCascadedData(content.data, cascadedData, inheritData);
135
+ }
113
136
  const schemas = meta?.schemas || null;
114
137
  if (schemas && content.data) {
115
138
  content.data = applySchemas(content.data, schemas);
@@ -122,6 +145,69 @@ function getComponentMeta(componentName) {
122
145
  function getComponentDefaults(componentName) {
123
146
  return globalThis.uniweb?.getComponentDefaults?.(componentName) || {};
124
147
  }
148
+ function getNestedValue(obj, path) {
149
+ if (!obj || !path) return obj;
150
+ const parts = path.split(".");
151
+ let current = obj;
152
+ for (const part of parts) {
153
+ if (current === null || current === void 0) return void 0;
154
+ current = current[part];
155
+ }
156
+ return current;
157
+ }
158
+ async function executeFetchClient(config) {
159
+ if (!config) return { data: null };
160
+ const { path, url, transform } = config;
161
+ try {
162
+ const fetchUrl = path || url;
163
+ if (!fetchUrl) {
164
+ return { data: [], error: "No path or url specified" };
165
+ }
166
+ const response = await fetch(fetchUrl);
167
+ if (!response.ok) {
168
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
169
+ }
170
+ const contentType = response.headers.get("content-type") || "";
171
+ let data;
172
+ if (contentType.includes("application/json")) {
173
+ data = await response.json();
174
+ } else {
175
+ const text = await response.text();
176
+ try {
177
+ data = JSON.parse(text);
178
+ } catch {
179
+ console.warn("[data-fetcher] Response is not JSON, returning as text");
180
+ data = text;
181
+ }
182
+ }
183
+ if (transform && data) {
184
+ data = getNestedValue(data, transform);
185
+ }
186
+ return { data: data ?? [] };
187
+ } catch (error) {
188
+ console.warn(`[data-fetcher] Client fetch failed: ${error.message}`);
189
+ return { data: [], error: error.message };
190
+ }
191
+ }
192
+ function mergeIntoData(currentData, fetchedData, schema, merge = false) {
193
+ if (fetchedData === null || fetchedData === void 0 || !schema) {
194
+ return currentData;
195
+ }
196
+ const result = { ...currentData || {} };
197
+ if (merge && result[schema] !== void 0) {
198
+ const existing = result[schema];
199
+ if (Array.isArray(existing) && Array.isArray(fetchedData)) {
200
+ result[schema] = [...existing, ...fetchedData];
201
+ } else if (typeof existing === "object" && existing !== null && typeof fetchedData === "object" && fetchedData !== null && !Array.isArray(existing) && !Array.isArray(fetchedData)) {
202
+ result[schema] = { ...existing, ...fetchedData };
203
+ } else {
204
+ result[schema] = fetchedData;
205
+ }
206
+ } else {
207
+ result[schema] = fetchedData;
208
+ }
209
+ return result;
210
+ }
125
211
  const hexToRgba = (hex, opacity) => {
126
212
  const r = parseInt(hex.slice(1, 3), 16);
127
213
  const g = parseInt(hex.slice(3, 5), 16);
@@ -169,7 +255,29 @@ const getWrapperProps = (block) => {
169
255
  };
170
256
  };
171
257
  function BlockRenderer({ block, pure = false, extra = {} }) {
258
+ const [runtimeData, setRuntimeData] = useState(null);
259
+ const [fetchError, setFetchError] = useState(null);
172
260
  const Component = block.initComponent();
261
+ const fetchConfig = block.fetch;
262
+ const shouldFetchAtRuntime = fetchConfig && fetchConfig.prerender === false;
263
+ useEffect(() => {
264
+ if (!shouldFetchAtRuntime) return;
265
+ let cancelled = false;
266
+ async function doFetch() {
267
+ const result = await executeFetchClient(fetchConfig);
268
+ if (cancelled) return;
269
+ if (result.error) {
270
+ setFetchError(result.error);
271
+ }
272
+ if (result.data) {
273
+ setRuntimeData({ [fetchConfig.schema]: result.data });
274
+ }
275
+ }
276
+ doFetch();
277
+ return () => {
278
+ cancelled = true;
279
+ };
280
+ }, [shouldFetchAtRuntime, fetchConfig]);
173
281
  if (!Component) {
174
282
  return /* @__PURE__ */ jsxs("div", { className: "block-error", style: { padding: "1rem", background: "#fef2f2", color: "#dc2626" }, children: [
175
283
  "Component not found: ",
@@ -191,6 +299,9 @@ function BlockRenderer({ block, pure = false, extra = {} }) {
191
299
  _prosemirror: block.parsedContent
192
300
  // Keep original for components that need raw access
193
301
  };
302
+ if (runtimeData && shouldFetchAtRuntime) {
303
+ content.data = mergeIntoData(content.data, runtimeData[fetchConfig.schema], fetchConfig.schema, fetchConfig.merge);
304
+ }
194
305
  }
195
306
  const componentProps = {
196
307
  content,
package/dist/ssr.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ssr.js","sources":["../src/prepare-props.js","../src/components/BlockRenderer.jsx","../src/components/Blocks.jsx","../src/components/Layout.jsx","../src/ssr.js"],"sourcesContent":["/**\n * Props Preparation for Runtime Guarantees\n *\n * Prepares props for foundation components with:\n * - Param defaults from runtime schema\n * - Guaranteed content structure (no null checks needed)\n *\n * This enables simpler component code by ensuring predictable prop shapes.\n */\n\n/**\n * Guarantee item has flat content structure\n *\n * @param {Object} item - Raw item from parser\n * @returns {Object} Item with guaranteed flat structure\n */\nfunction guaranteeItemStructure(item) {\n return {\n title: item.title || '',\n pretitle: item.pretitle || '',\n subtitle: item.subtitle || '',\n paragraphs: item.paragraphs || [],\n links: item.links || [],\n imgs: item.imgs || [],\n lists: item.lists || [],\n icons: item.icons || [],\n videos: item.videos || [],\n buttons: item.buttons || [],\n data: item.data || {},\n cards: item.cards || [],\n documents: item.documents || [],\n forms: item.forms || [],\n quotes: item.quotes || [],\n headings: item.headings || [],\n }\n}\n\n/**\n * Guarantee content structure exists\n * Returns a flat content object with all standard fields guaranteed to exist\n *\n * @param {Object} parsedContent - Raw parsed content from semantic parser (flat structure)\n * @returns {Object} Content with guaranteed flat structure\n */\nexport function guaranteeContentStructure(parsedContent) {\n const content = parsedContent || {}\n\n return {\n // Flat header fields\n title: content.title || '',\n pretitle: content.pretitle || '',\n subtitle: content.subtitle || '',\n subtitle2: content.subtitle2 || '',\n alignment: content.alignment || null,\n\n // Flat body fields\n paragraphs: content.paragraphs || [],\n links: content.links || [],\n imgs: content.imgs || [],\n lists: content.lists || [],\n icons: content.icons || [],\n videos: content.videos || [],\n buttons: content.buttons || [],\n data: content.data || {},\n cards: content.cards || [],\n documents: content.documents || [],\n forms: content.forms || [],\n quotes: content.quotes || [],\n headings: content.headings || [],\n\n // Items with guaranteed structure\n items: (content.items || []).map(guaranteeItemStructure),\n\n // Sequence for ordered rendering\n sequence: content.sequence || [],\n\n // Preserve raw content if present\n raw: content.raw,\n }\n}\n\n/**\n * Apply a schema to a single object\n * Only processes fields defined in the schema, preserves unknown fields\n *\n * @param {Object} obj - The object to process\n * @param {Object} schema - Schema definition (fieldName -> fieldDef)\n * @returns {Object} Object with schema defaults applied\n */\nfunction applySchemaToObject(obj, schema) {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {\n return obj\n }\n\n const result = { ...obj }\n\n for (const [field, fieldDef] of Object.entries(schema)) {\n // Get the default value - handle both shorthand and full form\n const defaultValue = typeof fieldDef === 'object' ? fieldDef.default : undefined\n\n // Apply default if field is missing and default exists\n if (result[field] === undefined && defaultValue !== undefined) {\n result[field] = defaultValue\n }\n\n // For select fields with options, apply default if value is not among valid options\n if (typeof fieldDef === 'object' && fieldDef.options && Array.isArray(fieldDef.options)) {\n if (result[field] !== undefined && !fieldDef.options.includes(result[field])) {\n // Value exists but is not valid - apply default if available\n if (defaultValue !== undefined) {\n result[field] = defaultValue\n }\n }\n }\n\n // Handle nested object schema\n if (typeof fieldDef === 'object' && fieldDef.type === 'object' && fieldDef.schema && result[field]) {\n result[field] = applySchemaToObject(result[field], fieldDef.schema)\n }\n\n // Handle array with inline schema\n if (typeof fieldDef === 'object' && fieldDef.type === 'array' && fieldDef.of && result[field]) {\n if (typeof fieldDef.of === 'object') {\n result[field] = result[field].map(item => applySchemaToObject(item, fieldDef.of))\n }\n }\n }\n\n return result\n}\n\n/**\n * Apply a schema to a value (object or array of objects)\n *\n * @param {Object|Array} value - The value to process\n * @param {Object} schema - Schema definition\n * @returns {Object|Array} Value with schema defaults applied\n */\nfunction applySchemaToValue(value, schema) {\n if (Array.isArray(value)) {\n return value.map(item => applySchemaToObject(item, schema))\n }\n return applySchemaToObject(value, schema)\n}\n\n/**\n * Apply schemas to content.data\n * Only processes tags that have a matching schema, leaves others untouched\n *\n * @param {Object} data - The data object from content\n * @param {Object} schemas - Schema definitions from runtime meta\n * @returns {Object} Data with schemas applied\n */\nexport function applySchemas(data, schemas) {\n if (!schemas || !data || typeof data !== 'object') {\n return data || {}\n }\n\n const result = { ...data }\n\n for (const [tag, rawValue] of Object.entries(data)) {\n const schema = schemas[tag]\n if (!schema) continue // No schema for this tag - leave as-is\n\n result[tag] = applySchemaToValue(rawValue, schema)\n }\n\n return result\n}\n\n/**\n * Apply param defaults from runtime schema\n *\n * @param {Object} params - Params from frontmatter\n * @param {Object} defaults - Default values from runtime schema\n * @returns {Object} Merged params with defaults applied\n */\nexport function applyDefaults(params, defaults) {\n if (!defaults || Object.keys(defaults).length === 0) {\n return params || {}\n }\n\n return {\n ...defaults,\n ...(params || {}),\n }\n}\n\n/**\n * Prepare props for a component with runtime guarantees\n *\n * @param {Object} block - The block instance\n * @param {Object} meta - Runtime metadata for the component (from meta[componentName])\n * @returns {Object} Prepared props: { content, params }\n */\nexport function prepareProps(block, meta) {\n // Apply param defaults\n const defaults = meta?.defaults || {}\n const params = applyDefaults(block.properties, defaults)\n\n // Guarantee content structure\n const content = guaranteeContentStructure(block.parsedContent)\n\n // Apply schemas to content.data\n const schemas = meta?.schemas || null\n if (schemas && content.data) {\n content.data = applySchemas(content.data, schemas)\n }\n\n return { content, params }\n}\n\n/**\n * Get runtime metadata for a component from the global uniweb instance\n *\n * @param {string} componentName\n * @returns {Object|null}\n */\nexport function getComponentMeta(componentName) {\n return globalThis.uniweb?.getComponentMeta?.(componentName) || null\n}\n\n/**\n * Get default param values for a component\n *\n * @param {string} componentName\n * @returns {Object}\n */\nexport function getComponentDefaults(componentName) {\n return globalThis.uniweb?.getComponentDefaults?.(componentName) || {}\n}\n","/**\n * BlockRenderer\n *\n * Bridges Block data to foundation components.\n * Handles theming, wrapper props, and runtime guarantees.\n */\n\nimport React from 'react'\nimport { prepareProps, getComponentMeta } from '../prepare-props.js'\n\n/**\n * Convert hex color to rgba\n */\nconst hexToRgba = (hex, opacity) => {\n const r = parseInt(hex.slice(1, 3), 16)\n const g = parseInt(hex.slice(3, 5), 16)\n const b = parseInt(hex.slice(5, 7), 16)\n return `rgba(${r},${g},${b},${opacity})`\n}\n\n/**\n * Build wrapper props from block configuration\n */\nconst getWrapperProps = (block) => {\n const theme = block.themeName\n const blockClassName = block.state?.className || ''\n\n let className = theme || ''\n if (blockClassName) {\n className = className ? `${className} ${blockClassName}` : blockClassName\n }\n\n const { background = {}, colors = {} } = block.standardOptions\n const style = {}\n\n // Handle background modes\n if (background.mode === 'gradient') {\n const {\n enabled = false,\n start = 'transparent',\n end = 'transparent',\n angle = 0,\n startPosition = 0,\n endPosition = 100,\n startOpacity = 0.7,\n endOpacity = 0.3\n } = background.gradient || {}\n\n if (enabled) {\n style['--bg-color'] = `linear-gradient(${angle}deg,\n ${hexToRgba(start, startOpacity)} ${startPosition}%,\n ${hexToRgba(end, endOpacity)} ${endPosition}%)`\n }\n } else if (background.mode === 'image' || background.mode === 'video') {\n const settings = background[background.mode] || {}\n const { url = '', file = '' } = settings\n\n if (url || file) {\n style['--bg-color'] = 'transparent'\n style.position = 'relative'\n style.maxWidth = '100%'\n }\n }\n\n return {\n id: `Section${block.id}`,\n style,\n className\n }\n}\n\n/**\n * BlockRenderer component\n */\nexport default function BlockRenderer({ block, pure = false, extra = {} }) {\n const Component = block.initComponent()\n\n if (!Component) {\n return (\n <div className=\"block-error\" style={{ padding: '1rem', background: '#fef2f2', color: '#dc2626' }}>\n Component not found: {block.type}\n </div>\n )\n }\n\n // Build content and params with runtime guarantees\n // Sources:\n // 1. parsedContent._isPoc - simple PoC format (hardcoded content)\n // 2. parsedContent - semantic parser output (flat: title, paragraphs, links, etc.)\n // 3. block.properties - params from frontmatter (theme, alignment, etc.)\n // 4. meta - defaults from component meta.js\n let content, params\n\n if (block.parsedContent?._isPoc) {\n // Simple PoC format - content was passed directly\n content = block.parsedContent._pocContent\n params = block.properties\n } else {\n // Get runtime metadata for this component (has defaults, data binding, etc.)\n const meta = getComponentMeta(block.type)\n\n // Prepare props with runtime guarantees:\n // - Apply param defaults from meta.js\n // - Guarantee content structure exists\n const prepared = prepareProps(block, meta)\n params = prepared.params\n\n // Merge prepared content with raw access for components that need it\n content = {\n ...prepared.content,\n ...block.properties, // Frontmatter params overlay (legacy support)\n _prosemirror: block.parsedContent // Keep original for components that need raw access\n }\n }\n\n const componentProps = {\n content,\n params,\n block,\n input: block.input\n }\n\n if (pure) {\n return <Component {...componentProps} extra={extra} />\n }\n\n const wrapperProps = getWrapperProps(block)\n\n return (\n <div {...wrapperProps}>\n <Component {...componentProps} />\n </div>\n )\n}\n","/**\n * Blocks\n *\n * Renders an array of blocks for a layout area (header, body, footer, panels).\n * Used by the Layout component to pre-render each area.\n */\n\nimport React from 'react'\nimport BlockRenderer from './BlockRenderer.jsx'\n\n/**\n * Render a list of blocks\n *\n * @param {Object} props\n * @param {Block[]} props.blocks - Array of Block instances to render\n * @param {Object} [props.extra] - Extra props to pass to each block\n */\nexport default function Blocks({ blocks, extra = {} }) {\n if (!blocks || blocks.length === 0) return null\n\n return blocks.map((block, index) => (\n <React.Fragment key={block.id || index}>\n <BlockRenderer block={block} extra={extra} />\n </React.Fragment>\n ))\n}\n","/**\n * Layout\n *\n * Orchestrates page rendering by assembling layout areas (header, body, footer, panels).\n * Supports foundation-provided custom Layout components via website.getRemoteLayout().\n *\n * Layout Areas:\n * - header: Top navigation, branding (from @header page)\n * - body: Main page content (from page sections)\n * - footer: Bottom navigation, copyright (from @footer page)\n * - left: Left sidebar/panel (from @left page)\n * - right: Right sidebar/panel (from @right page)\n *\n * Custom Layouts:\n * Foundations can provide a custom Layout via src/exports.js:\n *\n * ```jsx\n * // src/exports.js\n * import Layout from './components/Layout'\n *\n * export default {\n * Layout,\n * props: {\n * themeToggleEnabled: true,\n * }\n * }\n * ```\n *\n * The Layout component receives pre-rendered areas as props:\n * - page, website: Runtime context\n * - header, body, footer: Pre-rendered React elements\n * - left, right (or leftPanel, rightPanel): Sidebar panels\n */\n\nimport Blocks from './Blocks.jsx'\n\n/**\n * Default layout - renders header, body, footer in sequence\n * (no panels in default layout)\n */\nfunction DefaultLayout({ header, body, footer }) {\n return (\n <>\n {header}\n {body}\n {footer}\n </>\n )\n}\n\n/**\n * Layout component\n *\n * @param {Object} props\n * @param {Page} props.page - Current page instance\n * @param {Website} props.website - Website instance\n */\nexport default function Layout({ page, website }) {\n // Check if foundation provides a custom Layout\n const RemoteLayout = website.getRemoteLayout()\n\n // Get block groups from page (respects layout preferences)\n const headerBlocks = page.getHeaderBlocks()\n const bodyBlocks = page.getBodyBlocks()\n const footerBlocks = page.getFooterBlocks()\n const leftBlocks = page.getLeftBlocks()\n const rightBlocks = page.getRightBlocks()\n\n // Pre-render each area as React elements\n const headerElement = headerBlocks ? <Blocks blocks={headerBlocks} /> : null\n const bodyElement = bodyBlocks ? <Blocks blocks={bodyBlocks} /> : null\n const footerElement = footerBlocks ? <Blocks blocks={footerBlocks} /> : null\n const leftElement = leftBlocks ? <Blocks blocks={leftBlocks} /> : null\n const rightElement = rightBlocks ? <Blocks blocks={rightBlocks} /> : null\n\n // Use foundation's custom Layout if provided\n if (RemoteLayout) {\n return (\n <RemoteLayout\n page={page}\n website={website}\n header={headerElement}\n body={bodyElement}\n footer={footerElement}\n left={leftElement}\n right={rightElement}\n // Aliases for backwards compatibility\n leftPanel={leftElement}\n rightPanel={rightElement}\n />\n )\n }\n\n // Default layout\n return (\n <DefaultLayout\n header={headerElement}\n body={bodyElement}\n footer={footerElement}\n />\n )\n}\n","/**\n * @uniweb/runtime/ssr - Server-Side Rendering Entry Point\n *\n * Node.js-compatible exports for SSG/prerendering.\n * This module is built to a standalone bundle that can be imported\n * directly by Node.js without Vite transpilation.\n *\n * Usage in prerender.js:\n * import { renderPage, Blocks, BlockRenderer } from '@uniweb/runtime/ssr'\n */\n\nimport React from 'react'\n\n// Props preparation (no browser APIs)\nexport {\n prepareProps,\n applySchemas,\n applyDefaults,\n guaranteeContentStructure,\n getComponentMeta,\n getComponentDefaults\n} from './prepare-props.js'\n\n// Components for rendering\nexport { default as BlockRenderer } from './components/BlockRenderer.jsx'\nexport { default as Blocks } from './components/Blocks.jsx'\nexport { default as Layout } from './components/Layout.jsx'\n\n// Re-export Layout's DefaultLayout for direct use\nimport LayoutComponent from './components/Layout.jsx'\n\n/**\n * Render a page to React elements\n *\n * This is the main entry point for SSG. It returns a React element\n * that can be passed to renderToString().\n *\n * @param {Object} props\n * @param {Page} props.page - The page instance to render\n * @param {Website} props.website - The website instance\n * @returns {React.ReactElement}\n */\nexport function PageElement({ page, website }) {\n return React.createElement(\n 'main',\n null,\n React.createElement(LayoutComponent, { page, website })\n )\n}\n"],"names":["LayoutComponent"],"mappings":"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"}
1
+ {"version":3,"file":"ssr.js","sources":["../src/prepare-props.js","../src/data-fetcher-client.js","../src/components/BlockRenderer.jsx","../src/components/Blocks.jsx","../src/components/Layout.jsx","../src/ssr.js"],"sourcesContent":["/**\n * Props Preparation for Runtime Guarantees\n *\n * Prepares props for foundation components with:\n * - Param defaults from runtime schema\n * - Guaranteed content structure (no null checks needed)\n *\n * This enables simpler component code by ensuring predictable prop shapes.\n */\n\n/**\n * Guarantee item has flat content structure\n *\n * @param {Object} item - Raw item from parser\n * @returns {Object} Item with guaranteed flat structure\n */\nfunction guaranteeItemStructure(item) {\n return {\n title: item.title || '',\n pretitle: item.pretitle || '',\n subtitle: item.subtitle || '',\n paragraphs: item.paragraphs || [],\n links: item.links || [],\n imgs: item.imgs || [],\n lists: item.lists || [],\n icons: item.icons || [],\n videos: item.videos || [],\n buttons: item.buttons || [],\n data: item.data || {},\n cards: item.cards || [],\n documents: item.documents || [],\n forms: item.forms || [],\n quotes: item.quotes || [],\n headings: item.headings || [],\n }\n}\n\n/**\n * Guarantee content structure exists\n * Returns a flat content object with all standard fields guaranteed to exist\n *\n * @param {Object} parsedContent - Raw parsed content from semantic parser (flat structure)\n * @returns {Object} Content with guaranteed flat structure\n */\nexport function guaranteeContentStructure(parsedContent) {\n const content = parsedContent || {}\n\n return {\n // Flat header fields\n title: content.title || '',\n pretitle: content.pretitle || '',\n subtitle: content.subtitle || '',\n subtitle2: content.subtitle2 || '',\n alignment: content.alignment || null,\n\n // Flat body fields\n paragraphs: content.paragraphs || [],\n links: content.links || [],\n imgs: content.imgs || [],\n lists: content.lists || [],\n icons: content.icons || [],\n videos: content.videos || [],\n buttons: content.buttons || [],\n data: content.data || {},\n cards: content.cards || [],\n documents: content.documents || [],\n forms: content.forms || [],\n quotes: content.quotes || [],\n headings: content.headings || [],\n\n // Items with guaranteed structure\n items: (content.items || []).map(guaranteeItemStructure),\n\n // Sequence for ordered rendering\n sequence: content.sequence || [],\n\n // Preserve raw content if present\n raw: content.raw,\n }\n}\n\n/**\n * Apply a schema to a single object\n * Only processes fields defined in the schema, preserves unknown fields\n *\n * @param {Object} obj - The object to process\n * @param {Object} schema - Schema definition (fieldName -> fieldDef)\n * @returns {Object} Object with schema defaults applied\n */\nfunction applySchemaToObject(obj, schema) {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {\n return obj\n }\n\n const result = { ...obj }\n\n for (const [field, fieldDef] of Object.entries(schema)) {\n // Get the default value - handle both shorthand and full form\n const defaultValue = typeof fieldDef === 'object' ? fieldDef.default : undefined\n\n // Apply default if field is missing and default exists\n if (result[field] === undefined && defaultValue !== undefined) {\n result[field] = defaultValue\n }\n\n // For select fields with options, apply default if value is not among valid options\n if (typeof fieldDef === 'object' && fieldDef.options && Array.isArray(fieldDef.options)) {\n if (result[field] !== undefined && !fieldDef.options.includes(result[field])) {\n // Value exists but is not valid - apply default if available\n if (defaultValue !== undefined) {\n result[field] = defaultValue\n }\n }\n }\n\n // Handle nested object schema\n if (typeof fieldDef === 'object' && fieldDef.type === 'object' && fieldDef.schema && result[field]) {\n result[field] = applySchemaToObject(result[field], fieldDef.schema)\n }\n\n // Handle array with inline schema\n if (typeof fieldDef === 'object' && fieldDef.type === 'array' && fieldDef.of && result[field]) {\n if (typeof fieldDef.of === 'object') {\n result[field] = result[field].map(item => applySchemaToObject(item, fieldDef.of))\n }\n }\n }\n\n return result\n}\n\n/**\n * Apply a schema to a value (object or array of objects)\n *\n * @param {Object|Array} value - The value to process\n * @param {Object} schema - Schema definition\n * @returns {Object|Array} Value with schema defaults applied\n */\nfunction applySchemaToValue(value, schema) {\n if (Array.isArray(value)) {\n return value.map(item => applySchemaToObject(item, schema))\n }\n return applySchemaToObject(value, schema)\n}\n\n/**\n * Apply schemas to content.data\n * Only processes tags that have a matching schema, leaves others untouched\n *\n * @param {Object} data - The data object from content\n * @param {Object} schemas - Schema definitions from runtime meta\n * @returns {Object} Data with schemas applied\n */\nexport function applySchemas(data, schemas) {\n if (!schemas || !data || typeof data !== 'object') {\n return data || {}\n }\n\n const result = { ...data }\n\n for (const [tag, rawValue] of Object.entries(data)) {\n const schema = schemas[tag]\n if (!schema) continue // No schema for this tag - leave as-is\n\n result[tag] = applySchemaToValue(rawValue, schema)\n }\n\n return result\n}\n\n/**\n * Apply param defaults from runtime schema\n *\n * @param {Object} params - Params from frontmatter\n * @param {Object} defaults - Default values from runtime schema\n * @returns {Object} Merged params with defaults applied\n */\nexport function applyDefaults(params, defaults) {\n if (!defaults || Object.keys(defaults).length === 0) {\n return params || {}\n }\n\n return {\n ...defaults,\n ...(params || {}),\n }\n}\n\n/**\n * Apply cascaded data based on component's inheritData setting\n *\n * @param {Object} localData - content.data from the section itself\n * @param {Object} cascadedData - Data from page/site level fetches\n * @param {boolean|Array} inheritData - Component's inheritData setting\n * @returns {Object} Merged data object\n */\nfunction applyCascadedData(localData, cascadedData, inheritData) {\n if (!inheritData || !cascadedData || Object.keys(cascadedData).length === 0) {\n return localData\n }\n\n if (inheritData === true) {\n // Inherit all: cascaded data as base, local data overrides\n return { ...cascadedData, ...localData }\n }\n\n if (Array.isArray(inheritData)) {\n // Selective: only specified schemas, local data takes precedence\n const result = { ...localData }\n for (const key of inheritData) {\n if (cascadedData[key] !== undefined && result[key] === undefined) {\n result[key] = cascadedData[key]\n }\n }\n return result\n }\n\n return localData\n}\n\n/**\n * Prepare props for a component with runtime guarantees\n *\n * @param {Object} block - The block instance\n * @param {Object} meta - Runtime metadata for the component (from meta[componentName])\n * @returns {Object} Prepared props: { content, params }\n */\nexport function prepareProps(block, meta) {\n // Apply param defaults\n const defaults = meta?.defaults || {}\n const params = applyDefaults(block.properties, defaults)\n\n // Guarantee content structure\n const content = guaranteeContentStructure(block.parsedContent)\n\n // Apply cascaded data based on component's inheritData setting\n const inheritData = meta?.inheritData\n const cascadedData = block.cascadedData || {}\n if (inheritData) {\n content.data = applyCascadedData(content.data, cascadedData, inheritData)\n }\n\n // Apply schemas to content.data\n const schemas = meta?.schemas || null\n if (schemas && content.data) {\n content.data = applySchemas(content.data, schemas)\n }\n\n return { content, params }\n}\n\n/**\n * Get runtime metadata for a component from the global uniweb instance\n *\n * @param {string} componentName\n * @returns {Object|null}\n */\nexport function getComponentMeta(componentName) {\n return globalThis.uniweb?.getComponentMeta?.(componentName) || null\n}\n\n/**\n * Get default param values for a component\n *\n * @param {string} componentName\n * @returns {Object}\n */\nexport function getComponentDefaults(componentName) {\n return globalThis.uniweb?.getComponentDefaults?.(componentName) || {}\n}\n","/**\n * Client-side Data Fetcher\n *\n * Executes fetch operations in the browser for runtime data loading.\n * Used when prerender: false is set on fetch configurations.\n *\n * @module @uniweb/runtime/data-fetcher-client\n */\n\n/**\n * Get a nested value from an object using dot notation\n *\n * @param {object} obj - Source object\n * @param {string} path - Dot-separated path (e.g., 'data.items')\n * @returns {any} The nested value or undefined\n */\nfunction getNestedValue(obj, path) {\n if (!obj || !path) return obj\n\n const parts = path.split('.')\n let current = obj\n\n for (const part of parts) {\n if (current === null || current === undefined) return undefined\n current = current[part]\n }\n\n return current\n}\n\n/**\n * Execute a fetch operation in the browser\n *\n * @param {object} config - Normalized fetch config\n * @param {string} config.path - Local path (relative to site root)\n * @param {string} config.url - Remote URL\n * @param {string} config.schema - Schema key for data\n * @param {string} config.transform - Optional path to extract from response\n * @returns {Promise<{ data: any, error?: string }>} Fetched data or error\n *\n * @example\n * const result = await executeFetchClient({\n * path: '/data/team.json',\n * schema: 'team'\n * })\n * // result.data contains the parsed JSON array\n */\nexport async function executeFetchClient(config) {\n if (!config) return { data: null }\n\n const { path, url, transform } = config\n\n try {\n // Determine the fetch URL\n // For local paths, they're relative to the site root (served from public/)\n // For remote URLs, use as-is\n const fetchUrl = path || url\n\n if (!fetchUrl) {\n return { data: [], error: 'No path or url specified' }\n }\n\n const response = await fetch(fetchUrl)\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n\n // Parse response based on content type\n const contentType = response.headers.get('content-type') || ''\n let data\n\n if (contentType.includes('application/json')) {\n data = await response.json()\n } else {\n // Try JSON first\n const text = await response.text()\n try {\n data = JSON.parse(text)\n } catch {\n // Return text as-is if not JSON\n console.warn('[data-fetcher] Response is not JSON, returning as text')\n data = text\n }\n }\n\n // Apply transform if specified (extract nested path)\n if (transform && data) {\n data = getNestedValue(data, transform)\n }\n\n return { data: data ?? [] }\n } catch (error) {\n console.warn(`[data-fetcher] Client fetch failed: ${error.message}`)\n return { data: [], error: error.message }\n }\n}\n\n/**\n * Merge fetched data into existing content.data\n *\n * @param {object} currentData - Current content.data object\n * @param {any} fetchedData - Data from fetch\n * @param {string} schema - Schema key to store under\n * @param {boolean} [merge=false] - If true, merge with existing; if false, replace\n * @returns {object} Updated data object\n */\nexport function mergeIntoData(currentData, fetchedData, schema, merge = false) {\n if (fetchedData === null || fetchedData === undefined || !schema) {\n return currentData\n }\n\n const result = { ...(currentData || {}) }\n\n if (merge && result[schema] !== undefined) {\n // Merge mode: combine with existing data\n const existing = result[schema]\n\n if (Array.isArray(existing) && Array.isArray(fetchedData)) {\n // Arrays: concatenate\n result[schema] = [...existing, ...fetchedData]\n } else if (\n typeof existing === 'object' &&\n existing !== null &&\n typeof fetchedData === 'object' &&\n fetchedData !== null &&\n !Array.isArray(existing) &&\n !Array.isArray(fetchedData)\n ) {\n // Objects: shallow merge\n result[schema] = { ...existing, ...fetchedData }\n } else {\n // Different types: fetched data wins\n result[schema] = fetchedData\n }\n } else {\n // Replace mode (default): fetched data overwrites\n result[schema] = fetchedData\n }\n\n return result\n}\n\nexport default executeFetchClient\n","/**\n * BlockRenderer\n *\n * Bridges Block data to foundation components.\n * Handles theming, wrapper props, and runtime guarantees.\n * Supports runtime data fetching for prerender: false configs.\n */\n\nimport React, { useState, useEffect } from 'react'\nimport { prepareProps, getComponentMeta } from '../prepare-props.js'\nimport { executeFetchClient, mergeIntoData } from '../data-fetcher-client.js'\n\n/**\n * Convert hex color to rgba\n */\nconst hexToRgba = (hex, opacity) => {\n const r = parseInt(hex.slice(1, 3), 16)\n const g = parseInt(hex.slice(3, 5), 16)\n const b = parseInt(hex.slice(5, 7), 16)\n return `rgba(${r},${g},${b},${opacity})`\n}\n\n/**\n * Build wrapper props from block configuration\n */\nconst getWrapperProps = (block) => {\n const theme = block.themeName\n const blockClassName = block.state?.className || ''\n\n let className = theme || ''\n if (blockClassName) {\n className = className ? `${className} ${blockClassName}` : blockClassName\n }\n\n const { background = {}, colors = {} } = block.standardOptions\n const style = {}\n\n // Handle background modes\n if (background.mode === 'gradient') {\n const {\n enabled = false,\n start = 'transparent',\n end = 'transparent',\n angle = 0,\n startPosition = 0,\n endPosition = 100,\n startOpacity = 0.7,\n endOpacity = 0.3\n } = background.gradient || {}\n\n if (enabled) {\n style['--bg-color'] = `linear-gradient(${angle}deg,\n ${hexToRgba(start, startOpacity)} ${startPosition}%,\n ${hexToRgba(end, endOpacity)} ${endPosition}%)`\n }\n } else if (background.mode === 'image' || background.mode === 'video') {\n const settings = background[background.mode] || {}\n const { url = '', file = '' } = settings\n\n if (url || file) {\n style['--bg-color'] = 'transparent'\n style.position = 'relative'\n style.maxWidth = '100%'\n }\n }\n\n return {\n id: `Section${block.id}`,\n style,\n className\n }\n}\n\n/**\n * BlockRenderer component\n */\nexport default function BlockRenderer({ block, pure = false, extra = {} }) {\n // State for runtime-fetched data (when prerender: false)\n const [runtimeData, setRuntimeData] = useState(null)\n const [fetchError, setFetchError] = useState(null)\n\n const Component = block.initComponent()\n\n // Runtime fetch for prerender: false configurations\n const fetchConfig = block.fetch\n const shouldFetchAtRuntime = fetchConfig && fetchConfig.prerender === false\n\n useEffect(() => {\n if (!shouldFetchAtRuntime) return\n\n let cancelled = false\n\n async function doFetch() {\n const result = await executeFetchClient(fetchConfig)\n if (cancelled) return\n\n if (result.error) {\n setFetchError(result.error)\n }\n if (result.data) {\n setRuntimeData({ [fetchConfig.schema]: result.data })\n }\n }\n\n doFetch()\n\n return () => {\n cancelled = true\n }\n }, [shouldFetchAtRuntime, fetchConfig])\n\n if (!Component) {\n return (\n <div className=\"block-error\" style={{ padding: '1rem', background: '#fef2f2', color: '#dc2626' }}>\n Component not found: {block.type}\n </div>\n )\n }\n\n // Build content and params with runtime guarantees\n // Sources:\n // 1. parsedContent._isPoc - simple PoC format (hardcoded content)\n // 2. parsedContent - semantic parser output (flat: title, paragraphs, links, etc.)\n // 3. block.properties - params from frontmatter (theme, alignment, etc.)\n // 4. meta - defaults from component meta.js\n let content, params\n\n if (block.parsedContent?._isPoc) {\n // Simple PoC format - content was passed directly\n content = block.parsedContent._pocContent\n params = block.properties\n } else {\n // Get runtime metadata for this component (has defaults, data binding, etc.)\n const meta = getComponentMeta(block.type)\n\n // Prepare props with runtime guarantees:\n // - Apply param defaults from meta.js\n // - Guarantee content structure exists\n // - Apply cascaded data based on inheritData\n const prepared = prepareProps(block, meta)\n params = prepared.params\n\n // Merge prepared content with raw access for components that need it\n content = {\n ...prepared.content,\n ...block.properties, // Frontmatter params overlay (legacy support)\n _prosemirror: block.parsedContent // Keep original for components that need raw access\n }\n\n // Merge runtime-fetched data if available\n if (runtimeData && shouldFetchAtRuntime) {\n content.data = mergeIntoData(content.data, runtimeData[fetchConfig.schema], fetchConfig.schema, fetchConfig.merge)\n }\n }\n\n const componentProps = {\n content,\n params,\n block,\n input: block.input\n }\n\n if (pure) {\n return <Component {...componentProps} extra={extra} />\n }\n\n const wrapperProps = getWrapperProps(block)\n\n return (\n <div {...wrapperProps}>\n <Component {...componentProps} />\n </div>\n )\n}\n","/**\n * Blocks\n *\n * Renders an array of blocks for a layout area (header, body, footer, panels).\n * Used by the Layout component to pre-render each area.\n */\n\nimport React from 'react'\nimport BlockRenderer from './BlockRenderer.jsx'\n\n/**\n * Render a list of blocks\n *\n * @param {Object} props\n * @param {Block[]} props.blocks - Array of Block instances to render\n * @param {Object} [props.extra] - Extra props to pass to each block\n */\nexport default function Blocks({ blocks, extra = {} }) {\n if (!blocks || blocks.length === 0) return null\n\n return blocks.map((block, index) => (\n <React.Fragment key={block.id || index}>\n <BlockRenderer block={block} extra={extra} />\n </React.Fragment>\n ))\n}\n","/**\n * Layout\n *\n * Orchestrates page rendering by assembling layout areas (header, body, footer, panels).\n * Supports foundation-provided custom Layout components via website.getRemoteLayout().\n *\n * Layout Areas:\n * - header: Top navigation, branding (from @header page)\n * - body: Main page content (from page sections)\n * - footer: Bottom navigation, copyright (from @footer page)\n * - left: Left sidebar/panel (from @left page)\n * - right: Right sidebar/panel (from @right page)\n *\n * Custom Layouts:\n * Foundations can provide a custom Layout via src/exports.js:\n *\n * ```jsx\n * // src/exports.js\n * import Layout from './components/Layout'\n *\n * export default {\n * Layout,\n * props: {\n * themeToggleEnabled: true,\n * }\n * }\n * ```\n *\n * The Layout component receives pre-rendered areas as props:\n * - page, website: Runtime context\n * - header, body, footer: Pre-rendered React elements\n * - left, right (or leftPanel, rightPanel): Sidebar panels\n */\n\nimport Blocks from './Blocks.jsx'\n\n/**\n * Default layout - renders header, body, footer in sequence\n * (no panels in default layout)\n */\nfunction DefaultLayout({ header, body, footer }) {\n return (\n <>\n {header}\n {body}\n {footer}\n </>\n )\n}\n\n/**\n * Layout component\n *\n * @param {Object} props\n * @param {Page} props.page - Current page instance\n * @param {Website} props.website - Website instance\n */\nexport default function Layout({ page, website }) {\n // Check if foundation provides a custom Layout\n const RemoteLayout = website.getRemoteLayout()\n\n // Get block groups from page (respects layout preferences)\n const headerBlocks = page.getHeaderBlocks()\n const bodyBlocks = page.getBodyBlocks()\n const footerBlocks = page.getFooterBlocks()\n const leftBlocks = page.getLeftBlocks()\n const rightBlocks = page.getRightBlocks()\n\n // Pre-render each area as React elements\n const headerElement = headerBlocks ? <Blocks blocks={headerBlocks} /> : null\n const bodyElement = bodyBlocks ? <Blocks blocks={bodyBlocks} /> : null\n const footerElement = footerBlocks ? <Blocks blocks={footerBlocks} /> : null\n const leftElement = leftBlocks ? <Blocks blocks={leftBlocks} /> : null\n const rightElement = rightBlocks ? <Blocks blocks={rightBlocks} /> : null\n\n // Use foundation's custom Layout if provided\n if (RemoteLayout) {\n return (\n <RemoteLayout\n page={page}\n website={website}\n header={headerElement}\n body={bodyElement}\n footer={footerElement}\n left={leftElement}\n right={rightElement}\n // Aliases for backwards compatibility\n leftPanel={leftElement}\n rightPanel={rightElement}\n />\n )\n }\n\n // Default layout\n return (\n <DefaultLayout\n header={headerElement}\n body={bodyElement}\n footer={footerElement}\n />\n )\n}\n","/**\n * @uniweb/runtime/ssr - Server-Side Rendering Entry Point\n *\n * Node.js-compatible exports for SSG/prerendering.\n * This module is built to a standalone bundle that can be imported\n * directly by Node.js without Vite transpilation.\n *\n * Usage in prerender.js:\n * import { renderPage, Blocks, BlockRenderer } from '@uniweb/runtime/ssr'\n */\n\nimport React from 'react'\n\n// Props preparation (no browser APIs)\nexport {\n prepareProps,\n applySchemas,\n applyDefaults,\n guaranteeContentStructure,\n getComponentMeta,\n getComponentDefaults\n} from './prepare-props.js'\n\n// Components for rendering\nexport { default as BlockRenderer } from './components/BlockRenderer.jsx'\nexport { default as Blocks } from './components/Blocks.jsx'\nexport { default as Layout } from './components/Layout.jsx'\n\n// Re-export Layout's DefaultLayout for direct use\nimport LayoutComponent from './components/Layout.jsx'\n\n/**\n * Render a page to React elements\n *\n * This is the main entry point for SSG. It returns a React element\n * that can be passed to renderToString().\n *\n * @param {Object} props\n * @param {Page} props.page - The page instance to render\n * @param {Website} props.website - The website instance\n * @returns {React.ReactElement}\n */\nexport function PageElement({ page, website }) {\n return React.createElement(\n 'main',\n null,\n React.createElement(LayoutComponent, { page, website })\n )\n}\n"],"names":["LayoutComponent"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/runtime",
3
- "version": "0.2.15",
3
+ "version": "0.2.16",
4
4
  "description": "Minimal runtime for loading Uniweb foundations",
5
5
  "type": "module",
6
6
  "exports": {
@@ -31,7 +31,7 @@
31
31
  "node": ">=20.19"
32
32
  },
33
33
  "dependencies": {
34
- "@uniweb/core": "0.1.12"
34
+ "@uniweb/core": "0.1.13"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@vitejs/plugin-react": "^4.5.2",
@@ -3,10 +3,12 @@
3
3
  *
4
4
  * Bridges Block data to foundation components.
5
5
  * Handles theming, wrapper props, and runtime guarantees.
6
+ * Supports runtime data fetching for prerender: false configs.
6
7
  */
7
8
 
8
- import React from 'react'
9
+ import React, { useState, useEffect } from 'react'
9
10
  import { prepareProps, getComponentMeta } from '../prepare-props.js'
11
+ import { executeFetchClient, mergeIntoData } from '../data-fetcher-client.js'
10
12
 
11
13
  /**
12
14
  * Convert hex color to rgba
@@ -73,8 +75,40 @@ const getWrapperProps = (block) => {
73
75
  * BlockRenderer component
74
76
  */
75
77
  export default function BlockRenderer({ block, pure = false, extra = {} }) {
78
+ // State for runtime-fetched data (when prerender: false)
79
+ const [runtimeData, setRuntimeData] = useState(null)
80
+ const [fetchError, setFetchError] = useState(null)
81
+
76
82
  const Component = block.initComponent()
77
83
 
84
+ // Runtime fetch for prerender: false configurations
85
+ const fetchConfig = block.fetch
86
+ const shouldFetchAtRuntime = fetchConfig && fetchConfig.prerender === false
87
+
88
+ useEffect(() => {
89
+ if (!shouldFetchAtRuntime) return
90
+
91
+ let cancelled = false
92
+
93
+ async function doFetch() {
94
+ const result = await executeFetchClient(fetchConfig)
95
+ if (cancelled) return
96
+
97
+ if (result.error) {
98
+ setFetchError(result.error)
99
+ }
100
+ if (result.data) {
101
+ setRuntimeData({ [fetchConfig.schema]: result.data })
102
+ }
103
+ }
104
+
105
+ doFetch()
106
+
107
+ return () => {
108
+ cancelled = true
109
+ }
110
+ }, [shouldFetchAtRuntime, fetchConfig])
111
+
78
112
  if (!Component) {
79
113
  return (
80
114
  <div className="block-error" style={{ padding: '1rem', background: '#fef2f2', color: '#dc2626' }}>
@@ -102,6 +136,7 @@ export default function BlockRenderer({ block, pure = false, extra = {} }) {
102
136
  // Prepare props with runtime guarantees:
103
137
  // - Apply param defaults from meta.js
104
138
  // - Guarantee content structure exists
139
+ // - Apply cascaded data based on inheritData
105
140
  const prepared = prepareProps(block, meta)
106
141
  params = prepared.params
107
142
 
@@ -111,6 +146,11 @@ export default function BlockRenderer({ block, pure = false, extra = {} }) {
111
146
  ...block.properties, // Frontmatter params overlay (legacy support)
112
147
  _prosemirror: block.parsedContent // Keep original for components that need raw access
113
148
  }
149
+
150
+ // Merge runtime-fetched data if available
151
+ if (runtimeData && shouldFetchAtRuntime) {
152
+ content.data = mergeIntoData(content.data, runtimeData[fetchConfig.schema], fetchConfig.schema, fetchConfig.merge)
153
+ }
114
154
  }
115
155
 
116
156
  const componentProps = {
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Client-side Data Fetcher
3
+ *
4
+ * Executes fetch operations in the browser for runtime data loading.
5
+ * Used when prerender: false is set on fetch configurations.
6
+ *
7
+ * @module @uniweb/runtime/data-fetcher-client
8
+ */
9
+
10
+ /**
11
+ * Get a nested value from an object using dot notation
12
+ *
13
+ * @param {object} obj - Source object
14
+ * @param {string} path - Dot-separated path (e.g., 'data.items')
15
+ * @returns {any} The nested value or undefined
16
+ */
17
+ function getNestedValue(obj, path) {
18
+ if (!obj || !path) return obj
19
+
20
+ const parts = path.split('.')
21
+ let current = obj
22
+
23
+ for (const part of parts) {
24
+ if (current === null || current === undefined) return undefined
25
+ current = current[part]
26
+ }
27
+
28
+ return current
29
+ }
30
+
31
+ /**
32
+ * Execute a fetch operation in the browser
33
+ *
34
+ * @param {object} config - Normalized fetch config
35
+ * @param {string} config.path - Local path (relative to site root)
36
+ * @param {string} config.url - Remote URL
37
+ * @param {string} config.schema - Schema key for data
38
+ * @param {string} config.transform - Optional path to extract from response
39
+ * @returns {Promise<{ data: any, error?: string }>} Fetched data or error
40
+ *
41
+ * @example
42
+ * const result = await executeFetchClient({
43
+ * path: '/data/team.json',
44
+ * schema: 'team'
45
+ * })
46
+ * // result.data contains the parsed JSON array
47
+ */
48
+ export async function executeFetchClient(config) {
49
+ if (!config) return { data: null }
50
+
51
+ const { path, url, transform } = config
52
+
53
+ try {
54
+ // Determine the fetch URL
55
+ // For local paths, they're relative to the site root (served from public/)
56
+ // For remote URLs, use as-is
57
+ const fetchUrl = path || url
58
+
59
+ if (!fetchUrl) {
60
+ return { data: [], error: 'No path or url specified' }
61
+ }
62
+
63
+ const response = await fetch(fetchUrl)
64
+
65
+ if (!response.ok) {
66
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`)
67
+ }
68
+
69
+ // Parse response based on content type
70
+ const contentType = response.headers.get('content-type') || ''
71
+ let data
72
+
73
+ if (contentType.includes('application/json')) {
74
+ data = await response.json()
75
+ } else {
76
+ // Try JSON first
77
+ const text = await response.text()
78
+ try {
79
+ data = JSON.parse(text)
80
+ } catch {
81
+ // Return text as-is if not JSON
82
+ console.warn('[data-fetcher] Response is not JSON, returning as text')
83
+ data = text
84
+ }
85
+ }
86
+
87
+ // Apply transform if specified (extract nested path)
88
+ if (transform && data) {
89
+ data = getNestedValue(data, transform)
90
+ }
91
+
92
+ return { data: data ?? [] }
93
+ } catch (error) {
94
+ console.warn(`[data-fetcher] Client fetch failed: ${error.message}`)
95
+ return { data: [], error: error.message }
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Merge fetched data into existing content.data
101
+ *
102
+ * @param {object} currentData - Current content.data object
103
+ * @param {any} fetchedData - Data from fetch
104
+ * @param {string} schema - Schema key to store under
105
+ * @param {boolean} [merge=false] - If true, merge with existing; if false, replace
106
+ * @returns {object} Updated data object
107
+ */
108
+ export function mergeIntoData(currentData, fetchedData, schema, merge = false) {
109
+ if (fetchedData === null || fetchedData === undefined || !schema) {
110
+ return currentData
111
+ }
112
+
113
+ const result = { ...(currentData || {}) }
114
+
115
+ if (merge && result[schema] !== undefined) {
116
+ // Merge mode: combine with existing data
117
+ const existing = result[schema]
118
+
119
+ if (Array.isArray(existing) && Array.isArray(fetchedData)) {
120
+ // Arrays: concatenate
121
+ result[schema] = [...existing, ...fetchedData]
122
+ } else if (
123
+ typeof existing === 'object' &&
124
+ existing !== null &&
125
+ typeof fetchedData === 'object' &&
126
+ fetchedData !== null &&
127
+ !Array.isArray(existing) &&
128
+ !Array.isArray(fetchedData)
129
+ ) {
130
+ // Objects: shallow merge
131
+ result[schema] = { ...existing, ...fetchedData }
132
+ } else {
133
+ // Different types: fetched data wins
134
+ result[schema] = fetchedData
135
+ }
136
+ } else {
137
+ // Replace mode (default): fetched data overwrites
138
+ result[schema] = fetchedData
139
+ }
140
+
141
+ return result
142
+ }
143
+
144
+ export default executeFetchClient
@@ -186,6 +186,38 @@ export function applyDefaults(params, defaults) {
186
186
  }
187
187
  }
188
188
 
189
+ /**
190
+ * Apply cascaded data based on component's inheritData setting
191
+ *
192
+ * @param {Object} localData - content.data from the section itself
193
+ * @param {Object} cascadedData - Data from page/site level fetches
194
+ * @param {boolean|Array} inheritData - Component's inheritData setting
195
+ * @returns {Object} Merged data object
196
+ */
197
+ function applyCascadedData(localData, cascadedData, inheritData) {
198
+ if (!inheritData || !cascadedData || Object.keys(cascadedData).length === 0) {
199
+ return localData
200
+ }
201
+
202
+ if (inheritData === true) {
203
+ // Inherit all: cascaded data as base, local data overrides
204
+ return { ...cascadedData, ...localData }
205
+ }
206
+
207
+ if (Array.isArray(inheritData)) {
208
+ // Selective: only specified schemas, local data takes precedence
209
+ const result = { ...localData }
210
+ for (const key of inheritData) {
211
+ if (cascadedData[key] !== undefined && result[key] === undefined) {
212
+ result[key] = cascadedData[key]
213
+ }
214
+ }
215
+ return result
216
+ }
217
+
218
+ return localData
219
+ }
220
+
189
221
  /**
190
222
  * Prepare props for a component with runtime guarantees
191
223
  *
@@ -201,6 +233,13 @@ export function prepareProps(block, meta) {
201
233
  // Guarantee content structure
202
234
  const content = guaranteeContentStructure(block.parsedContent)
203
235
 
236
+ // Apply cascaded data based on component's inheritData setting
237
+ const inheritData = meta?.inheritData
238
+ const cascadedData = block.cascadedData || {}
239
+ if (inheritData) {
240
+ content.data = applyCascadedData(content.data, cascadedData, inheritData)
241
+ }
242
+
204
243
  // Apply schemas to content.data
205
244
  const schemas = meta?.schemas || null
206
245
  if (schemas && content.data) {