@coherent.js/integrations 1.1.2 → 2.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.
@@ -6,10 +6,44 @@
6
6
  import {
7
7
  render,
8
8
  performanceMonitor,
9
- importPeerDependency,
10
9
  renderComponentFactory
11
10
  } from '@coherent.js/core';
12
11
 
12
+ function missingPeer(packageName, integrationName, cause) {
13
+ const error = new Error(
14
+ `${integrationName} requires the '${packageName}' package to be installed.\n` +
15
+ `Please install it with: npm install ${packageName} (or pnpm add / yarn add)`
16
+ );
17
+ error.cause = cause;
18
+ return error;
19
+ }
20
+
21
+ /**
22
+ * The React module to build elements with.
23
+ *
24
+ * An injected `options.React` wins. Otherwise React is imported from *this*
25
+ * package: it declares `react` as an optional peer, so package managers link
26
+ * the app's copy next to it. (core's `importPeerDependency` imports relative
27
+ * to @coherent.js/core instead, which cannot see the app's `react` under
28
+ * pnpm's isolated layout.) The literal specifier keeps it bundler-friendly.
29
+ *
30
+ * @param {Object} [injected] - A React module or namespace supplied by the app
31
+ * @param {string} integrationName - Used in the error message
32
+ * @returns {Promise<Object>} Object exposing createElement / useState / useEffect
33
+ */
34
+ async function loadReact(injected, integrationName) {
35
+ let mod = injected;
36
+ if (!mod) {
37
+ try {
38
+ mod = await import('react');
39
+ } catch (_error) {
40
+ throw missingPeer('react', integrationName, _error);
41
+ }
42
+ }
43
+ // Namespace imports of CommonJS React carry the API on `default`.
44
+ return typeof mod.createElement === 'function' ? mod : mod.default;
45
+ }
46
+
13
47
  /**
14
48
  * Create a Next.js API route handler for Coherent.js components
15
49
  *
@@ -33,8 +67,8 @@ export function createCoherentNextHandler(componentFactory, options = {}) {
33
67
  res.setHeader('Content-Type', 'text/html; charset=utf-8');
34
68
  res.status(200).send(finalHtml);
35
69
  } catch (_error) {
36
- console.error('Coherent.js Next.js handler _error:', _error);
37
- res.status(500).json({ _error: _error.message });
70
+ console.error('Coherent.js Next.js handler error:', _error);
71
+ res.status(500).json({ error: _error.message });
38
72
  }
39
73
  };
40
74
  }
@@ -42,17 +76,21 @@ export function createCoherentNextHandler(componentFactory, options = {}) {
42
76
  /**
43
77
  * Create a Next.js App Router route handler for Coherent.js components
44
78
  *
45
- * @param {Function} componentFactory - Function that returns a Coherent.js component
79
+ * The factory receives the same `(request, context)` pair Next.js passes to
80
+ * route handlers, so dynamic segments are available as `context.params`
81
+ * (a Promise since Next.js 15).
82
+ *
83
+ * @param {Function} componentFactory - `(request, context) => component`
46
84
  * @param {Object} options - Handler options
47
85
  * @returns {Function} Next.js App Router route handler
48
86
  */
