@coherent.js/koa 1.0.0-beta.2 → 1.0.0-beta.5

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/README.md ADDED
@@ -0,0 +1,421 @@
1
+ # @coherent.js/koa
2
+
3
+ Koa.js adapter for Coherent.js - High-performance server-side rendering with Koa integration.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @coherent.js/koa
9
+ # or
10
+ pnpm add @coherent.js/koa
11
+ # or
12
+ yarn add @coherent.js/koa
13
+ ```
14
+
15
+ **Note:** You also need to install Koa and Coherent.js core:
16
+
17
+ ```bash
18
+ npm install koa @coherent.js/core
19
+ # or
20
+ pnpm add koa @coherent.js/core
21
+ ```
22
+
23
+ ## Overview
24
+
25
+ The `@coherent.js/koa` package provides seamless integration between Coherent.js and Koa.js, enabling you to build high-performance server-side rendered applications with Koa's middleware ecosystem.
26
+
27
+ ## Quick Start
28
+
29
+ ```javascript
30
+ import Koa from 'koa';
31
+ import { createCoherentHandler } from '@coherent.js/koa';
32
+
33
+ // Create your Coherent.js component
34
+ function App({ name = 'World' }) {
35
+ return {
36
+ div: {
37
+ children: [
38
+ { h1: { text: `Hello, ${name}!` } },
39
+ { p: { text: 'This is rendered with Coherent.js' } }
40
+ ]
41
+ }
42
+ };
43
+ }
44
+
45
+ // Create Koa app
46
+ const app = new Koa();
47
+
48
+ // Add Coherent.js handler
49
+ app.use(createCoherentHandler(App, {
50
+ // Optional configuration
51
+ template: ({ html, head, body }) => `
52
+ <!DOCTYPE html>
53
+ <html>
54
+ <head>
55
+ <title>Coherent.js Koa App</title>
56
+ ${head}
57
+ </head>
58
+ <body>
59
+ <div id="app">${body}</div>
60
+ </body>
61
+ </html>
62
+ `
63
+ }));
64
+
65
+ // Start server
66
+ const PORT = process.env.PORT || 3000;
67
+ app.listen(PORT, () => {
68
+ console.log(`Server running on http://localhost:${PORT}`);
69
+ });
70
+ ```
71
+
72
+ ## Features
73
+
74
+ ### Middleware Integration
75
+
76
+ Use Koa middleware alongside Coherent.js rendering:
77
+
78
+ ```javascript
79
+ import Koa from 'koa';
80
+ import logger from 'koa-logger';
81
+ import { createCoherentHandler } from '@coherent.js/koa';
82
+
83
+ const app = new Koa();
84
+
85
+ // Add Koa middleware
86
+ app.use(logger());
87
+
88
+ // Add Coherent.js rendering
89
+ app.use(createCoherentHandler(App));
90
+
91
+ app.listen(3000);
92
+ ```
93
+
94
+ ### Request Context Access
95
+
96
+ Access Koa request context in your components:
97
+
98
+ ```javascript
99
+ function App({ ctx }) {
100
+ // ctx is the Koa context object
101
+ const userAgent = ctx.get('User-Agent');
102
+ const ipAddress = ctx.ip;
103
+
104
+ return {
105
+ div: {
106
+ children: [
107
+ { p: { text: `Your IP: ${ipAddress}` } },
108
+ { p: { text: `User Agent: ${userAgent}` } }
109
+ ]
110
+ }
111
+ };
112
+ }
113
+ ```
114
+
115
+ ### Custom Routes
116
+
117
+ Handle specific routes with Coherent.js:
118
+
119
+ ```javascript
120
+ import Koa from 'koa';
121
+ import Router from '@koa/router';
122
+ import { createCoherentHandler } from '@coherent.js/koa';
123
+
124
+ const app = new Koa();
125
+ const router = new Router();
126
+
127
+ // Handle specific routes
128
+ router.get('/', createCoherentHandler(HomePage));
129
+ router.get('/about', createCoherentHandler(AboutPage));
130
+ router.get('/users/:id', createCoherentHandler(UserProfile));
131
+
132
+ app.use(router.routes());
133
+ app.listen(3000);
134
+ ```
135
+
136
+ ### Error Handling
137
+
138
+ Integrate with Koa's error handling:
139
+
140
+ ```javascript
141
+ import Koa from 'koa';
142
+ import { createCoherentHandler } from '@coherent.js/koa';
143
+
144
+ const app = new Koa();
145
+
146
+ // Error handling middleware
147
+ app.use(async (ctx, next) => {
148
+ try {
149
+ await next();
150
+ } catch (err) {
151
+ ctx.status = err.status || 500;
152
+ ctx.body = { error: err.message };
153
+ ctx.app.emit('error', err, ctx);
154
+ }
155
+ });
156
+
157
+ // Coherent.js handler
158
+ app.use(createCoherentHandler(App));
159
+
160
+ app.listen(3000);
161
+ ```
162
+
163
+ ## Configuration Options
164
+
165
+ ### Template Function
166
+
167
+ Customize the HTML template:
168
+
169
+ ```javascript
170
+ app.use(createCoherentHandler(App, {
171
+ template: ({ html, head, body, ctx }) => `
172
+ <!DOCTYPE html>
173
+ <html lang="${ctx.acceptsLanguages()[0] || 'en'}">
174
+ <head>
175
+ <meta charset="utf-8">
176
+ <meta name="viewport" content="width=device-width, initial-scale=1">
177
+ ${head}
178
+ </head>
179
+ <body>
180
+ <div id="app">${body}</div>
181
+ <script>
182
+ window.__INITIAL_DATA__ = ${JSON.stringify(ctx.state)};
183
+ </script>
184
+ </body>
185
+ </html>
186
+ `
187
+ }));
188
+ ```
189
+
190
+ ### Props Function
191
+
192
+ Customize component props based on Koa context:
193
+
194
+ ```javascript
195
+ app.use(createCoherentHandler(App, {
196
+ props: (ctx) => ({
197
+ userAgent: ctx.get('User-Agent'),
198
+ url: ctx.url,
199
+ user: ctx.state.user || null,
200
+ query: ctx.query
201
+ })
202
+ }));
203
+ ```
204
+
205
+ ### Error Component
206
+
207
+ Provide a custom error component:
208
+
209
+ ```javascript
210
+ function ErrorPage({ error, status }) {
211
+ return {
212
+ div: {
213
+ className: 'error-page',
214
+ children: [
215
+ { h1: { text: `Error ${status}` } },
216
+ { p: { text: error.message } }
217
+ ]
218
+ }
219
+ };
220
+ }
221
+
222
+ app.use(createCoherentHandler(App, {
223
+ errorComponent: ErrorPage
224
+ }));
225
+ ```
226
+
227
+ ## Performance Optimizations
228
+
229
+ ### Caching
230
+
231
+ Enable response caching:
232
+
233
+ ```javascript
234
+ import cache from 'koa-cache-lite';
235
+
236
+ app.use(cache({
237
+ '/api/*': 5 * 60 * 1000, // 5 minutes for API routes
238
+ '/static/*': 60 * 60 * 1000 // 1 hour for static assets
239
+ }));
240
+
241
+ app.use(createCoherentHandler(App));
242
+ ```
243
+
244
+ ### Compression
245
+
246
+ Add response compression:
247
+
248
+ ```javascript
249
+ import compress from 'koa-compress';
250
+
251
+ app.use(compress({
252
+ threshold: 2048,
253
+ gzip: { flush: require('zlib').constants.Z_SYNC_FLUSH }
254
+ }));
255
+
256
+ app.use(createCoherentHandler(App));
257
+ ```
258
+
259
+ ## Advanced Usage
260
+
261
+ ### Server-Side State Management
262
+
263
+ Manage state during server rendering:
264
+
265
+ ```javascript
266
+ function App({ ctx }) {
267
+ // Create request-scoped state
268
+ const requestState = {
269
+ requestId: Math.random().toString(36),
270
+ renderStartTime: Date.now()
271
+ };
272
+
273
+ ctx.state.requestState = requestState;
274
+
275
+ return {
276
+ div: {
277
+ children: [
278
+ { p: { text: `Request ID: ${requestState.requestId}` } }
279
+ ]
280
+ }
281
+ };
282
+ }
283
+ ```
284
+
285
+ ### Integration with Koa Router
286
+
287
+ Advanced routing with parameters:
288
+
289
+ ```javascript
290
+ import Router from '@koa/router';
291
+
292
+ const router = new Router();
293
+
294
+ router.get('/', createCoherentHandler(HomePage));
295
+ router.get('/users/:id', createCoherentHandler(UserProfile, {
296
+ props: (ctx) => ({
297
+ userId: ctx.params.id,
298
+ query: ctx.query
299
+ })
300
+ }));
301
+ router.get('/api/*', createCoherentHandler(ApiDocs));
302
+
303
+ app.use(router.routes());
304
+ app.use(router.allowedMethods());
305
+ ```
306
+
307
+ ## API Reference
308
+
309
+ ### createCoherentHandler(component, options)
310
+
311
+ Create a Koa middleware for Coherent.js rendering.
312
+
313
+ **Parameters:**
314
+ - `component` - Coherent.js component function
315
+ - `options.template` - Function to customize HTML template
316
+ - `options.props` - Function to generate component props from Koa context
317
+ - `options.errorComponent` - Component to render on errors
318
+ - `options.enableHydration` - Boolean to enable client-side hydration (default: true)
319
+
320
+ **Returns:** Koa middleware function
321
+
322
+ ### Options
323
+
324
+ - `template` - Function receiving { html, head, body, ctx } and returning HTML string
325
+ - `props` - Function receiving Koa context and returning component props
326
+ - `errorComponent` - Component for error rendering
327
+ - `enableHydration` - Enable/disable hydration support
328
+
329
+ ## Examples
330
+
331
+ ### Full Application Example
332
+
333
+ ```javascript
334
+ import Koa from 'koa';
335
+ import Router from '@koa/router';
336
+ import bodyParser from 'koa-bodyparser';
337
+ import logger from 'koa-logger';
338
+ import session from 'koa-session';
339
+ import { createCoherentHandler } from '@coherent.js/koa';
340
+
341
+ // Components
342
+ function HomePage({ user }) {
343
+ return {
344
+ div: {
345
+ children: [
346
+ { h1: { text: 'Welcome to Coherent.js + Koa' } },
347
+ user
348
+ ? { p: { text: `Hello, ${user.name}!` } }
349
+ : { a: { href: '/login', text: 'Login' } }
350
+ ]
351
+ }
352
+ };
353
+ }
354
+
355
+ function LoginPage() {
356
+ return {
357
+ form: {
358
+ action: '/login',
359
+ method: 'POST',
360
+ children: [
361
+ { input: { type: 'email', name: 'email', placeholder: 'Email' } },
362
+ { input: { type: 'password', name: 'password', placeholder: 'Password' } },
363
+ { button: { type: 'submit', text: 'Login' } }
364
+ ]
365
+ }
366
+ };
367
+ }
368
+
369
+ // Create app
370
+ const app = new Koa();
371
+ const router = new Router();
372
+
373
+ // Middleware
374
+ app.use(logger());
375
+ app.use(bodyParser());
376
+ app.keys = ['secret-key'];
377
+ app.use(session(app));
378
+
379
+ // Routes
380
+ router.get('/', createCoherentHandler(HomePage, {
381
+ props: (ctx) => ({ user: ctx.session.user })
382
+ }));
383
+
384
+ router.get('/login', createCoherentHandler(LoginPage));
385
+ router.post('/login', async (ctx) => {
386
+ // Simple auth (in production, use proper authentication)
387
+ const { email, password } = ctx.request.body;
388
+ if (email && password) {
389
+ ctx.session.user = { name: email.split('@')[0], email };
390
+ ctx.redirect('/');
391
+ } else {
392
+ ctx.status = 400;
393
+ ctx.body = { error: 'Invalid credentials' };
394
+ }
395
+ });
396
+
397
+ router.get('/logout', (ctx) => {
398
+ ctx.session = null;
399
+ ctx.redirect('/');
400
+ });
401
+
402
+ // Setup
403
+ app.use(router.routes());
404
+ app.use(router.allowedMethods());
405
+
406
+ const PORT = process.env.PORT || 3000;
407
+ app.listen(PORT, () => {
408
+ console.log(`Server running on http://localhost:${PORT}`);
409
+ });
410
+ ```
411
+
412
+ ## Related Packages
413
+
414
+ - [@coherent.js/core](../core/README.md) - Core framework
415
+ - [@coherent.js/express](../express/README.md) - Express.js adapter
416
+ - [@coherent.js/fastify](../fastify/README.md) - Fastify adapter
417
+ - [@coherent.js/nextjs](../nextjs/README.md) - Next.js integration
418
+
419
+ ## License
420
+
421
+ MIT
package/dist/index.cjs CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // src/index.js
@@ -53,9 +43,9 @@ Or with yarn: yarn add ${packageName}`
53
43
  }
