All files / core/src/utils render-utils.js

75% Statements 15/20
80% Branches 12/15
83.33% Functions 5/6
73.68% Lines 14/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125                                          24x       24x         24x     19x                             24x   24x       24x                             3x       3x         3x                                             9x 4x     5x 5x                                    
/**
 * Shared rendering utilities for framework integrations
 * Eliminates code duplication across Express, Fastify, Koa, Next.js integrations
 */
 
import { render } from '../rendering/html-renderer.js';
import { performanceMonitor } from '../performance/monitor.js';
 
/**
 * Render a component with optional performance monitoring
 * This is the canonical rendering function used by all framework integrations
 * 
 * @param {Object} component - Coherent.js component to render
 * @param {Object} options - Rendering options
 * @param {boolean} options.enablePerformanceMonitoring - Enable performance tracking
 * @param {string} options.template - HTML template with {{content}} placeholder
 * @returns {string} Rendered HTML
 */
export function renderWithMonitoring(component, options = {}) {
  const {
    enablePerformanceMonitoring = false
  } = options;
 
  let html;
 
  Iif (enablePerformanceMonitoring) {
    const renderId = performanceMonitor.startRender();
    html = render(component);
    performanceMonitor.endRender(renderId);
  } else {
    html = render(component);
  }
 
  return html;
}
 
/**
 * Render a component and apply an HTML template
 * 
 * @param {Object} component - Coherent.js component to render
 * @param {Object} options - Rendering options
 * @param {boolean} options.enablePerformanceMonitoring - Enable performance tracking
 * @param {string} options.template - HTML template with {{content}} placeholder
 * @returns {string} Final HTML with template applied
 */
export function renderWithTemplate(component, options = {}) {
  const {
    template = '<!DOCTYPE html>\n{{content}}'
  } = options;
 
  const html = renderWithMonitoring(component, options);
  // Replacer function, not string: a string replacement expands `$&`, `$'`,
  // `` $` `` and `$$`, so page text like "Pay $$10" would be corrupted and
  // "$'" would splice the rest of the template into the page.
  return template.replace('{{content}}', () => html);
}
 
/**
 * Create a component factory handler for framework integrations
 * Handles component creation, rendering, and error handling
 * 
 * @param {Function} componentFactory - Function that creates a component
 * @param {Object} factoryArgs - Arguments to pass to the component factory
 * @param {Object} options - Rendering options
 * @returns {Promise<string>} Rendered HTML
 * @throws {Error} If component factory returns null/undefined or rendering fails
 */
export async function renderComponentFactory(componentFactory, factoryArgs, options = {}) {
  // Create component with provided arguments
  const component = await Promise.resolve(
    componentFactory(...factoryArgs)
  );
 
  Iif (!component) {
    throw new Error('Component factory returned null/undefined');
  }
 
  // Render with template
  return renderWithTemplate(component, options);
}
 
/**
 * Structural check for the *shape* of a Coherent.js element: a non-array
 * object with exactly one own key (the tag name).
 *
 * This is a heuristic, not a type test. It cannot tell a component from an
 * ordinary JSON payload that happens to have one key — `{ ok: true }`,
 * `{ users: [...] }`, `{ error: 'Invalid credentials' }` all return `true` —
 * and tag-name lists do not help, because `data`, `meta`, `title`, `label`,
 * `summary` and `code` are both HTML tags and common JSON keys.
 *
 * Framework integrations therefore only use it when auto-rendering has been
 * opted into explicitly (`autoRender: true`); by default they render only
 * what is handed to their explicit APIs (`res.coherent()`,
 * `reply.coherent()`, `ctx.coherent()`, the handler factories). Do not use it
 * to decide how to serialize data you do not control.
 *
 * @param {any} obj - Value to check
 * @returns {boolean} True if the value has the shape of a Coherent.js element
 */
export function isCoherentComponent(obj) {
  if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
    return false;
  }
 
  const keys = Object.keys(obj);
  return keys.length === 1;
}
 
/**
 * Create a standardized error response for framework integrations
 * 
 * @param {Error} error - The error that occurred
 * @param {string} context - Context where the error occurred
 * @returns {Object} Error response object
 */
export function createErrorResponse(error, context = 'rendering') {
  return {
    error: 'Internal Server Error',
    message: error.message,
    context,
    timestamp: new Date().toISOString()
  };
}