@coherent.js/integrations 1.0.0-rc.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Thomas Drouvin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # @coherent.js/integrations
2
+
3
+ Framework integration adapters for Coherent.js — bridges between your chosen HTTP/SSG framework and the Coherent.js rendering engine.
4
+
5
+ ## Subpath exports
6
+
7
+ - `@coherent.js/integrations/express` — Express.js adapter
8
+ - `@coherent.js/integrations/fastify` — Fastify adapter (added in 1.0.0)
9
+ - `@coherent.js/integrations/koa` — Koa adapter (added in 1.0.0)
10
+ - `@coherent.js/integrations/nextjs` — Next.js adapter (added in 1.0.0)
11
+ - `@coherent.js/integrations/astro` — Astro adapter (added in 1.0.0)
12
+ - `@coherent.js/integrations/remix` — Remix adapter (added in 1.0.0)
13
+ - `@coherent.js/integrations/sveltekit` — SvelteKit adapter (added in 1.0.0)
14
+
15
+ ## Migration from pre-1.0
16
+
17
+ Each framework previously shipped as its own package (`@coherent.js/express`, etc.). Migrate by changing import paths:
18
+
19
+ ```diff
20
+ - import { setupCoherent } from '@coherent.js/express';
21
+ + import { setupCoherent } from '@coherent.js/integrations/express';
22
+ ```
23
+
24
+ Public API is unchanged — only the import path moves.
25
+
26
+ ## Install
27
+
28
+ ```bash
29
+ pnpm add @coherent.js/core @coherent.js/integrations
30
+ pnpm add express # or fastify / koa / next / etc. — only the ones you use
31
+ ```
32
+
33
+ Framework peer dependencies are declared optional, so consumers only install the framework(s) they actually use.
34
+
35
+ ## License
36
+
37
+ MIT
package/package.json ADDED
@@ -0,0 +1,99 @@
1
+ {
2
+ "name": "@coherent.js/integrations",
3
+ "version": "1.0.0-rc.1",
4
+ "description": "Framework integration adapters for Coherent.js: Express, Fastify, Koa, Next.js, Astro, Remix, SvelteKit.",
5
+ "type": "module",
6
+ "exports": {
7
+ "./express": {
8
+ "types": "./types/express/index.d.ts",
9
+ "import": "./src/express/index.js"
10
+ },
11
+ "./fastify": {
12
+ "types": "./types/fastify/index.d.ts",
13
+ "import": "./src/fastify/index.js"
14
+ },
15
+ "./koa": {
16
+ "import": "./src/koa/index.js"
17
+ },
18
+ "./nextjs": {
19
+ "types": "./types/nextjs/index.d.ts",
20
+ "import": "./src/nextjs/index.js"
21
+ },
22
+ "./astro": {
23
+ "import": "./src/astro/index.js"
24
+ },
25
+ "./remix": {
26
+ "import": "./src/remix/index.js"
27
+ },
28
+ "./sveltekit": {
29
+ "import": "./src/sveltekit/index.js"
30
+ }
31
+ },
32
+ "files": [
33
+ "src/",
34
+ "types/",
35
+ "README.md",
36
+ "LICENSE"
37
+ ],
38
+ "engines": {
39
+ "node": ">=22.0.0"
40
+ },
41
+ "license": "MIT",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/Tomdrouv1/coherent.js.git"
45
+ },
46
+ "homepage": "https://github.com/Tomdrouv1/coherent.js",
47
+ "bugs": {
48
+ "url": "https://github.com/Tomdrouv1/coherent.js/issues"
49
+ },
50
+ "peerDependencies": {
51
+ "@remix-run/server-runtime": ">=2.0.0",
52
+ "@sveltejs/kit": ">=2.0.0",
53
+ "astro": ">=4.0.0",
54
+ "express": ">=4.18.0 < 6.0.0",
55
+ "fastify": ">=4.0.0 < 6.0.0",
56
+ "koa": ">=2.13.0 < 4.0.0",
57
+ "next": ">=13.0.0",
58
+ "react": ">=18.0.0",
59
+ "@coherent.js/core": "1.0.0-rc.1"
60
+ },
61
+ "peerDependenciesMeta": {
62
+ "@remix-run/server-runtime": {
63
+ "optional": true
64
+ },
65
+ "@sveltejs/kit": {
66
+ "optional": true
67
+ },
68
+ "astro": {
69
+ "optional": true
70
+ },
71
+ "express": {
72
+ "optional": true
73
+ },
74
+ "fastify": {
75
+ "optional": true
76
+ },
77
+ "koa": {
78
+ "optional": true
79
+ },
80
+ "next": {
81
+ "optional": true
82
+ },
83
+ "react": {
84
+ "optional": true
85
+ }
86
+ },
87
+ "publishConfig": {
88
+ "access": "public",
89
+ "registry": "https://registry.npmjs.org/"
90
+ },
91
+ "sideEffects": false,
92
+ "scripts": {
93
+ "build": "node build.mjs",
94
+ "clean": "rm -rf dist/",
95
+ "test": "vitest run",
96
+ "test:watch": "vitest",
97
+ "typecheck": "tsc -p tsconfig.json --noEmit"
98
+ }
99
+ }
@@ -0,0 +1,88 @@
1
+ // src/astro/index.js
2
+ //
3
+ // Public entry point for @coherent.js/integrations/astro. Provides
4
+ // server-side rendering of Coherent.js components within Astro projects.
5
+ //
6
+ // Astro must be installed as a peer dependency to use this integration.
7
+ //
8
+ // Usage:
9
+ // import { createAstroIntegration } from '@coherent.js/integrations/astro';
10
+
11
+ import { render } from '@coherent.js/core';
12
+
13
+ /**
14
+ * Create an Astro integration for Coherent.js
15
+ *
16
+ * @param {Object} [options] - Integration options
17
+ * @param {boolean} [options.hydrate] - Enable client-side hydration
18
+ * @param {string} [options.hydrateScript] - Custom hydration script path
19
+ * @returns {Object} Astro integration configuration
20
+ *
21
+ * @example
22
+ * // astro.config.mjs
23
+ * import { createAstroIntegration } from '@coherent.js/integrations/astro';
24
+ * export default { integrations: [createAstroIntegration()] };
25
+ */
26
+ export function createAstroIntegration(options = {}) {
27
+ return {
28
+ name: 'coherent',
29
+ hooks: {
30
+ 'astro:config:setup': ({ addRenderer, updateConfig }) => {
31
+ addRenderer({
32
+ name: '@coherent.js/integrations/astro',
33
+ serverEntrypoint: '@coherent.js/integrations/astro',
34
+ });
35
+
36
+ if (options.hydrate) {
37
+ updateConfig({
38
+ vite: {
39
+ optimizeDeps: {
40
+ include: ['@coherent.js/client']
41
+ }
42
+ }
43
+ });
44
+ }
45
+ },
46
+ 'astro:build:done': ({ logger }) => {
47
+ logger?.info('Coherent.js components built successfully');
48
+ }
49
+ }
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Render a Coherent.js component to HTML string
55
+ *
56
+ * @param {Function|Object} Component - Coherent.js component
57
+ * @param {Object} [props] - Component props
58
+ * @param {Object} [renderOptions] - Rendering options
59
+ * @returns {string} Rendered HTML
60
+ */
61
+ export function renderComponent(Component, props = {}, renderOptions = {}) {
62
+ const componentDef = typeof Component === 'function'
63
+ ? Component(props)
64
+ : Component;
65
+
66
+ return render(componentDef, renderOptions);
67
+ }
68
+
69
+ /**
70
+ * Create a server-side component renderer for Astro
71
+ *
72
+ * @param {Object} [options] - Renderer options
73
+ * @returns {Object} Astro renderer configuration
74
+ */
75
+ export function createRenderer(options = {}) {
76
+ return {
77
+ name: '@coherent.js/astro-renderer',
78
+ check(Component) {
79
+ // Check if this looks like a Coherent.js component (function or plain object with tag keys)
80
+ return typeof Component === 'function' ||
81
+ (typeof Component === 'object' && Component !== null && !Array.isArray(Component));
82
+ },
83
+ renderToStaticMarkup(Component, props) {
84
+ const html = renderComponent(Component, props, options);
85
+ return { html };
86
+ }
87
+ };
88
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Enhanced Express.js integration for Coherent.js
3
+ * Provides middleware and utilities for using Coherent.js with Express
4
+ */
5
+
6
+ import {
7
+ render,
8
+ importPeerDependency,
9
+ renderWithTemplate,
10
+ renderComponentFactory,
11
+ isCoherentComponent
12
+ } from '@coherent.js/core';
13
+
14
+ /**
15
+ * Coherent.js Express middleware
16
+ * Automatically renders Coherent.js components and handles errors
17
+ *
18
+ * @param {Object} options - Configuration options
19
+ * @param {boolean} options.enablePerformanceMonitoring - Enable performance monitoring
20
+ * @param {string} options.template - HTML template with {{content}} placeholder
21
+ * @returns {Function} Express middleware function
22
+ */
23
+ export function coherentMiddleware(options = {}) {
24
+ const {
25
+ enablePerformanceMonitoring = false,
26
+ template = '<!DOCTYPE html>\n{{content}}'
27
+ } = options;
28
+
29
+ return (req, res, next) => {
30
+ // Store original send method
31
+ const originalSend = res.send;
32
+
33
+ // Override send method to handle Coherent.js objects
34
+ res.send = function(data) {
35
+ // If data is a Coherent.js object (plain object with a single key), render it
36
+ if (isCoherentComponent(data)) {
37
+ try {
38
+ // Use shared rendering utility
39
+ const finalHtml = renderWithTemplate(data, { enablePerformanceMonitoring, template });
40
+
41
+ // Set content type and send HTML
42
+ res.set('Content-Type', 'text/html');
43
+ return originalSend.call(this, finalHtml);
44
+ } catch (_error) {
45
+ console.error('Coherent.js rendering _error:', _error);
46
+ return next(_error);
47
+ }
48
+ }
49
+
50
+ // For non-Coherent.js data, use original send method
51
+ return originalSend.call(this, data);
52
+ };
53
+
54
+ next();
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Create an Express route handler for Coherent.js components
60
+ *
61
+ * @param {Function} componentFactory - Function that returns a Coherent.js component
62
+ * @param {Object} options - Handler options
63
+ * @returns {Function} Express route handler
64
+ */
65
+ export function createCoherentHandler(componentFactory, options = {}) {
66
+ return async (req, res, next) => {
67
+ try {
68
+ // Use shared rendering utility
69
+ const finalHtml = await renderComponentFactory(
70
+ componentFactory,
71
+ [req, res, next],
72
+ options
73
+ );
74
+
75
+ // Send HTML response
76
+ res.set('Content-Type', 'text/html');
77
+ res.send(finalHtml);
78
+ } catch (_error) {
79
+ console.error('Coherent.js handler _error:', _error);
80
+ next(_error);
81
+ }
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Enhanced Express engine for Coherent.js views
87
+ *
88
+ * @param {string} filePath - Path to view file (not used in Coherent.js)
89
+ * @param {Object} options - View options containing Coherent.js component
90
+ * @param {Function} callback - Callback function
91
+ */
92
+ export function enhancedExpressEngine(filePath, options, callback) {
93
+ try {
94
+ // Render Coherent.js component from options
95
+ const html = render(options);
96
+ callback(null, html);
97
+ } catch (_error) {
98
+ callback(_error);
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Setup Coherent.js with Express app
104
+ *
105
+ * @param {Object} app - Express app instance
106
+ * @param {Object} options - Setup options
107
+ */
108
+ export function setupCoherent(app, options = {}) {
109
+ const {
110
+ useMiddleware = true,
111
+ useEngine = true,
112
+ engineName = 'coherent',
113
+ enablePerformanceMonitoring = false
114
+ } = options;
115
+
116
+ // Register enhanced engine
117
+ if (useEngine) {
118
+ app.engine(engineName, enhancedExpressEngine);
119
+ app.set('view engine', engineName);
120
+ }
121
+
122
+ // Use middleware for automatic rendering
123
+ if (useMiddleware) {
124
+ app.use(coherentMiddleware({ enablePerformanceMonitoring }));
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Create Express integration with dependency checking
130
+ * This function ensures Express is available before setting up the integration
131
+ *
132
+ * @param {Object} options - Setup options
133
+ * @returns {Promise<Function>} - Function to setup Express integration
134
+ */
135
+ export async function createExpressIntegration(options = {}) {
136
+ try {
137
+ // Verify Express is available
138
+ await importPeerDependency('express', 'Express.js');
139
+
140
+ return function(app) {
141
+ if (!app || typeof app.use !== 'function') {
142
+ throw new Error('Invalid Express app instance provided');
143
+ }
144
+
145
+ setupCoherent(app, options);
146
+ return app;
147
+ };
148
+ } catch (_error) {
149
+ throw _error;
150
+ }
151
+ }
152
+
153
+ // Export all utilities
154
+ export default {
155
+ coherentMiddleware,
156
+ createCoherentHandler,
157
+ enhancedExpressEngine,
158
+ setupCoherent,
159
+ createExpressIntegration
160
+ };
@@ -0,0 +1,36 @@
1
+ // src/express/index.js
2
+ //
3
+ // Public entry point for @coherent.js/integrations/express. Re-exports the
4
+ // full runtime surface from coherent-express.js so the runtime matches the
5
+ // TypeScript declarations in ../../types/express/index.d.ts.
6
+ //
7
+ // `expressEngine` is preserved as a thin factory wrapper for backwards
8
+ // compatibility with consumers that imported it from the legacy
9
+ // @coherent.js/express package.
10
+
11
+ import { render } from '@coherent.js/core';
12
+
13
+ export {
14
+ coherentMiddleware,
15
+ createCoherentHandler,
16
+ enhancedExpressEngine,
17
+ setupCoherent,
18
+ createExpressIntegration
19
+ } from './coherent-express.js';
20
+
21
+ /**
22
+ * Factory returning a classic Express view engine that renders a Coherent.js
23
+ * component tree passed via the `options` argument.
24
+ *
25
+ * @returns {(filePath: string, options: unknown, callback: (err: Error | null, html?: string) => void) => void}
26
+ */
27
+ export function expressEngine() {
28
+ return (filePath, options, callback) => {
29
+ try {
30
+ const html = render(options);
31
+ callback(null, html);
32
+ } catch (_error) {
33
+ callback(_error);
34
+ }
35
+ };
36
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Fastify integration for Coherent.js
3
+ * Provides plugins and utilities for using Coherent.js with Fastify
4
+ */
5
+
6
+ import {
7
+ renderWithTemplate,
8
+ renderComponentFactory
9
+ } from '@coherent.js/core';
10
+
11
+ /**
12
+ * Fastify plugin for Coherent.js
13
+ * Automatically renders Coherent.js components and handles errors
14
+ *
15
+ * @param {Object} fastify - Fastify instance
16
+ * @param {Object} options - Plugin options
17
+ * @param {boolean} options.enablePerformanceMonitoring - Enable performance monitoring
18
+ * @param {string} options.template - HTML template with {{content}} placeholder
19
+ * @param {Function} done - Callback to signal plugin registration completion
20
+ */
21
+ export function coherentFastify(fastify, options, done) {
22
+ const {
23
+ enablePerformanceMonitoring = false,
24
+ template = '<!DOCTYPE html>\n{{content}}'
25
+ } = options;
26
+
27
+ // Add decorator to check if an object is a Coherent.js component
28
+ fastify.decorateReply('isCoherentObject', (obj) => {
29
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
30
+ return false;
31
+ }
32
+
33
+ const keys = Object.keys(obj);
34
+ return keys.length === 1;
35
+ });
36
+
37
+ // Add decorator for rendering Coherent.js components
38
+ fastify.decorateReply('coherent', function(component, renderOptions = {}) {
39
+ const {
40
+ enablePerformanceMonitoring: renderPerformanceMonitoring = enablePerformanceMonitoring,
41
+ template: renderTemplate = template
42
+ } = renderOptions;
43
+
44
+ try {
45
+ // Use shared rendering utility
46
+ const finalHtml = renderWithTemplate(component, {
47
+ enablePerformanceMonitoring: renderPerformanceMonitoring,
48
+ template: renderTemplate
49
+ });
50
+
51
+ // Set content type and send HTML
52
+ this.header('Content-Type', 'text/html; charset=utf-8');
53
+ this.send(finalHtml);
54
+ } catch (_error) {
55
+ console.error('Coherent.js rendering _error:', _error);
56
+ this.status(500).send({
57
+ _error: 'Internal Server Error',
58
+ message: _error.message
59
+ });
60
+ }
61
+ });
62
+
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
+ }
78
+ }
79
+
80
+ // For non-Coherent.js data, return as-is
81
+ return payload;
82
+ });
83
+
84
+ done();
85
+ }
86
+
87
+ /**
88
+ * Create a Fastify route handler for Coherent.js components
89
+ *
90
+ * @param {Function} componentFactory - Function that returns a Coherent.js component
91
+ * @param {Object} options - Handler options
92
+ * @returns {Function} Fastify route handler
93
+ */
94
+ export function createHandler(componentFactory, options = {}) {
95
+ return async (request, reply) => {
96
+ try {
97
+ // Use shared rendering utility
98
+ const finalHtml = await renderComponentFactory(
99
+ componentFactory,
100
+ [request, reply],
101
+ options
102
+ );
103
+
104
+ // Send HTML response
105
+ reply.header('Content-Type', 'text/html; charset=utf-8');
106
+ return finalHtml;
107
+ } catch (_error) {
108
+ console.error('Coherent.js handler _error:', _error);
109
+ throw _error;
110
+ }
111
+ };
112
+ }
113
+
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
125
+ export default coherentFastify;
@@ -0,0 +1,13 @@
1
+ // src/fastify/index.js
2
+ //
3
+ // Public entry point for @coherent.js/integrations/fastify. Re-exports the
4
+ // full runtime surface from coherent-fastify.js so the runtime matches the
5
+ // TypeScript declarations in ../../types/fastify/index.d.ts.
6
+
7
+ export {
8
+ coherentFastify,
9
+ createHandler,
10
+ setupCoherent
11
+ } from './coherent-fastify.js';
12
+
13
+ export { default } from './coherent-fastify.js';