54
44
 
55
45
  // ../core/src/core/object-utils.js
56
- function validateComponent(component, path2 = "root") {
46
+ function validateComponent(component, path = "root") {
57
47
  if (component === null || component === void 0) {
58
- throw new Error(`Invalid component at ${path2}: null or undefined`);
48
+ throw new Error(`Invalid component at ${path}: null or undefined`);
59
49
  }
60
50
  if (["string", "number", "boolean"].includes(typeof component)) {
61
51
  return true;
@@ -65,31 +55,31 @@ function validateComponent(component, path2 = "root") {
65
55
  }
66
56
  if (Array.isArray(component)) {
67
57
  component.forEach((child, index) => {
68
- validateComponent(child, `${path2}[${index}]`);
58
+ validateComponent(child, `${path}[${index}]`);
69
59
  });
70
60
  return true;
71
61
  }
72
62
  if (typeof component === "object") {
73
63
  const keys = Object.keys(component);
74
64
  if (keys.length === 0) {
75
- throw new Error(`Empty object at ${path2}`);
65
+ throw new Error(`Empty object at ${path}`);
76
66
  }
77
67
  keys.forEach((key) => {
78
68
  const value = component[key];
79
69
  if (!/^[a-zA-Z][a-zA-Z0-9-]*$/.test(key) && key !== "text") {
80
- console.warn(`Potentially invalid tag name at ${path2}: ${key}`);
70
+ console.warn(`Potentially invalid tag name at ${path}: ${key}`);
81
71
  }
82
72
  if (value && typeof value === "object" && !Array.isArray(value)) {
83
73
  if (value.children) {
84
- validateComponent(value.children, `${path2}.${key}.children`);
74
+ validateComponent(value.children, `${path}.${key}.children`);
85
75
  }
86
76
  } else if (value && typeof value !== "string" && typeof value !== "number" && typeof value !== "function") {
87
- throw new Error(`Invalid value type at ${path2}.${key}: ${typeof value}`);
77
+ throw new Error(`Invalid value type at ${path}.${key}: ${typeof value}`);
88
78
  }
89
79
  });
90
80
  return true;
91
81
  }
92
- throw new Error(`Invalid component type at ${path2}: ${typeof component}`);
82
+ throw new Error(`Invalid component type at ${path}: ${typeof component}`);
93
83
  }
94
84
  function isCoherentObject(obj) {
95
85
  if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
@@ -171,6 +161,7 @@ function createPerformanceMonitor(options = {}) {
171
161
  },
172
162
  alerts: {
173
163
  enabled: true,
164
+ debounceMs: 5e3,
174
165
  rules: []
175
166
  },
176
167
  resources: {
@@ -332,7 +323,7 @@ function createPerformanceMonitor(options = {}) {
332
323
  const alertKey = `${rule.metric}-${rule.condition}-${rule.threshold}`;
333
324
  const lastTriggered = alertState.triggered.get(alertKey);
334
325
  const now = Date.now();
335
- if (!lastTriggered || now - lastTriggered > 5e3) {
326
+ if (!lastTriggered || now - lastTriggered > opts.alerts.debounceMs) {
336
327
  alertState.triggered.set(alertKey, now);
337
328
  alertState.history.push({
338
329
  rule,
@@ -1314,107 +1305,6 @@ function createCacheManager(options = {}) {
1314
1305
  }
1315
1306
  var cacheManager = createCacheManager();
1316
1307
 
1317
- // ../core/src/rendering/css-manager.js
1318
- var import_promises = __toESM(require("node:fs/promises"), 1);
1319
- var import_node_path = __toESM(require("node:path"), 1);
1320
- var CSSManager = class {
1321
- constructor(options = {}) {
1322
- this.options = {
1323
- basePath: process.cwd(),
1324
- minify: false,
1325
- cache: true,
1326
- autoprefixer: false,
1327
- ...options
1328
- };
1329
- this.cache = /* @__PURE__ */ new Map();
1330
- this.loadedFiles = /* @__PURE__ */ new Set();
1331
- }
1332
- /**
1333
- * Load CSS file content
1334
- */
1335
- async loadCSSFile(filePath) {
1336
- const fullPath = import_node_path.default.resolve(this.options.basePath, filePath);
1337
- const cacheKey = fullPath;
1338
- if (this.options.cache && this.cache.has(cacheKey)) {
1339
- return this.cache.get(cacheKey);
1340
- }
1341
- try {
1342
- let content = await import_promises.default.readFile(fullPath, "utf8");
1343
- if (this.options.minify) {
1344
- content = this.minifyCSS(content);
1345
- }
1346
- if (this.options.cache) {
1347
- this.cache.set(cacheKey, content);
1348
- }
1349
- this.loadedFiles.add(filePath);
1350
- return content;
1351
- } catch (_error) {
1352
- console.warn(`Failed to load CSS file: ${filePath}`, _error.message);
1353
- return "";
1354
- }
1355
- }
1356
- /**
1357
- * Load multiple CSS files
1358
- */
1359
- async loadCSSFiles(filePaths) {
1360
- if (!Array.isArray(filePaths)) {
1361
- filePaths = [filePaths];
1362
- }
1363
- const cssContents = await Promise.all(
1364
- filePaths.map((filePath) => this.loadCSSFile(filePath))
1365
- );
1366
- return cssContents.join("\n");
1367
- }
1368
- /**
1369
- * Generate CSS link tags for external files
1370
- */
1371
- generateCSSLinks(filePaths, baseUrl = "/") {
1372
- if (!Array.isArray(filePaths)) {
1373
- filePaths = [filePaths];
1374
- }
1375
- return filePaths.map((filePath) => {
1376
- const href = filePath.startsWith("http") ? filePath : `${baseUrl}${filePath}`.replace(/\/+/g, "/");
1377
- return `<link rel="stylesheet" href="${this.escapeHtml(href)}" />`;
1378
- }).join("\n");
1379
- }
1380
- /**
1381
- * Generate inline style tag with CSS content
1382
- */
1383
- generateInlineStyles(cssContent) {
1384
- if (!cssContent) return "";
1385
- return `<style type="text/css">
1386
- ${cssContent}
1387
- </style>`;
1388
- }
1389
- /**
1390
- * Basic CSS minification
1391
- */
1392
- minifyCSS(css) {
1393
- return css.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").replace(/;\s*}/g, "}").replace(/{\s+/g, "{").replace(/;\s+/g, ";").trim();
1394
- }
1395
- /**
1396
- * Escape HTML entities
1397
- */
1398
- escapeHtml(text) {
1399
- const div = { textContent: text };
1400
- return div.innerHTML || text;
1401
- }
1402
- /**
1403
- * Clear cache
1404
- */
1405
- clearCache() {
1406
- this.cache.clear();
1407
- this.loadedFiles.clear();
1408
- }
1409
- /**
1410
- * Get loaded file list
1411
- */
1412
- getLoadedFiles() {
1413
- return Array.from(this.loadedFiles);
1414
- }
1415
- };
1416
- var defaultCSSManager = new CSSManager();
1417
-
1418
1308
  // ../core/src/rendering/html-renderer.js
1419
1309
  var rendererCache = createCacheManager({
1420
1310
  maxSize: 1e3,