@coherent.js/integrations 1.0.0-rc.1 → 1.0.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coherent.js/integrations",
3
- "version": "1.0.0-rc.1",
3
+ "version": "1.0.0-rc.3",
4
4
  "description": "Framework integration adapters for Coherent.js: Express, Fastify, Koa, Next.js, Astro, Remix, SvelteKit.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -47,6 +47,9 @@
47
47
  "bugs": {
48
48
  "url": "https://github.com/Tomdrouv1/coherent.js/issues"
49
49
  },
50
+ "dependencies": {
51
+ "fastify-plugin": "^4.5.1"
52
+ },
50
53
  "peerDependencies": {
51
54
  "@remix-run/server-runtime": ">=2.0.0",
52
55
  "@sveltejs/kit": ">=2.0.0",
@@ -56,7 +59,7 @@
56
59
  "koa": ">=2.13.0 < 4.0.0",
57
60
  "next": ">=13.0.0",
58
61
  "react": ">=18.0.0",
59
- "@coherent.js/core": "1.0.0-rc.1"
62
+ "@coherent.js/core": "1.0.0-rc.3"
60
63
  },
61
64
  "peerDependenciesMeta": {
62
65
  "@remix-run/server-runtime": {
@@ -1,24 +1,32 @@
1
1
  /**
2
2
  * Fastify integration for Coherent.js
3
- * Provides plugins and utilities for using Coherent.js with Fastify
3
+ * Provides plugins and utilities for using Coherent.js with Fastify.
4
+ *
5
+ * The plugin must be wrapped with fastify-plugin (fp). Without fp, every
6
+ * `fastify.register(...)` boundary creates a fresh encapsulated context,
7
+ * and the `preSerialization` hook + `isCoherentObject` decorator only
8
+ * apply to routes registered INSIDE that context. The user's root-level
9
+ * routes would never see them, and responses would JSON-serialize the
10
+ * raw component object instead of rendering it.
4
11
  */
5
12
 
13
+ import fp from 'fastify-plugin';
6
14
  import {
7
15
  renderWithTemplate,
8
16
  renderComponentFactory
9
17
  } from '@coherent.js/core';
10
18
 
11
19
  /**
12
- * Fastify plugin for Coherent.js
13
- * Automatically renders Coherent.js components and handles errors
20
+ * Fastify plugin implementation. Not exported directly — use the fp-wrapped
21
+ * `coherentFastify` (or its alias `setupCoherent`) instead.
14
22
  *
15
23
  * @param {Object} fastify - Fastify instance
16
24
  * @param {Object} options - Plugin options
17
- * @param {boolean} options.enablePerformanceMonitoring - Enable performance monitoring
18
- * @param {string} options.template - HTML template with {{content}} placeholder
25
+ * @param {boolean} [options.enablePerformanceMonitoring] - Enable performance monitoring
26
+ * @param {string} [options.template] - HTML template with {{content}} placeholder
19
27
  * @param {Function} done - Callback to signal plugin registration completion
20
28
  */
21
- export function coherentFastify(fastify, options, done) {
29
+ function coherentFastifyImpl(fastify, options = {}, done) {
22
30
  const {
23
31
  enablePerformanceMonitoring = false,
24
32
  template = '<!DOCTYPE html>\n{{content}}'
@@ -29,12 +37,11 @@ export function coherentFastify(fastify, options, done) {
29
37
  if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
30
38
  return false;
31
39
  }
32
-
33
40
  const keys = Object.keys(obj);
34
41
  return keys.length === 1;
35
42
  });
36
43
 
37
- // Add decorator for rendering Coherent.js components
44
+ // Add decorator for explicit rendering: reply.coherent(component, opts?)
38
45
  fastify.decorateReply('coherent', function(component, renderOptions = {}) {
39
46
  const {
40
47
  enablePerformanceMonitoring: renderPerformanceMonitoring = enablePerformanceMonitoring,
@@ -42,13 +49,10 @@ export function coherentFastify(fastify, options, done) {
42
49
  } = renderOptions;
43
50
 
44
51
  try {
45
- // Use shared rendering utility
46
52
  const finalHtml = renderWithTemplate(component, {
47
53
  enablePerformanceMonitoring: renderPerformanceMonitoring,
48
54
  template: renderTemplate
49
55
  });
50
-
51
- // Set content type and send HTML
52
56
  this.header('Content-Type', 'text/html; charset=utf-8');
53
57
  this.send(finalHtml);
54
58
  } catch (_error) {
@@ -60,24 +64,21 @@ export function coherentFastify(fastify, options, done) {
60
64
  }
61
65
  });
62
66
 
63
- // Hook to automatically render Coherent.js objects
64
- fastify.addHook('onSend', async (request, reply, payload) => {
65
- // If payload is a Coherent.js object, render it
66
- if (reply.isCoherentObject(payload)) {
67
- try {
68
- // Use shared rendering utility
69
- const finalHtml = renderWithTemplate(payload, { enablePerformanceMonitoring, template });
70
-
71
- // Set content type and return HTML
72
- reply.header('Content-Type', 'text/html; charset=utf-8');
73
- return finalHtml;
74
- } catch (_error) {
75
- console.error('Coherent.js rendering _error:', _error);
76
- throw _error;
77
- }
67
+ // Auto-render: if a handler returns a Coherent.js component object,
68
+ // intercept before serialization and replace the payload with HTML.
69
+ //
70
+ // - `onSend` runs after JSON serialization (payload is already a string),
71
+ // so the component object would never be detected there.
72
+ // - `preSerialization` runs before serialization. We render to HTML and
73
+ // install an identity serializer for this reply, so Fastify doesn't
74
+ // JSON-stringify the HTML string we just produced.
75
+ fastify.addHook('preSerialization', async (request, reply, payload) => {
76
+ if (reply.isCoherentObject?.(payload)) {
77
+ const finalHtml = renderWithTemplate(payload, { enablePerformanceMonitoring, template });
78
+ reply.header('Content-Type', 'text/html; charset=utf-8');
79
+ reply.serializer((p) => p);
80
+ return finalHtml;
78
81
  }
79
-
80
- // For non-Coherent.js data, return as-is
81
82
  return payload;
82
83
  });
83
84
 
@@ -85,7 +86,27 @@ export function coherentFastify(fastify, options, done) {
85
86
  }
86
87
 
87
88
  /**
88
- * Create a Fastify route handler for Coherent.js components
89
+ * Fastify plugin for Coherent.js — wrapped with fastify-plugin so decorators
90
+ * and hooks apply to the parent (root) context. Register at the top of your
91
+ * app, then define routes that return Coherent.js component objects:
92
+ *
93
+ * await fastify.register(coherentFastify, { template: APP_HTML_TEMPLATE });
94
+ * fastify.get('/', async () => HomePage({}));
95
+ */
96
+ export const coherentFastify = fp(coherentFastifyImpl, {
97
+ name: 'coherent-fastify',
98
+ fastify: '>=4.0.0'
99
+ });
100
+
101
+ /**
102
+ * Alias for `coherentFastify`. Preserved for backward compatibility with
103
+ * scaffolds and examples that use `setupCoherent`. Behaves identically:
104
+ * `await fastify.register(setupCoherent, options)`.
105
+ */
106
+ export const setupCoherent = coherentFastify;
107
+
108
+ /**
109
+ * Create a Fastify route handler for Coherent.js components.
89
110
  *
90
111
  * @param {Function} componentFactory - Function that returns a Coherent.js component
91
112
  * @param {Object} options - Handler options
@@ -94,14 +115,11 @@ export function coherentFastify(fastify, options, done) {
94
115
  export function createHandler(componentFactory, options = {}) {
95
116
  return async (request, reply) => {
96
117
  try {
97
- // Use shared rendering utility
98
118
  const finalHtml = await renderComponentFactory(
99
119
  componentFactory,
100
120
  [request, reply],
101
121
  options
102
122
  );
103
-
104
- // Send HTML response
105
123
  reply.header('Content-Type', 'text/html; charset=utf-8');
106
124
  return finalHtml;
107
125
  } catch (_error) {
@@ -111,15 +129,5 @@ export function createHandler(componentFactory, options = {}) {
111
129
  };
112
130
  }
113
131
 
114
- /**
115
- * Setup Coherent.js with Fastify instance
116
- *
117
- * @param {Object} fastify - Fastify instance
118
- * @param {Object} options - Setup options
119
- */
120
- export function setupCoherent(fastify, options = {}) {
121
- fastify.register(coherentFastify, options);
122
- }
123
-
124
- // Export plugin as default for Fastify's plugin system
132
+ // Default export = the plugin, for `fastify.register(import('@coherent.js/integrations/fastify'))`.
125
133
  export default coherentFastify;
@@ -79,14 +79,13 @@ export function createHandler(componentFactory, options = {}) {
79
79
  * @param {Object} options - Setup options
80
80
  */
81
81
  export function setupCoherent(app, options = {}) {
82
- const {
83
- useMiddleware = true,
84
- enablePerformanceMonitoring = false
85
- } = options;
82
+ const { useMiddleware = true, ...middlewareOptions } = options;
86
83
 
87
- // Use middleware for automatic rendering
84
+ // Use middleware for automatic rendering. Forward everything except the
85
+ // setup-only flag so callers can supply `template`, `enablePerformanceMonitoring`,
86
+ // and any future middleware options.
88
87
  if (useMiddleware) {
89
- app.use(coherentKoaMiddleware({ enablePerformanceMonitoring }));
88
+ app.use(coherentKoaMiddleware(middlewareOptions));
90
89
  }
91
90
  }
92
91