49
87
  export function createCoherentAppRouterHandler(componentFactory, options = {}) {
50
- return async function handler(request) {
88
+ return async function handler(request, context) {
51
89
  try {
52
90
  // Use shared rendering utility
53
91
  const finalHtml = await renderComponentFactory(
54
92
  componentFactory,
55
- [request],
93
+ [request, context],
56
94
  options
57
95
  );
58
96
 
@@ -62,9 +100,9 @@ export function createCoherentAppRouterHandler(componentFactory, options = {}) {
62
100
  headers: { 'Content-Type': 'text/html; charset=utf-8' }
63
101
  });
64
102
  } catch (_error) {
65
- console.error('Coherent.js Next.js App Router handler _error:', _error);
103
+ console.error('Coherent.js Next.js App Router handler error:', _error);
66
104
  return new Response(
67
- JSON.stringify({ _error: _error.message }),
105
+ JSON.stringify({ error: _error.message }),
68
106
  {
69
107
  status: 500,
70
108
  headers: { 'Content-Type': 'application/json' }
@@ -79,22 +117,16 @@ export function createCoherentAppRouterHandler(componentFactory, options = {}) {
79
117
  *
80
118
  * @param {Function} componentFactory - Function that returns a Coherent.js component
81
119
  * @param {Object} options - Component options
82
- * @returns {Function} Next.js Server Component
120
+ * @param {boolean} [options.enablePerformanceMonitoring=false] - Enable performance monitoring
121
+ * @param {Object} [options.React] - React module to use instead of importing `react`
122
+ * @returns {Promise<Function>} Next.js Server Component
83
123
  */
84
124
  export async function createCoherentServerComponent(componentFactory, options = {}) {
85
125
  const {
86
126
  enablePerformanceMonitoring = false
87
127
  } = options;
88
128
 
89
- // Import React using dependency utilities
90
- let React;
91
- try {
92
- React = await importPeerDependency('react', 'React');
93
- } catch (_error) {
94
- throw new Error(
95
- `Next.js Server Component integration requires React. ${ _error.message}`
96
- );
97
- }
129
+ const React = await loadReact(options.React, 'Next.js Server Component integration');
98
130
 
99
131
  return async function CoherentServerComponent(props) {
100
132
  try {
@@ -104,7 +136,7 @@ export async function createCoherentServerComponent(componentFactory, options =
104
136
  );
105
137
 
106
138
  if (!component) {
107
- return React.default.createElement('div', null, 'Error: Component factory returned null/undefined');
139
+ return React.createElement('div', null, 'Error: Component factory returned null/undefined');
108
140
  }
109
141
 
110
142
  // Render component
@@ -118,12 +150,12 @@ export async function createCoherentServerComponent(componentFactory, options =
118
150
  }
119
151
 
120
152
  // Return dangerouslySetInnerHTML to render HTML
121
- return React.default.createElement('div', {
153
+ return React.createElement('div', {
122
154
  dangerouslySetInnerHTML: { __html: html }
123
155
  });
124
156
  } catch (_error) {
125
- console.error('Coherent.js Next.js Server Component _error:', _error);
126
- return React.default.createElement('div', null, `Error: ${_error.message}`);
157
+ console.error('Coherent.js Next.js Server Component error:', _error);
158
+ return React.createElement('div', null, `Error: ${_error.message}`);
127
159
  }
128
160
  };
129
161
  }
@@ -133,22 +165,16 @@ export async function createCoherentServerComponent(componentFactory, options =
133
165
  *
134
166
  * @param {Function} componentFactory - Function that returns a Coherent.js component
135
167
  * @param {Object} options - Component options
136
- * @returns {Function} Next.js Client Component
168
+ * @param {boolean} [options.enablePerformanceMonitoring=false] - Enable performance monitoring
169
+ * @param {Object} [options.React] - React module to use instead of importing `react`
170
+ * @returns {Promise<Function>} Next.js Client Component
137
171
  */
138
172
  export async function createCoherentClientComponent(componentFactory, options = {}) {
139
173
  const {
140
174
  enablePerformanceMonitoring = false
141
175
  } = options;
142
176
 
143
- // Import React using dependency utilities
144
- let React;
145
- try {
146
- React = await importPeerDependency('react', 'React');
147
- } catch (_error) {
148
- throw new Error(
149
- `Next.js Client Component integration requires React. ${ _error.message}`
150
- );
151
- }
177
+ const React = await loadReact(options.React, 'Next.js Client Component integration');
152
178
 
153
179
  return function CoherentClientComponent(props) {
154
180
  const [html, setHtml] = React.useState('');
@@ -178,7 +204,7 @@ export async function createCoherentClientComponent(componentFactory, options =
178
204
 
179
205
  setHtml(renderedHtml);
180
206
  } catch (_error) {
181
- console.error('Coherent.js Next.js Client Component _error:', _error);
207
+ console.error('Coherent.js Next.js Client Component error:', _error);
182
208
  setHtml(`Error: ${_error.message}`);
183
209
  }
184
210
  }
@@ -201,9 +227,14 @@ export async function createCoherentClientComponent(componentFactory, options =
201
227
  */
202
228
  export async function createNextIntegration(options = {}) {
203
229
  try {
204
- // Verify Next.js and React are available
205
- await importPeerDependency('next', 'Next.js');
206
- await importPeerDependency('react', 'React');
230
+ // Verify Next.js and React are available, resolved from this package
231
+ // (see loadReact for why not core's importPeerDependency)
232
+ try {
233
+ await import('next');
234
+ } catch (_error) {
235
+ throw missingPeer('next', 'Next.js integration', _error);
236
+ }
237
+ await loadReact(options.React, 'Next.js integration');
207
238
 
208
239
  return {
209
240
  createCoherentNextHandler: (componentFactory, handlerOptions = {}) =>
@@ -3,11 +3,13 @@
3
3
  // Public entry point for @coherent.js/integrations/remix. Provides SSR
4
4
  // integration and loader utilities for Remix projects.
5
5
  //
6
- // Remix must be installed as a peer dependency to use this integration.
6
+ // Remix (and therefore React) must be installed as a peer dependency to use
7
+ // this integration.
7
8
  //
8
9
  // Usage:
9
10
  // import { createRemixAdapter } from '@coherent.js/integrations/remix';
10
11
 
12
+ import { createElement } from 'react';
11
13
  import { render } from '@coherent.js/core';
12
14
 
13
15
  /**
@@ -75,12 +77,22 @@ export function createRemixAdapter(_options = {}) {
75
77
  /**
76
78
  * HOC: Wrap a Coherent.js component for use in Remix routes
77
79
  *
78
- * @param {Function} Component - Coherent.js component function
79
- * @returns {Function} Remix-compatible component
80
+ * Returns a React component that renders the Coherent.js markup inside a
81
+ * wrapper element via `dangerouslySetInnerHTML`. Returning the HTML string
82
+ * itself would make React escape it and show the tags as text. The
83
+ * Coherent.js renderer escapes text and attribute values, so only markup the
84
+ * component describes (or passes through its explicit `html` field) is raw.
85
+ *
86
+ * @param {Function|Object} Component - Coherent.js component function or object
87
+ * @param {Object} [options] - Wrapper options
88
+ * @param {string} [options.as='div'] - Tag name of the wrapper element
89
+ * @returns {Function} Remix-compatible React component
80
90
  */
81
- export function withCoherent(Component) {
91
+ export function withCoherent(Component, options = {}) {
92
+ const { as = 'div' } = options;
93
+
82
94
  return function CoherentRemixComponent(props) {
83
95
  const def = typeof Component === 'function' ? Component(props) : Component;
84
- return render(def);
96
+ return createElement(as, { dangerouslySetInnerHTML: { __html: render(def) } });
85
97
  };
86
98
  }
@@ -61,10 +61,40 @@ export function createSvelteKitAdapter(_options = {}) {
61
61
  };
62
62
  }
63
63
 
64
+ /** Local name the preprocessor imports Coherent.js's `render` under. */
65
+ const RENDER_IDENTIFIER = '__coherentRender';
66
+ const RENDER_IMPORT = `import { render as ${RENDER_IDENTIFIER} } from '@coherent.js/core';`;
67
+
68
+ /** `<script context="module">` (Svelte 4) or `<script module>` (Svelte 5). */
69
+ function isModuleScript(attributes) {
70
+ return /\bcontext\s*=\s*["']?module\b/.test(attributes) || /(?:^|\s)module(?:\s|=|$)/.test(attributes);
71
+ }
72
+
73
+ /**
74
+ * Make `RENDER_IDENTIFIER` available to the markup: add the import to the
75
+ * instance `<script>`, or add an instance script if there is none. Inserted
76
+ * without newlines so the component's line numbers do not move.
77
+ */
78
+ function injectRenderImport(code) {
79
+ // Case-insensitive like HTML: an instance script written <SCRIPT> was
80
+ // missed, and a second instance script was added next to it.
81
+ for (const match of code.matchAll(/<script\b([^>]*)>/gi)) {
82
+ if (!isModuleScript(match[1])) {
83
+ const at = match.index + match[0].length;
84
+ return `${code.slice(0, at)}${RENDER_IMPORT}${code.slice(at)}`;
85
+ }
86
+ }
87
+ return `<script>${RENDER_IMPORT}</script>${code}`;
88
+ }
89
+
64
90
  /**
65
91
  * Create a Svelte preprocessor for Coherent.js templates
66
92
  *
67
- * Transforms inline Coherent.js object syntax within Svelte components.
93
+ * Replaces each `<coherent>{ ...object literal... }</coherent>` block with
94
+ * `{@html ...}` of the rendered component, and imports `render` from
95
+ * `@coherent.js/core` into the component's instance script so the generated
96
+ * expression resolves. The renderer escapes text content, so `{@html}` only
97
+ * emits markup the component itself describes.
68
98
  *
69
99
  * @param {Object} [options] - Preprocessor options
70
100
  * @param {string} [options.tag] - Custom tag to process (default: 'coherent')
@@ -76,24 +106,18 @@ export function createPreprocessor(options = {}) {
76
106
  return {
77
107
  name: 'coherent-preprocessor',
78
108
  markup({ content, filename: _filename }) {
79
- // Find <coherent> blocks and render them to HTML
109
+ // Find <coherent> blocks; their content is a JS expression (usually an object literal)
80
110
  const regex = new RegExp(`<${tag}>(.*?)</${tag}>`, 'gs');
81
- let transformed = content;
82
- let match;
83
-
84
- while ((match = regex.exec(content)) !== null) {
85
- try {
86
- // The content inside <coherent> tags should be a JS object literal
87
- // This is a basic transform — production use should handle more cases
88
- const objectStr = match[1].trim();
89
- transformed = transformed.replace(match[0], `{@html coherentRender(${objectStr})}`);
90
- } catch {
91
- // Leave unchanged if parsing fails
92
- }
93
- }
111
+ let found = false;
112
+
113
+ // Replacer function, so `$&`-style sequences in the block stay literal
114
+ const transformed = content.replace(regex, (_block, expression) => {
115
+ found = true;
116
+ return `{@html ${RENDER_IDENTIFIER}(${expression.trim()})}`;
117
+ });
94
118
 
95
119
  return {
96
- code: transformed,
120
+ code: found ? injectRenderImport(transformed) : content,
97
121
  map: null
98
122
  };
99
123
  }
@@ -0,0 +1,41 @@
1
+ // Type definitions for Coherent.js Astro integration.
2
+ //
3
+ // Aligned with the runtime exports of ../../src/astro/index.js.
4
+
5
+ import type { AstroIntegration } from 'astro';
6
+ import type { CoherentNode, RenderOptions } from '@coherent.js/core';
7
+
8
+ export interface CoherentAstroOptions {
9
+ /** Pre-bundle `@coherent.js/client` for client-side hydration. */
10
+ hydrate?: boolean;
11
+ /** Custom hydration script path. */
12
+ hydrateScript?: string;
13
+ }
14
+
15
+ /** A Coherent.js component: a node, or a function of props returning one. */
16
+ export type CoherentAstroComponent<Props = Record<string, unknown>> =
17
+ | CoherentNode
18
+ | ((props: Props) => CoherentNode);
19
+
20
+ /** The SSR renderer Astro loads from the integration's server entrypoint. */
21
+ export interface CoherentAstroRenderer {
22
+ name: string;
23
+ check(Component: unknown, props?: Record<string, unknown>): boolean;
24
+ renderToStaticMarkup(Component: unknown, props?: Record<string, unknown>): { html: string };
25
+ }
26
+
27
+ /**
28
+ * Astro integration that registers the Coherent.js renderer
29
+ * (`@coherent.js/integrations/astro/server`).
30
+ */
31
+ export function createAstroIntegration(options?: CoherentAstroOptions): AstroIntegration;
32
+
33
+ /** Render a Coherent.js component (or component function) to an HTML string. */
34
+ export function renderComponent<Props = Record<string, unknown>>(
35
+ Component: CoherentAstroComponent<Props>,
36
+ props?: Props,
37
+ renderOptions?: RenderOptions
38
+ ): string;
39
+
40
+ /** Create an Astro SSR renderer for Coherent.js components. */
41
+ export function createRenderer(options?: RenderOptions): CoherentAstroRenderer;
@@ -0,0 +1,9 @@
1
+ // Type definitions for the Coherent.js Astro server entrypoint
2
+ // (../../src/astro/server.js): Astro imports its default export as the
3
+ // SSR renderer.
4
+
5
+ import type { CoherentAstroRenderer } from './index.js';
6
+
7
+ declare const renderer: CoherentAstroRenderer;
8
+
9
+ export default renderer;
@@ -12,7 +12,7 @@ export interface CoherentMiddlewareOptions {
12
12
 
13
13
  /**
14
14
  * HTML template to wrap rendered components
15
- * @default '<!DOCTYPE html><html><body>{{content}}</body></html>'
15
+ * @default '<!DOCTYPE html>\n{{content}}'
16
16
  */
17
17
  template?: string;
18
18
 
@@ -21,6 +21,26 @@ export interface CoherentMiddlewareOptions {
21
21
  * @default true
22
22
  */
23
23
  enableSSR?: boolean;
24
+
25
+ /**
26
+ * Also render component-shaped objects passed to `res.send()`.
27
+ *
28
+ * Detection is a heuristic: every single-key object qualifies, so JSON
29
+ * payloads such as `{ ok: true }` or `{ users: [...] }` would be rendered
30
+ * as HTML. Use `res.coherent(component)` instead unless the app never
31
+ * sends single-key JSON objects.
32
+ * @default false
33
+ */
34
+ autoRender?: boolean;
35
+ }
36
+
37
+ /**
38
+ * Per-call options for `res.coherent()`; each falls back to the middleware's.
39
+ */
40
+ export interface CoherentRenderOptions {
41
+ enablePerformanceMonitoring?: boolean;
42
+ /** HTML template with a `{{content}}` placeholder. */
43
+ template?: string;
24
44
  }
25
45
 
26
46
  export interface CoherentHandlerOptions {
@@ -32,7 +52,7 @@ export interface CoherentHandlerOptions {
32
52
 
33
53
  /**
34
54
  * HTML template to wrap rendered components
35
- * @default '<!DOCTYPE html><html><body>{{content}}</body></html>'
55
+ * @default '<!DOCTYPE html>\n{{content}}'
36
56
  */
37
57
  template?: string;
38
58
 
@@ -51,13 +71,15 @@ export interface SetupCoherentExpressOptions {
51
71
  useMiddleware?: boolean;
52
72
 
53
73
  /**
54
- * Use Coherent.js view engine
55
- * @default true
74
+ * Register {@link enhancedExpressEngine} as a view engine. It becomes the
75
+ * app's default `view engine` only if none is set yet.
76
+ * @default false
56
77
  */
57
78
  useEngine?: boolean;
58
-
79
+
59
80
  /**
60
- * Name of the view engine
81
+ * Name of the view engine, i.e. the view file extension. Use `'js'` to
82
+ * render `views/*.js` modules whose default export is the component.
61
83
  * @default 'coherent'
62
84
  */
63
85
  engineName?: string;
@@ -67,7 +89,20 @@ export interface SetupCoherentExpressOptions {
67
89
  * @default false
68
90
  */
69
91
  enablePerformanceMonitoring?: boolean;
70
-
92
+
93
+ /**
94
+ * HTML template with a `{{content}}` placeholder, used by `res.coherent()`
95
+ * @default '<!DOCTYPE html>\n{{content}}'
96
+ */
97
+ template?: string;
98
+
99
+ /**
100
+ * Also render component-shaped objects passed to `res.send()`.
101
+ * See {@link CoherentMiddlewareOptions.autoRender} for why this is opt-in.
102
+ * @default false
103
+ */
104
+ autoRender?: boolean;
105
+
71
106
  /**
72
107
  * Static file directory for client-side assets
73
108
  * @default 'public'
@@ -75,9 +110,23 @@ export interface SetupCoherentExpressOptions {
75
110
  staticDir?: string;
76
111
  }
77
112
 
113
+ declare global {
114
+ namespace Express {
115
+ interface Response {
116
+ /**
117
+ * Render a Coherent.js component (wrapped in the middleware's template)
118
+ * and send it as `text/html`. Rendering errors are passed to the app's
119
+ * error middleware, as `res.render()` does. Added by
120
+ * `coherentMiddleware()` / `setupCoherent()`.
121
+ */
122
+ coherent(component: CoherentNode, options?: CoherentRenderOptions): this;
123
+ }
124
+ }
125
+ }
126
+
78
127
  /**
79
128
  * Coherent.js Express middleware
80
- * Adds Coherent.js rendering capabilities to Express
129
+ * Adds `res.coherent()` (and, with `autoRender`, `res.send` rendering)
81
130
  * @param options Configuration options
82
131
  * @returns Express middleware function
83
132
  */
@@ -89,19 +138,29 @@ export function coherentMiddleware(options?: CoherentMiddlewareOptions): (
89
138
 
90
139
  /**
91
140
  * Create an Express route handler for Coherent.js components
92
- * @param componentFactory Function that returns a Coherent component
141
+ * @param componentFactory Function that returns a Coherent component. If it
142
+ * responds itself (e.g. `res.redirect()`), its return value is ignored.
93
143
  * @param options Configuration options
94
144
  * @returns Express route handler
95
145
  */
96
146
  export function createCoherentHandler(
97
- componentFactory: (req: Request, res: Response, next: NextFunction) => CoherentNode | Promise<CoherentNode>,
147
+ componentFactory: (
148
+ req: Request,
149
+ res: Response,
150
+ next: NextFunction
151
+ ) => CoherentNode | void | Promise<CoherentNode | void>,
98
152
  options?: CoherentHandlerOptions
99
- ): (req: Request, res: Response, next: NextFunction) => void;
153
+ ): (req: Request, res: Response, next: NextFunction) => Promise<void>;
100
154
 
101
155
  /**
102
- * Enhanced Express engine for Coherent.js views
156
+ * Express view engine for Coherent.js views.
157
+ *
158
+ * A `.js`/`.mjs`/`.cjs` view module's default export is the component (a
159
+ * function is called with the render locals). For any other view file the
160
+ * locals themselves are rendered as the component. Express's `settings`,
161
+ * `_locals` and `cache` keys are stripped from the locals first.
103
162
  * @param filePath Path to the view file
104
- * @param options Rendering options
163
+ * @param options Render locals
105
164
  * @param callback Callback function
106
165
  */
107
166
  export function enhancedExpressEngine(
@@ -112,7 +171,7 @@ export function enhancedExpressEngine(
112
171
 
113
172
  /**
114
173
  * Setup Coherent.js with Express app
115
- * Configures middleware, view engine, and static files
174
+ * Installs {@link coherentMiddleware} and, with `useEngine`, the view engine
116
175
  * @param app Express application instance
117
176
  * @param options Configuration options
118
177
  */
@@ -135,8 +194,7 @@ export function createExpressIntegration(
135
194
  ): Promise<(app: Application) => Application>;
136
195
 
137
196
  /**
138
- * A classic Express view engine that renders the component passed as the
139
- * `options` argument.
197
+ * Returns {@link enhancedExpressEngine}, for `app.engine('js', expressEngine())`.
140
198
  *
141
199
  * Kept for consumers migrating from the standalone `@coherent.js/express`
142
200
  * package; new code should prefer {@link setupCoherent}.
@@ -4,7 +4,7 @@
4
4
  // Wave 2c (integrations consolidation). Declarations are aligned with the
5
5
  // runtime exports of ../../src/fastify/coherent-fastify.js.
6
6
 
7
- import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
7
+ import type { FastifyPluginCallback, FastifyReply, FastifyRequest } from 'fastify';
8
8
  import type { CoherentNode } from '@coherent.js/core';
9
9
 
10
10
  export interface CoherentFastifyOptions {
@@ -26,6 +26,17 @@ export interface CoherentFastifyOptions {
26
26
  */
27
27
  enableSSR?: boolean;
28
28
 
29
+ /**
30
+ * Also render component-shaped objects returned from route handlers.
31
+ *
32
+ * Detection is a heuristic: every single-key object qualifies, so JSON
33
+ * payloads such as `{ ok: true }` or `{ error: '...' }` would be rendered
34
+ * as HTML, even with a JSON response schema. Use `reply.coherent(component)`
35
+ * instead unless the app never returns single-key JSON objects.
36
+ * @default false
37
+ */
38
+ autoRender?: boolean;
39
+
29
40
  /**
30
41
  * Static file directory for client-side assets
31
42
  * @default 'public'
@@ -54,17 +65,17 @@ export interface CoherentFastifyHandlerOptions {
54
65
  }
55
66
 
56
67
  /**
57
- * Fastify plugin for Coherent.js
58
- * Adds Coherent.js rendering capabilities to Fastify
59
- * @param fastify Fastify instance
60
- * @param options Configuration options
61
- * @param done Callback to signal plugin registration completion
68
+ * Fastify plugin for Coherent.js (wrapped with fastify-plugin, so its
69
+ * decorators apply to the registering scope). Adds `reply.coherent()` and,
70
+ * with `autoRender`, rendering of returned components.
71
+ *
72
+ * Register it; do not call it:
73
+ *
74
+ * ```ts
75
+ * await fastify.register(coherentFastify, { template });
76
+ * ```
62
77
  */
63
- export function coherentFastify(
64
- fastify: FastifyInstance,
65
- options: CoherentFastifyOptions,
66
- done: () => void
67
- ): void;
78
+ export const coherentFastify: FastifyPluginCallback<CoherentFastifyOptions>;
68
79
 
69
80
  /**
70
81
  * Create a Fastify route handler for Coherent.js components
@@ -81,15 +92,10 @@ export function createHandler(
81
92
  ): (request: FastifyRequest, reply: FastifyReply) => Promise<any>;
82
93
 
83
94
  /**
84
- * Setup Coherent.js with Fastify instance
85
- * Configures plugin, static files, and reply extensions
86
- * @param fastify Fastify instance
87
- * @param options Configuration options
95
+ * Alias of {@link coherentFastify}. It is a Fastify plugin: register it with
96
+ * `await fastify.register(setupCoherent, options)` rather than calling it.
88
97
  */
89
- export function setupCoherent(
90
- fastify: FastifyInstance,
91
- options?: CoherentFastifyOptions
92
- ): void;
98
+ export const setupCoherent: FastifyPluginCallback<CoherentFastifyOptions>;
93
99
 
94
100
  /**
95
101
  * Fastify reply extensions
@@ -104,14 +110,17 @@ declare module 'fastify' {
104
110
  isCoherentObject(obj: any): boolean;
105
111
 
106
112
  /**
107
- * Render and send a Coherent component as HTML response
113
+ * Render and send a Coherent component as HTML response. A rendering
114
+ * error is sent through Fastify's error handling (`setErrorHandler`).
115
+ * Returns the reply, so `return reply.coherent(page)` works in async
116
+ * handlers.
108
117
  * @param component Coherent component to render
109
118
  * @param options Rendering options
110
119
  */
111
120
  coherent(
112
121
  component: CoherentNode,
113
122
  options?: CoherentFastifyHandlerOptions
114
- ): void;
123
+ ): FastifyReply;
115
124
  }
116
125
  }
117
126